diff options
| author | Alexander Valavanis <valavanisalex@gmail.com> | 2019-06-05 19:33:55 +0000 |
|---|---|---|
| committer | Alexander Valavanis <valavanisalex@gmail.com> | 2019-06-05 19:33:55 +0000 |
| commit | 2e40143d6e75d50bf659abddc5c7c25fb1bc2436 (patch) | |
| tree | e2fa747c95569dc3f77fcdfa770b3d404ff065cc /src | |
| parent | Hackfest2019: Rm tautological tests (diff) | |
| parent | Use a flowbox when there are more than two options for font feature settings. (diff) | |
| download | inkscape-2e40143d6e75d50bf659abddc5c7c25fb1bc2436.tar.gz inkscape-2e40143d6e75d50bf659abddc5c7c25fb1bc2436.zip | |
Merge changes
Diffstat (limited to 'src')
31 files changed, 1652 insertions, 480 deletions
diff --git a/src/attributes.cpp b/src/attributes.cpp index d7081d4c9..7353c35ed 100644 --- a/src/attributes.cpp +++ b/src/attributes.cpp @@ -9,11 +9,13 @@ * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ +#include "attributes.h" #include <cstring> #include <map> +#include <algorithm> #include <glib.h> // g_assert() -#include "attributes.h" + struct SPStyleProp { SPAttributeEnum code; @@ -602,6 +604,27 @@ sp_attribute_name(SPAttributeEnum id) return props[id].name; } +std::vector<Glib::ustring> sp_attribute_name_list(bool cssattr, bool attr) +{ + std::vector<Glib::ustring> result; + static AttributeLookupImpl const _instance; + bool add = attr; + for (auto prop : props) { + if (prop.code == SP_ATTR_D) { + if (cssattr) { + add = true; + } else if (attr) { + add = false; + } + } + if (add) { + result.emplace_back(prop.name); + } + } + std::sort(result.begin(), result.end()); + return result; +} + /* Local Variables: diff --git a/src/attributes.h b/src/attributes.h index 512c00499..7a49e2808 100644 --- a/src/attributes.h +++ b/src/attributes.h @@ -15,6 +15,7 @@ * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ #include <glibmm/value.h> +#include <vector> /** * True iff k is a property in SVG, i.e. something that can be written either in a style attribute @@ -574,6 +575,11 @@ SPAttributeEnum sp_attribute_lookup(gchar const *key); */ gchar const *sp_attribute_name(SPAttributeEnum id); +/** + * Get attribute name css list. + */ +std::vector<Glib::ustring> sp_attribute_name_list(bool cssattr = false, bool attr = false); + #endif diff --git a/src/extension/effect.cpp b/src/extension/effect.cpp index f69ece42f..3c9940240 100644 --- a/src/extension/effect.cpp +++ b/src/extension/effect.cpp @@ -58,10 +58,13 @@ Effect::Effect (Inkscape::XML::Node * in_repr, Implementation::Implementation * for (Inkscape::XML::Node *child = repr->firstChild(); child != nullptr; child = child->next()) { if (!strcmp(child->name(), INKSCAPE_EXTENSION_NS "effect")) { if (child->attribute("needs-document") && !strcmp(child->attribute("needs-document"), "false")) { - no_doc = true; + no_doc = true; } if (child->attribute("needs-live-preview") && !strcmp(child->attribute("needs-live-preview"), "false")) { - no_live_preview = true; + no_live_preview = true; + } + if (child->attribute("implements-custom-gui") && !strcmp(child->attribute("implements-custom-gui"), "true")) { + _workingDialog = false; } for (Inkscape::XML::Node *effect_child = child->firstChild(); effect_child != nullptr; effect_child = effect_child->next()) { if (!strcmp(effect_child->name(), INKSCAPE_EXTENSION_NS "effects-menu")) { @@ -269,7 +272,7 @@ Effect::effect (Inkscape::UI::View::View * doc) if (!loaded()) set_state(Extension::STATE_LOADED); if (!loaded()) return; - ExecutionEnv executionEnv(this, doc); + ExecutionEnv executionEnv(this, doc, nullptr, _workingDialog, true); execution_env = &executionEnv; timer->lock(); executionEnv.run(); diff --git a/src/extension/internal/pdfinput/pdf-input.cpp b/src/extension/internal/pdfinput/pdf-input.cpp index ed6af682e..2a3947ec1 100644 --- a/src/extension/internal/pdfinput/pdf-input.cpp +++ b/src/extension/internal/pdfinput/pdf-input.cpp @@ -62,6 +62,21 @@ #include <gdkmm/general.h> + +namespace { + +void sanitize_page_number(int &page_num, const int num_pages) { + if (page_num < 1 || page_num > num_pages) { + std::cerr << "Inkscape::Extension::Internal::PdfInput::open: Bad page number " + << page_num + << ". Import first page instead." + << std::endl; + page_num = 1; + } +} + +} + namespace Inkscape { namespace Extension { namespace Internal { @@ -788,6 +803,8 @@ PdfInput::open(::Inkscape::Extension::Input * /*mod*/, const gchar * uri) { sp_repr_get_double(prefs, "cropTo", &crop_setting); Catalog *catalog = pdf_doc->getCatalog(); + int const num_pages = catalog->getNumPages(); + sanitize_page_number(page_num, num_pages); Page *page = catalog->getPage(page_num); if ( crop_setting >= 0.0 ) { // Do page clipping @@ -872,6 +889,8 @@ PdfInput::open(::Inkscape::Extension::Input * /*mod*/, const gchar * uri) { if (document != NULL) { double width, height; + int const num_pages = poppler_document_get_n_pages(document); + sanitize_page_number(page_num, num_pages); PopplerPage* page = poppler_document_get_page(document, page_num - 1); poppler_page_get_size(page, &width, &height); diff --git a/src/inkscape-application.cpp b/src/inkscape-application.cpp index 3b034b06b..a019d8515 100644 --- a/src/inkscape-application.cpp +++ b/src/inkscape-application.cpp @@ -42,6 +42,10 @@ #include "actions/actions-selection.h" // Actions #include "actions/actions-transform.h" // Actions +#ifdef GDK_WINDOWING_QUARTZ +#include <gtkosxapplication.h> +#endif + #ifdef WITH_DBUS # include "extension/dbus/dbus-init.h" #endif @@ -66,7 +70,7 @@ InkscapeApplication::InkscapeApplication() , _active_document(nullptr) , _active_selection(nullptr) , _active_view(nullptr) - , _pdf_page(0) + , _pdf_page(1) , _pdf_poppler(false) {} @@ -572,6 +576,11 @@ ConcreteInkscapeApplication<Gio::Application>::on_startup2() Inkscape::Application::create(nullptr, false); } +#ifdef GDK_WINDOWING_QUARTZ +static gboolean osx_openfile_callback(GtkosxApplication *, gchar const *, + ConcreteInkscapeApplication<Gtk::Application> *); +#endif + template<> void ConcreteInkscapeApplication<Gtk::Application>::on_startup2() @@ -614,6 +623,11 @@ ConcreteInkscapeApplication<Gtk::Application>::on_startup2() } else { // set_app_menu(menu); } + +#ifdef GDK_WINDOWING_QUARTZ + GtkosxApplication *osxapp = gtkosx_application_get(); + g_signal_connect(G_OBJECT(osxapp), "NSApplicationOpenFile", G_CALLBACK(osx_openfile_callback), this); +#endif } /** We should not create a window if T is Gio::Applicaton. @@ -709,6 +723,20 @@ ConcreteInkscapeApplication<Gtk::Application>::create_window(const Glib::RefPtr< return (desktop); // Temp: Need to track desktop for shell mode. } +#ifdef GDK_WINDOWING_QUARTZ +/** + * On macOS, handle dropping files on Inkscape.app icon and "Open With" file association. + */ +static gboolean osx_openfile_callback(GtkosxApplication *osxapp, gchar const *path, + ConcreteInkscapeApplication<Gtk::Application> *app) +{ + auto ptr = Gio::File::create_for_path(path); + g_return_val_if_fail(ptr, false); + app->create_window(ptr); + return true; +} +#endif + /** No need to destroy window if T is Gio::Application. */ template<class T> diff --git a/src/io/resource-manager.cpp b/src/io/resource-manager.cpp index 1ae02a886..c5ac0ef45 100644 --- a/src/io/resource-manager.cpp +++ b/src/io/resource-manager.cpp @@ -423,8 +423,7 @@ bool ResourceManagerImpl::searchUpwards( std::string const &base, std::string co static ResourceManagerImpl* theInstance = nullptr; ResourceManager::ResourceManager() -{ -} += default; ResourceManager::~ResourceManager() = default; diff --git a/src/object/sp-rect.cpp b/src/object/sp-rect.cpp index 207d2fc5a..1ecfab558 100644 --- a/src/object/sp-rect.cpp +++ b/src/object/sp-rect.cpp @@ -19,9 +19,10 @@ #include "attributes.h" #include "style.h" #include "sp-rect.h" -#include <glibmm/i18n.h> #include "sp-guide.h" #include "preferences.h" +#include "svg/svg.h" +#include <glibmm/i18n.h> #define noRECT_VERBOSE @@ -193,6 +194,20 @@ const char* SPRect::displayName() const { #define C1 0.554 void SPRect::set_shape() { + if (hasBrokenPathEffect()) { + g_warning ("The spiral shape has unknown LPE on it! Convert to path to make it editable preserving the appearance; editing it as spiral will remove the bad LPE"); + + if (this->getRepr()->attribute("d")) { + // unconditionally read the curve from d, if any, to preserve appearance + Geom::PathVector pv = sp_svg_read_pathv(this->getRepr()->attribute("d")); + SPCurve *cold = new SPCurve(pv); + this->setCurveInsync(cold); + this->setCurveBeforeLPE( cold ); + cold->unref(); + } + + return; + } if ((this->height.computed < 1e-18) || (this->width.computed < 1e-18)) { this->setCurveInsync(nullptr); this->setCurveBeforeLPE(nullptr); @@ -259,13 +274,26 @@ void SPRect::set_shape() { c->lineto(x + w, y + h); c->lineto(x + 0.0, y + h); } - - c->closepath(); - this->setCurveInsync(c); - this->setCurveBeforeLPE(c); - - // LPE is not applied because result can generally not be represented as SPRect - + /* Reset the shape's curve to the "original_curve" + * This is very important for LPEs to work properly! (the bbox might be recalculated depending on the curve in shape)*/ + SPCurve * before = this->getCurveBeforeLPE(); + bool haslpe = this->hasPathEffectOnClipOrMaskRecursive(this); + if (before || haslpe) { + if (c && before && before->get_pathvector() != c->get_pathvector()){ + this->setCurveBeforeLPE(c); + sp_lpe_item_update_patheffect(this, true, false); + } else if(haslpe) { + this->setCurveBeforeLPE(c); + } else { + //This happends on undo, fix bug:#1791784 + this->setCurveInsync(c); + } + } else { + this->setCurveInsync(c); + } + if (before) { + before->unref(); + } c->unref(); } @@ -300,6 +328,10 @@ void SPRect::setRy(bool set, gdouble value) { this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); } +void SPRect::update_patheffect(bool write) { + SPShape::update_patheffect(write); +} + Geom::Affine SPRect::set_transform(Geom::Affine const& xform) { /* Calculate rect start in parent coords. */ Geom::Point pos(Geom::Point(this->x.computed, this->y.computed) * xform); diff --git a/src/object/sp-rect.h b/src/object/sp-rect.h index 39fac16f9..bbc9d3c69 100644 --- a/src/object/sp-rect.h +++ b/src/object/sp-rect.h @@ -57,7 +57,7 @@ public: Inkscape::XML::Node* write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, unsigned int flags) override; const char* displayName() const override; - + void update_patheffect(bool write) override; void set_shape() override; Geom::Affine set_transform(Geom::Affine const& xform) override; diff --git a/src/object/sp-shape.cpp b/src/object/sp-shape.cpp index d615980c0..eb4c01e54 100644 --- a/src/object/sp-shape.cpp +++ b/src/object/sp-shape.cpp @@ -121,6 +121,10 @@ void SPShape::update(SPCtx* ctx, guint flags) { // std::cout << "SPShape::update(): " << (getId()?getId():"null") << std::endl; SPLPEItem::update(ctx, flags); + // Any update can change the bounding box, + // so the cached version can no longer be used. + bbox_cache_is_valid = false; + /* This stanza checks that an object's marker style agrees with * the marker objects it has allocated. sp_shape_set_marker ensures * that the appropriate marker objects are present (or absent) to @@ -456,6 +460,13 @@ void SPShape::modified(unsigned int flags) { } Geom::OptRect SPShape::bbox(Geom::Affine const &transform, SPItem::BBoxType bboxtype) const { + // If the object is clipped, the update funcation that invalidates + // the cache doesn't get called if the object is moved, so we need + // to compare the transformations as well. + if (bbox_cache_is_valid && transform == bbox_transform_cache) { + return bbox_cache; + } + Geom::OptRect bbox; if (!this->_curve || this->_curve->get_pathvector().empty()) { @@ -641,6 +652,10 @@ Geom::OptRect SPShape::bbox(Geom::Affine const &transform, SPItem::BBoxType bbox } } + bbox_transform_cache = transform; + bbox_cache = bbox; + bbox_cache_is_valid = true; + return bbox; } diff --git a/src/object/sp-shape.h b/src/object/sp-shape.h index 6289be079..10d719648 100644 --- a/src/object/sp-shape.h +++ b/src/object/sp-shape.h @@ -49,6 +49,11 @@ public: int hasMarkers () const; int numberOfMarkers (int type) const; + // bbox cache + mutable bool bbox_cache_is_valid = false; + mutable Geom::Affine bbox_transform_cache; + mutable Geom::OptRect bbox_cache; + public: // temporarily public, until SPPath is properly classed, etc. SPCurve *_curve_before_lpe; diff --git a/src/seltrans.cpp b/src/seltrans.cpp index ff3804acb..c173c9474 100644 --- a/src/seltrans.cpp +++ b/src/seltrans.cpp @@ -677,8 +677,8 @@ void Inkscape::SelTrans::_makeHandles() knots[i]->setSize(13); knots[i]->setAnchor(hands[i].anchor); knots[i]->setMode(SP_CTRL_MODE_XOR); - knots[i]->setFill(info.color[0], info.color[1], info.color[2], info.color[1]); - knots[i]->setStroke(info.color[3], info.color[4], info.color[5], info.color[4]); + knots[i]->setFill(info.color[0], info.color[1], info.color[1], info.color[1]); + knots[i]->setStroke(info.color[2], info.color[3], info.color[3], info.color[3]); knots[i]->setPixbuf(handles[hands[i].control]); knots[i]->updateCtrl(); diff --git a/src/style.cpp b/src/style.cpp index c83b19230..17ad85dc3 100644 --- a/src/style.cpp +++ b/src/style.cpp @@ -720,6 +720,33 @@ SPStyle::readIfUnset(SPAttributeEnum id, gchar const *val, SPStyleSrc const &sou } } +// return if is seted property +bool SPStyle::isSet(SPAttributeEnum id) +{ + bool set = false; + switch (id) { + case SP_PROP_CLIP_PATH: + return set; + case SP_PROP_MASK: + return set; + case SP_PROP_FILTER: + if (!filter.inherit) + set = filter.set; + return set; + case SP_PROP_COLOR_INTERPOLATION: + // We read it but issue warning + return color_interpolation.set; + } + + auto p = _prop_helper.get(this, id); + if (p) { + return p->set; + } else { + g_warning("Unimplemented style property %d", id); + return set; + } +} + /** * Outputs the style to a CSS string. * @@ -881,16 +908,18 @@ SPStyle::_mergeDecl( CRDeclaration const *const decl, SPStyleSrc const &source * convert to string. Alternatively, set from CRTerm directly rather * than converting to string. */ - guchar *const str_value_unsigned = cr_term_to_string(decl->value); - gchar *const str_value = reinterpret_cast<gchar *>(str_value_unsigned); + if (!isSet(prop_idx) || decl->important) { + guchar *const str_value_unsigned = cr_term_to_string(decl->value); + gchar *const str_value = reinterpret_cast<gchar *>(str_value_unsigned); - // Add "!important" rule if necessary as this is not handled by cr_term_to_string(). - gchar const * important = decl->important ? " !important" : ""; - Inkscape::CSSOStringStream os; - os << str_value << important; + // Add "!important" rule if necessary as this is not handled by cr_term_to_string(). + gchar const *important = decl->important ? " !important" : ""; + Inkscape::CSSOStringStream os; + os << str_value << important; - readIfUnset( prop_idx, os.str().c_str(), source ); - g_free(str_value); + readIfUnset(prop_idx, os.str().c_str(), source); + g_free(str_value); + } } } diff --git a/src/style.h b/src/style.h index c9235f49a..bb0705f61 100644 --- a/src/style.h +++ b/src/style.h @@ -52,6 +52,7 @@ public: void read(SPObject *object, Inkscape::XML::Node *repr); void readFromObject(SPObject *object); void readFromPrefs(Glib::ustring const &path); + bool isSet(SPAttributeEnum id); void readIfUnset(SPAttributeEnum id, char const *val, SPStyleSrc const &source = SP_STYLE_SRC_STYLE_PROP ); Glib::ustring write( unsigned int const flags = SP_STYLE_FLAG_IFSET, SPStyleSrc const &style_src_req = SP_STYLE_SRC_STYLE_PROP, diff --git a/src/text-chemistry.cpp b/src/text-chemistry.cpp index 8a2433ec6..802f10a3f 100644 --- a/src/text-chemistry.cpp +++ b/src/text-chemistry.cpp @@ -334,9 +334,10 @@ text_flow_into_shape() } else { // SVG 1.2 Flowed Text - if (SP_IS_TEXT(text)) { + if (SP_IS_TEXT(text) || SP_IS_FLOWTEXT(text)) { // remove transform from text, but recursively scale text's fontsize by the expansion - SP_TEXT(text)->_adjustFontsizeRecursive(text, text->transform.descrim()); + auto ex = i2i_affine(text, shape->parent).descrim(); + SP_TEXT(text)->_adjustFontsizeRecursive(text, ex); text->getRepr()->setAttribute("transform", nullptr); } diff --git a/src/ui/clipboard.cpp b/src/ui/clipboard.cpp index f96fa607e..7460822c9 100644 --- a/src/ui/clipboard.cpp +++ b/src/ui/clipboard.cpp @@ -1100,9 +1100,6 @@ void ClipboardManagerImpl::_applyPathEffect(SPItem *item, gchar const *effectsta if ( item == nullptr ) { return; } - if ( dynamic_cast<SPRect *>(item) ) { - return; - } SPLPEItem *lpeitem = dynamic_cast<SPLPEItem *>(item); if (lpeitem) diff --git a/src/ui/dialog/align-and-distribute.cpp b/src/ui/dialog/align-and-distribute.cpp index c112f965d..a714b96f6 100644 --- a/src/ui/dialog/align-and-distribute.cpp +++ b/src/ui/dialog/align-and-distribute.cpp @@ -135,10 +135,10 @@ void ActionAlign::do_action(SPDesktop *desktop, int index) switch (AlignTarget(prefs->getInt("/dialogs/align/align-to", 6))) { case LAST: - focus = SP_ITEM(*--(selected.end())); + focus = SP_ITEM(selected.back()); break; case FIRST: - focus = SP_ITEM(*selected.begin()); + focus = SP_ITEM(selected.front()); break; case BIGGEST: focus = selection->largestItem(horiz); @@ -844,10 +844,10 @@ private : switch (AlignTarget(prefs->getInt("/dialogs/align/align-to", 6))) { case LAST: - focus = SP_ITEM(*selected.begin()); + focus = SP_ITEM(selected.back()); break; case FIRST: - focus = SP_ITEM(*--(selected.end())); + focus = SP_ITEM(selected.front()); break; case BIGGEST: focus = selection->largestItem(Selection::AREA); @@ -867,7 +867,7 @@ private : default: g_assert_not_reached (); break; - }; + }; if(focus) { if (SP_IS_TEXT (focus) || SP_IS_FLOWTEXT (focus)) { diff --git a/src/ui/dialog/attrdialog.cpp b/src/ui/dialog/attrdialog.cpp index ce24e3272..0403f9b7c 100644 --- a/src/ui/dialog/attrdialog.cpp +++ b/src/ui/dialog/attrdialog.cpp @@ -18,6 +18,7 @@ #include "message-context.h" #include "message-stack.h" +#include "style.h" #include "ui/icon-loader.h" #include "ui/widget/iconrenderer.h" @@ -234,7 +235,7 @@ void AttrDialog::onAttrChanged(Inkscape::XML::Node *repr, const gchar * name, co } } } - if (new_value) { + if (new_value && strcmp(new_value, "") != 0) { if ((repr->type() == Inkscape::XML::TEXT_NODE || repr->type() == Inkscape::XML::COMMENT_NODE) && strcmp(name, "content") != 0) { @@ -335,9 +336,16 @@ bool AttrDialog::onKeyPressed(GdkEventKey *event) */ void AttrDialog::nameEdited (const Glib::ustring& path, const Glib::ustring& name) { - Gtk::TreeModel::Row row = *_store->get_iter(path); + Gtk::TreeIter iter = *_store->get_iter(path); + Gtk::TreeModel::Path modelpath = (Gtk::TreeModel::Path)iter; + Gtk::TreeModel::Row row = *iter; if(row && this->_repr) { Glib::ustring old_name = row[_attrColumns._attributeName]; + if (old_name == name) { + _treeView.set_cursor(modelpath, *_valueCol, true); + grab_focus(); + return; + } if (old_name == "content" || old_name == name) { @@ -350,8 +358,10 @@ void AttrDialog::nameEdited (const Glib::ustring& path, const Glib::ustring& nam _repr->setAttribute(old_name.c_str(), nullptr, false); } if (!name.empty()) { - _repr->setAttribute(name.c_str(), value, false); row[_attrColumns._attributeName] = name; + _repr->setAttribute(name.c_str(), value, false); + _treeView.set_cursor(modelpath, *_valueCol, true); + grab_focus(); } this->setUndo(_("Rename attribute")); } diff --git a/src/ui/dialog/livepatheffect-add.cpp b/src/ui/dialog/livepatheffect-add.cpp index 5d5517149..a31f71a8c 100644 --- a/src/ui/dialog/livepatheffect-add.cpp +++ b/src/ui/dialog/livepatheffect-add.cpp @@ -125,7 +125,7 @@ LivePathEffectAdd::LivePathEffectAdd() sigc::mem_fun(*this, &LivePathEffectAdd::mouseout), GTK_WIDGET(LPESelectorEffect->gobj()))); Gtk::Label *LPEName; builder_effect->get_widget("LPEName", LPEName); - const Glib::ustring label = converter.get_label(data->id); + const Glib::ustring label = _(converter.get_label(data->id).c_str()); const Glib::ustring untranslated_label = converter.get_untranslated_label(data->id); if (untranslated_label == label) { LPEName->set_text(label); diff --git a/src/ui/dialog/livepatheffect-editor.cpp b/src/ui/dialog/livepatheffect-editor.cpp index 097078e56..8767a109f 100644 --- a/src/ui/dialog/livepatheffect-editor.cpp +++ b/src/ui/dialog/livepatheffect-editor.cpp @@ -35,7 +35,6 @@ #include "object/sp-item-group.h" #include "object/sp-path.h" -#include "object/sp-rect.h" #include "object/sp-use.h" #include "object/sp-text.h" @@ -407,27 +406,6 @@ LivePathEffectEditor::setDesktop(SPDesktop *desktop) } } -void -LivePathEffectEditor::rectsToCurves(SPItem* topitem, SPItem *item) -{ - Inkscape::Selection *sel = _getSelection(); - if ( dynamic_cast<SPRect *>(item) ) { - sel->clear(); - sel->set(item); - sel->toCurves(); - if (topitem == item) { - return; - } - } else if( SPGroup *group = dynamic_cast<SPGroup *>(item)){ - std::vector<SPItem*> const item_list = sp_item_group_item_list(group); - for (auto sub_item : item_list) { - rectsToCurves(topitem, sub_item); - } - } - sel->set(topitem); -} - - /*######################################################################## # BUTTON CLICK HANDLERS (callbacks) ########################################################################*/ @@ -455,7 +433,6 @@ LivePathEffectEditor::onAdd() if (!data) { return; } - rectsToCurves(item, item); item = sel->singleItem(); // get new item LivePathEffect::Effect::createAndApply(data->key.c_str(), doc, item); diff --git a/src/ui/dialog/livepatheffect-editor.h b/src/ui/dialog/livepatheffect-editor.h index 600754ce3..30524e483 100644 --- a/src/ui/dialog/livepatheffect-editor.h +++ b/src/ui/dialog/livepatheffect-editor.h @@ -71,7 +71,6 @@ private: void effect_list_reload(SPLPEItem *lpeitem); void set_sensitize_all(bool sensitive); - void rectsToCurves(SPItem *topitem, SPItem *item); void showParams(LivePathEffect::Effect& effect); void showText(Glib::ustring const &str); void selectInList(LivePathEffect::Effect* effect); diff --git a/src/ui/dialog/selectordialog.cpp b/src/ui/dialog/selectordialog.cpp index 243a33750..fefd23e51 100644 --- a/src/ui/dialog/selectordialog.cpp +++ b/src/ui/dialog/selectordialog.cpp @@ -52,17 +52,17 @@ namespace Dialog { // Keeps a watch on style element class SelectorDialog::NodeObserver : public Inkscape::XML::NodeObserver { public: - NodeObserver(SelectorDialog* selectorDialog) : - _selectorDialog(selectorDialog) - { - g_debug("SelectorDialog::NodeObserver: Constructor"); - }; + NodeObserver(SelectorDialog *selectordialog) + : _selectordialog(selectordialog) + { + g_debug("SelectorDialog::NodeObserver: Constructor"); + }; void notifyContentChanged(Inkscape::XML::Node &node, Inkscape::Util::ptr_shared old_content, Inkscape::Util::ptr_shared new_content) override; - SelectorDialog * _selectorDialog; + SelectorDialog *_selectordialog; }; @@ -73,9 +73,9 @@ SelectorDialog::NodeObserver::notifyContentChanged( Inkscape::Util::ptr_shared /*new_content*/ ) { g_debug("SelectorDialog::NodeObserver::notifyContentChanged"); - _selectorDialog->_updating = false; - _selectorDialog->_readStyleElement(); - _selectorDialog->_selectRow(); + _selectordialog->_updating = false; + _selectordialog->_readStyleElement(); + _selectordialog->_selectRow(); } @@ -83,19 +83,19 @@ SelectorDialog::NodeObserver::notifyContentChanged( // (Must update objects that selectors match.) class SelectorDialog::NodeWatcher : public Inkscape::XML::NodeObserver { public: - NodeWatcher(SelectorDialog* selectorDialog, Inkscape::XML::Node *repr) : - _selectorDialog(selectorDialog), - _repr(repr) - { - g_debug("SelectorDialog::NodeWatcher: Constructor"); - }; + NodeWatcher(SelectorDialog *selectordialog, Inkscape::XML::Node *repr) + : _selectordialog(selectordialog) + , _repr(repr) + { + g_debug("SelectorDialog::NodeWatcher: Constructor"); + }; void notifyChildAdded( Inkscape::XML::Node &/*node*/, Inkscape::XML::Node &child, Inkscape::XML::Node */*prev*/ ) override { - if ( _selectorDialog && _repr ) { - _selectorDialog->_nodeAdded( child ); + if (_selectordialog && _repr) { + _selectordialog->_nodeAdded(child); } } @@ -103,8 +103,8 @@ public: Inkscape::XML::Node &child, Inkscape::XML::Node */*prev*/ ) override { - if ( _selectorDialog && _repr ) { - _selectorDialog->_nodeRemoved( child ); + if (_selectordialog && _repr) { + _selectordialog->_nodeRemoved(child); } } @@ -112,7 +112,7 @@ public: GQuark qname, Util::ptr_shared /*old_value*/, Util::ptr_shared /*new_value*/ ) override { - if ( _selectorDialog && _repr ) { + if (_selectordialog && _repr) { // For the moment only care about attributes that are directly used in selectors. const gchar * cname = g_quark_to_string (qname ); @@ -122,12 +122,12 @@ public: } if ( name == "id" || name == "class" ) { - _selectorDialog->_nodeChanged( node ); + _selectordialog->_nodeChanged(node); } } } - SelectorDialog * _selectorDialog; + SelectorDialog *_selectordialog; Inkscape::XML::Node * _repr; // Need to track if document changes. }; @@ -246,6 +246,7 @@ SelectorDialog::SelectorDialog() : _store = TreeStore::create(this); _treeView.set_model(_store); + _treeView.set_headers_visible(true); _treeView.enable_model_drag_source(); _treeView.enable_model_drag_dest( Gdk::ACTION_MOVE ); @@ -264,7 +265,6 @@ SelectorDialog::SelectorDialog() : _mainBox.pack_start(_scrolledWindow, Gtk::PACK_EXPAND_WIDGET); _scrolledWindow.add(_treeView); _scrolledWindow.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC); - create = manage( new Gtk::Button() ); _styleButton(*create, "list-add", "Add a new CSS Selector"); create->signal_clicked().connect(sigc::mem_fun(*this, &SelectorDialog::_addSelector)); @@ -272,13 +272,11 @@ SelectorDialog::SelectorDialog() : del = manage( new Gtk::Button() ); _styleButton(*del, "list-remove", "Remove a CSS Selector"); del->signal_clicked().connect(sigc::mem_fun(*this, &SelectorDialog::_delSelector)); - del->set_sensitive(false); - + del->hide(); _mainBox.pack_end(_buttonBox, Gtk::PACK_SHRINK); _buttonBox.pack_start(*create, Gtk::PACK_SHRINK); _buttonBox.pack_start(*del, Gtk::PACK_SHRINK); - _getContents()->pack_start(_paned, Gtk::PACK_EXPAND_WIDGET); @@ -314,14 +312,13 @@ SelectorDialog::SelectorDialog() : _selectRow(); if (!_store->children().empty()) { - del->set_sensitive(true); + del->show(); } } /** - * @brief SelectorDialog::~SelectorDialog * Class destructor */ SelectorDialog::~SelectorDialog() @@ -334,7 +331,6 @@ SelectorDialog::~SelectorDialog() /** - * @brief SelectorDialog::_styleTextNode * @return Inkscape::XML::Node* pointing to a style element's text node. * Returns the style element's text node. If there is no style element, one is created. * Ditto for text node. @@ -390,7 +386,6 @@ Inkscape::XML::Node* SelectorDialog::_getStyleTextNode() /** - * @brief SelectorDialog::_readStyleElement * Fill the Gtk::TreeStore from the svg:style element. */ void SelectorDialog::_readStyleElement() @@ -412,10 +407,10 @@ void SelectorDialog::_readStyleElement() content.erase(std::remove(content.begin(), content.end(), '\n'), content.end()); // Remove comments (/* xxx */) - while(content.find("/*") != std::string::npos) { - size_t start = content.find("/*"); - content.erase(start, (content.find("*/", start) - start) +2); - } + /* while(content.find("/*") != std::string::npos) { + size_t start = content.find("/*"); + content.erase(start, (content.find("*\/", start) - start) +2); + } */ // First split into selector/value chunks. // An attempt to use Glib::Regex failed. A C++11 version worked but @@ -480,22 +475,28 @@ void SelectorDialog::_readStyleElement() colExpand = rowstatus.second; } } + std::vector<Glib::ustring> properties_data = Glib::Regex::split_simple(";", properties); Gtk::TreeModel::Row row = *(_store->append()); row[_mColumns._colSelector] = selector; row[_mColumns._colExpand] = colExpand; row[_mColumns._colType] = colType; row[_mColumns._colObj] = objVec; row[_mColumns._colProperties] = properties; + row[_mColumns._colVisible] = true; // Add as children, objects that match selector. for (auto &obj : objVec) { Gtk::TreeModel::Row childrow = *(_store->append(row->children())); childrow[_mColumns._colSelector] = "#" + Glib::ustring(obj->getId()); childrow[_mColumns._colExpand] = false; childrow[_mColumns._colType] = colType == UNHANDLED ? UNHANDLED : OBJECT; + ; childrow[_mColumns._colObj] = std::vector<SPObject *>(1, obj); childrow[_mColumns._colProperties] = ""; // Unused + childrow[_mColumns._colVisible] = true; // Unused } } + + _updating = false; } @@ -513,7 +514,6 @@ void SelectorDialog::_rowCollapse(const Gtk::TreeModel::iterator &iter, const Gt row[_mColumns._colExpand] = false; } /** - * @brief SelectorDialog::_writeStyleElement * Update the content of the style element as selectors (or objects) are added/removed. */ void SelectorDialog::_writeStyleElement() @@ -562,7 +562,6 @@ void SelectorDialog::_addWatcherRecursive(Inkscape::XML::Node *node) { } /** - * @brief SelectorDialog::_updateWatchers * Update the watchers on objects. */ void SelectorDialog::_updateWatchers() @@ -588,7 +587,6 @@ void SelectorDialog::_updateWatchers() /** - * @brief SelectorDialog::_addToSelector * @param row * Add selected objects on the desktop to the selector corresponding to 'row'. */ @@ -627,6 +625,7 @@ void SelectorDialog::_addToSelector(Gtk::TreeModel::Row row) childrow[_mColumns._colType] = OBJECT; childrow[_mColumns._colObj] = std::vector<SPObject *>(1, obj); childrow[_mColumns._colProperties] = ""; // Unused + childrow[_mColumns._colVisible] = true; // Unused } } } @@ -667,6 +666,7 @@ void SelectorDialog::_addToSelector(Gtk::TreeModel::Row row) childrow[_mColumns._colType] = OBJECT; childrow[_mColumns._colObj] = std::vector<SPObject *>(1, obj); childrow[_mColumns._colProperties] = ""; // Unused + childrow[_mColumns._colVisible] = true; // Unused } } } @@ -682,7 +682,6 @@ void SelectorDialog::_addToSelector(Gtk::TreeModel::Row row) /** - * @brief SelectorDialog::_removeFromSelector * @param row * Remove the object corresponding to 'row' from the parent selector. */ @@ -764,7 +763,6 @@ void SelectorDialog::_removeFromSelector(Gtk::TreeModel::Row row) /** - * @brief SelectorDialog::_getIdList * @param sel * @return This function returns a comma separated list of ids for objects in input vector. * It is used in creating an 'id' selector. It relies on objects having 'id's. @@ -783,13 +781,13 @@ Glib::ustring SelectorDialog::_getIdList(std::vector<SPObject*> sel) } /** - * @brief SelectorDialog::_getObjVec * @param selector: a valid CSS selector string. * @return objVec: a vector of pointers to SPObject's the selector matches. * Return a vector of all objects that selector matches. */ std::vector<SPObject *> SelectorDialog::_getObjVec(Glib::ustring selector) { + g_debug("SelectorDialog::_getObjVec: | %s |", selector.c_str()); std::vector<SPObject *> objVec; std::vector<Glib::ustring> tokensplus = Glib::Regex::split_simple("[,]+", selector); bool unhandled = false; @@ -805,7 +803,7 @@ std::vector<SPObject *> SelectorDialog::_getObjVec(Glib::ustring selector) { if (!unhandled) { objVec = SP_ACTIVE_DOCUMENT->getObjectsBySelector(selector); } - g_debug("SelectorDialog::_getObjVec: | %s |", selector.c_str()); + for (auto& obj: objVec) { g_debug(" %s", obj->getId() ? obj->getId() : "null"); } @@ -815,7 +813,6 @@ std::vector<SPObject *> SelectorDialog::_getObjVec(Glib::ustring selector) { /** - * @brief SelectorDialog::_insertClass * @param objs: list of objects to insert class * @param class: class to insert * Insert a class name into objects' 'class' attribute. @@ -849,7 +846,6 @@ void SelectorDialog::_insertClass(const std::vector<SPObject *>& objVec, const G /** - * @brief SelectorDialog::_selectObjects * @param eventX * @param eventY * This function selects objects in the drawing corresponding to the selector @@ -858,7 +854,6 @@ void SelectorDialog::_insertClass(const std::vector<SPObject *>& objVec, const G void SelectorDialog::_selectObjects(int eventX, int eventY) { g_debug("SelectorDialog::_selectObjects: %d, %d", eventX, eventY); - getDesktop()->selection->clear(); Gtk::TreeViewColumn *col = _treeView.get_column(1); Gtk::TreeModel::Path path; @@ -871,8 +866,8 @@ void SelectorDialog::_selectObjects(int eventX, int eventY) if (iter) { Gtk::TreeModel::Row row = *iter; Gtk::TreeModel::Children children = row.children(); - if (children.empty()) { - del->set_sensitive(true); + if (children.empty() || children.size() == 1) { + del->show(); } std::vector<SPObject *> objVec = row[_mColumns._colObj]; @@ -886,8 +881,6 @@ void SelectorDialog::_selectObjects(int eventX, int eventY) /** - * @brief SelectorDialog::_addSelector - * * This function opens a dialog to add a selector. The dialog is prefilled * with an 'id' selector containing a list of the id's of selected objects * or with a 'class' selector if no objects are selected. @@ -958,8 +951,7 @@ void SelectorDialog::_addSelector() * set to ".Class1" */ selectorValue = textEditPtr->get_text(); - - del->set_sensitive(true); + del->show(); std::vector<Glib::ustring> tokensplus = Glib::Regex::split_simple("[,]+", selectorValue); bool unhandled = false; bool partialinvalid = false; @@ -972,7 +964,6 @@ void SelectorDialog::_addSelector() if (firstWord != tok) { handled = false; } - if (!partialinvalid && (tok[0] == '.' || tok[0] == '#' || tok[0] == '*' || SPAttributeRelSVG::isSVGElement(tok))) { partialinvalid = false; @@ -1043,7 +1034,6 @@ void SelectorDialog::_addSelector() void SelectorDialog::_closeDialog(Gtk::Dialog *textDialogPtr) { textDialogPtr->response(Gtk::RESPONSE_OK); } /** - * @brief SelectorDialog::_delSelector * This function deletes selector when '-' at the bottom is clicked. * Note: If deleting a class selector, class attributes are NOT changed. */ @@ -1056,19 +1046,18 @@ void SelectorDialog::_delSelector() Gtk::TreeModel::iterator iter = refTreeSelection->get_selected(); if (iter) { Gtk::TreeModel::Row row = *iter; - if (!row.children().empty()) { + if (row.children().size() > 2) { return; } _updating = true; _store->erase(iter); _updating = false; _writeStyleElement(); - del->set_sensitive(false); + del->hide(); } } /** - * @brief SelectorDialog::_handleButtonEvent * @param event * @return * Handles the event when '+' button in front of a selector name is clicked or when a '-' button in @@ -1193,7 +1182,6 @@ SelectorDialog::_handleSelectionChanged() { /** - * @brief SelectorDialog::_buttonEventsSelectObjs * @param event * This function detects single or double click on a selector in any row. Clicking * on a selector selects the matching objects on the desktop. A double click will @@ -1204,7 +1192,7 @@ void SelectorDialog::_buttonEventsSelectObjs(GdkEventButton* event ) g_debug("SelectorDialog::_buttonEventsSelectObjs"); _treeView.get_selection()->set_mode(Gtk::SELECTION_SINGLE); _updating = true; - del->set_sensitive(true); + del->show(); if (event->type == GDK_BUTTON_RELEASE && event->button == 1) { int x = static_cast<int>(event->x); int y = static_cast<int>(event->y); @@ -1215,19 +1203,28 @@ void SelectorDialog::_buttonEventsSelectObjs(GdkEventButton* event ) /** - * @brief SelectorDialog::_selectRow * This function selects the row in treeview corresponding to an object selected * in the drawing. If more than one row matches, the first is chosen. */ void SelectorDialog::_selectRow() { g_debug("SelectorDialog::_selectRow: updating: %s", (_updating ? "true" : "false")); - del->set_sensitive(false); + del->hide(); + std::vector<Gtk::TreeModel::Path> selectedrows = _treeView.get_selection()->get_selected_rows(); + if (selectedrows.size() == 1) { + Gtk::TreeModel::Row row = *_store->get_iter(selectedrows[0]); + if (!row->parent() && row->children().size() < 2) { + del->show(); + } + } else if (selectedrows.size() == 0) { + del->show(); + } if (_updating || !getDesktop()) return; // Avoid updating if we have set row via dialog. if (SP_ACTIVE_DESKTOP != getDesktop()) { std::cerr << "SelectorDialog::_selectRow: SP_ACTIVE_DESKTOP != getDesktop()" << std::endl; return; } + _treeView.get_selection()->unselect_all(); Gtk::TreeModel::Children children = _store->children(); Inkscape::Selection* selection = getDesktop()->getSelection(); @@ -1235,12 +1232,15 @@ void SelectorDialog::_selectRow() if (!selection->isEmpty()) { obj = selection->objects().back(); } + for (auto row : children) { std::vector<SPObject *> objVec = row[_mColumns._colObj]; if (obj) { for (auto & i : objVec) { if (obj->getId() == i->getId()) { _treeView.get_selection()->select(row); + row[_mColumns._colVisible] = true; + break; } } } @@ -1250,9 +1250,7 @@ void SelectorDialog::_selectRow() } } - /** - * @brief SelectorDialog::_styleButton * @param btn * @param iconName * @param tooltip diff --git a/src/ui/dialog/selectordialog.h b/src/ui/dialog/selectordialog.h index 306169611..1702f087d 100644 --- a/src/ui/dialog/selectordialog.h +++ b/src/ui/dialog/selectordialog.h @@ -15,13 +15,14 @@ #ifndef SELECTORDIALOG_H #define SELECTORDIALOG_H -#include <ui/widget/panel.h> -#include <gtkmm/treeview.h> -#include <gtkmm/treestore.h> -#include <gtkmm/scrolledwindow.h> #include <gtkmm/dialog.h> -#include <gtkmm/treeselection.h> #include <gtkmm/paned.h> +#include <gtkmm/scrolledwindow.h> +#include <gtkmm/treemodelfilter.h> +#include <gtkmm/treeselection.h> +#include <gtkmm/treestore.h> +#include <gtkmm/treeview.h> +#include <ui/widget/panel.h> #include "ui/dialog/desktop-tracker.h" @@ -52,7 +53,6 @@ public: SelectorDialog operator=(SelectorDialog const &d) = delete; static SelectorDialog &getInstance() { return *new SelectorDialog(); } - private: // Monitor <style> element for changes. class NodeObserver; @@ -74,12 +74,14 @@ public: add(_colType); add(_colObj); add(_colProperties); + add(_colVisible); } Gtk::TreeModelColumn<Glib::ustring> _colSelector; // Selector or matching object id. Gtk::TreeModelColumn<bool> _colExpand; // Open/Close store row. Gtk::TreeModelColumn<gint> _colType; // Selector row or child object row. Gtk::TreeModelColumn<std::vector<SPObject *> > _colObj; // List of matching objects. Gtk::TreeModelColumn<Glib::ustring> _colProperties; // List of properties. + Gtk::TreeModelColumn<bool> _colVisible; // Make visible or not. }; ModelColumns _mColumns; @@ -98,16 +100,16 @@ public: void on_row_deleted(const TreeModel::Path& path) override; public: - static Glib::RefPtr<SelectorDialog::TreeStore> create(SelectorDialog *selectordialog); + static Glib::RefPtr<SelectorDialog::TreeStore> create(SelectorDialog *styledialog); private: SelectorDialog *_selectordialog; }; // TreeView - Gtk::TreeView _treeView; + Glib::RefPtr<Gtk::TreeModelFilter> _modelfilter; Glib::RefPtr<TreeStore> _store; - + Gtk::TreeView _treeView; // Widgets Gtk::Paned _paned; Gtk::Box _mainBox; diff --git a/src/ui/dialog/styledialog.cpp b/src/ui/dialog/styledialog.cpp index 93f699ec2..2e43838b2 100644 --- a/src/ui/dialog/styledialog.cpp +++ b/src/ui/dialog/styledialog.cpp @@ -13,187 +13,801 @@ */ #include "styledialog.h" - -#include "message-context.h" -#include "message-stack.h" +#include "attribute-rel-svg.h" +#include "attributes.h" +#include "document-undo.h" +#include "inkscape.h" +#include "io/resource.h" #include "selection.h" #include "style-internal.h" #include "style.h" - +#include "svg/svg-color.h" #include "ui/icon-loader.h" #include "ui/widget/iconrenderer.h" #include "verbs.h" - #include "xml/attribute-record.h" -#include "xml/node-event-vector.h" -#include <glibmm/i18n.h> +#include "xml/node-observer.h" + +#include <map> +#include <regex> +#include <utility> #include <glibmm/i18n.h> -static void on_attr_changed(Inkscape::XML::Node *repr, const gchar *name, const gchar * /*old_value*/, - const gchar *new_value, bool /*is_interactive*/, gpointer data) -{ - STYLE_DIALOG(data)->onAttrChanged(repr, name, new_value); -} +//#define DEBUG_STYLEDIALOG +//#define G_LOG_DOMAIN "STYLEDIALOG" -Inkscape::XML::NodeEventVector css_repr_events = { - nullptr, /* child_added */ - nullptr, /* child_removed */ - on_attr_changed, nullptr, /* content_changed */ - nullptr /* order_changed */ -}; +using Inkscape::DocumentUndo; +using Inkscape::Util::List; +using Inkscape::XML::AttributeRecord; + +/** + * This macro is used to remove spaces around selectors or any strings when + * parsing is done to update XML style element or row labels in this dialog. + */ +#define REMOVE_SPACES(x) \ + x.erase(0, x.find_first_not_of(' ')); \ + x.erase(x.find_last_not_of(' ') + 1); namespace Inkscape { namespace UI { namespace Dialog { +// Keeps a watch on style element +class StyleDialog::NodeObserver : public Inkscape::XML::NodeObserver { + public: + NodeObserver(StyleDialog *styledialog) + : _styledialog(styledialog) + { + g_debug("StyleDialog::NodeObserver: Constructor"); + }; + + void notifyContentChanged(Inkscape::XML::Node &node, Inkscape::Util::ptr_shared old_content, + Inkscape::Util::ptr_shared new_content) override; + + StyleDialog *_styledialog; +}; + + +void StyleDialog::NodeObserver::notifyContentChanged(Inkscape::XML::Node & /*node*/, + Inkscape::Util::ptr_shared /*old_content*/, + Inkscape::Util::ptr_shared /*new_content*/) +{ + + g_debug("StyleDialog::NodeObserver::notifyContentChanged"); + _styledialog->_updating = false; + _styledialog->_readStyleElement(); +} + + +// Keeps a watch for new/removed/changed nodes +// (Must update objects that selectors match.) +class StyleDialog::NodeWatcher : public Inkscape::XML::NodeObserver { + public: + NodeWatcher(StyleDialog *styledialog, Inkscape::XML::Node *repr) + : _styledialog(styledialog) + , _repr(repr) + { + g_debug("StyleDialog::NodeWatcher: Constructor"); + }; + + void notifyChildAdded(Inkscape::XML::Node & /*node*/, Inkscape::XML::Node &child, + Inkscape::XML::Node * /*prev*/) override + { + if (_styledialog && _repr) { + _styledialog->_nodeAdded(child); + } + } + + void notifyChildRemoved(Inkscape::XML::Node & /*node*/, Inkscape::XML::Node &child, + Inkscape::XML::Node * /*prev*/) override + { + if (_styledialog && _repr) { + _styledialog->_nodeRemoved(child); + } + } + /* void notifyContentChanged(Inkscape::XML::Node &node, + Inkscape::Util::ptr_shared old_content, + Inkscape::Util::ptr_shared new_content) override{ + if ( _styledialog && _repr && _textNode == node) { + _styledialog->_stylesheetChanged( node ); + } + }; + */ + void notifyAttributeChanged(Inkscape::XML::Node &node, GQuark qname, Util::ptr_shared /*old_value*/, + Util::ptr_shared /*new_value*/) override + { + if (_styledialog && _repr) { + + // For the moment only care about attributes that are directly used in selectors. + const gchar *cname = g_quark_to_string(qname); + Glib::ustring name; + if (cname) { + name = cname; + } + + if (name == "id" || name == "class" || name == "style") { + _styledialog->_nodeChanged(node); + } + SPObject *obj = SP_ACTIVE_DOCUMENT->getObjectById(node.attribute("id")); + if (obj) { + for (auto iter : obj->style->properties()) { + if (iter->name == name) { + obj->style->readFromObject(obj); + obj->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); + _styledialog->_nodeChanged(node); + break; + } + } + } + } + } + + StyleDialog *_styledialog; + Inkscape::XML::Node *_repr; // Need to track if document changes. +}; + +void StyleDialog::_nodeAdded(Inkscape::XML::Node &node) +{ + + StyleDialog::NodeWatcher *w = new StyleDialog::NodeWatcher(this, &node); + node.addObserver(*w); + _nodeWatchers.push_back(w); + + _readStyleElement(); +} + +void StyleDialog::_nodeRemoved(Inkscape::XML::Node &repr) +{ + + for (auto it = _nodeWatchers.begin(); it != _nodeWatchers.end(); ++it) { + if ((*it)->_repr == &repr) { + (*it)->_repr->removeObserver(**it); + _nodeWatchers.erase(it); + break; + } + } + _readStyleElement(); +} + +void StyleDialog::_nodeChanged(Inkscape::XML::Node &object) { _readStyleElement(); } + +/* void +StyleDialog::_stylesheetChanged( Inkscape::XML::Node &repr ) { + std::cout << "Style tag modified" << std::endl; + _readStyleElement(); +} */ + /** * Constructor - * A treeview whose each row corresponds to a CSS property of selector selected. - * New CSS property can be added by clicking '+' at bottom of the CSS pane. '-' - * in front of the CSS property row can be clicked to delete the CSS property. - * Besides clicking on an already selected property row makes the property editable - * and clicking 'Enter' updates the property with changes reflected in the - * drawing. + * A treeview and a set of two buttons are added to the dialog. _addSelector + * adds selectors to treeview. _delSelector deletes the selector from the dialog. + * Any addition/deletion of the selectors updates XML style element accordingly. */ StyleDialog::StyleDialog() - : UI::Widget::Panel("/dialogs/css", SP_VERB_DIALOG_CSS) - , _desktop(nullptr) - , _repr(nullptr) + : UI::Widget::Panel("/dialogs/style", SP_VERB_DIALOG_STYLE) + , _updating(false) + , _textNode(nullptr) + , _desktopTracker() { - set_size_request(20, 15); - _treeView.set_headers_visible(true); - auto _scrolledWindow = new Gtk::ScrolledWindow(); - _scrolledWindow->add(_treeView); - _scrolledWindow->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC); + g_debug("StyleDialog::StyleDialog"); + // Pack widgets + _mainBox.pack_start(_scrolledWindow, Gtk::PACK_EXPAND_WIDGET); + _scrolledWindow.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC); + _styleBox.set_orientation(Gtk::ORIENTATION_VERTICAL); + _styleBox.set_valign(Gtk::ALIGN_START); + _scrolledWindow.add(_styleBox); + Gtk::Box *alltoggler = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL)); + Gtk::Label *infotoggler = Gtk::manage(new Gtk::Label(_("Edit Full Stylesheet"))); + _all_css = Gtk::manage(new Gtk::Switch()); + _all_css->set_margin_right(5); + _all_css->set_margin_top(2); + _all_css->property_active().signal_changed().connect(sigc::mem_fun(*this, &StyleDialog::_reload)); + alltoggler->pack_start(*_all_css, false, false, 0); + alltoggler->pack_start(*infotoggler, false, false, 0); + _all_css->set_active(false); + _mainBox.pack_start(*alltoggler, false, false, 0); + _mainBox.set_orientation(Gtk::ORIENTATION_VERTICAL); - _store = Gtk::ListStore::create(_cssColumns); - _treeView.set_model(_store); + _getContents()->pack_start(_mainBox, Gtk::PACK_EXPAND_WIDGET); + _all_css->get_style_context()->add_class("stylesheettoggler"); + // Document & Desktop + _desktop_changed_connection = + _desktopTracker.connectDesktopChanged(sigc::mem_fun(*this, &StyleDialog::_handleDesktopChanged)); + _desktopTracker.connect(GTK_WIDGET(gobj())); - Inkscape::UI::Widget::IconRenderer *addRenderer = manage(new Inkscape::UI::Widget::IconRenderer()); - addRenderer->add_icon("edit-delete"); - addRenderer->signal_activated().connect(sigc::mem_fun(*this, &StyleDialog::onPropertyDelete)); + _document_replaced_connection = + getDesktop()->connectDocumentReplaced(sigc::mem_fun(this, &StyleDialog::_handleDocumentReplaced)); - _message_stack = std::make_shared<Inkscape::MessageStack>(); - _message_context = std::unique_ptr<Inkscape::MessageContext>(new Inkscape::MessageContext(_message_stack)); - _message_changed_connection = - _message_stack->connectChanged(sigc::bind(sigc::ptr_fun(_set_status_message), GTK_WIDGET(status.gobj()))); + _selection_changed_connection = getDesktop()->getSelection()->connectChanged( + sigc::hide(sigc::mem_fun(this, &StyleDialog::_handleSelectionChanged))); + // Add watchers + _updateWatchers(); - int addCol = _treeView.append_column("", *addRenderer) - 1; - Gtk::TreeViewColumn *col = _treeView.get_column(addCol); - if (col) { - col->add_attribute(addRenderer->property_visible(), _cssColumns.deleteButton); - col->set_sort_column(_cssColumns.deleteButton); - - Gtk::Image *add_icon = Gtk::manage(sp_get_icon_image("list-add", Gtk::ICON_SIZE_SMALL_TOOLBAR)); - col->set_clickable(true); - col->set_widget(*add_icon); - add_icon->set_tooltip_text(_("Add a new style property")); - add_icon->show(); - // This gets the GtkButton inside the GtkBox, inside the GtkAlignment, inside the GtkImage icon. - auto button = add_icon->get_parent()->get_parent()->get_parent(); - // Assign the button event so that create happens BEFORE delete. If this code - // isn't in this exact way, the onAttrDelete is called when the header lines are pressed. - button->signal_button_release_event().connect(sigc::mem_fun(*this, &StyleDialog::onPropertyCreate), false); - } - - Gtk::CellRendererText *renderer = Gtk::manage(new Gtk::CellRendererText()); - _treeView.set_reorderable(false); - renderer->property_editable() = true; - int nameColNum = _treeView.append_column("Property", *renderer) - 1; - _propCol = _treeView.get_column(nameColNum); - if (_propCol) { - _propCol->add_attribute(renderer->property_text(), _cssColumns.label); - _propCol->add_attribute(renderer->property_foreground_rgba(), _cssColumns.label_color); - _propCol->set_sort_column(_cssColumns.label); - } - - renderer = Gtk::manage(new Gtk::CellRendererText()); - renderer->property_editable() = false; - int attrColNum = _treeView.append_column("Set", *renderer) - 1; - _attrCol = _treeView.get_column(attrColNum); - if (_attrCol) { - _attrCol->add_attribute(renderer->property_text(), _cssColumns._styleAttrVal); - _attrCol->add_attribute(renderer->property_foreground_rgba(), _cssColumns.attr_color); - _attrCol->add_attribute(renderer->property_strikethrough(), _cssColumns.attr_strike); - _attrCol->add_attribute(renderer->property_editable(), _cssColumns.editable); - _attrCol->set_sort_column(_cssColumns._styleAttrVal); - } - - renderer = Gtk::manage(new Gtk::CellRendererText()); - renderer->property_editable() = true; - int sheetColNum = _treeView.append_column("Actual", *renderer) - 1; - _sheetCol = _treeView.get_column(sheetColNum); - if (_sheetCol) { - _sheetCol->add_attribute(renderer->property_text(), _cssColumns._styleSheetVal); - _sheetCol->add_attribute(renderer->property_foreground_rgba(), _cssColumns.label_color); - _sheetCol->set_sort_column(_cssColumns._styleSheetVal); - } - - // Set the initial sort column (and direction) to place real attributes at the top. - _store->set_sort_column(_cssColumns.deleteButton, Gtk::SORT_DESCENDING); - - _getContents()->pack_start(*_scrolledWindow, Gtk::PACK_EXPAND_WIDGET); - - css_reset_context(0); - setDesktop(getDesktop()); + // Load tree + _readStyleElement(); } /** - * @brief StyleDialog::~StyleDialog * Class destructor */ StyleDialog::~StyleDialog() { - setDesktop(nullptr); - _repr = nullptr; - _message_changed_connection.disconnect(); - _message_context = nullptr; - _message_stack = nullptr; - _message_changed_connection.~connection(); + g_debug("StyleDialog::~StyleDialog"); + _desktop_changed_connection.disconnect(); + _document_replaced_connection.disconnect(); + _selection_changed_connection.disconnect(); } -void StyleDialog::_set_status_message(Inkscape::MessageType /*type*/, const gchar *message, GtkWidget *widget) +void StyleDialog::_reload() { _readStyleElement(); } + +/** + * @return Inkscape::XML::Node* pointing to a style element's text node. + * Returns the style element's text node. If there is no style element, one is created. + * Ditto for text node. + */ +Inkscape::XML::Node *StyleDialog::_getStyleTextNode() { - if (widget) { - gtk_label_set_markup(GTK_LABEL(widget), message ? message : ""); + + Inkscape::XML::Node *styleNode = nullptr; + Inkscape::XML::Node *textNode = nullptr; + + Inkscape::XML::Node *root = SP_ACTIVE_DOCUMENT->getReprRoot(); + for (unsigned i = 0; i < root->childCount(); ++i) { + if (Glib::ustring(root->nthChild(i)->name()) == "svg:style") { + + styleNode = root->nthChild(i); + + for (unsigned j = 0; j < styleNode->childCount(); ++j) { + if (styleNode->nthChild(j)->type() == Inkscape::XML::TEXT_NODE) { + textNode = styleNode->nthChild(j); + } + } + + if (textNode == nullptr) { + // Style element found but does not contain text node! + std::cerr << "StyleDialog::_getStyleTextNode(): No text node!" << std::endl; + textNode = SP_ACTIVE_DOCUMENT->getReprDoc()->createTextNode(""); + styleNode->appendChild(textNode); + Inkscape::GC::release(textNode); + } + } } + + if (styleNode == nullptr) { + // Style element not found, create one + styleNode = SP_ACTIVE_DOCUMENT->getReprDoc()->createElement("svg:style"); + textNode = SP_ACTIVE_DOCUMENT->getReprDoc()->createTextNode(""); + + styleNode->appendChild(textNode); + Inkscape::GC::release(textNode); + + root->addChild(styleNode, nullptr); + Inkscape::GC::release(styleNode); + } + + if (_textNode != textNode) { + _textNode = textNode; + NodeObserver *no = new NodeObserver(this); + textNode->addObserver(*no); + } + + return textNode; } +Glib::RefPtr<Gtk::TreeModel> StyleDialog::_selectTree(Glib::ustring selector) +{ + Gtk::Label *selectorlabel; + Glib::RefPtr<Gtk::TreeModel> model; + for (auto fullstyle : _styleBox.get_children()) { + Gtk::Box *style = dynamic_cast<Gtk::Box *>(fullstyle); + for (auto stylepart : style->get_children()) { + switch (style->child_property_position(*stylepart)) { + case 0: { + Gtk::Box *selectorbox = dynamic_cast<Gtk::Box *>(stylepart); + for (auto styleheader : selectorbox->get_children()) { + if (!selectorbox->child_property_position(*styleheader)) { + selectorlabel = dynamic_cast<Gtk::Label *>(styleheader); + } + } + break; + } + case 1: { + Glib::ustring wdg_selector = selectorlabel->get_text(); + if (wdg_selector == selector) { + Gtk::TreeView *treeview = dynamic_cast<Gtk::TreeView *>(stylepart); + if (treeview) { + return treeview->get_model(); + } + } + break; + } + default: + break; + } + } + } + return model; +} /** - * @brief StyleDialog::setDesktop - * @param desktop - * This function sets the 'desktop' for the CSS pane. + * Fill the Gtk::TreeStore from the svg:style element. */ -void StyleDialog::setDesktop(SPDesktop* desktop) +void StyleDialog::_readStyleElement() +{ + g_debug("StyleDialog::_readStyleElement"); + + if (_updating) + return; // Don't read if we wrote style element. + _updating = true; + + Inkscape::XML::Node *textNode = _getStyleTextNode(); + if (textNode == nullptr) { + std::cerr << "StyleDialog::_readStyleElement: No text node!" << std::endl; + } + + // Get content from style text node. + std::string content = (textNode->content() ? textNode->content() : ""); + + // Remove end-of-lines (check it works on Windoze). + content.erase(std::remove(content.begin(), content.end(), '\n'), content.end()); + + // Remove comments (/* xxx */) + + bool breakme = false; + size_t start = content.find("/*"); + size_t open = content.find("{", start + 1); + size_t close = content.find("}", start + 1); + size_t end = content.find("*/", close + 1); + while (!breakme) { + if (open == std::string::npos || close == std::string::npos || end == std::string::npos) { + breakme = true; + break; + } + while (open < close) { + open = content.find("{", close + 1); + close = content.find("}", close + 1); + end = content.find("*/", close + 1); + size_t reopen = content.find("{", close + 1); + if (open == std::string::npos || end == std::string::npos || end < reopen) { + if (end < reopen) { + content = content.erase(start, end - start + 2); + } else { + breakme = true; + } + break; + } + } + start = content.find("/*", start + 1); + open = content.find("{", start + 1); + close = content.find("}", start + 1); + end = content.find("*/", close + 1); + } + + // First split into selector/value chunks. + // An attempt to use Glib::Regex failed. A C++11 version worked but + // reportedly has problems on Windows. Using split_simple() is simpler + // and probably faster. + // + // Glib::RefPtr<Glib::Regex> regex1 = + // Glib::Regex::create("([^\\{]+)\\{([^\\{]+)\\}"); + // + // Glib::MatchInfo minfo; + // regex1->match(content, minfo); + + // Split on curly brackets. Even tokens are selectors, odd are values. + std::vector<Glib::ustring> tokens = Glib::Regex::split_simple("[}{]", content); + _ownerStyle.clear(); + // If text node is empty, return (avoids problem with negative below). + + for (auto child : _styleBox.get_children()) { + _styleBox.remove(*child); + delete child; + } + Inkscape::Selection *selection = getDesktop()->getSelection(); + SPObject *obj = nullptr; + if (selection->objects().size() == 1) { + obj = selection->objects().back(); + } + + Glib::ustring gladefile = get_filename(Inkscape::IO::Resource::UIS, "dialog-css.ui"); + Glib::RefPtr<Gtk::Builder> _builder; + try { + _builder = Gtk::Builder::create_from_file(gladefile); + } catch (const Glib::Error &ex) { + g_warning("Glade file loading failed for filter effect dialog"); + return; + } + gint selectorpos = 0; + Gtk::Box *css_selector_container; + _builder->get_widget("CSSSelectorContainer", css_selector_container); + Gtk::Label *css_selector; + _builder->get_widget("CSSSelector", css_selector); + Gtk::EventBox *css_selector_event_add; + _builder->get_widget("CSSSelectorEventAdd", css_selector_event_add); + css_selector_event_add->add_events(Gdk::BUTTON_RELEASE_MASK); + css_selector->set_text("element"); + Gtk::TreeView *css_tree; + _builder->get_widget("CSSTree", css_tree); + Glib::RefPtr<Gtk::TreeStore> store = Gtk::TreeStore::create(_mColumns); + css_tree->set_model(store); + css_selector_event_add->signal_button_release_event().connect( + sigc::bind<Glib::RefPtr<Gtk::TreeStore>, Gtk::TreeView *, Glib::ustring, gint>( + sigc::mem_fun(*this, &StyleDialog::_addRow), store, css_tree, "style_properties", selectorpos)); + Inkscape::UI::Widget::IconRenderer *addRenderer = manage(new Inkscape::UI::Widget::IconRenderer()); + addRenderer->add_icon("edit-delete"); + int addCol = css_tree->append_column("Delete row", *addRenderer) - 1; + Gtk::TreeViewColumn *col = css_tree->get_column(addCol); + if (col) { + addRenderer->signal_activated().connect( + sigc::bind<Glib::RefPtr<Gtk::TreeStore>>(sigc::mem_fun(*this, &StyleDialog::_onPropDelete), store)); + } + Gtk::CellRendererText *label = Gtk::manage(new Gtk::CellRendererText()); + label->property_placeholder_text() = _("property"); + label->property_editable() = true; + label->signal_edited().connect(sigc::bind<Glib::RefPtr<Gtk::TreeStore>, Gtk::TreeView *>( + sigc::mem_fun(*this, &StyleDialog::_nameEdited), store, css_tree)); + label->signal_editing_started().connect(sigc::mem_fun(*this, &StyleDialog::_startNameEdit)); + addCol = css_tree->append_column("CSS Property", *label) - 1; + col = css_tree->get_column(addCol); + if (col) { + col->add_attribute(label->property_text(), _mColumns._colName); + } + Gtk::CellRendererText *value = Gtk::manage(new Gtk::CellRendererText()); + value->property_placeholder_text() = _("value"); + value->property_editable() = true; + value->signal_edited().connect( + sigc::bind<Glib::RefPtr<Gtk::TreeStore>>(sigc::mem_fun(*this, &StyleDialog::_valueEdited), store)); + value->signal_editing_started().connect( + sigc::bind<Glib::RefPtr<Gtk::TreeStore>>(sigc::mem_fun(*this, &StyleDialog::_startValueEdit), store)); + addCol = css_tree->append_column("CSS Value", *value) - 1; + col = css_tree->get_column(addCol); + if (col) { + col->add_attribute(value->property_text(), _mColumns._colValue); + col->add_attribute(value->property_strikethrough(), _mColumns._colStrike); + } + std::map<Glib::ustring, Glib::ustring> attr_prop; + if (obj || _all_css->get_active()) { + Gtk::TreeModel::Path path; + if (!_all_css->get_active() && obj && obj->getRepr()->attribute("style")) { + Glib::ustring style = obj->getRepr()->attribute("style"); + attr_prop = parseStyle(style); + for (auto iter : obj->style->properties()) { + if (attr_prop.count(iter->name)) { + Gtk::TreeModel::Row row = *(store->append()); + row[_mColumns._colSelector] = "style_properties"; + row[_mColumns._colSelectorPos] = 0; + row[_mColumns._colActive] = true; + row[_mColumns._colName] = iter->name; + row[_mColumns._colValue] = iter->get_value(); + row[_mColumns._colStrike] = false; + row[_mColumns._colOwner] = Glib::ustring("Value active"); + _addOwnerStyle(iter->name, "style attribute"); + } + } + _styleBox.pack_start(*css_selector_container, Gtk::PACK_EXPAND_WIDGET); + } + selectorpos++; + if (tokens.size() == 0) { + _updating = false; + return; + } + for (unsigned i = 0; i < tokens.size() - 1; i += 2) { + Glib::ustring selector = tokens[i]; + REMOVE_SPACES(selector); // Remove leading/trailing spaces + std::vector<Glib::ustring> tokensplus = Glib::Regex::split_simple("[,]+", selector); + for (auto tok : tokensplus) { + REMOVE_SPACES(tok); + } + // Get list of objects selector matches + std::vector<SPObject *> objVec = _getObjVec(selector); + if (!_all_css->get_active()) { + bool stop = true; + for (auto objel : objVec) { + if (objel->getId() == obj->getId()) { + stop = false; + } + } + if (stop) { + _updating = false; + selectorpos++; + continue; + } + } + Glib::ustring properties; + // Check to make sure we do have a value to match selector. + if ((i + 1) < tokens.size()) { + properties = tokens[i + 1]; + } else { + std::cerr << "StyleDialog::_readStyleElement: Missing values " + "for last selector!" + << std::endl; + } + Glib::RefPtr<Gtk::Builder> _builder; + try { + _builder = Gtk::Builder::create_from_file(gladefile); + } catch (const Glib::Error &ex) { + g_warning("Glade file loading failed for filter effect dialog"); + return; + } + Gtk::Box *css_selector_container; + _builder->get_widget("CSSSelectorContainer", css_selector_container); + Gtk::Label *css_selector; + _builder->get_widget("CSSSelector", css_selector); + Gtk::EventBox *css_selector_event_add; + _builder->get_widget("CSSSelectorEventAdd", css_selector_event_add); + css_selector_event_add->add_events(Gdk::BUTTON_RELEASE_MASK); + css_selector->set_text(selector); + Gtk::TreeView *css_tree; + _builder->get_widget("CSSTree", css_tree); + Glib::RefPtr<Gtk::TreeStore> store = Gtk::TreeStore::create(_mColumns); + css_tree->set_model(store); + Inkscape::UI::Widget::IconRenderer *addRenderer = manage(new Inkscape::UI::Widget::IconRenderer()); + addRenderer->add_icon("edit-delete"); + int addCol = css_tree->append_column("Delete row", *addRenderer) - 1; + Gtk::TreeViewColumn *col = css_tree->get_column(addCol); + if (col) { + addRenderer->signal_activated().connect( + sigc::bind<Glib::RefPtr<Gtk::TreeStore>>(sigc::mem_fun(*this, &StyleDialog::_onPropDelete), store)); + } + Gtk::CellRendererToggle *active = Gtk::manage(new Gtk::CellRendererToggle); + addCol = css_tree->append_column("Active Property", *active) - 1; + col = css_tree->get_column(addCol); + if (col) { + col->add_attribute(active->property_active(), _mColumns._colActive); + active->signal_toggled().connect(sigc::bind<Glib::RefPtr<Gtk::TreeStore>>( + sigc::mem_fun(*this, &StyleDialog::_activeToggled), store)); + } + Gtk::CellRendererText *label = Gtk::manage(new Gtk::CellRendererText()); + label->property_placeholder_text() = _("property"); + label->property_editable() = true; + label->signal_edited().connect(sigc::bind<Glib::RefPtr<Gtk::TreeStore>, Gtk::TreeView *>( + sigc::mem_fun(*this, &StyleDialog::_nameEdited), store, css_tree)); + label->signal_editing_started().connect(sigc::mem_fun(*this, &StyleDialog::_startNameEdit)); + addCol = css_tree->append_column("CSS Selector", *label) - 1; + col = css_tree->get_column(addCol); + if (col) { + col->add_attribute(label->property_text(), _mColumns._colName); + } + Gtk::CellRendererText *value = Gtk::manage(new Gtk::CellRendererText()); + value->property_editable() = true; + value->property_placeholder_text() = _("value"); + value->signal_edited().connect( + sigc::bind<Glib::RefPtr<Gtk::TreeStore>>(sigc::mem_fun(*this, &StyleDialog::_valueEdited), store)); + value->signal_editing_started().connect( + sigc::bind<Glib::RefPtr<Gtk::TreeStore>>(sigc::mem_fun(*this, &StyleDialog::_startValueEdit), store)); + addCol = css_tree->append_column("CSS Value", *value) - 1; + col = css_tree->get_column(addCol); + if (col) { + col->add_attribute(value->property_text(), _mColumns._colValue); + col->add_attribute(value->property_strikethrough(), _mColumns._colStrike); + } + Glib::ustring style = properties; + Glib::ustring comments = ""; + while (style.find("/*") != std::string::npos) { + size_t beg = style.find("/*"); + size_t end = style.find("*/"); + if (end != std::string::npos && beg != std::string::npos) { + comments = comments.append(style, beg + 2, end - beg - 2); + style = style.erase(beg, end - beg + 2); + } + } + std::map<Glib::ustring, Glib::ustring> attr_prop_styleshet = parseStyle(style); + css_selector_event_add->signal_button_release_event().connect( + sigc::bind<Glib::RefPtr<Gtk::TreeStore>, Gtk::TreeView *, Glib::ustring, gint>( + sigc::mem_fun(*this, &StyleDialog::_addRow), store, css_tree, selector, selectorpos)); + if (!_all_css->get_active()) { + for (auto iter : obj->style->properties()) { + if (iter->style_src != SP_STYLE_SRC_UNSET) { + if (attr_prop_styleshet.count(iter->name)) { + Gtk::TreeIter iterstore = store->append(); + Gtk::TreeModel::Path path = (Gtk::TreeModel::Path)iterstore; + Gtk::TreeModel::Row row = *(iterstore); + row[_mColumns._colSelector] = selector; + row[_mColumns._colSelectorPos] = selectorpos; + row[_mColumns._colActive] = true; + row[_mColumns._colName] = iter->name; + row[_mColumns._colValue] = attr_prop_styleshet[iter->name]; + const Glib::ustring value = row[_mColumns._colValue]; + guint32 r1 = 0; // if there's no color, return black + r1 = sp_svg_read_color(value.c_str(), r1); + guint32 r2 = 0; // if there's no color, return black + r2 = sp_svg_read_color(iter->get_value().c_str(), r2); + if (attr_prop.count(iter->name) || + (value != iter->get_value() && ((r1 & 0x000000ff) == 0 || r1 != r2))) { + row[_mColumns._colStrike] = true; + row[_mColumns._colOwner] = Glib::ustring(""); + } else { + row[_mColumns._colStrike] = false; + row[_mColumns._colOwner] = Glib::ustring("Value active"); + _addOwnerStyle(iter->name, selector); + } + } + } + } + } else { + for (auto iter : attr_prop_styleshet) { + Gtk::TreeModel::Row row = *(store->append()); + row[_mColumns._colSelector] = selector; + row[_mColumns._colSelectorPos] = selectorpos; + row[_mColumns._colActive] = true; + row[_mColumns._colName] = iter.first; + row[_mColumns._colValue] = iter.second; + row[_mColumns._colStrike] = false; + row[_mColumns._colOwner] = Glib::ustring("Stylesheet value"); + } + } + std::map<Glib::ustring, Glib::ustring> attr_prop_styleshet_comments = parseStyle(comments); + + for (auto iter : attr_prop_styleshet_comments) { + if (!attr_prop_styleshet.count(iter.first)) { + Gtk::TreeIter iterstore = store->append(); + Gtk::TreeModel::Path path = (Gtk::TreeModel::Path)iterstore; + Gtk::TreeModel::Row row = *(iterstore); + row[_mColumns._colSelector] = selector; + row[_mColumns._colSelectorPos] = selectorpos; + row[_mColumns._colActive] = false; + row[_mColumns._colName] = iter.first; + row[_mColumns._colValue] = iter.second; + row[_mColumns._colStrike] = true; + Glib::ustring tooltiptext = _("This value is comented"); + row[_mColumns._colOwner] = tooltiptext; + } + } + _styleBox.pack_start(*css_selector_container, Gtk::PACK_EXPAND_WIDGET); + selectorpos++; + } + try { + _builder = Gtk::Builder::create_from_file(gladefile); + } catch (const Glib::Error &ex) { + g_warning("Glade file loading failed for filter effect dialog"); + return; + } + _builder->get_widget("CSSSelector", css_selector); + css_selector->set_text("element.attributes"); + _builder->get_widget("CSSSelectorContainer", css_selector_container); + _builder->get_widget("CSSSelectorEventAdd", css_selector_event_add); + css_selector_event_add->add_events(Gdk::BUTTON_RELEASE_MASK); + store = Gtk::TreeStore::create(_mColumns); + _builder->get_widget("CSSTree", css_tree); + css_tree->set_model(store); + css_selector_event_add->signal_button_release_event().connect( + sigc::bind<Glib::RefPtr<Gtk::TreeStore>, Gtk::TreeView *, Glib::ustring, gint>( + sigc::mem_fun(*this, &StyleDialog::_addRow), store, css_tree, "attributes", selectorpos)); + bool hasattributes = false; + if (!_all_css->get_active()) { + for (auto iter : obj->style->properties()) { + if (iter->style_src != SP_STYLE_SRC_UNSET) { + if (iter->name != "font" && iter->name != "d" && iter->name != "marker") { + const gchar *attr = obj->getRepr()->attribute(iter->name.c_str()); + if (attr) { + if (!hasattributes) { + Inkscape::UI::Widget::IconRenderer *addRenderer = + manage(new Inkscape::UI::Widget::IconRenderer()); + addRenderer->add_icon("edit-delete"); + int addCol = css_tree->append_column("Delete row", *addRenderer) - 1; + Gtk::TreeViewColumn *col = css_tree->get_column(addCol); + if (col) { + addRenderer->signal_activated().connect(sigc::bind<Glib::RefPtr<Gtk::TreeStore>>( + sigc::mem_fun(*this, &StyleDialog::_onPropDelete), store)); + } + Gtk::CellRendererText *label = Gtk::manage(new Gtk::CellRendererText()); + label->property_placeholder_text() = _("property"); + label->property_editable() = true; + label->signal_edited().connect( + sigc::bind<Glib::RefPtr<Gtk::TreeStore>, Gtk::TreeView *>( + sigc::mem_fun(*this, &StyleDialog::_nameEdited), store, css_tree)); + label->signal_editing_started().connect( + sigc::mem_fun(*this, &StyleDialog::_startNameEdit)); + addCol = css_tree->append_column("CSS Property", *label) - 1; + col = css_tree->get_column(addCol); + if (col) { + col->add_attribute(label->property_text(), _mColumns._colName); + } + Gtk::CellRendererText *value = Gtk::manage(new Gtk::CellRendererText()); + value->property_placeholder_text() = _("value"); + value->property_editable() = true; + value->signal_edited().connect(sigc::bind<Glib::RefPtr<Gtk::TreeStore>>( + sigc::mem_fun(*this, &StyleDialog::_valueEdited), store)); + value->signal_editing_started().connect(sigc::bind<Glib::RefPtr<Gtk::TreeStore>>( + sigc::mem_fun(*this, &StyleDialog::_startValueEdit), store)); + + addCol = css_tree->append_column("CSS Value", *value) - 1; + col = css_tree->get_column(addCol); + if (col) { + col->add_attribute(value->property_text(), _mColumns._colValue); + col->add_attribute(value->property_strikethrough(), _mColumns._colStrike); + } + } + Gtk::TreeIter iterstore = store->append(); + Gtk::TreeModel::Path path = (Gtk::TreeModel::Path)iterstore; + Gtk::TreeModel::Row row = *(iterstore); + row[_mColumns._colSelector] = "attributes"; + row[_mColumns._colSelectorPos] = selectorpos; + row[_mColumns._colActive] = true; + row[_mColumns._colName] = iter->name; + row[_mColumns._colValue] = attr; + if (_ownerStyle.find(iter->name) != _ownerStyle.end()) { + row[_mColumns._colStrike] = true; + Glib::ustring tooltiptext = Glib::ustring(""); + row[_mColumns._colOwner] = tooltiptext; + } else { + row[_mColumns._colStrike] = false; + row[_mColumns._colOwner] = Glib::ustring("Value active"); + _addOwnerStyle(iter->name, "inline attributes"); + } + hasattributes = true; + } + } + } + } + if (!hasattributes) { + for (auto widg : css_selector_container->get_children()) { + delete widg; + } + } + _styleBox.pack_start(*css_selector_container, Gtk::PACK_EXPAND_WIDGET); + } + for (auto selector : _styleBox.get_children()) { + Gtk::Box *box = dynamic_cast<Gtk::Box *>(&selector[0]); + if (box) { + std::vector<Gtk::Widget *> childs = box->get_children(); + if (childs.size() > 1) { + Gtk::TreeView *css_tree = dynamic_cast<Gtk::TreeView *>(childs[1]); + if (css_tree) { + Glib::RefPtr<Gtk::TreeModel> model = css_tree->get_model(); + if (model) { + model->foreach_iter(sigc::mem_fun(*this, &StyleDialog::_on_foreach_iter)); + } + } + } + } + } + } + if (obj) { + obj->style->readFromObject(obj); + obj->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); + } + _mainBox.show_all_children(); + _updating = false; +} + +bool StyleDialog::_on_foreach_iter(const Gtk::TreeModel::iterator &iter) { - _desktop = desktop; + Gtk::TreeModel::Row row = *(iter); + Glib::ustring owner = row[_mColumns._colOwner]; + if (owner.empty()) { + Glib::ustring tooltiptext = Glib::ustring(_("Used in ") + _ownerStyle[row[_mColumns._colName]]); + row[_mColumns._colOwner] = tooltiptext; + } + return false; } /** - * @brief StyleDialog::setRepr - * - * Set the internal xml object that I'm working on right now. + * @brief StyleDialog::_onPropDelete + * @param event + * @return true + * Delete the attribute from the style */ -void StyleDialog::setRepr(Inkscape::XML::Node *repr) +void StyleDialog::_onPropDelete(Glib::ustring path, Glib::RefPtr<Gtk::TreeStore> store) { - if (repr == _repr) - return; - if (_repr) { - _store->clear(); - _repr->removeListenerByData(this); - Inkscape::GC::release(_repr); - _repr = nullptr; - } - _repr = repr; - if (repr) { - Inkscape::GC::anchor(_repr); - _repr->addListener(&css_repr_events, this); - _repr->synthesizeEvents(&css_repr_events, this); + Gtk::TreeModel::Row row = *store->get_iter(path); + if (row) { + Glib::ustring selector = row[_mColumns._colSelector]; + row[_mColumns._colName] = ""; + store->erase(row); + _writeStyleElement(store, selector); } } +void StyleDialog::_addOwnerStyle(Glib::ustring name, Glib::ustring selector) +{ + if (_ownerStyle.find(name) == _ownerStyle.end()) { + _ownerStyle[name] = selector; + } +} + + /** * @brief StyleDialog::parseStyle * @@ -207,7 +821,9 @@ std::map<Glib::ustring, Glib::ustring> StyleDialog::parseStyle(Glib::ustring sty REMOVE_SPACES(style_string); // We'd use const, but we need to trip spaces std::vector<Glib::ustring> props = r_props->split(style_string); - for (auto const token : props) { + for (auto token : props) { + REMOVE_SPACES(token); + if (token.empty()) break; std::vector<Glib::ustring> pair = r_pair->split(token); @@ -219,149 +835,451 @@ std::map<Glib::ustring, Glib::ustring> StyleDialog::parseStyle(Glib::ustring sty return ret; } + /** - * @brief StyleDialog::compileStyle - * - * Turn a vector map back into a style string. - * + * Update the content of the style element as selectors (or objects) are added/removed. */ -Glib::ustring StyleDialog::compileStyle(std::map<Glib::ustring, Glib::ustring> props) +void StyleDialog::_writeStyleElement(Glib::RefPtr<Gtk::TreeStore> store, Glib::ustring selector) { - auto ret = Glib::ustring(""); - for (auto const pair : props) { - if (!pair.first.empty() && !pair.second.empty()) { - ret += pair.first; - ret += ":"; - ret += pair.second; - ret += ";"; + if (_updating) { + return; + } + Inkscape::Selection *selection = getDesktop()->getSelection(); + SPObject *obj = nullptr; + if (selection->objects().size() == 1) { + obj = selection->objects().back(); + } + if (!obj && !_all_css->get_active()) { + _readStyleElement(); + return; + } + _updating = true; + gint selectorpos = 0; + std::string styleContent = ""; + if (selector != "style_properties" && selector != "attributes") { + styleContent = "\n" + selector + " { \n"; + } + for (auto &row : store->children()) { + selector = row[_mColumns._colSelector]; + selectorpos = row[_mColumns._colSelectorPos]; + Glib::ustring opencomment = ""; + Glib::ustring closecomment = ""; + if (selector != "style_properties" && selector != "attributes") { + opencomment = row[_mColumns._colActive] ? " " : " /*"; + closecomment = row[_mColumns._colActive] ? "\n" : "*/\n"; + } + Glib::ustring name = row[_mColumns._colName]; + Glib::ustring value = row[_mColumns._colValue]; + if (!(name.empty() && value.empty())) { + styleContent = styleContent + opencomment + name + ":" + value + ";" + closecomment; } } - return ret; + if (selector != "style_properties" && selector != "attributes") { + styleContent = styleContent + "}"; + } + if (selector == "style_properties") { + obj->getRepr()->setAttribute("style", styleContent, false); + } else if (selector == "attributes") { + for (auto iter : obj->style->properties()) { + if (iter->name != "font" && iter->name != "d" && iter->name != "marker") { + const gchar *attr = obj->getRepr()->attribute(iter->name.c_str()); + if (attr) { + obj->getRepr()->setAttribute(iter->name.c_str(), nullptr); + } + } + } + for (auto &row : store->children()) { + Glib::ustring name = row[_mColumns._colName]; + Glib::ustring value = row[_mColumns._colValue]; + if (!(name.empty() && value.empty())) { + obj->getRepr()->setAttribute(name.c_str(), value, false); + } + } + } else if (!selector.empty()) { // styleshet + // We could test if styleContent is empty and then delete the style node here but there is no + // harm in keeping it around ... + SPDocument *document = SP_ACTIVE_DOCUMENT; + document->setStyleSheet(nullptr); + std::string pos = std::to_string(selectorpos); + std::string selectormatch = "("; + for (selectorpos; selectorpos > 1; selectorpos--) { + selectormatch = selectormatch + "[^}]*?}"; + } + selectormatch = selectormatch + ")([^}]*?})((.|\n)*)"; + Inkscape::XML::Node *textNode = _getStyleTextNode(); + std::regex e(selectormatch.c_str()); + std::string content = (textNode->content() ? textNode->content() : ""); + std::string result; + std::regex_replace(std::back_inserter(result), content.begin(), content.end(), e, "$1" + styleContent + "$3"); + textNode->setContent(result.c_str()); + std::vector<Glib::ustring> tokens = Glib::Regex::split_simple("[,]+", selector); + for (auto token : tokens) { + REMOVE_SPACES(token); + std::vector<Glib::ustring> selectorlist = Glib::Regex::split_simple("[ ]+", token); + if (selectorlist.size() > 0) { + Glib::ustring lastselector = REMOVE_SPACES(selectorlist[selectorlist.size() - 1]); + Glib::ustring selectorname = lastselector; + selectorname.erase(0, 1); + lastselector.erase(1); + if (lastselector == ".") { + for (auto iter : document->getObjectsByClass(selectorname)) { + iter->style->readFromObject(iter); + iter->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); + } + } else { + SPObject *obj = document->getObjectById(selectorname); + obj->style->readFromObject(obj); + obj->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); + } + } + } + } + _updating = false; + _readStyleElement(); + DocumentUndo::done(SP_ACTIVE_DOCUMENT, SP_VERB_DIALOG_STYLE, _("Edited style element.")); + + g_debug("StyleDialog::_writeStyleElement(): | %s |", styleContent.c_str()); } +bool StyleDialog::_addRow(GdkEventButton *evt, Glib::RefPtr<Gtk::TreeStore> store, Gtk::TreeView *css_tree, + Glib::ustring selector, gint pos) +{ + if (evt->type == GDK_BUTTON_RELEASE && evt->button == 1) { + Gtk::TreeIter iter = store->append(); + Gtk::TreeModel::Path path = (Gtk::TreeModel::Path)iter; + Gtk::TreeModel::Row row = *(iter); + row[_mColumns._colSelector] = selector; + row[_mColumns._colSelectorPos] = pos; + row[_mColumns._colActive] = true; + row[_mColumns._colName] = ""; + row[_mColumns._colValue] = ""; + row[_mColumns._colStrike] = false; + gint col = 2; + if (pos < 1) { + col = 1; + } + css_tree->set_cursor(path, *(css_tree->get_column(col)), true); + grab_focus(); + return true; + } + return false; +} + +void StyleDialog::_setAutocompletion(Gtk::Entry *entry, SPStyleEnum const cssenum[]) +{ + Glib::RefPtr<Gtk::ListStore> completionModel = Gtk::ListStore::create(_mCSSData); + Glib::RefPtr<Gtk::EntryCompletion> entry_completion = Gtk::EntryCompletion::create(); + entry_completion->set_model(completionModel); + entry_completion->set_text_column (_mCSSData._colCSSData); + entry_completion->set_minimum_key_length(0); + entry_completion->set_popup_completion(true); + gint counter = 0; + const char * key = cssenum[counter].key; + while (key) { + Gtk::TreeModel::Row row = *(completionModel->append()); + row[_mCSSData._colCSSData] = Glib::ustring(key); + counter++; + key = cssenum[counter].key; + } + entry->set_completion(entry_completion); +} +/*Harcode values non in enum*/ +void StyleDialog::_setAutocompletion(Gtk::Entry *entry, Glib::ustring name) +{ + Glib::RefPtr<Gtk::ListStore> completionModel = Gtk::ListStore::create(_mCSSData); + Glib::RefPtr<Gtk::EntryCompletion> entry_completion = Gtk::EntryCompletion::create(); + entry_completion->set_model(completionModel); + entry_completion->set_text_column(_mCSSData._colCSSData); + entry_completion->set_minimum_key_length(0); + entry_completion->set_popup_completion(true); + gint counter = 0; + if (name == "paint-order") { + Gtk::TreeModel::Row row = *(completionModel->append()); + row[_mCSSData._colCSSData] = Glib::ustring("fill markers stroke"); + row = *(completionModel->append()); + row[_mCSSData._colCSSData] = Glib::ustring("fill stroke markers"); + row = *(completionModel->append()); + row[_mCSSData._colCSSData] = Glib::ustring("stroke markers fill"); + row = *(completionModel->append()); + row[_mCSSData._colCSSData] = Glib::ustring("stroke fill markers"); + row = *(completionModel->append()); + row[_mCSSData._colCSSData] = Glib::ustring("markers fill stroke"); + row = *(completionModel->append()); + row[_mCSSData._colCSSData] = Glib::ustring("markers stroke fill"); + } + entry->set_completion(entry_completion); +} + +void +StyleDialog::_startValueEdit(Gtk::CellEditable* cell, const Glib::ustring& path, Glib::RefPtr<Gtk::TreeStore> store) +{ + Gtk::TreeModel::Row row = *store->get_iter(path); + if (row) { + Gtk::Entry *entry = dynamic_cast<Gtk::Entry *>(cell); + Glib::ustring name = row[_mColumns._colName]; + if (name == "paint-order") { + _setAutocompletion(entry, name); + } else if (name == "fill-rule") { + _setAutocompletion(entry, enum_fill_rule); + } else if (name == "stroke-linecap") { + _setAutocompletion(entry, enum_stroke_linecap); + } else if (name == "stroke-linejoin") { + _setAutocompletion(entry, enum_stroke_linejoin); + } else if (name == "font-style") { + _setAutocompletion(entry, enum_font_style); + } else if (name == "font-variant") { + _setAutocompletion(entry, enum_font_variant); + } else if (name == "font-weight") { + _setAutocompletion(entry, enum_font_weight); + } else if (name == "font-stretch") { + _setAutocompletion(entry, enum_font_stretch); + } else if (name == "font-variant-position") { + _setAutocompletion(entry, enum_font_variant_position); + } else if (name == "text-align") { + _setAutocompletion(entry, enum_text_align); + } else if (name == "text-transform") { + _setAutocompletion(entry, enum_text_transform); + } else if (name == "text-anchor") { + _setAutocompletion(entry, enum_text_anchor); + } else if (name == "white-space") { + _setAutocompletion(entry, enum_white_space); + } else if (name == "direction") { + _setAutocompletion(entry, enum_direction); + } else if (name == "baseline-shift") { + _setAutocompletion(entry, enum_baseline_shift); + } else if (name == "visibility") { + _setAutocompletion(entry, enum_visibility); + } else if (name == "overflow") { + _setAutocompletion(entry, enum_overflow); + } else if (name == "display") { + _setAutocompletion(entry, enum_display); + } else if (name == "shape-rendering") { + _setAutocompletion(entry, enum_shape_rendering); + } else if (name == "color-rendering") { + _setAutocompletion(entry, enum_color_rendering); + } else if (name == "overflow") { + _setAutocompletion(entry, enum_overflow); + } else if (name == "clip-rule") { + _setAutocompletion(entry, enum_clip_rule); + } else if (name == "color-interpolation") { + _setAutocompletion(entry, enum_color_interpolation); + } + } +} + +void StyleDialog::_startNameEdit(Gtk::CellEditable *cell, const Glib::ustring &path) +{ + Glib::RefPtr<Gtk::ListStore> completionModel = Gtk::ListStore::create(_mCSSData); + Glib::RefPtr<Gtk::EntryCompletion> entry_completion = Gtk::EntryCompletion::create(); + entry_completion->set_model(completionModel); + entry_completion->set_text_column(_mCSSData._colCSSData); + entry_completion->set_minimum_key_length(1); + entry_completion->set_popup_completion(true); + for (auto prop : sp_attribute_name_list(true)) { + Gtk::TreeModel::Row row = *(completionModel->append()); + row[_mCSSData._colCSSData] = prop; + } + Gtk::Entry *entry = dynamic_cast<Gtk::Entry *>(cell); + entry->set_completion(entry_completion); +} /** - * @brief StyleDialog::onAttrChanged - * - * This is called when the XML has an updated attribute (we only care about style) + * @brief StyleDialog::nameEdited + * @param event + * @return + * Called when the name is edited in the TreeView editable column */ -void StyleDialog::onAttrChanged(Inkscape::XML::Node *repr, const gchar *name, const gchar *new_value) +void StyleDialog::_nameEdited(const Glib::ustring &path, const Glib::ustring &name, Glib::RefPtr<Gtk::TreeStore> store, + Gtk::TreeView *css_tree) { - if (strcmp(name, "style") != 0) - return; + Gtk::TreeModel::Row row = *store->get_iter(path); + Gtk::TreeModel::Path pathel = (Gtk::TreeModel::Path)*store->get_iter(path); - // Clear the list and return if the new_value is empty - _store->clear(); - if (!new_value || new_value[0] == 0) - return; + if (row) { + gint pos = row[_mColumns._colSelectorPos]; + bool write = false; + if (row[_mColumns._colName] != name && row[_mColumns._colValue] != "") { + write = true; + } + Glib::ustring selector = row[_mColumns._colSelector]; + Glib::ustring value = row[_mColumns._colValue]; + bool is_attr = selector == "attributes"; - // Get the object's style attribute and it's calculated properties - SPDocument *document = this->_desktop->doc(); - SPObject *obj = document->getObjectByRepr(repr); - // std::vector<SPIBase *> calc_prop = obj->style->properties(); - - // Get a dictionary lookup of the style in the attribute - std::map<Glib::ustring, Glib::ustring> attr_prop = parseStyle(new_value); - - for (auto iter : obj->style->properties()) { - if (iter->style && iter->style_src != SP_STYLE_SRC_UNSET) { - Gtk::TreeModel::Row row = *(_store->append()); - // Delete is available to attribute properties only in attr mode. - row[_cssColumns.deleteButton] = iter->style_src == SP_STYLE_SRC_ATTRIBUTE; - row[_cssColumns.label] = iter->name; - if (attr_prop.count(iter->name)) { - row[_cssColumns._styleAttrVal] = attr_prop[iter->name]; - if (attr_prop[iter->name] != iter->get_value()) { - row[_cssColumns._styleSheetVal] = iter->get_value(); - row[_cssColumns.attr_color] = Gdk::RGBA("gray"); - row[_cssColumns.attr_strike] = true; - } - row[_cssColumns.deleteButton] = true; - } else { - row[_cssColumns._styleSheetVal] = iter->get_value(); - row[_cssColumns.label_color] = Gdk::RGBA("gray"); - row[_cssColumns.attr_color] = Gdk::RGBA("gray"); - row[_cssColumns.deleteButton] = false; - } + row[_mColumns._colName] = name; + if (name.empty() && value.empty()) { + store->erase(row); + } + gint col = 3; + if (pos < 1 || is_attr) { + col = 2; + } + if (write) { + _writeStyleElement(store, selector); + } else { + css_tree->set_cursor(pathel, *(css_tree->get_column(col)), true); + grab_focus(); } } } -/* - * Sets the CSSDialog status bar, depending on which attr is selected. + +/** + * @brief StyleDialog::valueEdited + * @param event + * @return + * Called when the value is edited in the TreeView editable column */ -void StyleDialog::css_reset_context(gint css) +void StyleDialog::_valueEdited(const Glib::ustring &path, const Glib::ustring &value, + Glib::RefPtr<Gtk::TreeStore> store) { - if (css == 0) { - _message_context->set(Inkscape::NORMAL_MESSAGE, _("<b>Click</b> CSS property to edit.")); - } else { - const gchar *name = g_quark_to_string(css); - _message_context->setF( - Inkscape::NORMAL_MESSAGE, - _("Property <b>%s</b> selected. Press <b>Ctrl+Enter</b> when done editing to commit changes."), name); + Gtk::TreeModel::Row row = *store->get_iter(path); + if (row) { + row[_mColumns._colValue] = value; + Glib::ustring selector = row[_mColumns._colSelector]; + Glib::ustring name = row[_mColumns._colName]; + if (name.empty() && value.empty()) { + store->erase(row); + } + _writeStyleElement(store, selector); + } +} + +void StyleDialog::_activeToggled(const Glib::ustring &path, Glib::RefPtr<Gtk::TreeStore> store) +{ + Gtk::TreeModel::Row row = *store->get_iter(path); + if (row) { + row[_mColumns._colActive] = !row[_mColumns._colActive]; + Glib::ustring selector = row[_mColumns._colSelector]; + _writeStyleElement(store, selector); + } +} + +void StyleDialog::_addWatcherRecursive(Inkscape::XML::Node *node) +{ + + g_debug("StyleDialog::_addWatcherRecursive()"); + + StyleDialog::NodeWatcher *w = new StyleDialog::NodeWatcher(this, node); + node->addObserver(*w); + _nodeWatchers.push_back(w); + + for (unsigned i = 0; i < node->childCount(); ++i) { + _addWatcherRecursive(node->nthChild(i)); } } /** - * @brief StyleDialog::setStyleProperty - * - * Set or delete a single property in the style attribute. + * Update the watchers on objects. */ -bool StyleDialog::setStyleProperty(Glib::ustring name, Glib::ustring value) +void StyleDialog::_updateWatchers() { - auto original = this->_repr->attribute("style"); - std::map<Glib::ustring, Glib::ustring> properties = parseStyle(original); - - bool updated = false; - if (!value.empty()) { - if (properties[name] != value) { - // Set value (create or update) - properties[name] = value; - updated = true; - } - } else if (properties.count(name)) { - // Delete value - properties.erase(name); - updated = true; - } + _updating = true; - if (updated) { - auto new_styles = this->compileStyle(properties); - this->_repr->setAttribute("style", new_styles, false); - // this->setUndo(_("Delete style property")); + // Remove old document watchers + while (!_nodeWatchers.empty()) { + StyleDialog::NodeWatcher *w = _nodeWatchers.back(); + w->_repr->removeObserver(*w); + _nodeWatchers.pop_back(); + delete w; } - return updated; + + // Recursively add new watchers + Inkscape::XML::Node *root = SP_ACTIVE_DOCUMENT->getReprRoot(); + _addWatcherRecursive(root); + + g_debug("StyleDialog::_updateWatchers(): %d", (int)_nodeWatchers.size()); + + _updating = false; } + /** - * @brief StyleDialog::onPropertyDelete - * - * This function is a slot to signal_activated for '-' button panel. + * @param selector: a valid CSS selector string. + * @return objVec: a vector of pointers to SPObject's the selector matches. + * Return a vector of all objects that selector matches. */ -void StyleDialog::onPropertyDelete(Glib::ustring path) +std::vector<SPObject *> StyleDialog::_getObjVec(Glib::ustring selector) { - Gtk::TreeModel::Row row = *_store->get_iter(path); - if (row) { - this->setStyleProperty(row[_cssColumns.label], ""); + + std::vector<SPObject *> objVec = SP_ACTIVE_DOCUMENT->getObjectsBySelector(selector); + + g_debug("StyleDialog::_getObjVec: | %s |", selector.c_str()); + for (auto &obj : objVec) { + g_debug(" %s", obj->getId() ? obj->getId() : "null"); } + + return objVec; } +void StyleDialog::_closeDialog(Gtk::Dialog *textDialogPtr) { textDialogPtr->response(Gtk::RESPONSE_OK); } + + /** - * @brief StyleDialog::onPropertyCreate - * This function is a slot to signal_clicked for '+' button panel. + * Handle document replaced. (Happens when a default document is immediately replaced by another + * document in a new window.) */ -bool StyleDialog::onPropertyCreate(GdkEventButton *event) +void StyleDialog::_handleDocumentReplaced(SPDesktop *desktop, SPDocument * /* document */) { - if (event->type == GDK_BUTTON_RELEASE && event->button == 1 && this->_repr) { - Gtk::TreeIter iter = _store->append(); - Gtk::TreeModel::Path path = (Gtk::TreeModel::Path)iter; - _treeView.set_cursor(path, *_propCol, true); - grab_focus(); - return true; + g_debug("StyleDialog::handleDocumentReplaced()"); + + _selection_changed_connection.disconnect(); + + _selection_changed_connection = + desktop->getSelection()->connectChanged(sigc::hide(sigc::mem_fun(this, &StyleDialog::_handleSelectionChanged))); + + _updateWatchers(); + _readStyleElement(); +} + + +/* + * When a dialog is floating, it is connected to the active desktop. + */ +void StyleDialog::_handleDesktopChanged(SPDesktop *desktop) +{ + g_debug("StyleDialog::handleDesktopReplaced()"); + + if (getDesktop() == desktop) { + // This will happen after construction of dialog. We've already + // set up signals so just return. + return; } - return false; + + _selection_changed_connection.disconnect(); + _document_replaced_connection.disconnect(); + + setDesktop(desktop); + + _selection_changed_connection = + desktop->getSelection()->connectChanged(sigc::hide(sigc::mem_fun(this, &StyleDialog::_handleSelectionChanged))); + _document_replaced_connection = + desktop->connectDocumentReplaced(sigc::mem_fun(this, &StyleDialog::_handleDocumentReplaced)); + + _updateWatchers(); + _readStyleElement(); +} + + +/* + * Handle a change in which objects are selected in a document. + */ +void StyleDialog::_handleSelectionChanged() +{ + g_debug("StyleDialog::_handleSelectionChanged()"); + _readStyleElement(); } } // 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 : diff --git a/src/ui/dialog/styledialog.h b/src/ui/dialog/styledialog.h index 07d409ded..1053a6639 100644 --- a/src/ui/dialog/styledialog.h +++ b/src/ui/dialog/styledialog.h @@ -12,125 +12,166 @@ * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ -#ifndef SEEN_UI_DIALOGS_STYLEDIALOG_H -#define SEEN_UI_DIALOGS_STYLEDIALOG_H - -#include "desktop.h" -#include "message.h" +#ifndef STYLEDIALOG_H +#define STYLEDIALOG_H +#include "style-enums.h" #include <glibmm/regex.h> +#include <gtkmm/builder.h> +#include <gtkmm/celleditable.h> +#include <gtkmm/cellrenderercombo.h> #include <gtkmm/dialog.h> +#include <gtkmm/entry.h> +#include <gtkmm/entrycompletion.h> +#include <gtkmm/eventbox.h> #include <gtkmm/liststore.h> +#include <gtkmm/paned.h> #include <gtkmm/scrolledwindow.h> +#include <gtkmm/switch.h> +#include <gtkmm/tooltip.h> +#include <gtkmm/treemodelfilter.h> +#include <gtkmm/treeselection.h> +#include <gtkmm/treestore.h> #include <gtkmm/treeview.h> +#include <gtkmm/viewport.h> #include <ui/widget/panel.h> -#define STYLE_DIALOG(obj) (dynamic_cast<Inkscape::UI::Dialog::StyleDialog *>((Inkscape::UI::Dialog::StyleDialog *)obj)) -#define REMOVE_SPACES(x) \ - x.erase(0, x.find_first_not_of(' ')); \ - x.erase(x.find_last_not_of(' ') + 1); +#include "ui/dialog/desktop-tracker.h" + +#include "xml/helper-observer.h" namespace Inkscape { -class MessageStack; -class MessageContext; namespace UI { namespace Dialog { /** * @brief The StyleDialog class - * This dialog allows to add, delete and modify CSS properties for selectors - * created in Style Dialog. Double clicking any selector in Style dialog, a list - * of CSS properties will show up in this dialog (if any exist), else new properties - * can be added and each new property forms a new row in this pane. + * A list of CSS selectors will show up in this dialog. This dialog allows one to + * add and delete selectors. Elements can be added to and removed from the selectors + * in the dialog. Selection of any selector row selects the matching objects in + * the drawing and vice-versa. (Only simple selectors supported for now.) + * + * This class must keep two things in sync: + * 1. The text node of the style element. + * 2. The Gtk::TreeModel. */ -class StyleDialog : public UI::Widget::Panel -{ -public: - StyleDialog(); +class StyleDialog : public Widget::Panel { + + public: ~StyleDialog() override; + // No default constructor, noncopyable, nonassignable + StyleDialog(); + StyleDialog(StyleDialog const &d) = delete; + StyleDialog operator=(StyleDialog const &d) = delete; static StyleDialog &getInstance() { return *new StyleDialog(); } - // Data structure - class CssColumns : public Gtk::TreeModel::ColumnRecord { - public: - CssColumns() { - add(deleteButton); - add(label); - add(_styleSheetVal); - add(_styleAttrVal); - add(label_color); - add(attr_color); - add(attr_strike); - add(editable); - } - Gtk::TreeModelColumn<bool> deleteButton; - Gtk::TreeModelColumn<Glib::ustring> label; - Gtk::TreeModelColumn<Glib::ustring> _styleAttrVal; - Gtk::TreeModelColumn<Glib::ustring> _styleSheetVal; - Gtk::TreeModelColumn<Gdk::RGBA> label_color; - Gtk::TreeModelColumn<Gdk::RGBA> attr_color; - Gtk::TreeModelColumn<bool> attr_strike; - Gtk::TreeModelColumn<bool> editable; - }; - CssColumns _cssColumns; - - /** - * Status bar - */ - std::shared_ptr<Inkscape::MessageStack> _message_stack; - std::unique_ptr<Inkscape::MessageContext> _message_context; - - // TreeView - Gtk::TreeView _treeView; - Glib::RefPtr<Gtk::ListStore> _store; - Gtk::TreeModel::Row _propRow; - Gtk::TreeViewColumn *_propCol; - Gtk::TreeViewColumn *_sheetCol; - Gtk::TreeViewColumn *_attrCol; - Gtk::HBox status_box; - Gtk::Label status; - - /** - * Sets the XML status bar, depending on which attr is selected. - */ - void css_reset_context(gint css); - static void _set_status_message(Inkscape::MessageType type, const gchar *message, GtkWidget *dialog); - - - // Variables - Inkscape - SPDesktop* _desktop; - Inkscape::XML::Node *_repr; - - // Helper functions - void setDesktop(SPDesktop* desktop) override; - void setRepr(Inkscape::XML::Node *repr); - - // Parsing functions - std::map<Glib::ustring, Glib::ustring> parseStyle(Glib::ustring style_string); - Glib::ustring compileStyle(std::map<Glib::ustring, Glib::ustring> props); - - // Signal handlers - void onAttrChanged(Inkscape::XML::Node *repr, const gchar *name, const gchar *new_value); - private: + // Monitor <style> element for changes. + class NodeObserver; + // Monitor all objects for addition/removal/attribute change + class NodeWatcher; Glib::RefPtr<Glib::Regex> r_props = Glib::Regex::create("\\s*;\\s*"); Glib::RefPtr<Glib::Regex> r_pair = Glib::Regex::create("\\s*:\\s*"); + std::vector<StyleDialog::NodeWatcher *> _nodeWatchers; + void _nodeAdded(Inkscape::XML::Node &repr); + void _nodeRemoved(Inkscape::XML::Node &repr); + void _nodeChanged(Inkscape::XML::Node &repr); + /* void _stylesheetChanged( Inkscape::XML::Node &repr ); */ + // Data structure + class ModelColumns : public Gtk::TreeModel::ColumnRecord { + public: + ModelColumns() + { + add(_colActive); + add(_colName); + add(_colValue); + add(_colStrike); + add(_colSelector); + add(_colSelectorPos); + add(_colOwner); + } + Gtk::TreeModelColumn<bool> _colActive; // Active or inative property + Gtk::TreeModelColumn<Glib::ustring> _colName; // Name of the property. + Gtk::TreeModelColumn<Glib::ustring> _colValue; // Value of the property. + Gtk::TreeModelColumn<bool> _colStrike; // Propery not used, overloaded + Gtk::TreeModelColumn<Glib::ustring> _colSelector; // Style or matching object id. + Gtk::TreeModelColumn<gint> _colSelectorPos; // Position of the selector to hadle dup selectors + Gtk::TreeModelColumn<Glib::ustring> _colOwner; // Store the owner of the property for popup + }; + ModelColumns _mColumns; - bool onPropertyCreate(GdkEventButton *event); - void onPropertyDelete(Glib::ustring path); - bool setStyleProperty(Glib::ustring name, Glib::ustring value); - - /** - * Signal handlers - */ - sigc::connection _message_changed_connection; - bool _addProperty(GdkEventButton *event); + class CSSData : public Gtk::TreeModel::ColumnRecord { + public: + CSSData() { add(_colCSSData); } + Gtk::TreeModelColumn<Glib::ustring> _colCSSData; // Name of the property. + }; + CSSData _mCSSData; + + // Widgets + Gtk::ScrolledWindow _scrolledWindow; + Gtk::Box _mainBox; + Gtk::Box _styleBox; + Gtk::Switch *_all_css; + + // Reading and writing the style element. + Inkscape::XML::Node *_getStyleTextNode(); + void _readStyleElement(); + Glib::RefPtr<Gtk::TreeModel> _selectTree(Glib::ustring selector); + void _writeStyleElement(Glib::RefPtr<Gtk::TreeStore> store, Glib::ustring selector); + void _activeToggled(const Glib::ustring &path, Glib::RefPtr<Gtk::TreeStore> store); + bool _addRow(GdkEventButton *evt, Glib::RefPtr<Gtk::TreeStore> store, Gtk::TreeView *css_tree, + Glib::ustring selector, gint pos); + void _onPropDelete(Glib::ustring path, Glib::RefPtr<Gtk::TreeStore> store); + void _nameEdited(const Glib::ustring &path, const Glib::ustring &name, Glib::RefPtr<Gtk::TreeStore> store, + Gtk::TreeView *css_tree); + void _valueEdited(const Glib::ustring &path, const Glib::ustring &value, Glib::RefPtr<Gtk::TreeStore> store); + void _startNameEdit(Gtk::CellEditable *cell, const Glib::ustring &path); + + void _startValueEdit(Gtk::CellEditable *cell, const Glib::ustring &path, Glib::RefPtr<Gtk::TreeStore> store); + void _setAutocompletion(Gtk::Entry *entry, SPStyleEnum const cssenum[]); + void _setAutocompletion(Gtk::Entry *entry, Glib::ustring name); + bool _on_foreach_iter(const Gtk::TreeModel::iterator &iter); + void _reload(); + + // Update watchers + void _addWatcherRecursive(Inkscape::XML::Node *node); + void _updateWatchers(); + + // Manipulate Tree + std::vector<SPObject *> _getObjVec(Glib::ustring selector); + std::map<Glib::ustring, Glib::ustring> parseStyle(Glib::ustring style_string); + std::map<Glib::ustring, Glib::ustring> _ownerStyle; + void _addOwnerStyle(Glib::ustring name, Glib::ustring selector); + // Variables + Inkscape::XML::Node *_textNode; // Track so we know when to add a NodeObserver. + bool _updating; // Prevent cyclic actions: read <-> write, select via dialog <-> via desktop + + // Signals and handlers - External + sigc::connection _document_replaced_connection; + sigc::connection _desktop_changed_connection; + sigc::connection _selection_changed_connection; + + void _handleDocumentReplaced(SPDesktop *desktop, SPDocument *document); + void _handleDesktopChanged(SPDesktop *desktop); + void _handleSelectionChanged(); + void _closeDialog(Gtk::Dialog *textDialogPtr); + DesktopTracker _desktopTracker; }; - } // namespace Dialog } // namespace UI } // namespace Inkscape #endif // STYLEDIALOG_H + +/* + 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 : diff --git a/src/ui/dialog/xml-tree.cpp b/src/ui/dialog/xml-tree.cpp index 0fe962946..0a3cabef2 100644 --- a/src/ui/dialog/xml-tree.cpp +++ b/src/ui/dialog/xml-tree.cpp @@ -74,7 +74,6 @@ XmlTree::XmlTree() : return; } flowbox_content = Gtk::manage(new Inkscape::UI::Widget::InkFlowBox("XMLFlow")); - Gtk::Box *contents = _getContents(); contents->set_spacing(0); contents->set_size_request(320, 260); @@ -357,11 +356,11 @@ void XmlTree::propagate_tree_select(Inkscape::XML::Node *repr) repr->type() == Inkscape::XML::COMMENT_NODE)) { attributes->setRepr(repr); - styles->setRepr(repr); + // styles->setRepr(repr); // selectors->setRepr(repr); } else { attributes->setRepr(nullptr); - styles->setRepr(nullptr); + // styles->setRepr(nullptr); // selectors->setRepr(nullptr); } } diff --git a/src/ui/dialog/xml-tree.h b/src/ui/dialog/xml-tree.h index 9aa96a9a2..e829a3c75 100644 --- a/src/ui/dialog/xml-tree.h +++ b/src/ui/dialog/xml-tree.h @@ -26,11 +26,13 @@ #include <gtkmm/toolbar.h> #include "message.h" + #include "ui/dialog/attrdialog.h" -#include "ui/dialog/selectordialog.h" #include "ui/dialog/desktop-tracker.h" +#include "ui/dialog/selectordialog.h" #include "ui/dialog/styledialog.h" + class SPDesktop; class SPObject; struct SPXMLViewAttrList; diff --git a/src/ui/icon-loader.cpp b/src/ui/icon-loader.cpp index e2aaf5093..45e885a74 100644 --- a/src/ui/icon-loader.cpp +++ b/src/ui/icon-loader.cpp @@ -62,8 +62,13 @@ Glib::RefPtr<Gdk::Pixbuf> sp_get_icon_pixbuf(Glib::ustring icon_name, gint size) Inkscape::Preferences *prefs = Inkscape::Preferences::get(); if (prefs->getBool("/theme/symbolicIcons", false)) { Gtk::IconInfo iconinfo = icon_theme->lookup_icon(icon_name + Glib::ustring("-symbolic"), size, Gtk::ICON_LOOKUP_FORCE_SIZE); - bool was_sumbolic = false; - _icon_pixbuf = iconinfo.load_symbolic(SP_ACTIVE_DESKTOP->getToplevel()->get_style_context(), was_sumbolic); + if (iconinfo) { + bool was_simbolic = false; + _icon_pixbuf = iconinfo.load_symbolic(SP_ACTIVE_DESKTOP->getToplevel()->get_style_context(), was_simbolic); + } else { + Gtk::IconInfo iconinfo = icon_theme->lookup_icon(icon_name, size, Gtk::ICON_LOOKUP_FORCE_SIZE); + _icon_pixbuf = iconinfo.load_icon(); + } } else { Gtk::IconInfo iconinfo = icon_theme->lookup_icon(icon_name, size, Gtk::ICON_LOOKUP_FORCE_SIZE); _icon_pixbuf = iconinfo.load_icon(); diff --git a/src/ui/widget/font-variants.cpp b/src/ui/widget/font-variants.cpp index 26d30652b..b81f809b2 100644 --- a/src/ui/widget/font-variants.cpp +++ b/src/ui/widget/font-variants.cpp @@ -35,12 +35,31 @@ namespace Widget { { Gtk::Label* table_name = Gtk::manage (new Gtk::Label()); table_name->set_markup ("\"" + name + "\" "); + grid.attach (*table_name, 0, row, 1, 1); + Gtk::FlowBox* flow_box = nullptr; + Gtk::ScrolledWindow* scrolled_window = nullptr; + if (options > 2) { + // If there are more than 2 option, pack them into a flowbox instead of directly putting them in the grid. + // Some fonts might have a table with many options (Bungee Hairline table 'ornm' has 113 entries). + flow_box = Gtk::manage (new Gtk::FlowBox()); + flow_box->set_selection_mode(); // Turn off selection + flow_box->set_homogeneous(); + flow_box->set_max_children_per_line (100); // Override default value + flow_box->set_min_children_per_line (10); // Override default value + + // We pack this into a scrollbar... otherwise the minimum height is set to what is required to fit all + // flow box children into the flow box when the flow box has minimum width. (Crazy if you ask me!) + scrolled_window = Gtk::manage (new Gtk::ScrolledWindow()); + scrolled_window->set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC); + scrolled_window->add(*flow_box); + } + Gtk::RadioButton::Group group; for (int i = 0; i < options; ++i) { - int col = i%10; // Some fonts might have a table with many options (Bungee Hairline table 'ornm' has 113 entries). - if (i > 10 && col == 0) row++; + + // Create radio button and create or add to button group. Gtk::RadioButton* button = Gtk::manage (new Gtk::RadioButton()); if (i == 0) { group = button->get_group(); @@ -48,9 +67,9 @@ namespace Widget { button->set_group (group); } button->signal_clicked().connect ( sigc::mem_fun(*parent, &FontVariants::feature_callback) ); - grid.attach (*button, 2*col+1, row, 1, 1); buttons.push_back (button); + // Create label. Gtk::Label* label = Gtk::manage (new Gtk::Label()); // Restrict label width (some fonts have lots of alternatives). @@ -58,6 +77,7 @@ namespace Widget { label->set_line_wrap_mode( Pango::WRAP_WORD_CHAR ); label->set_ellipsize( Pango::ELLIPSIZE_END ); label->set_lines(3); + label->set_hexpand(); Glib::ustring markup; markup += "<span font_family='"; @@ -70,7 +90,26 @@ namespace Widget { markup += Glib::Markup::escape_text (glyphs.input); markup += "</span>"; label->set_markup (markup); - grid.attach (*label, 2*col+2, row, 1, 1); + + // Add button and label to widget + if (!flow_box) { + // Attach directly to grid (keeps things aligned row-to-row). + grid.attach (*button, 2*i+1, row, 1, 1); + grid.attach (*label, 2*i+2, row, 1, 1); + } else { + // Pack into FlowBox + + // Pack button and label into a box so they stay together. + Gtk::Box* box = Gtk::manage (new Gtk::Box()); + box->add(*button); + box->add(*label); + + flow_box->add(*box); + } + } + + if (scrolled_window) { + grid.attach (*scrolled_window, 1, row, 4, 1); } } @@ -299,7 +338,7 @@ namespace Widget { _caps_grid.set_margin_start(15); _caps_grid.set_margin_end(15); - + _caps_frame.add( _caps_grid ); pack_start( _caps_frame, Gtk::PACK_SHRINK ); @@ -373,7 +412,7 @@ namespace Widget { _numeric_grid.set_margin_start(15); _numeric_grid.set_margin_end(15); - + _numeric_frame.add( _numeric_grid ); pack_start( _numeric_frame, Gtk::PACK_SHRINK ); @@ -420,7 +459,7 @@ namespace Widget { _asian_grid.set_margin_start(15); _asian_grid.set_margin_end(15); - + _asian_frame.add( _asian_grid ); pack_start( _asian_frame, Gtk::PACK_SHRINK ); @@ -450,17 +489,17 @@ namespace Widget { _feature_list.set_line_wrap( true ); // Add to frame - _feature_vbox.pack_start( _feature_grid ); - _feature_vbox.pack_start( _feature_entry ); - _feature_vbox.pack_start( _feature_label ); - _feature_vbox.pack_start( _feature_substitutions ); - _feature_vbox.pack_start( _feature_list ); + _feature_vbox.pack_start( _feature_grid, Gtk::PACK_SHRINK ); + _feature_vbox.pack_start( _feature_entry, Gtk::PACK_SHRINK ); + _feature_vbox.pack_start( _feature_label, Gtk::PACK_SHRINK ); + _feature_vbox.pack_start( _feature_substitutions, Gtk::PACK_SHRINK ); + _feature_vbox.pack_start( _feature_list, Gtk::PACK_SHRINK ); _feature_vbox.set_margin_start(15); _feature_vbox.set_margin_end(15); - + _feature_frame.add( _feature_vbox ); - pack_start( _feature_frame, Gtk::PACK_SHRINK ); + pack_start( _feature_frame, Gtk::PACK_SHRINK ); // Add signals //_feature_entry.signal_key_press_event().connect ( sigc::mem_fun(*this, &FontVariants::feature_callback) ); diff --git a/src/ui/widget/ink-flow-box.cpp b/src/ui/widget/ink-flow-box.cpp index d102ceb6c..8485dd932 100644 --- a/src/ui/widget/ink-flow-box.cpp +++ b/src/ui/widget/ink-flow-box.cpp @@ -28,7 +28,9 @@ InkFlowBox::InkFlowBox(const gchar *name) _flowbox.set_activate_on_single_click(true); Gtk::ToggleButton *tbutton = new Gtk::ToggleButton("", false); tbutton->set_always_show_image(true); + _flowbox.set_selection_mode(Gtk::SelectionMode::SELECTION_NONE); Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setBool(Glib::ustring("/dialogs/") + get_name() + Glib::ustring("/flowbox/lock"), false); tbutton->set_active(prefs->getBool(Glib::ustring("/dialogs/") + get_name() + Glib::ustring("/flowbox/lock"), true)); Glib::ustring iconname = "object-unlocked"; if (tbutton->get_active()) { @@ -38,7 +40,8 @@ InkFlowBox::InkFlowBox(const gchar *name) tbutton->signal_toggled().connect( sigc::bind<Gtk::ToggleButton *>(sigc::mem_fun(*this, &InkFlowBox::on_global_toggle), tbutton)); _controller.pack_start(*tbutton); - tbutton->show(); + tbutton->hide(); + tbutton->set_no_show_all(true); showing = 0; sensitive = true; } diff --git a/src/widgets/desktop-widget.cpp b/src/widgets/desktop-widget.cpp index c48d6fbd5..e94448e2c 100644 --- a/src/widgets/desktop-widget.cpp +++ b/src/widgets/desktop-widget.cpp @@ -79,6 +79,9 @@ #include "toolbox.h" #include "widget-sizes.h" +#ifdef GDK_WINDOWING_QUARTZ +#include <gtkosxapplication.h> +#endif using Inkscape::DocumentUndo; using Inkscape::UI::Widget::UnitTracker; @@ -919,6 +922,17 @@ sp_desktop_widget_realize (GtkWidget *widget) window->get_style_context()->remove_class("symbolic"); } } + +#ifdef GDK_WINDOWING_QUARTZ + // native macOS menu + auto osxapp = gtkosx_application_get(); + auto menushell = static_cast<Gtk::MenuShell *>(dtw->menubar()); + if (osxapp && menushell && window) { + menushell->set_parent(*window); + gtkosx_application_set_menu_bar(osxapp, menushell->gobj()); + gtkosx_application_set_use_quartz_accelerators(osxapp, false); + } +#endif } /* This is just to provide access to common functionality from sp_desktop_widget_realize() above @@ -1677,7 +1691,12 @@ SPDesktopWidget* SPDesktopWidget::createInstance(SPDocument *document) dtw->_menubar = build_menubar(dtw->desktop); dtw->_menubar->set_name("MenuBar"); dtw->_menubar->show_all(); + +#ifdef GDK_WINDOWING_QUARTZ + // native macOS menu: do this later because we don't have the window handle yet +#else dtw->_vbox->pack_start(*dtw->_menubar, false, false); +#endif dtw->layoutWidgets(); diff --git a/src/widgets/desktop-widget.h b/src/widgets/desktop-widget.h index f0b42222d..d21ba7bb8 100644 --- a/src/widgets/desktop-widget.h +++ b/src/widgets/desktop-widget.h @@ -269,6 +269,8 @@ public: void updateTitle(gchar const *uri); bool onFocusInEvent(GdkEventFocus*); + Gtk::MenuBar *menubar() { return _menubar; } + Inkscape::UI::Widget::Dock* getDock(); static GType getType(); |
