summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAlexander Valavanis <valavanisalex@gmail.com>2019-06-05 19:33:55 +0000
committerAlexander Valavanis <valavanisalex@gmail.com>2019-06-05 19:33:55 +0000
commit2e40143d6e75d50bf659abddc5c7c25fb1bc2436 (patch)
treee2fa747c95569dc3f77fcdfa770b3d404ff065cc /src
parentHackfest2019: Rm tautological tests (diff)
parentUse a flowbox when there are more than two options for font feature settings. (diff)
downloadinkscape-2e40143d6e75d50bf659abddc5c7c25fb1bc2436.tar.gz
inkscape-2e40143d6e75d50bf659abddc5c7c25fb1bc2436.zip
Merge changes
Diffstat (limited to 'src')
-rw-r--r--src/attributes.cpp25
-rw-r--r--src/attributes.h6
-rw-r--r--src/extension/effect.cpp9
-rw-r--r--src/extension/internal/pdfinput/pdf-input.cpp19
-rw-r--r--src/inkscape-application.cpp30
-rw-r--r--src/io/resource-manager.cpp3
-rw-r--r--src/object/sp-rect.cpp48
-rw-r--r--src/object/sp-rect.h2
-rw-r--r--src/object/sp-shape.cpp15
-rw-r--r--src/object/sp-shape.h5
-rw-r--r--src/seltrans.cpp4
-rw-r--r--src/style.cpp45
-rw-r--r--src/style.h1
-rw-r--r--src/text-chemistry.cpp5
-rw-r--r--src/ui/clipboard.cpp3
-rw-r--r--src/ui/dialog/align-and-distribute.cpp10
-rw-r--r--src/ui/dialog/attrdialog.cpp16
-rw-r--r--src/ui/dialog/livepatheffect-add.cpp2
-rw-r--r--src/ui/dialog/livepatheffect-editor.cpp23
-rw-r--r--src/ui/dialog/livepatheffect-editor.h1
-rw-r--r--src/ui/dialog/selectordialog.cpp120
-rw-r--r--src/ui/dialog/selectordialog.h20
-rw-r--r--src/ui/dialog/styledialog.cpp1386
-rw-r--r--src/ui/dialog/styledialog.h223
-rw-r--r--src/ui/dialog/xml-tree.cpp5
-rw-r--r--src/ui/dialog/xml-tree.h4
-rw-r--r--src/ui/icon-loader.cpp9
-rw-r--r--src/ui/widget/font-variants.cpp67
-rw-r--r--src/ui/widget/ink-flow-box.cpp5
-rw-r--r--src/widgets/desktop-widget.cpp19
-rw-r--r--src/widgets/desktop-widget.h2
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();