diff options
| author | Shlomi Fish <shlomif@shlomifish.org> | 2017-02-23 14:39:39 +0000 |
|---|---|---|
| committer | Shlomi Fish <shlomif@shlomifish.org> | 2017-02-23 14:39:39 +0000 |
| commit | d82d8e6de61b5a3da73af7d7003b8281720c3dc1 (patch) | |
| tree | 50a151920b32f95afd0fa0c04f175322657ebb18 /src | |
| parent | Merged. (diff) | |
| parent | Display style attribute properties when object row selected. Allow their dele... (diff) | |
| download | inkscape-d82d8e6de61b5a3da73af7d7003b8281720c3dc1.tar.gz inkscape-d82d8e6de61b5a3da73af7d7003b8281720c3dc1.zip | |
Merged.
(bzr r15369.1.19)
Diffstat (limited to 'src')
74 files changed, 1952 insertions, 1339 deletions
diff --git a/src/2geom/CMakeLists.txt b/src/2geom/CMakeLists.txt index aa51d51bd..d2c1ea50e 100644 --- a/src/2geom/CMakeLists.txt +++ b/src/2geom/CMakeLists.txt @@ -1,3 +1,7 @@ +# Override error flag just for this folder +if (CMAKE_BUILD_TYPE MATCHES Strict) + set(CMAKE_CXX_FLAGS_STRICT "${CMAKE_CXX_FLAGS_STRICT} -Wno-error=deprecated-declarations") +endif() set(2geom_SRC affine.cpp diff --git a/src/attribute-rel-svg.cpp b/src/attribute-rel-svg.cpp index afa578061..1f4bee1b3 100644 --- a/src/attribute-rel-svg.cpp +++ b/src/attribute-rel-svg.cpp @@ -28,6 +28,27 @@ SPAttributeRelSVG * SPAttributeRelSVG::instance = NULL; bool SPAttributeRelSVG::foundFile = false; /* + * This function returns true if element is an SVG element. + */ +bool SPAttributeRelSVG::isSVGElement(Glib::ustring element) +{ + if (SPAttributeRelSVG::instance == NULL) { + SPAttributeRelSVG::instance = new SPAttributeRelSVG(); + } + + // Always valid if data file not found! + if( !foundFile ) return true; + + // Strip off "svg:" from the element's name + Glib::ustring temp = element; + if ( temp.find("svg:") != std::string::npos ) { + temp.erase( temp.find("svg:"), 4 ); + } + + return (SPAttributeRelSVG::instance->attributesOfElements.count(temp) > 0); +} + +/* * This functions checks whether an element -> attribute pair is allowed or not */ bool SPAttributeRelSVG::findIfValid(Glib::ustring attribute, Glib::ustring element) @@ -39,7 +60,7 @@ bool SPAttributeRelSVG::findIfValid(Glib::ustring attribute, Glib::ustring eleme // Always valid if data file not found! if( !foundFile ) return true; - // Strip of "svg:" from the element's name + // Strip off "svg:" from the element's name Glib::ustring temp = element; if ( temp.find("svg:") != std::string::npos ) { temp.erase( temp.find("svg:"), 4 ); diff --git a/src/attribute-rel-svg.h b/src/attribute-rel-svg.h index 74c6d3b60..c0a9cd215 100644 --- a/src/attribute-rel-svg.h +++ b/src/attribute-rel-svg.h @@ -22,6 +22,7 @@ typedef std::map<Glib::ustring, std::set<Glib::ustring> > hashList; */ class SPAttributeRelSVG { public: + static bool isSVGElement(Glib::ustring element); static bool findIfValid(Glib::ustring attribute, Glib::ustring element); private: diff --git a/src/debug/log-display-config.cpp b/src/debug/log-display-config.cpp index ecc05b7b7..1f436a708 100644 --- a/src/debug/log-display-config.cpp +++ b/src/debug/log-display-config.cpp @@ -27,9 +27,20 @@ typedef SimpleEvent<Event::CONFIGURATION> ConfigurationEvent; class Monitor : public ConfigurationEvent { public: - Monitor(GdkScreen *screen, gint monitor) : ConfigurationEvent("monitor") { +#if GTK_CHECK_VERSION(3,22,0) + Monitor(GdkMonitor *monitor) +#else + Monitor(GdkScreen *screen, gint monitor) +#endif + : ConfigurationEvent("monitor") { GdkRectangle area; + +#if GTK_CHECK_VERSION(3,22,0) + gdk_monitor_get_geometry(monitor, &area); +#else gdk_screen_get_monitor_geometry(screen, monitor, &area); +#endif + _addProperty("x", area.x); _addProperty("y", area.y); _addProperty("width", area.width); @@ -37,10 +48,13 @@ public: } }; +#if !GTK_CHECK_VERSION(3,22,0) +// We don't need this in newer Gtk+ versions as GdkMonitor information is now +// returned directly from GdkDisplay rather than needing GdkScreen first class Screen : public ConfigurationEvent { public: Screen(GdkScreen *s) : ConfigurationEvent("screen"), screen(s) { - _addProperty("width", gdk_screen_get_width(screen)); + _addProperty("width", gdk_screen_get_width(screen)); _addProperty("height", gdk_screen_get_height(screen)); } void generateChildEvents() const { @@ -53,21 +67,28 @@ public: private: GdkScreen *screen; }; +#endif class Display : public ConfigurationEvent { public: Display() : ConfigurationEvent("display") {} void generateChildEvents() const { GdkDisplay *display=gdk_display_get_default(); -#if GTK_CHECK_VERSION(3,10,0) - GdkScreen *screen = gdk_display_get_screen(display, 0); - Logger::write<Screen>(screen); -#else - gint n_screens = gdk_display_get_n_screens(display); - for ( gint i = 0 ; i < n_screens ; i++ ) { - GdkScreen *screen = gdk_display_get_screen(display, i); - Logger::write<Screen>(screen); + +#if GTK_CHECK_VERSION(3,22,0) + gint const n_monitors = gdk_display_get_n_monitors(display); + + // Loop through all monitors and log their details + for (gint i_monitor = 0; i_monitor < n_monitors; ++i_monitor) { + GdkMonitor *monitor = gdk_display_get_monitor(display, i_monitor); + Logger::write<Monitor>(monitor); } +#else + // We used to find the number of screens, and log info for + // each of them. However, the number of screens is always + // one in Gtk+ 3 + GdkScreen *screen = gdk_display_get_default_screen(display); + Logger::write<Screen>(screen); #endif } }; diff --git a/src/display/sp-canvas.cpp b/src/display/sp-canvas.cpp index 68eae0a65..623e417c6 100644 --- a/src/display/sp-canvas.cpp +++ b/src/display/sp-canvas.cpp @@ -48,6 +48,10 @@ #include <iomanip> #include <glibmm/i18n.h> +#if GTK_CHECK_VERSION(3,20,0) +# include <gdkmm/seat.h> +#endif + using Inkscape::Debug::GdkEventLatencyTracker; // gtk_check_version returns non-NULL on failure @@ -72,8 +76,14 @@ struct SPCanvasGroupClass { static void ungrab_default_client_pointer(guint32 const time = GDK_CURRENT_TIME) { auto const display = Gdk::Display::get_default(); + +#if GTK_CHECK_VERSION(3,20,0) + auto const seat = display->get_default_seat(); + auto const device = seat->get_pointer(); +#else auto const dm = display->get_device_manager(); auto const device = dm->get_client_pointer(); +#endif device->ungrab(time); } @@ -625,9 +635,16 @@ int sp_canvas_item_grab(SPCanvasItem *item, guint event_mask, GdkCursor *cursor, // fixme: Top hack (Lauris) // fixme: If we add key masks to event mask, Gdk will abort (Lauris) - // fixme: But Canvas actualle does get key events, so all we need is routing these here - auto dm = gdk_display_get_device_manager(gdk_display_get_default()); + // fixme: But Canvas actually does get key events, so all we need is routing these here + auto display = gdk_display_get_default(); +#if GTK_CHECK_VERSION(3,20,0) + auto seat = gdk_display_get_default_seat(display); + auto device = gdk_seat_get_pointer(seat); +#else + auto dm = gdk_display_get_device_manager(display); auto device = gdk_device_manager_get_client_pointer(dm); +#endif + gdk_device_grab(device, getWindow(item->canvas), GDK_OWNERSHIP_NONE, @@ -1034,7 +1051,7 @@ void SPCanvas::handle_realize(GtkWidget *widget) attributes.width = allocation.width; attributes.height = allocation.height; attributes.wclass = GDK_INPUT_OUTPUT; - attributes.visual = gdk_visual_get_system(); + attributes.visual = gdk_screen_get_system_visual(gdk_screen_get_default()); attributes.event_mask = (gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK | @@ -1694,8 +1711,14 @@ bool SPCanvas::paintRect(int xx0, int yy0, int xx1, int yy1) gint x, y; auto const display = Gdk::Display::get_default(); + +#if GTK_CHECK_VERSION(3,20,0) + auto const seat = display->get_default_seat(); + auto const device = seat->get_pointer(); +#else auto const dm = display->get_device_manager(); auto const device = dm->get_client_pointer(); +#endif gdk_window_get_device_position(gtk_widget_get_window(GTK_WIDGET(this)), device->gobj(), diff --git a/src/document.cpp b/src/document.cpp index b69508751..c7115f906 100644 --- a/src/document.cpp +++ b/src/document.cpp @@ -62,6 +62,11 @@ #include "sp-symbol.h" #include "xml/rebase-hrefs.h" +#include "libcroco/cr-sel-eng.h" +#include "libcroco/cr-selector.h" +#include "libcroco/cr-parser.h" +#include "src/xml/croco-node-iface.h" + using Inkscape::DocumentUndo; using Inkscape::Util::unit_table; @@ -1119,6 +1124,50 @@ std::vector<SPObject *> SPDocument::getObjectsByElement(Glib::ustring const &ele return objects; } +void _getObjectsBySelectorRecursive(SPObject *parent, + CRSelEng *sel_eng, CRSimpleSel *simple_sel, + std::vector<SPObject *> &objects) +{ + if (parent) { + gboolean result = false; + cr_sel_eng_matches_node( sel_eng, simple_sel, parent->getRepr(), &result ); + if (result) { + objects.push_back(parent); + } + + // Check children + for (auto& child : parent->children) { + _getObjectsBySelectorRecursive(&child, sel_eng, simple_sel, objects); + } + } +} + +std::vector<SPObject *> SPDocument::getObjectsBySelector(Glib::ustring const &selector) const +{ + // std::cout << "\nSPDocument::getObjectsBySelector: " << selector << std::endl; + + std::vector<SPObject *> objects; + g_return_val_if_fail(!selector.empty(), objects); + + static CRSelEng *sel_eng = NULL; + if (!sel_eng) { + sel_eng = cr_sel_eng_new(); + cr_sel_eng_set_node_iface(sel_eng, &Inkscape::XML::croco_node_iface); + } + + Glib::ustring my_selector = selector + " {"; // Parsing fails sometimes without '{'. Fix me + CRSelector *cr_selector = cr_selector_parse_from_buf ((guchar*)my_selector.c_str(), CR_UTF_8); + // char * cr_string = (char*)cr_selector_to_string( cr_selector ); + // std::cout << " selector: |" << (cr_string?cr_string:"Empty") << "|" << std::endl; + CRSelector const *cur = NULL; + for (cur = cr_selector; cur; cur = cur->next) { + if (cur->simple_sel ) { + _getObjectsBySelectorRecursive(root, sel_eng, cur->simple_sel, objects); + } + } + return objects; +} + void SPDocument::bindObjectToRepr(Inkscape::XML::Node *repr, SPObject *object) { if (object) { @@ -1474,7 +1523,9 @@ static SPItem *find_group_at_point(unsigned int dkey, SPGroup *group, Geom::Poin if (SP_IS_GROUP(&o) && SP_GROUP(&o)->effectiveLayerMode(dkey) != SPGroup::LAYER ) { SPItem *child = SP_ITEM(&o); Inkscape::DrawingItem *arenaitem = child->get_arenaitem(dkey); - arenaitem->drawing().update(); + if (arenaitem) { + arenaitem->drawing().update(); + } // seen remembers the last (topmost) of groups pickable at this point if (arenaitem && arenaitem->pick(p, delta, 1) != NULL) { diff --git a/src/document.h b/src/document.h index 142eb5000..db50f1717 100644 --- a/src/document.h +++ b/src/document.h @@ -199,7 +199,8 @@ public: std::vector<SPObject *> getObjectsByClass(Glib::ustring const &klass) const; std::vector<SPObject *> getObjectsByElement(Glib::ustring const &element) const; - + std::vector<SPObject *> getObjectsBySelector(Glib::ustring const &selector) const; + void bindObjectToRepr(Inkscape::XML::Node *repr, SPObject *object); SPObject *getObjectByRepr(Inkscape::XML::Node *repr) const; diff --git a/src/extension/extension.cpp b/src/extension/extension.cpp index c6dee1b70..8b5a687ef 100644 --- a/src/extension/extension.cpp +++ b/src/extension/extension.cpp @@ -1,6 +1,6 @@ /** \file * - * Inkscape::Extension::Extension: + * Inkscape::Extension::Extension: * the ability to have features that are more modular so that they * can be added and removed easily. This is the basis for defining * those actions. @@ -144,15 +144,15 @@ Extension::~Extension (void) delete timer; timer = NULL; /** \todo Need to do parameters here */ - - // delete parameters: + + // delete parameters: for (GSList * list = parameters; list != NULL; list = g_slist_next(list)) { Parameter * param = reinterpret_cast<Parameter *>(list->data); delete param; } g_slist_free(parameters); - - + + for (unsigned int i = 0 ; i < _deps.size(); i++) { delete _deps[i]; } @@ -330,7 +330,7 @@ Extension::get_repr (void) } /** - \return bool + \return bool \brief Whether this extension should hide the "working, please wait" dialog */ bool @@ -444,7 +444,7 @@ Extension::get_param_enum (const gchar * name, const SPDocument * doc, const Ink /** * This is useful to find out, if a given string \c value is selectable in a ComboBox named \cname. - * + * * @param name The name of the enum parameter to get. * @param doc The document to look in for document specific parameters. * @param node The node to look in for a specific parameter. @@ -692,9 +692,10 @@ public: * @param widg Widget to add. * @param tooltip Tooltip for the widget. */ - void addWidget(Gtk::Widget *widg, gchar const *tooltip) { + void addWidget(Gtk::Widget *widg, gchar const *tooltip, int indent) { if (widg) { - this->pack_start(*widg, false, false, 2); + widg->set_margin_left(indent * Parameter::GUI_INDENTATION); + this->pack_start(*widg, false, false, 0); if (tooltip) { widg->set_tooltip_text(_(tooltip)); } else { @@ -713,7 +714,7 @@ public: a Gtk::VBox, which is then returned to the calling function. If there are no visible parameters, this function just returns NULL. - If all parameters are gui_visible = false NULL is returned as well. + If all parameters are gui_visible = false NULL is returned as well. */ Gtk::Widget * Extension::autogui (SPDocument * doc, Inkscape::XML::Node * node, sigc::signal<void> * changeSignal) @@ -721,6 +722,8 @@ Extension::autogui (SPDocument * doc, Inkscape::XML::Node * node, sigc::signal<v if (!_gui || param_visible_count() == 0) return NULL; AutoGUI * agui = Gtk::manage(new AutoGUI()); + agui->set_border_width(Parameter::GUI_BOX_MARGIN); + agui->set_spacing(Parameter::GUI_BOX_SPACING); //go through the list of parameters to see if there are any non-hidden ones for (GSList * list = parameters; list != NULL; list = g_slist_next(list)) { @@ -728,9 +731,10 @@ Extension::autogui (SPDocument * doc, Inkscape::XML::Node * node, sigc::signal<v if (param->get_gui_hidden()) continue; //Ignore hidden parameters Gtk::Widget * widg = param->get_widget(doc, node, changeSignal); gchar const * tip = param->get_tooltip(); - agui->addWidget(widg, tip); - } - + int indent = param->get_indent(); + agui->addWidget(widg, tip, indent); + } + agui->show(); return agui; }; @@ -780,7 +784,7 @@ void Extension::add_val(Glib::ustring labelstr, Glib::ustring valuestr, Gtk::Gri Gtk::Label * label; Gtk::Label * value; - (*row)++; + (*row)++; label = Gtk::manage(new Gtk::Label(labelstr)); value = Gtk::manage(new Gtk::Label(valuestr)); @@ -824,13 +828,13 @@ Extension::get_params_widget(void) return retval; } -unsigned int Extension::param_visible_count ( ) +unsigned int Extension::param_visible_count ( ) { unsigned int _visible_count = 0; for (GSList * list = parameters; list != NULL; list = g_slist_next(list)) { Parameter * param = reinterpret_cast<Parameter *>(list->data); if (!param->get_gui_hidden()) _visible_count++; - } + } return _visible_count; } diff --git a/src/extension/extension.h b/src/extension/extension.h index cd29e1636..bbd6d068b 100644 --- a/src/extension/extension.h +++ b/src/extension/extension.h @@ -176,7 +176,7 @@ public: private: void make_param (Inkscape::XML::Node * paramrepr); - + /** * This function looks through the linked list for a parameter * structure with the name of the passed in name. diff --git a/src/extension/implementation/script.cpp b/src/extension/implementation/script.cpp index d2319c2e0..b1058d415 100644 --- a/src/extension/implementation/script.cpp +++ b/src/extension/implementation/script.cpp @@ -963,6 +963,9 @@ void Script::checkStderr (const Glib::ustring &data, warning.run(); + delete textview; + delete scrollwindow; + return; } diff --git a/src/extension/internal/bitmap/imagemagick.cpp b/src/extension/internal/bitmap/imagemagick.cpp index 472c2db91..cc5b3d1bc 100644 --- a/src/extension/internal/bitmap/imagemagick.cpp +++ b/src/extension/internal/bitmap/imagemagick.cpp @@ -213,6 +213,7 @@ ImageMagick::effect (Inkscape::Extension::Effect *module, Inkscape::UI::View::Vi dc->_nodes[i]->setAttribute("xlink:href", dc->_caches[i], true); dc->_nodes[i]->setAttribute("sodipodi:absref", NULL, true); + delete blob; } catch (Magick::Exception &error_) { printf("Caught exception: %s \n", error_.what()); diff --git a/src/extension/internal/cdr-input.cpp b/src/extension/internal/cdr-input.cpp index 0435f1396..dbe88c668 100644 --- a/src/extension/internal/cdr-input.cpp +++ b/src/extension/internal/cdr-input.cpp @@ -218,11 +218,13 @@ SPDocument *CdrInput::open(Inkscape::Extension::Input * /*mod*/, const gchar * u // RVNGFileStream uses fopen() internally which unfortunately only uses ANSI encoding on Windows // therefore attempt to convert uri to the system codepage // even if this is not possible the alternate short (8.3) file name will be used if available - uri = g_win32_locale_filename_from_utf8(uri); + gchar * converted_uri = g_win32_locale_filename_from_utf8(uri); + RVNGFileStream input(converted_uri); + g_free(converted_uri); + #else + RVNGFileStream input(uri); #endif - RVNGFileStream input(uri); - if (!libcdr::CDRDocument::isSupported(&input)) { return NULL; } diff --git a/src/extension/internal/filter/color.h b/src/extension/internal/filter/color.h index a6b777d20..6324dafa9 100644 --- a/src/extension/internal/filter/color.h +++ b/src/extension/internal/filter/color.h @@ -1480,7 +1480,7 @@ public: static void init (void) { Inkscape::Extension::build_from_mem( "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" - "<name>" N_("Quadritone fantasy") "</name>\n" + "<name>" N_("Quadritone Fantasy") "</name>\n" "<id>org.inkscape.effect.filter.Quadritone</id>\n" "<param name=\"dist\" _gui-text=\"" N_("Hue distribution (°)") "\" type=\"int\" appearance=\"full\" min=\"0\" max=\"360\">280</param>\n" "<param name=\"colors\" _gui-text=\"" N_("Colors") "\" type=\"int\" appearance=\"full\" min=\"0\" max=\"360\">100</param>\n" diff --git a/src/extension/internal/filter/protrusions.h b/src/extension/internal/filter/protrusions.h index ad75d8896..d6fd315db 100644 --- a/src/extension/internal/filter/protrusions.h +++ b/src/extension/internal/filter/protrusions.h @@ -45,7 +45,7 @@ public: static void init (void) { Inkscape::Extension::build_from_mem( "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" - "<name>" N_("Snow crest") "</name>\n" + "<name>" N_("Snow Crest") "</name>\n" "<id>org.inkscape.effect.filter.snow</id>\n" "<param name=\"drift\" _gui-text=\"" N_("Drift Size") "\" type=\"float\" appearance=\"full\" min=\"0.0\" max=\"20.0\">3.5</param>\n" "<effect>\n" diff --git a/src/extension/internal/vsd-input.cpp b/src/extension/internal/vsd-input.cpp index 78783aa2d..85698387a 100644 --- a/src/extension/internal/vsd-input.cpp +++ b/src/extension/internal/vsd-input.cpp @@ -220,11 +220,13 @@ SPDocument *VsdInput::open(Inkscape::Extension::Input * /*mod*/, const gchar * u // RVNGFileStream uses fopen() internally which unfortunately only uses ANSI encoding on Windows // therefore attempt to convert uri to the system codepage // even if this is not possible the alternate short (8.3) file name will be used if available - uri = g_win32_locale_filename_from_utf8(uri); + gchar * converted_uri = g_win32_locale_filename_from_utf8(uri); + RVNGFileStream input(converted_uri); + g_free(converted_uri); + #else + RVNGFileStream input(uri); #endif - RVNGFileStream input(uri); - if (!libvisio::VisioDocument::isSupported(&input)) { return NULL; } diff --git a/src/extension/internal/wmf-inout.cpp b/src/extension/internal/wmf-inout.cpp index a79af5ec1..42ec2f2bb 100644 --- a/src/extension/internal/wmf-inout.cpp +++ b/src/extension/internal/wmf-inout.cpp @@ -1765,11 +1765,20 @@ std::cout << "BEFORE DRAW" << " test0 " << ( d->mask & U_DRAW_VISIBLE) << " test1 " << ( d->mask & U_DRAW_FORCE) << " test2 " << (wmr_mask & U_DRAW_ALTERS) + << " test2.5 " << ((d->mask & U_DRAW_NOFILL) != (wmr_mask & U_DRAW_NOFILL) ) << " test3 " << (wmr_mask & U_DRAW_VISIBLE) << " test4 " << !(d->mask & U_DRAW_ONLYTO) << " test5 " << ((d->mask & U_DRAW_ONLYTO) && !(wmr_mask & U_DRAW_ONLYTO) ) << std::endl; */ + /* spurious moveto records should not affect the drawing. However, they set the NOFILL + bit and that messes up the logic about when to emit a path. So prune out any + stray moveto records. That is those which were never followed by a lineto. + */ + if((d->mask & U_DRAW_NOFILL) && !(d->mask & U_DRAW_VISIBLE) && + !(wmr_mask & U_DRAW_ONLYTO) && (wmr_mask & U_DRAW_VISIBLE)){ + d->mask ^= U_DRAW_NOFILL; + } if( (wmr_mask != U_WMR_INVALID) && // next record is valid type @@ -1777,6 +1786,7 @@ std::cout << "BEFORE DRAW" ( (d->mask & U_DRAW_FORCE) || // This draw is forced by STROKE/FILL/STROKEANDFILL PATH (wmr_mask & U_DRAW_ALTERS) || // Next record would alter the drawing environment in some way + ((d->mask & U_DRAW_NOFILL) != (wmr_mask & U_DRAW_NOFILL)) || // Fill<->!Fill requires a draw between ( (wmr_mask & U_DRAW_VISIBLE) && // Next record is visible... ( ( !(d->mask & U_DRAW_ONLYTO) ) || // Non *TO records cannot be followed by any Visible diff --git a/src/extension/internal/wmf-print.cpp b/src/extension/internal/wmf-print.cpp index 3d913bf1e..6dd9d895e 100644 --- a/src/extension/internal/wmf-print.cpp +++ b/src/extension/internal/wmf-print.cpp @@ -598,7 +598,28 @@ int PrintWmf::create_pen(SPStyle const *style, const Geom::Affine &transform) if (!style->stroke_dasharray.values.empty()) { if (!FixPPTDashLine) { // if this is set code elsewhere will break dots/dashes into many smaller lines. - penstyle = U_PS_DASH;// userstyle not supported apparently, for now map all Inkscape dot/dash to just dash + int n_dash = style->stroke_dasharray.values.size(); + /* options are dash, dot, dashdot and dashdotdot. Try to pick the closest one. */ + int mark_short=INT_MAX; + int mark_long =0; + int i; + for (i=0;i<n_dash;i++) { + int mark = style->stroke_dasharray.values[i]; + if (mark>mark_long) { mark_long = mark; } + if (mark<mark_short) { mark_short = mark; } + } + if(mark_long == mark_short){ // only one mark size + penstyle = U_PS_DOT; + } + else if (n_dash==2) { + penstyle = U_PS_DASH; + } + else if (n_dash==4) { + penstyle = U_PS_DASHDOT; + } + else { + penstyle = U_PS_DASHDOTDOT; + } } } diff --git a/src/extension/internal/wpg-input.cpp b/src/extension/internal/wpg-input.cpp index 12457791b..299614c94 100644 --- a/src/extension/internal/wpg-input.cpp +++ b/src/extension/internal/wpg-input.cpp @@ -85,10 +85,13 @@ SPDocument *WpgInput::open(Inkscape::Extension::Input * /*mod*/, const gchar * u // RVNGFileStream uses fopen() internally which unfortunately only uses ANSI encoding on Windows // therefore attempt to convert uri to the system codepage // even if this is not possible the alternate short (8.3) file name will be used if available - uri = g_win32_locale_filename_from_utf8(uri); + gchar * converted_uri = g_win32_locale_filename_from_utf8(uri); + RVNGInputStream* input = new RVNGFileStream(converted_uri); + g_free(converted_uri); + #else + RVNGInputStream* input = new RVNGFileStream(uri); #endif - RVNGInputStream* input = new RVNGFileStream(uri); #if WITH_LIBWPG03 if (input->isStructured()) { RVNGInputStream* olestream = input->getSubStreamByName("PerfectOffice_MAIN"); diff --git a/src/extension/param/bool.cpp b/src/extension/param/bool.cpp index ca61d8c51..9f8d3dcc0 100644 --- a/src/extension/param/bool.cpp +++ b/src/extension/param/bool.cpp @@ -24,9 +24,17 @@ namespace Inkscape { namespace Extension { -ParamBool::ParamBool(const gchar * name, const gchar * guitext, const gchar * desc, const Parameter::_scope_t scope, bool gui_hidden, const gchar * gui_tip, Inkscape::Extension::Extension * ext, Inkscape::XML::Node * xml) : - Parameter(name, guitext, desc, scope, gui_hidden, gui_tip, ext), - _value(false), _indent(0) +ParamBool::ParamBool(const gchar * name, + const gchar * guitext, + const gchar * desc, + const Parameter::_scope_t scope, + bool gui_hidden, + const gchar * gui_tip, + int indent, + Inkscape::Extension::Extension * ext, + Inkscape::XML::Node * xml) + : Parameter(name, guitext, desc, scope, gui_hidden, gui_tip, indent, ext) + , _value(false) { const char * defaultval = NULL; if (xml->firstChild() != NULL) { @@ -39,11 +47,6 @@ ParamBool::ParamBool(const gchar * name, const gchar * guitext, const gchar * de _value = false; } - const char * indent = xml->attribute("indent"); - if (indent != NULL) { - _indent = atoi(indent) * 12; - } - gchar * pref_name = this->pref_name(); Inkscape::Preferences *prefs = Inkscape::Preferences::get(); _value = prefs->getBool(extension_pref_root + pref_name, _value); @@ -66,7 +69,7 @@ bool ParamBool::set( bool in, SPDocument * /*doc*/, Inkscape::XML::Node * /*node bool ParamBool::get(const SPDocument * /*doc*/, const Inkscape::XML::Node * /*node*/) const { - return _value; + return _value; } /** @@ -130,7 +133,7 @@ Gtk::Widget *ParamBool::get_widget(SPDocument * doc, Inkscape::XML::Node * node, return NULL; } - auto hbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 4)); + auto hbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, Parameter::GUI_PARAM_WIDGETS_SPACING)); hbox->set_homogeneous(false); Gtk::Label * label = Gtk::manage(new Gtk::Label(_(_text), Gtk::ALIGN_START)); @@ -139,7 +142,7 @@ Gtk::Widget *ParamBool::get_widget(SPDocument * doc, Inkscape::XML::Node * node, ParamBoolCheckButton * checkbox = Gtk::manage(new ParamBoolCheckButton(this, doc, node, changeSignal)); checkbox->show(); - hbox->pack_start(*checkbox, false, false, _indent); + hbox->pack_start(*checkbox, false, false); hbox->show(); diff --git a/src/extension/param/bool.h b/src/extension/param/bool.h index 5d190b9d3..0af12b105 100644 --- a/src/extension/param/bool.h +++ b/src/extension/param/bool.h @@ -32,7 +32,15 @@ public: /** * Use the superclass' allocator and set the \c _value. */ - ParamBool(const gchar * name, const gchar * guitext, const gchar * desc, const Parameter::_scope_t scope, bool gui_hidden, const gchar * gui_tip, Inkscape::Extension::Extension * ext, Inkscape::XML::Node * xml); + ParamBool(const gchar * name, + const gchar * guitext, + const gchar * desc, + const Parameter::_scope_t scope, + bool gui_hidden, + const gchar * gui_tip, + int indent, + Inkscape::Extension::Extension * ext, + Inkscape::XML::Node * xml); /** * Returns the current state/value. @@ -69,7 +77,6 @@ public: private: /** Internal value. */ bool _value; - int _indent; }; } // namespace Extension diff --git a/src/extension/param/color.cpp b/src/extension/param/color.cpp index 0b58c5011..fd88e9adb 100644 --- a/src/extension/param/color.cpp +++ b/src/extension/param/color.cpp @@ -52,9 +52,17 @@ guint32 ParamColor::set( guint32 in, SPDocument * /*doc*/, Inkscape::XML::Node * return in; } -ParamColor::ParamColor (const gchar * name, const gchar * guitext, const gchar * desc, const Parameter::_scope_t scope, bool gui_hidden, const gchar * gui_tip, Inkscape::Extension::Extension * ext, Inkscape::XML::Node * xml) : - Parameter(name, guitext, desc, scope, gui_hidden, gui_tip, ext), - _changeSignal(0) +ParamColor::ParamColor(const gchar * name, + const gchar * guitext, + const gchar * desc, + const Parameter::_scope_t scope, + bool gui_hidden, + const gchar * gui_tip, + int indent, + Inkscape::Extension::Extension * ext, + Inkscape::XML::Node * xml) + : Parameter(name, guitext, desc, scope, gui_hidden, gui_tip, indent, ext) + , _changeSignal(0) { const char * defaulthex = NULL; if (xml->firstChild() != NULL) @@ -86,7 +94,7 @@ Gtk::Widget *ParamColor::get_widget( SPDocument * /*doc*/, Inkscape::XML::Node * { using Inkscape::UI::Widget::ColorNotebook; - if (_gui_hidden) return NULL; + if (_gui_hidden) return NULL; if (changeSignal) { _changeSignal = new sigc::signal<void>(*changeSignal); @@ -98,9 +106,9 @@ Gtk::Widget *ParamColor::get_widget( SPDocument * /*doc*/, Inkscape::XML::Node * _color_changed.block(false); } - Gtk::HBox *hbox = Gtk::manage(new Gtk::HBox(false, 4)); + Gtk::HBox *hbox = Gtk::manage(new Gtk::HBox(false, Parameter::GUI_PARAM_WIDGETS_SPACING)); Gtk::Widget *selector = Gtk::manage(new ColorNotebook(_color)); - hbox->pack_start (*selector, true, true, 0); + hbox->pack_start(*selector, true, true, 0); selector->show(); hbox->show(); return hbox; diff --git a/src/extension/param/color.h b/src/extension/param/color.h index ed2e57ceb..d6e9d1fbe 100644 --- a/src/extension/param/color.h +++ b/src/extension/param/color.h @@ -31,8 +31,15 @@ private: Inkscape::UI::SelectedColor _color; sigc::connection _color_changed; public: - ParamColor(const gchar * name, const gchar * guitext, const gchar * desc, const Parameter::_scope_t scope, bool gui_hidden, const gchar * gui_tip, Inkscape::Extension::Extension * ext, Inkscape::XML::Node * xml); - + ParamColor(const gchar * name, + const gchar * guitext, + const gchar * desc, + const Parameter::_scope_t scope, + bool gui_hidden, + const gchar * gui_tip, + int indent, + Inkscape::Extension::Extension * ext, + Inkscape::XML::Node * xml); virtual ~ParamColor(void); /** Returns \c _value, with a \i const to protect it. */ diff --git a/src/extension/param/description.cpp b/src/extension/param/description.cpp index 07aaa07cc..7cf818280 100644 --- a/src/extension/param/description.cpp +++ b/src/extension/param/description.cpp @@ -16,6 +16,7 @@ #include <gtkmm/box.h> #include <gtkmm/label.h> #include <glibmm/i18n.h> +#include <glibmm/markup.h> #include "xml/node.h" #include "extension/extension.h" @@ -26,17 +27,19 @@ namespace Extension { /** \brief Initialize the object, to do that, copy the data. */ -ParamDescription::ParamDescription (const gchar * name, - const gchar * guitext, - const gchar * desc, - const Parameter::_scope_t scope, - bool gui_hidden, - const gchar * gui_tip, - Inkscape::Extension::Extension * ext, - Inkscape::XML::Node * xml, - AppearanceMode mode) : - Parameter(name, guitext, desc, scope, gui_hidden, gui_tip, ext), - _value(NULL), _mode(mode), _indent(0) +ParamDescription::ParamDescription(const gchar * name, + const gchar * guitext, + const gchar * desc, + const Parameter::_scope_t scope, + bool gui_hidden, + const gchar * gui_tip, + int indent, + Inkscape::Extension::Extension * ext, + Inkscape::XML::Node * xml, + AppearanceMode mode) + : Parameter(name, guitext, desc, scope, gui_hidden, gui_tip, indent, ext) + , _value(NULL) + , _mode(mode) { // printf("Building Description\n"); const char * defaultval = NULL; @@ -50,11 +53,6 @@ ParamDescription::ParamDescription (const gchar * name, _context = xml->attribute("msgctxt"); - const char * indent = xml->attribute("indent"); - if (indent != NULL) { - _indent = atoi(indent) * 12; - } - return; } @@ -76,23 +74,37 @@ ParamDescription::get_widget (SPDocument * /*doc*/, Inkscape::XML::Node * /*node } else { newguitext = _(_value); } - - Gtk::Label * label; - int padding = 12 + _indent; + + Gtk::Label * label = Gtk::manage(new Gtk::Label()); if (_mode == HEADER) { - label = Gtk::manage(new Gtk::Label(Glib::ustring("<b>") +newguitext + Glib::ustring("</b>"), Gtk::ALIGN_START)); - label->set_margin_top(5); - label->set_margin_bottom(5); - label->set_use_markup(true); - padding = _indent; + label->set_markup(Glib::ustring("<b>") + Glib::Markup::escape_text(newguitext) + Glib::ustring("</b>")); + label->set_margin_top(5); + label->set_margin_bottom(5); + } else if (_mode == URL) { + Glib::ustring escaped_url = Glib::Markup::escape_text(newguitext); + label->set_markup(Glib::ustring::compose("<a href='%1'>%1</a>", escaped_url)); } else { - label = Gtk::manage(new Gtk::Label(newguitext, Gtk::ALIGN_START)); + label->set_text(newguitext); } label->set_line_wrap(); + //label->set_xalign(0); // requires gtkmm 3.16 + label->set_alignment(Gtk::ALIGN_START); + + // TODO: Ugly "fix" for gtk3 width/height calculation of labels. + // - If not applying any limits long labels will make the window grow horizontally until it uses up + // most of the available space (i.e. most of the screen area) which is ridicously wide + // - By using "set_default_size(0,0)" in prefidalog.cpp we tell the window to shrink as much as possible, + // however this can result in a much to narrow dialog instead and much unnecessary wrapping + // - Here we set a lower limit of GUI_MAX_LINE_LENGTH characters per line that long texts will always use + // This means texts can not shrink anymore (they can still grow, though) and it's also necessary + // to prevent https://bugzilla.gnome.org/show_bug.cgi?id=773572 + int len = newguitext.length(); + label->set_width_chars(len > Parameter::GUI_MAX_LINE_LENGTH ? Parameter::GUI_MAX_LINE_LENGTH : len); + label->show(); - Gtk::HBox * hbox = Gtk::manage(new Gtk::HBox(false, 4)); - hbox->pack_start(*label, true, true, padding); + Gtk::HBox * hbox = Gtk::manage(new Gtk::HBox()); + hbox->pack_start(*label, true, true); hbox->show(); return hbox; diff --git a/src/extension/param/description.h b/src/extension/param/description.h index 3a72c4112..c6c5f4013 100644 --- a/src/extension/param/description.h +++ b/src/extension/param/description.h @@ -27,23 +27,24 @@ namespace Extension { class ParamDescription : public Parameter { public: enum AppearanceMode { - DESC, HEADER + DESC, HEADER, URL }; ParamDescription(const gchar * name, const gchar * guitext, const gchar * desc, const Parameter::_scope_t scope, bool gui_hidden, - const gchar * gui_tip, + const gchar * gui_tip, + int indent, Inkscape::Extension::Extension * ext, Inkscape::XML::Node * xml, AppearanceMode mode); + Gtk::Widget * get_widget(SPDocument * doc, Inkscape::XML::Node * node, sigc::signal<void> * changeSignal); private: /** \brief Internal value. */ gchar * _value; AppearanceMode _mode; - int _indent; const gchar* _context; }; diff --git a/src/extension/param/enum.cpp b/src/extension/param/enum.cpp index 8bc0fbda7..a26458c35 100644 --- a/src/extension/param/enum.cpp +++ b/src/extension/param/enum.cpp @@ -46,12 +46,17 @@ public: }; -ParamComboBox::ParamComboBox(const gchar *name, const gchar *guitext, const gchar *desc, - const Parameter::_scope_t scope, bool gui_hidden, const gchar *gui_tip, - Inkscape::Extension::Extension *ext, Inkscape::XML::Node *xml) - : Parameter(name, guitext, desc, scope, gui_hidden, gui_tip, ext) +ParamComboBox::ParamComboBox(const gchar * name, + const gchar * guitext, + const gchar * desc, + const Parameter::_scope_t scope, + bool gui_hidden, + const gchar * gui_tip, + int indent, + Inkscape::Extension::Extension * ext, + Inkscape::XML::Node * xml) + : Parameter(name, guitext, desc, scope, gui_hidden, gui_tip, indent, ext) , _value(NULL) - , _indent(0) , choices(NULL) { const char *xmlval = NULL; // the value stored in XML @@ -94,17 +99,12 @@ ParamComboBox::ParamComboBox(const gchar *name, const gchar *guitext, const gcha } } } - + // Initialize _value with the default value from xml // for simplicity : default to the contents of the first xml-child if (xml->firstChild() && xml->firstChild()->firstChild()) { xmlval = xml->firstChild()->attribute("value"); } - - const char *indent = xml->attribute("indent"); - if (indent != NULL) { - _indent = atoi(indent) * 12; - } } gchar * pref_name = this->pref_name(); @@ -250,10 +250,10 @@ Gtk::Widget *ParamComboBox::get_widget(SPDocument * doc, Inkscape::XML::Node * n return NULL; } - Gtk::HBox * hbox = Gtk::manage(new Gtk::HBox(false, 4)); + Gtk::HBox * hbox = Gtk::manage(new Gtk::HBox(false, Parameter::GUI_PARAM_WIDGETS_SPACING)); Gtk::Label * label = Gtk::manage(new Gtk::Label(_(_text), Gtk::ALIGN_START)); label->show(); - hbox->pack_start(*label, false, false, _indent); + hbox->pack_start(*label, false, false); ParamComboBoxEntry * combo = Gtk::manage(new ParamComboBoxEntry(this, doc, node, changeSignal)); // add choice strings: diff --git a/src/extension/param/enum.h b/src/extension/param/enum.h index 52e018469..f9d3d75f7 100644 --- a/src/extension/param/enum.h +++ b/src/extension/param/enum.h @@ -34,12 +34,20 @@ private: been allocated in memory. And should be free'd. It is the value of the current selected string */ gchar * _value; - int _indent; GSList * choices; /**< A table to store the choice strings */ public: - ParamComboBox(const gchar * name, const gchar * guitext, const gchar * desc, const Parameter::_scope_t scope, bool gui_hidden, const gchar * gui_tip, Inkscape::Extension::Extension * ext, Inkscape::XML::Node * xml); + ParamComboBox(const gchar * name, + const gchar * guitext, + const gchar * desc, + const Parameter::_scope_t scope, + bool gui_hidden, + const gchar * gui_tip, + int indent, + Inkscape::Extension::Extension * ext, + Inkscape::XML::Node * xml); virtual ~ParamComboBox(void); + Gtk::Widget * get_widget(SPDocument * doc, Inkscape::XML::Node * node, sigc::signal<void> * changeSignal); // Explicitly call superclass version to avoid method being hidden. diff --git a/src/extension/param/float.cpp b/src/extension/param/float.cpp index 23a03ea8f..e3fdba826 100644 --- a/src/extension/param/float.cpp +++ b/src/extension/param/float.cpp @@ -27,17 +27,21 @@ namespace Extension { /** Use the superclass' allocator and set the \c _value. */ -ParamFloat::ParamFloat (const gchar * name, - const gchar * guitext, - const gchar * desc, - const Parameter::_scope_t scope, - bool gui_hidden, - const gchar * gui_tip, - Inkscape::Extension::Extension * ext, - Inkscape::XML::Node * xml, - AppearanceMode mode) : - Parameter(name, guitext, desc, scope, gui_hidden, gui_tip, ext), - _value(0.0), _mode(mode), _indent(0), _min(0.0), _max(10.0) +ParamFloat::ParamFloat(const gchar * name, + const gchar * guitext, + const gchar * desc, + const Parameter::_scope_t scope, + bool gui_hidden, + const gchar * gui_tip, + int indent, + Inkscape::Extension::Extension * ext, + Inkscape::XML::Node * xml, + AppearanceMode mode) + : Parameter(name, guitext, desc, scope, gui_hidden, gui_tip, indent, ext) + , _value(0.0) + , _mode(mode) + , _min(0.0) + , _max(10.0) { const gchar * defaultval = NULL; if (xml->firstChild() != NULL) { @@ -69,11 +73,6 @@ ParamFloat::ParamFloat (const gchar * name, _min = 0.0; } - const char * indent = xml->attribute("indent"); - if (indent != NULL) { - _indent = atoi(indent) * 12; - } - gchar * pref_name = this->pref_name(); Inkscape::Preferences *prefs = Inkscape::Preferences::get(); _value = prefs->getDouble(extension_pref_root + pref_name, _value); @@ -175,24 +174,24 @@ Gtk::Widget * ParamFloat::get_widget(SPDocument * doc, Inkscape::XML::Node * nod return NULL; } - Gtk::HBox * hbox = Gtk::manage(new Gtk::HBox(false, 4)); + Gtk::HBox * hbox = Gtk::manage(new Gtk::HBox(false, Parameter::GUI_PARAM_WIDGETS_SPACING)); auto pfa = new ParamFloatAdjustment(this, doc, node, changeSignal); Glib::RefPtr<Gtk::Adjustment> fadjust(pfa); - + if (_mode == FULL) { UI::Widget::SpinScale *scale = new UI::Widget::SpinScale(_(_text), fadjust, _precision); scale->set_size_request(400, -1); scale->show(); - hbox->pack_start(*scale, false, false); + hbox->pack_start(*scale, true, true); } else if (_mode == MINIMAL) { Gtk::Label * label = Gtk::manage(new Gtk::Label(_(_text), Gtk::ALIGN_START)); label->show(); - hbox->pack_start(*label, true, true, _indent); + hbox->pack_start(*label, true, true); auto spin = Gtk::manage(new Inkscape::UI::Widget::SpinButton(fadjust, 0.1, _precision)); spin->show(); diff --git a/src/extension/param/float.h b/src/extension/param/float.h index 42b1698b1..7fd86cea7 100644 --- a/src/extension/param/float.h +++ b/src/extension/param/float.h @@ -29,15 +29,17 @@ public: enum AppearanceMode { FULL, MINIMAL }; - ParamFloat (const gchar * name, - const gchar * guitext, - const gchar * desc, - const Parameter::_scope_t scope, - bool gui_hidden, - const gchar * gui_tip, - Inkscape::Extension::Extension * ext, - Inkscape::XML::Node * xml, - AppearanceMode mode); + ParamFloat(const gchar * name, + const gchar * guitext, + const gchar * desc, + const Parameter::_scope_t scope, + bool gui_hidden, + const gchar * gui_tip, + int indent, + Inkscape::Extension::Extension * ext, + Inkscape::XML::Node * xml, + AppearanceMode mode); + /** Returns \c _value. */ float get(const SPDocument * /*doc*/, const Inkscape::XML::Node * /*node*/) const { return _value; } @@ -60,7 +62,6 @@ private: /** Internal value. */ float _value; AppearanceMode _mode; - int _indent; float _min; float _max; int _precision; diff --git a/src/extension/param/int.cpp b/src/extension/param/int.cpp index 222d4f243..538ddc08d 100644 --- a/src/extension/param/int.cpp +++ b/src/extension/param/int.cpp @@ -27,17 +27,21 @@ namespace Extension { /** Use the superclass' allocator and set the \c _value. */ -ParamInt::ParamInt (const gchar * name, - const gchar * guitext, - const gchar * desc, - const Parameter::_scope_t scope, - bool gui_hidden, - const gchar * gui_tip, - Inkscape::Extension::Extension * ext, - Inkscape::XML::Node * xml, - AppearanceMode mode) : - Parameter(name, guitext, desc, scope, gui_hidden, gui_tip, ext), - _value(0), _mode(mode), _indent(0), _min(0), _max(10) +ParamInt::ParamInt(const gchar * name, + const gchar * guitext, + const gchar * desc, + const Parameter::_scope_t scope, + bool gui_hidden, + const gchar * gui_tip, + int indent, + Inkscape::Extension::Extension * ext, + Inkscape::XML::Node * xml, + AppearanceMode mode) + : Parameter(name, guitext, desc, scope, gui_hidden, gui_tip, indent, ext) + , _value(0) + , _mode(mode) + , _min(0) + , _max(10) { const char * defaultval = NULL; if (xml->firstChild() != NULL) { @@ -62,11 +66,6 @@ ParamInt::ParamInt (const gchar * name, _min = 0; } - const char * indent = xml->attribute("indent"); - if (indent != NULL) { - _indent = atoi(indent) * 12; - } - gchar *pref_name = this->pref_name(); Inkscape::Preferences *prefs = Inkscape::Preferences::get(); _value = prefs->getInt(extension_pref_root + pref_name, _value); @@ -156,7 +155,7 @@ ParamInt::get_widget (SPDocument * doc, Inkscape::XML::Node * node, sigc::signal return NULL; } - Gtk::HBox * hbox = Gtk::manage(new Gtk::HBox(false, 4)); + Gtk::HBox * hbox = Gtk::manage(new Gtk::HBox(false, Parameter::GUI_PARAM_WIDGETS_SPACING)); auto pia = new ParamIntAdjustment(this, doc, node, changeSignal); Glib::RefPtr<Gtk::Adjustment> fadjust(pia); @@ -166,13 +165,13 @@ ParamInt::get_widget (SPDocument * doc, Inkscape::XML::Node * node, sigc::signal UI::Widget::SpinScale *scale = new UI::Widget::SpinScale(_(_text), fadjust, 0); scale->set_size_request(400, -1); scale->show(); - hbox->pack_start(*scale, false, false); + hbox->pack_start(*scale, true, true); } else if (_mode == MINIMAL) { - + Gtk::Label * label = Gtk::manage(new Gtk::Label(_(_text), Gtk::ALIGN_START)); label->show(); - hbox->pack_start(*label, true, true, _indent); + hbox->pack_start(*label, true, true); auto spin = Gtk::manage(new Inkscape::UI::Widget::SpinButton(fadjust, 1.0, 0)); spin->show(); diff --git a/src/extension/param/int.h b/src/extension/param/int.h index 3fd6cea9b..40c0395ec 100644 --- a/src/extension/param/int.h +++ b/src/extension/param/int.h @@ -29,15 +29,16 @@ public: enum AppearanceMode { FULL, MINIMAL }; - ParamInt (const gchar * name, - const gchar * guitext, - const gchar * desc, - const Parameter::_scope_t scope, - bool gui_hidden, - const gchar * gui_tip, - Inkscape::Extension::Extension * ext, - Inkscape::XML::Node * xml, - AppearanceMode mode); + ParamInt(const gchar * name, + const gchar * guitext, + const gchar * desc, + const Parameter::_scope_t scope, + bool gui_hidden, + const gchar * gui_tip, + int indent, + Inkscape::Extension::Extension * ext, + Inkscape::XML::Node * xml, + AppearanceMode mode); /** Returns \c _value. */ int get(const SPDocument * /*doc*/, const Inkscape::XML::Node * /*node*/) const { return _value; } @@ -59,7 +60,6 @@ private: /** Internal value. */ int _value; AppearanceMode _mode; - int _indent; int _min; int _max; }; diff --git a/src/extension/param/notebook.cpp b/src/extension/param/notebook.cpp index 957d12d06..8ef48926e 100644 --- a/src/extension/param/notebook.cpp +++ b/src/extension/param/notebook.cpp @@ -54,8 +54,16 @@ private: public: static ParamNotebookPage * makepage (Inkscape::XML::Node * in_repr, Inkscape::Extension::Extension * in_ext); - ParamNotebookPage(const gchar * name, const gchar * guitext, const gchar * desc, const Parameter::_scope_t scope, bool gui_hidden, const gchar * gui_tip, Inkscape::Extension::Extension * ext, Inkscape::XML::Node * xml); + ParamNotebookPage(const gchar * name, + const gchar * guitext, + const gchar * desc, + const Parameter::_scope_t scope, + bool gui_hidden, + const gchar * gui_tip, + Inkscape::Extension::Extension * ext, + Inkscape::XML::Node * xml); ~ParamNotebookPage(void); + Gtk::Widget * get_widget(SPDocument * doc, Inkscape::XML::Node * node, sigc::signal<void> * changeSignal); void paramString (std::list <std::string> &list); gchar * get_guitext (void) {return _text;}; @@ -63,8 +71,15 @@ public: }; /* class ParamNotebookPage */ -ParamNotebookPage::ParamNotebookPage (const gchar * name, const gchar * guitext, const gchar * desc, const Parameter::_scope_t scope, bool gui_hidden, const gchar * gui_tip, Inkscape::Extension::Extension * ext, Inkscape::XML::Node * xml) : - Parameter(name, guitext, desc, scope, gui_hidden, gui_tip, ext) +ParamNotebookPage::ParamNotebookPage(const gchar * name, + const gchar * guitext, + const gchar * desc, + const Parameter::_scope_t scope, + bool gui_hidden, + const gchar * gui_tip, + Inkscape::Extension::Extension * ext, + Inkscape::XML::Node * xml) + : Parameter(name, guitext, desc, scope, gui_hidden, gui_tip, /*indent*/ 0, ext) { parameters = NULL; @@ -199,16 +214,19 @@ Gtk::Widget * ParamNotebookPage::get_widget(SPDocument * doc, Inkscape::XML::Nod } Gtk::VBox * vbox = Gtk::manage(new Gtk::VBox); - vbox->set_border_width(5); + vbox->set_border_width(Parameter::GUI_BOX_MARGIN); + vbox->set_spacing(Parameter::GUI_BOX_SPACING); // add parameters onto page (if any) for (GSList * list = parameters; list != NULL; list = g_slist_next(list)) { Parameter * param = reinterpret_cast<Parameter *>(list->data); Gtk::Widget * widg = param->get_widget(doc, node, changeSignal); if (widg) { + int indent = param->get_indent(); + widg->set_margin_left(indent * Parameter::GUI_INDENTATION); + vbox->pack_start(*widg, false, false, 0); + gchar const * tip = param->get_tooltip(); - // printf("Tip: '%s'\n", tip); - vbox->pack_start(*widg, false, false, 2); if (tip) { widg->set_tooltip_text(_(tip)); } else { @@ -224,8 +242,16 @@ Gtk::Widget * ParamNotebookPage::get_widget(SPDocument * doc, Inkscape::XML::Nod } -ParamNotebook::ParamNotebook (const gchar * name, const gchar * guitext, const gchar * desc, const Parameter::_scope_t scope, bool gui_hidden, const gchar * gui_tip, Inkscape::Extension::Extension * ext, Inkscape::XML::Node * xml) : - Parameter(name, guitext, desc, scope, gui_hidden, gui_tip, ext) +ParamNotebook::ParamNotebook(const gchar * name, + const gchar * guitext, + const gchar * desc, + const Parameter::_scope_t scope, + bool gui_hidden, + const gchar * gui_tip, + int indent, + Inkscape::Extension::Extension * ext, + Inkscape::XML::Node * xml) + : Parameter(name, guitext, desc, scope, gui_hidden, gui_tip, indent, ext) { pages = NULL; diff --git a/src/extension/param/notebook.h b/src/extension/param/notebook.h index 3c90964fd..278c4de12 100644 --- a/src/extension/param/notebook.h +++ b/src/extension/param/notebook.h @@ -41,8 +41,17 @@ private: This only gets created if there are pages in this notebook */ public: - ParamNotebook(const gchar * name, const gchar * guitext, const gchar * desc, const Parameter::_scope_t scope, bool gui_hidden, const gchar * gui_tip, Inkscape::Extension::Extension * ext, Inkscape::XML::Node * xml); + ParamNotebook(const gchar * name, + const gchar * guitext, + const gchar * desc, + const Parameter::_scope_t scope, + bool gui_hidden, + const gchar * gui_tip, + int indent, + Inkscape::Extension::Extension * ext, + Inkscape::XML::Node * xml); virtual ~ParamNotebook(void); + Gtk::Widget * get_widget(SPDocument * doc, Inkscape::XML::Node * node, sigc::signal<void> * changeSignal); /** diff --git a/src/extension/param/parameter.cpp b/src/extension/param/parameter.cpp index a5632a39a..0eb491078 100644 --- a/src/extension/param/parameter.cpp +++ b/src/extension/param/parameter.cpp @@ -50,10 +50,17 @@ Parameter *Parameter::make(Inkscape::XML::Node *in_repr, Inkscape::Extension::Ex const char *name = in_repr->attribute("name"); const char *type = in_repr->attribute("type"); - // In this case we just don't have enough information - if (!name || !type) { + // we can't create a parameter without type + if (!type) { return NULL; } + // also require name unless it's a pure UI element that does not store its value + if (!name) { + static std::vector<std::string> ui_elements = {"description"}; + if (std::find(ui_elements.begin(), ui_elements.end(), type) == ui_elements.end()) { + return NULL; + } + } const char *guitext = in_repr->attribute("gui-text"); if (guitext == NULL) { @@ -78,6 +85,17 @@ Parameter *Parameter::make(Inkscape::XML::Node *in_repr, Inkscape::Extension::Ex /* else stays false */ } } + int indent = 0; + { + const char *indent_attr = in_repr->attribute("indent"); + if (indent_attr != NULL) { + if (strcmp(indent_attr, "true") == 0) { + indent = 1; + } else { + indent = atoi(indent_attr); + } + } + } const gchar* appearance = in_repr->attribute("appearance"); Parameter::_scope_t scope = Parameter::SCOPE_USER; @@ -96,44 +114,48 @@ Parameter *Parameter::make(Inkscape::XML::Node *in_repr, Inkscape::Extension::Ex Parameter * param = NULL; if (!strcmp(type, "boolean")) { - param = new ParamBool(name, guitext, desc, scope, gui_hidden, gui_tip, in_ext, in_repr); + param = new ParamBool(name, guitext, desc, scope, gui_hidden, gui_tip, indent, in_ext, in_repr); } else if (!strcmp(type, "int")) { if (appearance && !strcmp(appearance, "full")) { - param = new ParamInt(name, guitext, desc, scope, gui_hidden, gui_tip, in_ext, in_repr, ParamInt::FULL); + param = new ParamInt(name, guitext, desc, scope, gui_hidden, gui_tip, indent, in_ext, in_repr, ParamInt::FULL); } else { - param = new ParamInt(name, guitext, desc, scope, gui_hidden, gui_tip, in_ext, in_repr, ParamInt::MINIMAL); + param = new ParamInt(name, guitext, desc, scope, gui_hidden, gui_tip, indent, in_ext, in_repr, ParamInt::MINIMAL); } } else if (!strcmp(type, "float")) { if (appearance && !strcmp(appearance, "full")) { - param = new ParamFloat(name, guitext, desc, scope, gui_hidden, gui_tip, in_ext, in_repr, ParamFloat::FULL); + param = new ParamFloat(name, guitext, desc, scope, gui_hidden, gui_tip, indent, in_ext, in_repr, ParamFloat::FULL); } else { - param = new ParamFloat(name, guitext, desc, scope, gui_hidden, gui_tip, in_ext, in_repr, ParamFloat::MINIMAL); + param = new ParamFloat(name, guitext, desc, scope, gui_hidden, gui_tip, indent, in_ext, in_repr, ParamFloat::MINIMAL); } } else if (!strcmp(type, "string")) { - param = new ParamString(name, guitext, desc, scope, gui_hidden, gui_tip, in_ext, in_repr); + param = new ParamString(name, guitext, desc, scope, gui_hidden, gui_tip, indent, in_ext, in_repr); gchar const * max_length = in_repr->attribute("max_length"); if (max_length != NULL) { ParamString * ps = dynamic_cast<ParamString *>(param); ps->setMaxLength(atoi(max_length)); } } else if (!strcmp(type, "description")) { - if (appearance && !strcmp(appearance, "header")) { - param = new ParamDescription(name, guitext, desc, scope, gui_hidden, gui_tip, in_ext, in_repr, ParamDescription::HEADER); - } else { - param = new ParamDescription(name, guitext, desc, scope, gui_hidden, gui_tip, in_ext, in_repr, ParamDescription::DESC); - } + ParamDescription::AppearanceMode appearance_mode = ParamDescription::DESC; + if (appearance) { + if (!strcmp(appearance, "header")) { + appearance_mode = ParamDescription::HEADER; + } else if (!strcmp(appearance, "url")) { + appearance_mode = ParamDescription::URL; + } + } + param = new ParamDescription(name, guitext, desc, scope, gui_hidden, gui_tip, indent, in_ext, in_repr, appearance_mode); } else if (!strcmp(type, "enum")) { - param = new ParamComboBox(name, guitext, desc, scope, gui_hidden, gui_tip, in_ext, in_repr); + param = new ParamComboBox(name, guitext, desc, scope, gui_hidden, gui_tip, indent, in_ext, in_repr); } else if (!strcmp(type, "notebook")) { - param = new ParamNotebook(name, guitext, desc, scope, gui_hidden, gui_tip, in_ext, in_repr); + param = new ParamNotebook(name, guitext, desc, scope, gui_hidden, gui_tip, indent, in_ext, in_repr); } else if (!strcmp(type, "optiongroup")) { if (appearance && !strcmp(appearance, "minimal")) { - param = new ParamRadioButton(name, guitext, desc, scope, gui_hidden, gui_tip, in_ext, in_repr, ParamRadioButton::MINIMAL); + param = new ParamRadioButton(name, guitext, desc, scope, gui_hidden, gui_tip, indent, in_ext, in_repr, ParamRadioButton::MINIMAL); } else { - param = new ParamRadioButton(name, guitext, desc, scope, gui_hidden, gui_tip, in_ext, in_repr, ParamRadioButton::FULL); + param = new ParamRadioButton(name, guitext, desc, scope, gui_hidden, gui_tip, indent, in_ext, in_repr, ParamRadioButton::FULL); } } else if (!strcmp(type, "color")) { - param = new ParamColor(name, guitext, desc, scope, gui_hidden, gui_tip, in_ext, in_repr); + param = new ParamColor(name, guitext, desc, scope, gui_hidden, gui_tip, indent, in_ext, in_repr); } // Note: param could equal NULL @@ -280,12 +302,13 @@ Parameter::set_color (guint32 in, SPDocument * doc, Inkscape::XML::Node * node) /** Oop, now that we need a parameter, we need it's name. */ -Parameter::Parameter(gchar const * name, gchar const * guitext, gchar const * desc, const Parameter::_scope_t scope, bool gui_hidden, gchar const * gui_tip, Inkscape::Extension::Extension * ext) : +Parameter::Parameter(gchar const * name, gchar const * guitext, gchar const * desc, const Parameter::_scope_t scope, bool gui_hidden, gchar const * gui_tip, int indent, Inkscape::Extension::Extension * ext) : _desc(0), _scope(scope), _text(0), _gui_hidden(gui_hidden), _gui_tip(0), + _indent(indent), extension(ext), _name(0) { @@ -316,6 +339,7 @@ Parameter::Parameter (gchar const * name, gchar const * guitext, Inkscape::Exten _text(0), _gui_hidden(false), _gui_tip(0), + _indent(0), extension(ext), _name(0) { diff --git a/src/extension/param/parameter.h b/src/extension/param/parameter.h index 5e1e3897f..c107f24f9 100644 --- a/src/extension/param/parameter.h +++ b/src/extension/param/parameter.h @@ -37,7 +37,7 @@ class Extension; */ extern Glib::ustring const extension_pref_root; -/** +/** * A class to represent the parameter of an extension. * * This is really a super class that allows them to abstract all @@ -62,6 +62,7 @@ public: const Parameter::_scope_t scope, bool gui_hidden, gchar const *gui_tip, + int indent, Inkscape::Extension::Extension * ext); Parameter(gchar const *name, @@ -144,6 +145,9 @@ public: /** Indicates if the GUI for this parameter is hidden or not */ bool get_gui_hidden() const { return _gui_hidden; } + /** Indentation level of the parameter */ + int get_indent() const { return _indent; } + virtual void string(std::list <std::string> &list) const; /** @@ -155,6 +159,19 @@ public: /** All the code in Notebook::get_param to get the notebook content. */ virtual Parameter *get_param(gchar const *name); + + /** Recommended margin of boxes containing multiple Parameters (in px) */ + const static int GUI_BOX_MARGIN = 10; + /** Recommended spacing between multiple Parameters packed into a box (in px) */ + const static int GUI_BOX_SPACING = 4; + /** Recommended spacing between the widgets making up a signle Parameter (e.g. label and input) (in px) */ + const static int GUI_PARAM_WIDGETS_SPACING = 4; + /** Recommended indentation width of parameters (in px) */ + const static int GUI_INDENTATION = 12; + /** Recommended maximum line lenght for wrapping textual parameters (in chars) */ + const static int GUI_MAX_LINE_LENGTH = 60; + + protected: /** Description of the parameter. */ gchar * _desc; @@ -171,6 +188,9 @@ protected: /** A tip for the GUI if there is one. */ gchar * _gui_tip; + /** Indentation level of the parameter. */ + int _indent; + /* **** funcs **** */ diff --git a/src/extension/param/radiobutton.cpp b/src/extension/param/radiobutton.cpp index c54cc0ec3..178dc20e8 100644 --- a/src/extension/param/radiobutton.cpp +++ b/src/extension/param/radiobutton.cpp @@ -57,17 +57,20 @@ public: Glib::ustring * guitext; }; -ParamRadioButton::ParamRadioButton (const gchar * name, - const gchar * guitext, - const gchar * desc, - const Parameter::_scope_t scope, - bool gui_hidden, - const gchar * gui_tip, - Inkscape::Extension::Extension * ext, - Inkscape::XML::Node * xml, - AppearanceMode mode) : - Parameter(name, guitext, desc, scope, gui_hidden, gui_tip, ext), - _value(0), _mode(mode), _indent(0), choices(0) +ParamRadioButton::ParamRadioButton(const gchar * name, + const gchar * guitext, + const gchar * desc, + const Parameter::_scope_t scope, + bool gui_hidden, + const gchar * gui_tip, + int indent, + Inkscape::Extension::Extension * ext, + Inkscape::XML::Node * xml, + AppearanceMode mode) + : Parameter(name, guitext, desc, scope, gui_hidden, gui_tip, indent, ext) + , _value(0) + , _mode(mode) + , choices(0) { // Read XML tree to add enumeration items: // printf("Extension Constructor: "); @@ -118,11 +121,6 @@ ParamRadioButton::ParamRadioButton (const gchar * name, defaultval = (static_cast<optionentry*> (choices->data))->value->c_str(); } - const char *indent = xml ? xml->attribute("indent") : NULL; - if (indent != NULL) { - _indent = atoi(indent) * 12; - } - gchar * pref_name = this->pref_name(); Inkscape::Preferences *prefs = Inkscape::Preferences::get(); Glib::ustring paramval = prefs->getString(extension_pref_root + pref_name); @@ -303,14 +301,14 @@ Gtk::Widget * ParamRadioButton::get_widget(SPDocument * doc, Inkscape::XML::Node return NULL; } - auto hbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 4)); + auto hbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, Parameter::GUI_PARAM_WIDGETS_SPACING)); hbox->set_homogeneous(false); auto vbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0)); vbox->set_homogeneous(false); Gtk::Label * label = Gtk::manage(new Gtk::Label(_(_text), Gtk::ALIGN_START, Gtk::ALIGN_START)); label->show(); - hbox->pack_start(*label, false, false, _indent); + hbox->pack_start(*label, false, false); Gtk::ComboBoxText* cbt = 0; bool comboSet = false; diff --git a/src/extension/param/radiobutton.h b/src/extension/param/radiobutton.h index 0e0f643c9..5e15dcb57 100644 --- a/src/extension/param/radiobutton.h +++ b/src/extension/param/radiobutton.h @@ -40,10 +40,12 @@ public: const Parameter::_scope_t scope, bool gui_hidden, const gchar * gui_tip, + int indent, Inkscape::Extension::Extension * ext, Inkscape::XML::Node * xml, AppearanceMode mode); virtual ~ParamRadioButton(void); + Gtk::Widget * get_widget(SPDocument * doc, Inkscape::XML::Node * node, sigc::signal<void> * changeSignal); // Explicitly call superclass version to avoid method being hidden. @@ -63,7 +65,6 @@ private: It is the value of the current selected string */ gchar * _value; AppearanceMode _mode; - int _indent; GSList * choices; /**< A table to store the choice strings */ }; /* class ParamRadioButton */ diff --git a/src/extension/param/string.cpp b/src/extension/param/string.cpp index 1d9205502..0c5238f99 100644 --- a/src/extension/param/string.cpp +++ b/src/extension/param/string.cpp @@ -24,7 +24,7 @@ namespace Inkscape { namespace Extension { - + /** Free the allocated data. */ ParamString::~ParamString(void) @@ -76,20 +76,23 @@ void ParamString::string(std::string &string) const } /** Initialize the object, to do that, copy the data. */ -ParamString::ParamString (const gchar * name, const gchar * guitext, const gchar * desc, const Parameter::_scope_t scope, bool gui_hidden, const gchar * gui_tip, Inkscape::Extension::Extension * ext, Inkscape::XML::Node * xml) : - Parameter(name, guitext, desc, scope, gui_hidden, gui_tip, ext), - _value(NULL), _indent(0) +ParamString::ParamString(const gchar * name, + const gchar * guitext, + const gchar * desc, + const Parameter::_scope_t scope, + bool gui_hidden, + const gchar * gui_tip, + int indent, + Inkscape::Extension::Extension * ext, + Inkscape::XML::Node * xml) + : Parameter(name, guitext, desc, scope, gui_hidden, gui_tip, indent, ext) + , _value(NULL) { const char * defaultval = NULL; if (xml->firstChild() != NULL) { defaultval = xml->firstChild()->content(); } - const char * indent = xml->attribute("indent"); - if (indent != NULL) { - _indent = atoi(indent) * 12; - } - gchar * pref_name = this->pref_name(); Inkscape::Preferences *prefs = Inkscape::Preferences::get(); Glib::ustring paramval = prefs->getString(extension_pref_root + pref_name); @@ -165,10 +168,10 @@ Gtk::Widget * ParamString::get_widget(SPDocument * doc, Inkscape::XML::Node * no return NULL; } - Gtk::HBox * hbox = Gtk::manage(new Gtk::HBox(false, 4)); + Gtk::HBox * hbox = Gtk::manage(new Gtk::HBox(false, Parameter::GUI_PARAM_WIDGETS_SPACING)); Gtk::Label * label = Gtk::manage(new Gtk::Label(_(_text), Gtk::ALIGN_START)); label->show(); - hbox->pack_start(*label, false, false, _indent); + hbox->pack_start(*label, false, false); ParamStringEntry * textbox = new ParamStringEntry(this, doc, node, changeSignal); textbox->show(); diff --git a/src/extension/param/string.h b/src/extension/param/string.h index 3b137aeac..f5412ebcc 100644 --- a/src/extension/param/string.h +++ b/src/extension/param/string.h @@ -19,12 +19,19 @@ private: /** \brief Internal value. This should point to a string that has been allocated in memory. And should be free'd. */ gchar * _value; - /** \brief Internal value. This indicates the maximum leght of the string. Zero meaning unlimited. + /** \brief Internal value. This indicates the maximum lenth of the string. Zero meaning unlimited. */ - int _indent; gint _max_length; public: - ParamString(const gchar * name, const gchar * guitext, const gchar * desc, const Parameter::_scope_t scope, bool gui_hidden, const gchar * gui_tip, Inkscape::Extension::Extension * ext, Inkscape::XML::Node * xml); + ParamString(const gchar * name, + const gchar * guitext, + const gchar * desc, + const Parameter::_scope_t scope, + bool gui_hidden, + const gchar * gui_tip, + int indent, + Inkscape::Extension::Extension * ext, + Inkscape::XML::Node * xml); virtual ~ParamString(void); /** \brief Returns \c _value, with a \i const to protect it. */ diff --git a/src/extension/prefdialog.cpp b/src/extension/prefdialog.cpp index fcc88853d..2bdbb081e 100644 --- a/src/extension/prefdialog.cpp +++ b/src/extension/prefdialog.cpp @@ -50,6 +50,8 @@ PrefDialog::PrefDialog (Glib::ustring name, gchar const * help, Gtk::Widget * co _effect(effect), _exEnv(NULL) { + this->set_default_size(0,0); // we want the window to be as small as possible instead of clobbering up space + Gtk::HBox * hbox = Gtk::manage(new Gtk::HBox()); if (controls == NULL) { if (_effect == NULL) { @@ -59,11 +61,10 @@ PrefDialog::PrefDialog (Glib::ustring name, gchar const * help, Gtk::Widget * co controls = _effect->get_imp()->prefs_effect(_effect, SP_ACTIVE_DESKTOP, &_signal_param_change, NULL); _signal_param_change.connect(sigc::mem_fun(this, &PrefDialog::param_change)); } - - hbox->pack_start(*controls, true, true, 6); + hbox->pack_start(*controls, true, true, 0); hbox->show(); - this->get_content_area()->pack_start(*hbox, true, true, 6); + this->get_content_area()->pack_start(*hbox, true, true, 0); /* Gtk::Button * help_button = add_button(Gtk::Stock::HELP, Gtk::RESPONSE_HELP); @@ -88,15 +89,16 @@ PrefDialog::PrefDialog (Glib::ustring name, gchar const * help, Gtk::Widget * co auto sep = Gtk::manage(new Gtk::Separator()); sep->show(); - this->get_content_area()->pack_start(*sep, true, true, 4); + this->get_content_area()->pack_start(*sep, false, false, Parameter::GUI_BOX_SPACING); hbox = Gtk::manage(new Gtk::HBox()); + hbox->set_border_width(Parameter::GUI_BOX_MARGIN); _button_preview = _param_preview->get_widget(NULL, NULL, &_signal_preview); _button_preview->show(); - hbox->pack_start(*_button_preview, true, true,6); + hbox->pack_start(*_button_preview, true, true, 0); hbox->show(); - this->get_content_area()->pack_start(*hbox, true, true, 6); + this->get_content_area()->pack_start(*hbox, false, false, 0); Gtk::Box * hbox = dynamic_cast<Gtk::Box *>(_button_preview); if (hbox != NULL) { diff --git a/src/file-update.cpp b/src/file-update.cpp index 40a8bc834..2275ab1c3 100644 --- a/src/file-update.cpp +++ b/src/file-update.cpp @@ -19,6 +19,7 @@ #include "style.h" #include "document.h" #include <string> +#include <clocale> #include "text-editing.h" using namespace std; @@ -149,10 +150,12 @@ void fix_update(SPObject *o) { void sp_file_convert_text_baseline_spacing(SPDocument *doc) { + setlocale(LC_NUMERIC,"C"); sp_file_text_run_recursive(fix_blank_line, doc->getRoot()); sp_file_text_run_recursive(fix_line_spacing, doc->getRoot()); sp_file_text_run_recursive(fix_font_size, doc->getRoot()); sp_file_text_run_recursive(fix_update, doc->getRoot()); + setlocale(LC_NUMERIC,""); } void sp_file_convert_font_name(SPDocument *doc) diff --git a/src/gradient-drag.cpp b/src/gradient-drag.cpp index a928cac0d..006e2f790 100644 --- a/src/gradient-drag.cpp +++ b/src/gradient-drag.cpp @@ -1067,7 +1067,7 @@ static void gr_knot_moved_midpoint_handler(SPKnot */*knot*/, Geom::Point const & -static void gr_knot_grabbed_handler(SPKnot */*knot*/, unsigned int /*state*/, gpointer data) +static void gr_knot_mousedown_handler(SPKnot */*knot*/, unsigned int /*state*/, gpointer data) { GrDragger *dragger = (GrDragger *) data; GrDrag *drag = dragger->parent; @@ -1688,7 +1688,7 @@ GrDragger::GrDragger(GrDrag *parent, Geom::Point p, GrDraggable *draggable) this->sizeUpdatedConn = ControlManager::getManager().connectCtrlSizeChanged(sigc::mem_fun(*this, &GrDragger::updateControlSizes)); this->_clicked_connection = this->knot->click_signal.connect(sigc::bind(sigc::ptr_fun(gr_knot_clicked_handler), this)); this->_doubleclicked_connection = this->knot->doubleclicked_signal.connect(sigc::bind(sigc::ptr_fun(gr_knot_doubleclicked_handler), this)); - this->_grabbed_connection = this->knot->grabbed_signal.connect(sigc::bind(sigc::ptr_fun(gr_knot_grabbed_handler), this)); + this->_mousedown_connection = this->knot->mousedown_signal.connect(sigc::bind(sigc::ptr_fun(gr_knot_mousedown_handler), this)); this->_ungrabbed_connection = this->knot->ungrabbed_signal.connect(sigc::bind(sigc::ptr_fun(gr_knot_ungrabbed_handler), this)); // add the initial draggable @@ -1712,7 +1712,7 @@ GrDragger::~GrDragger() this->_moved_connection.disconnect(); this->_clicked_connection.disconnect(); this->_doubleclicked_connection.disconnect(); - this->_grabbed_connection.disconnect(); + this->_mousedown_connection.disconnect(); this->_ungrabbed_connection.disconnect(); /* unref should call destroy */ diff --git a/src/gradient-drag.h b/src/gradient-drag.h index b4a64c78d..1fe2fb3f0 100644 --- a/src/gradient-drag.h +++ b/src/gradient-drag.h @@ -130,7 +130,7 @@ private: sigc::connection _moved_connection; sigc::connection _clicked_connection; sigc::connection _doubleclicked_connection; - sigc::connection _grabbed_connection; + sigc::connection _mousedown_connection; sigc::connection _ungrabbed_connection; }; diff --git a/src/id-clash.cpp b/src/id-clash.cpp index c284843b8..162cb9e89 100644 --- a/src/id-clash.cpp +++ b/src/id-clash.cpp @@ -373,9 +373,9 @@ void rename_id(SPObject *elem, Glib::ustring const &new_name) gchar *id = g_strdup(new_name.c_str()); //id is not empty here as new_name is check to be not empty g_strcanon (id, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.:", '_'); Glib::ustring new_name2 = id; //will not fail as id can not be NULL, see length check on new_name - g_free (id); if (!isalnum (new_name2[0])) { g_message("Invalid Id, will not change."); + g_free (id); return; } @@ -396,7 +396,7 @@ void rename_id(SPObject *elem, Glib::ustring const &new_name) break; } } - + g_free (id); // Change to the new ID elem->getRepr()->setAttribute("id", new_name2); // Make a note of this change, if we need to fix up refs to it diff --git a/src/knot.cpp b/src/knot.cpp index c04206dee..50fa73391 100644 --- a/src/knot.cpp +++ b/src/knot.cpp @@ -214,7 +214,7 @@ static int sp_knot_handler(SPCanvasItem */*item*/, GdkEvent *event, SPKnot *knot if ((event->button.button == 1) && knot->desktop && knot->desktop->event_context && !knot->desktop->event_context->space_panning) { Geom::Point const p = knot->desktop->w2d(Geom::Point(event->button.x, event->button.y)); knot->startDragging(p, (gint) event->button.x, (gint) event->button.y, event->button.time); - knot->grabbed_signal.emit(knot, event->button.state); + knot->mousedown_signal.emit(knot, event->button.state); consumed = TRUE; } break; @@ -241,7 +241,6 @@ static int sp_knot_handler(SPCanvasItem */*item*/, GdkEvent *event, SPKnot *knot if (moved) { knot->setFlag(SP_KNOT_DRAGGING, FALSE); - knot->ungrabbed_signal.emit(knot, event->button.state); } else { knot->click_signal.emit(knot, event->button.state); @@ -277,6 +276,7 @@ static int sp_knot_handler(SPCanvasItem */*item*/, GdkEvent *event, SPKnot *knot if (!moved) { knot->setFlag(SP_KNOT_DRAGGING, TRUE); + knot->grabbed_signal.emit(knot, event->button.state); } sp_event_context_snap_delay_handler(knot->desktop->event_context, NULL, knot, (GdkEventMotion *)event, Inkscape::UI::Tools::DelayedSnapEvent::KNOT_HANDLER); diff --git a/src/knot.h b/src/knot.h index 954ddd8a6..0a0ce73d2 100644 --- a/src/knot.h +++ b/src/knot.h @@ -79,6 +79,7 @@ public: // be able to figure that out sigc::signal<void, SPKnot *, unsigned int> click_signal; sigc::signal<void, SPKnot*, unsigned int> doubleclicked_signal; + sigc::signal<void, SPKnot*, unsigned int> mousedown_signal; sigc::signal<void, SPKnot*, unsigned int> grabbed_signal; sigc::signal<void, SPKnot *, unsigned int> ungrabbed_signal; sigc::signal<void, SPKnot *, Geom::Point const &, unsigned int> moved_signal; diff --git a/src/libcola/gradient_projection.cpp b/src/libcola/gradient_projection.cpp index c8488a545..c079c3149 100644 --- a/src/libcola/gradient_projection.cpp +++ b/src/libcola/gradient_projection.cpp @@ -50,7 +50,7 @@ unsigned GradientProjection::solve(double * b) { //cerr << "in gradient projection: n=" << n << endl; for (i=0;i<n;i++) { assert(!IS_NAN(place[i])); - assert(!isinf(place[i])); + assert(IS_FINITE(place[i])); vars[i]->desiredPosition=place[i]; } try { @@ -98,7 +98,7 @@ unsigned GradientProjection::solve(double * b) { for (i=0; i<n; i++) { place[i]-=alpha*g[i]; assert(!IS_NAN(place[i])); - assert(!isinf(place[i])); + assert(IS_FINITE(place[i])); vars[i]->desiredPosition=place[i]; } for (DummyVars::iterator it=dummy_vars.begin();it!=dummy_vars.end();++it){ diff --git a/src/libcroco/cr-parser.c b/src/libcroco/cr-parser.c index 544b35ab0..4e50b5402 100644 --- a/src/libcroco/cr-parser.c +++ b/src/libcroco/cr-parser.c @@ -2002,7 +2002,7 @@ cr_parser_parse_simple_sels (CRParser * a_this, *Returns CR_OK upon successful completion, an error *code otherwise. */ -static enum CRStatus +enum CRStatus cr_parser_parse_selector (CRParser * a_this, CRSelector ** a_selector) { diff --git a/src/libcroco/cr-parser.h b/src/libcroco/cr-parser.h index 6dce9439e..24cf5dfe8 100644 --- a/src/libcroco/cr-parser.h +++ b/src/libcroco/cr-parser.h @@ -97,6 +97,8 @@ enum CRStatus cr_parser_set_default_sac_handler (CRParser *a_this) ; enum CRStatus cr_parser_parse_term (CRParser *a_this, CRTerm **a_term) ; +enum CRStatus cr_parser_parse_selector (CRParser * a_this, CRSelector ** a_selector) ; + enum CRStatus cr_parser_parse_expr (CRParser *a_this, CRTerm **a_expr) ; enum CRStatus cr_parser_parse_prio (CRParser *a_this, CRString **a_prio) ; diff --git a/src/libcroco/cr-selector.c b/src/libcroco/cr-selector.c index c56c43fda..f9193feda 100644 --- a/src/libcroco/cr-selector.c +++ b/src/libcroco/cr-selector.c @@ -51,6 +51,19 @@ cr_selector_new (CRSimpleSel * a_simple_sel) return result; } +/** + * cr_selector_parse_from_buf: + * + *@a_char_buf: the buffer to parse. + *@a_enc: the encoding of the input buffer a_char_buf. + * + *Parses a buf for selectors. + * + *Fix Me: parsing will fail for some cases if buf does not end with '{'. + * + *Returns the newly built instance of #CRSelector, or + *NULL in case of failure. + */ CRSelector * cr_selector_parse_from_buf (const guchar * a_char_buf, enum CREncoding a_enc) { @@ -62,7 +75,17 @@ cr_selector_parse_from_buf (const guchar * a_char_buf, enum CREncoding a_enc) a_enc, FALSE); g_return_val_if_fail (parser, NULL); - return NULL; + CRSelector *selector = NULL; + enum CRStatus status = CR_OK; + status = cr_parser_parse_selector (parser, &selector); + + if (status != CR_OK) { + if (selector) { + cr_selector_unref (selector); + selector = NULL; + } + } + return selector; } /** diff --git a/src/live_effects/effect.cpp b/src/live_effects/effect.cpp index 50e2aa353..f664870ec 100644 --- a/src/live_effects/effect.cpp +++ b/src/live_effects/effect.cpp @@ -430,6 +430,9 @@ void Effect::processObjects(LpeAction lpe_action) { SPDocument * document = SP_ACTIVE_DOCUMENT; + if (!document) { + return; + } for (std::vector<const char *>::iterator el_it = items.begin(); el_it != items.end(); ++el_it) { const char * id = *el_it; diff --git a/src/live_effects/lpe-clone-original.cpp b/src/live_effects/lpe-clone-original.cpp index 04db6171e..440af6f9c 100644 --- a/src/live_effects/lpe-clone-original.cpp +++ b/src/live_effects/lpe-clone-original.cpp @@ -64,6 +64,9 @@ void LPECloneOriginal::cloneAttrbutes(SPObject *origin, SPObject *dest, bool live, const char * attributes, const char * style_attributes, bool root) { SPDocument * document = SP_ACTIVE_DOCUMENT; + if (!document) { + return; + } if ( SP_IS_GROUP(origin) && SP_IS_GROUP(dest) && SP_GROUP(origin)->getItemCount() == SP_GROUP(dest)->getItemCount() ) { std::vector< SPObject * > childs = origin->childList(true); size_t index = 0; @@ -344,7 +347,6 @@ void LPECloneOriginal::transform_multiply(Geom::Affine const& postmul, bool set) { if (linked_item.linksToItem()) { - bool changed = false; linked_item.getObject()->requestModified(SP_OBJECT_MODIFIED_FLAG); } } diff --git a/src/live_effects/lpe-copy_rotate.cpp b/src/live_effects/lpe-copy_rotate.cpp index 42e055062..9ba680cd4 100644 --- a/src/live_effects/lpe-copy_rotate.cpp +++ b/src/live_effects/lpe-copy_rotate.cpp @@ -92,11 +92,13 @@ LPECopyRotate::doAfterEffect (SPLPEItem const* lpeitem) { if (split_items) { SPDocument * document = SP_ACTIVE_DOCUMENT; + if (!document) { + return; + } items.clear(); container = dynamic_cast<SPObject *>(sp_lpe_item->parent); - SPDocument * doc = SP_ACTIVE_DOCUMENT; Inkscape::XML::Node *root = sp_lpe_item->document->getReprRoot(); - Inkscape::XML::Node *root_origin = doc->getReprRoot(); + Inkscape::XML::Node *root_origin = document->getReprRoot(); if (root_origin != root) { return; } @@ -168,15 +170,15 @@ LPECopyRotate::doAfterEffect (SPLPEItem const* lpeitem) processObjects(LPE_ERASE); items.clear(); } - - std::cout << previous_num_copies << "previous_num_copies\n"; - std::cout << num_copies << "num_copies\n"; } void LPECopyRotate::cloneD(SPObject *origin, SPObject *dest, bool root, bool reset) { SPDocument * document = SP_ACTIVE_DOCUMENT; + if (!document) { + return; + } Inkscape::XML::Document *xml_doc = document->getReprDoc(); if ( SP_IS_GROUP(origin) && SP_IS_GROUP(dest) && SP_GROUP(origin)->getItemCount() == SP_GROUP(dest)->getItemCount() ) { std::vector< SPObject * > childs = origin->childList(true); @@ -190,7 +192,7 @@ LPECopyRotate::cloneD(SPObject *origin, SPObject *dest, bool root, bool reset) } SPShape * shape = SP_SHAPE(origin); SPPath * path = SP_PATH(dest); - if (!path && !SP_IS_GROUP(dest)) { + if (shape && !path) { Inkscape::XML::Node *dest_node = sp_selected_item_to_curved_repr(SP_ITEM(dest), 0); dest->updateRepr(xml_doc, dest_node, SP_OBJECT_WRITE_ALL); path = SP_PATH(dest); @@ -219,6 +221,9 @@ void LPECopyRotate::toItem(Geom::Affine transform, size_t i, bool reset) { SPDocument * document = SP_ACTIVE_DOCUMENT; + if (!document) { + return; + } Inkscape::XML::Document *xml_doc = document->getReprDoc(); const char * elemref_id = g_strdup(Glib::ustring("rotated-").append(std::to_string(i)).append("-").append(sp_lpe_item->getRepr()->attribute("id")).c_str()); items.push_back(elemref_id); @@ -377,11 +382,11 @@ LPECopyRotate::doBeforeEffect (SPLPEItem const* lpeitem) num_copies.param_set_increments(1.0, 10.0); } - if (dist_angle_handle < 1.0) { - dist_angle_handle = 1.0; - } A = Point(boundingbox_X.min(), boundingbox_Y.middle()); B = Point(boundingbox_X.middle(), boundingbox_Y.middle()); + if (Geom::are_near(A, B, 0.01)) { + B += Geom::Point(1.0, 0.0); + } dir = unit_vector(B - A); // I first suspected the minus sign to be a bug in 2geom but it is // likely due to SVG's choice of coordinate system orientation (max) @@ -394,6 +399,9 @@ LPECopyRotate::doBeforeEffect (SPLPEItem const* lpeitem) dist_angle_handle = L2(starting_point - origin); } } + if (dist_angle_handle < 1.0) { + dist_angle_handle = 1.0; + } start_pos = origin + dir * Rotate(-rad_from_deg(starting_angle)) * dist_angle_handle; rot_pos = origin + dir * Rotate(-rad_from_deg(rotation_angle+starting_angle)) * dist_angle_handle; near = Geom::are_near(start_pos, (Geom::Point)starting_point, 0.01); diff --git a/src/live_effects/lpe-fill-between-many.cpp b/src/live_effects/lpe-fill-between-many.cpp index 2690ce160..2e1fe0dc1 100644 --- a/src/live_effects/lpe-fill-between-many.cpp +++ b/src/live_effects/lpe-fill-between-many.cpp @@ -4,13 +4,13 @@ * Released under GNU GPL, read the file 'COPYING' for more information */ -#include <gtkmm/box.h> #include "live_effects/lpe-fill-between-many.h" #include "display/curve.h" #include "sp-shape.h" #include "sp-text.h" +#include "svg/svg.h" // TODO due to internal breakage in glibmm headers, this must be last: #include <glibmm/i18n.h> @@ -20,11 +20,17 @@ namespace LivePathEffect { LPEFillBetweenMany::LPEFillBetweenMany(LivePathEffectObject *lpeobject) : Effect(lpeobject), linked_paths(_("Linked path:"), _("Paths from which to take the original path data"), "linkedpaths", &wr, this), - fuse(_("Fuse coincident points"), _("Fuse coincident points"), "fuse", &wr, this) + fuse(_("Fuse coincident points"), _("Fuse coincident points"), "fuse", &wr, this, false), + allow_transforms(_("Allow transforms"), _("Allow transforms"), "allow_transforms", &wr, this, false), + join(_("Join subpaths"), _("Join subpaths"), "join", &wr, this, true), + close(_("Close"), _("Close path"), "close", &wr, this, true) { registerParameter( dynamic_cast<Parameter *>(&linked_paths) ); registerParameter( dynamic_cast<Parameter *>(&fuse) ); - //perceived_path = true; + registerParameter( dynamic_cast<Parameter *>(&allow_transforms) ); + registerParameter( dynamic_cast<Parameter *>(&join) ); + registerParameter( dynamic_cast<Parameter *>(&close) ); + transformmultiply = false; } LPEFillBetweenMany::~LPEFillBetweenMany() @@ -46,7 +52,7 @@ void LPEFillBetweenMany::doEffect (SPCurve * curve) linked_path = (*iter)->_pathvector.front(); } - if (!res_pathv.empty()) { + if (!res_pathv.empty() && join) { linked_path = linked_path * SP_ITEM(obj)->getRelativeTransform(firstObj); if (!are_near(res_pathv.front().finalPoint(), linked_path.initialPoint(), 0.01) || !fuse) { res_pathv.front().appendNew<Geom::LineSegment>(linked_path.initialPoint()); @@ -56,19 +62,40 @@ void LPEFillBetweenMany::doEffect (SPCurve * curve) res_pathv.front().append(linked_path); } else { firstObj = SP_ITEM(obj); + if (close && !join) { + linked_path.close(); + } res_pathv.push_back(linked_path); } } } - if (!res_pathv.empty()) { + if (!res_pathv.empty() && close) { res_pathv.front().close(); } if (res_pathv.empty()) { res_pathv = curve->get_pathvector(); } + if(!allow_transforms && !transformmultiply) { + Geom::Affine affine = Geom::identity(); + sp_svg_transform_read(SP_ITEM(sp_lpe_item)->getAttribute("transform"), &affine); + res_pathv *= affine.inverse(); + } + if(transformmultiply) { + transformmultiply = false; + } curve->set_pathvector(res_pathv); } +void +LPEFillBetweenMany::transform_multiply(Geom::Affine const& postmul, bool set) +{ + if(!allow_transforms && sp_lpe_item) { + SP_ITEM(sp_lpe_item)->transform *= postmul.inverse(); + transformmultiply = true; + sp_lpe_item_update_patheffect(sp_lpe_item, false, false); + } +} + } // namespace LivePathEffect } /* namespace Inkscape */ diff --git a/src/live_effects/lpe-fill-between-many.h b/src/live_effects/lpe-fill-between-many.h index 552c8037d..fe824e936 100644 --- a/src/live_effects/lpe-fill-between-many.h +++ b/src/live_effects/lpe-fill-between-many.h @@ -19,13 +19,16 @@ class LPEFillBetweenMany : public Effect { public: LPEFillBetweenMany(LivePathEffectObject *lpeobject); virtual ~LPEFillBetweenMany(); - + virtual void transform_multiply(Geom::Affine const& postmul, bool set); virtual void doEffect (SPCurve * curve); private: OriginalPathArrayParam linked_paths; BoolParam fuse; - + BoolParam allow_transforms; + BoolParam join; + BoolParam close; + bool transformmultiply; private: LPEFillBetweenMany(const LPEFillBetweenMany&); LPEFillBetweenMany& operator=(const LPEFillBetweenMany&); diff --git a/src/live_effects/lpe-fill-between-strokes.cpp b/src/live_effects/lpe-fill-between-strokes.cpp index 8dc55357f..43fef4288 100644 --- a/src/live_effects/lpe-fill-between-strokes.cpp +++ b/src/live_effects/lpe-fill-between-strokes.cpp @@ -8,6 +8,7 @@ #include "display/curve.h" #include "sp-shape.h" #include "sp-text.h" +#include "svg/svg.h" // TODO due to internal breakage in glibmm headers, this must be last: #include <glibmm/i18n.h> @@ -19,15 +20,19 @@ LPEFillBetweenStrokes::LPEFillBetweenStrokes(LivePathEffectObject *lpeobject) : linked_path(_("Linked path:"), _("Path from which to take the original path data"), "linkedpath", &wr, this), second_path(_("Second path:"), _("Second path from which to take the original path data"), "secondpath", &wr, this), reverse_second(_("Reverse Second"), _("Reverses the second path order"), "reversesecond", &wr, this), - close(_("Close path"), _("Close path"), "close", &wr, this), - fuse(_("Fuse coincident points"), _("Fuse coincident points"), "fuse", &wr, this) + fuse(_("Fuse coincident points"), _("Fuse coincident points"), "fuse", &wr, this, false), + allow_transforms(_("Allow transforms"), _("Allow transforms"), "allow_transforms", &wr, this, false), + join(_("Join subpaths"), _("Join subpaths"), "join", &wr, this, true), + close(_("Close"), _("Close path"), "close", &wr, this, true) { registerParameter( dynamic_cast<Parameter *>(&linked_path) ); registerParameter( dynamic_cast<Parameter *>(&second_path) ); registerParameter( dynamic_cast<Parameter *>(&reverse_second) ); - registerParameter( dynamic_cast<Parameter *>(&close) ); registerParameter( dynamic_cast<Parameter *>(&fuse) ); - //perceived_path = true; + registerParameter( dynamic_cast<Parameter *>(&allow_transforms) ); + registerParameter( dynamic_cast<Parameter *>(&join) ); + registerParameter( dynamic_cast<Parameter *>(&close) ); + transformmultiply = false; } LPEFillBetweenStrokes::~LPEFillBetweenStrokes() @@ -38,6 +43,13 @@ LPEFillBetweenStrokes::~LPEFillBetweenStrokes() void LPEFillBetweenStrokes::doEffect (SPCurve * curve) { if (curve) { + Geom::Affine affine = Geom::identity(); + if(!allow_transforms && !transformmultiply) { + sp_svg_transform_read(SP_ITEM(sp_lpe_item)->getAttribute("transform"), &affine); + } + if(transformmultiply) { + transformmultiply = false; + } if ( linked_path.linksToPath() && second_path.linksToPath() && linked_path.getObject() && second_path.getObject() ) { Geom::PathVector linked_pathv = linked_path.get_pathvector(); Geom::PathVector second_pathv = second_path.get_pathvector(); @@ -58,19 +70,30 @@ void LPEFillBetweenStrokes::doEffect (SPCurve * curve) if (reverse_second.get_value()) { result_second_pathv.front() = result_second_pathv.front().reversed(); } - if (!are_near(result_linked_pathv.front().finalPoint(), result_second_pathv.front().initialPoint(),0.01) || !fuse) { - result_linked_pathv.front().appendNew<Geom::LineSegment>(result_second_pathv.front().initialPoint()); + if (join) { + if (!are_near(result_linked_pathv.front().finalPoint(), result_second_pathv.front().initialPoint(),0.01) || !fuse) { + result_linked_pathv.front().appendNew<Geom::LineSegment>(result_second_pathv.front().initialPoint()); + } else { + result_second_pathv.front().setInitial(result_linked_pathv.front().finalPoint()); + } + result_linked_pathv.front().append(result_second_pathv.front()); + if (close) { + result_linked_pathv.front().close(); + } } else { - result_second_pathv.front().setInitial(result_linked_pathv.front().finalPoint()); - } - result_linked_pathv.front().append(result_second_pathv.front()); - if (close) { - result_linked_pathv.front().close(); + if (close) { + result_linked_pathv.front().close(); + result_second_pathv.front().close(); + } + result_linked_pathv.push_back(result_second_pathv.front()); } + result_linked_pathv *= affine.inverse(); curve->set_pathvector(result_linked_pathv); } else if ( !result_linked_pathv.empty() ) { + result_linked_pathv *= affine.inverse(); curve->set_pathvector(result_linked_pathv); } else if ( !result_second_pathv.empty() ) { + result_second_pathv *= affine.inverse(); curve->set_pathvector(result_second_pathv); } } @@ -83,6 +106,10 @@ void LPEFillBetweenStrokes::doEffect (SPCurve * curve) result_pathv.push_back((*iter)); } if ( !result_pathv.empty() ) { + result_pathv *= affine.inverse(); + if (close) { + result_pathv.front().close(); + } curve->set_pathvector(result_pathv); } } @@ -95,12 +122,26 @@ void LPEFillBetweenStrokes::doEffect (SPCurve * curve) result_pathv.push_back((*iter)); } if ( !result_pathv.empty() ) { + result_pathv *= affine.inverse(); + if (close) { + result_pathv.front().close(); + } curve->set_pathvector(result_pathv); } } } } +void +LPEFillBetweenStrokes::transform_multiply(Geom::Affine const& postmul, bool set) +{ + if(!allow_transforms && sp_lpe_item) { + SP_ITEM(sp_lpe_item)->transform *= postmul.inverse(); + transformmultiply = true; + sp_lpe_item_update_patheffect(sp_lpe_item, false, false); + } +} + } // namespace LivePathEffect } /* namespace Inkscape */ diff --git a/src/live_effects/lpe-fill-between-strokes.h b/src/live_effects/lpe-fill-between-strokes.h index 14eb9167c..5bbd6e7da 100644 --- a/src/live_effects/lpe-fill-between-strokes.h +++ b/src/live_effects/lpe-fill-between-strokes.h @@ -19,15 +19,18 @@ class LPEFillBetweenStrokes : public Effect { public: LPEFillBetweenStrokes(LivePathEffectObject *lpeobject); virtual ~LPEFillBetweenStrokes(); - + virtual void transform_multiply(Geom::Affine const& postmul, bool set); virtual void doEffect (SPCurve * curve); private: OriginalPathParam linked_path; OriginalPathParam second_path; BoolParam reverse_second; - BoolParam close; BoolParam fuse; + BoolParam allow_transforms; + BoolParam join; + BoolParam close; + bool transformmultiply; private: LPEFillBetweenStrokes(const LPEFillBetweenStrokes&); diff --git a/src/live_effects/lpe-measure-line.cpp b/src/live_effects/lpe-measure-line.cpp index ef87be81c..625db150b 100644 --- a/src/live_effects/lpe-measure-line.cpp +++ b/src/live_effects/lpe-measure-line.cpp @@ -171,6 +171,9 @@ void LPEMeasureLine::createArrowMarker(const char * mode) { SPDocument * document = SP_ACTIVE_DOCUMENT; + if (!document) { + return; + } Inkscape::XML::Document *xml_doc = document->getReprDoc(); SPObject *elemref = NULL; Inkscape::XML::Node *arrow = NULL; @@ -232,6 +235,9 @@ void LPEMeasureLine::createTextLabel(Geom::Point pos, double length, Geom::Coord angle, bool remove, bool valid) { SPDocument * document = SP_ACTIVE_DOCUMENT; + if (!document) { + return; + } Inkscape::XML::Document *xml_doc = document->getReprDoc(); Inkscape::XML::Node *rtext = NULL; double doc_w = document->getRoot()->width.value; @@ -376,6 +382,9 @@ void LPEMeasureLine::createLine(Geom::Point start,Geom::Point end, const char * id, bool main, bool overflow, bool remove, bool arrows) { SPDocument * document = SP_ACTIVE_DOCUMENT; + if (!document) { + return; + } Inkscape::XML::Document *xml_doc = document->getReprDoc(); SPObject *elemref = NULL; Inkscape::XML::Node *line = NULL; @@ -500,6 +509,9 @@ LPEMeasureLine::doBeforeEffect (SPLPEItem const* lpeitem) SPLPEItem * splpeitem = const_cast<SPLPEItem *>(lpeitem); sp_lpe_item->parent = dynamic_cast<SPObject *>(splpeitem->parent); SPDocument * document = SP_ACTIVE_DOCUMENT; + if (!document) { + return; + } Inkscape::XML::Node *root = splpeitem->document->getReprRoot(); Inkscape::XML::Node *root_origin = document->getReprRoot(); if (root_origin != root) { diff --git a/src/live_effects/lpe-mirror_symmetry.cpp b/src/live_effects/lpe-mirror_symmetry.cpp index 7f0a93c52..c31ce7c01 100644 --- a/src/live_effects/lpe-mirror_symmetry.cpp +++ b/src/live_effects/lpe-mirror_symmetry.cpp @@ -87,11 +87,14 @@ LPEMirrorSymmetry::~LPEMirrorSymmetry() void LPEMirrorSymmetry::doAfterEffect (SPLPEItem const* lpeitem) { + SPDocument * document = SP_ACTIVE_DOCUMENT; + if (!document) { + return; + } if (split_items && !discard_orig_path) { container = dynamic_cast<SPObject *>(sp_lpe_item->parent); - SPDocument * doc = SP_ACTIVE_DOCUMENT; Inkscape::XML::Node *root = sp_lpe_item->document->getReprRoot(); - Inkscape::XML::Node *root_origin = doc->getReprRoot(); + Inkscape::XML::Node *root_origin = document->getReprRoot(); if (root_origin != root) { return; } @@ -161,20 +164,24 @@ LPEMirrorSymmetry::doBeforeEffect (SPLPEItem const* lpeitem) } } else if ( mode == MT_V){ SPDocument * document = SP_ACTIVE_DOCUMENT; - Geom::Affine transform = i2anc_affine(SP_OBJECT(lpeitem), NULL).inverse(); - Geom::Point sp = Geom::Point(document->getWidth().value("px")/2.0, 0) * transform; - start_point.param_setValue(sp, true); - Geom::Point ep = Geom::Point(document->getWidth().value("px")/2.0, document->getHeight().value("px")) * transform; - end_point.param_setValue(ep, true); - center_point.param_setValue(Geom::middle_point((Geom::Point)start_point, (Geom::Point)end_point), true); + if (document) { + Geom::Affine transform = i2anc_affine(SP_OBJECT(lpeitem), NULL).inverse(); + Geom::Point sp = Geom::Point(document->getWidth().value("px")/2.0, 0) * transform; + start_point.param_setValue(sp, true); + Geom::Point ep = Geom::Point(document->getWidth().value("px")/2.0, document->getHeight().value("px")) * transform; + end_point.param_setValue(ep, true); + center_point.param_setValue(Geom::middle_point((Geom::Point)start_point, (Geom::Point)end_point), true); + } } else { //horizontal page SPDocument * document = SP_ACTIVE_DOCUMENT; - Geom::Affine transform = i2anc_affine(SP_OBJECT(lpeitem), NULL).inverse(); - Geom::Point sp = Geom::Point(0, document->getHeight().value("px")/2.0) * transform; - start_point.param_setValue(sp, true); - Geom::Point ep = Geom::Point(document->getWidth().value("px"), document->getHeight().value("px")/2.0) * transform; - end_point.param_setValue(ep, true); - center_point.param_setValue(Geom::middle_point((Geom::Point)start_point, (Geom::Point)end_point), true); + if (document) { + Geom::Affine transform = i2anc_affine(SP_OBJECT(lpeitem), NULL).inverse(); + Geom::Point sp = Geom::Point(0, document->getHeight().value("px")/2.0) * transform; + start_point.param_setValue(sp, true); + Geom::Point ep = Geom::Point(document->getWidth().value("px"), document->getHeight().value("px")/2.0) * transform; + end_point.param_setValue(ep, true); + center_point.param_setValue(Geom::middle_point((Geom::Point)start_point, (Geom::Point)end_point), true); + } } previous_center = center_point; } @@ -183,6 +190,9 @@ void LPEMirrorSymmetry::cloneD(SPObject *origin, SPObject *dest, bool live, bool root) { SPDocument * document = SP_ACTIVE_DOCUMENT; + if (!document) { + return; + } Inkscape::XML::Document *xml_doc = document->getReprDoc(); if ( SP_IS_GROUP(origin) && SP_IS_GROUP(dest) && SP_GROUP(origin)->getItemCount() == SP_GROUP(dest)->getItemCount() ) { std::vector< SPObject * > childs = origin->childList(true); @@ -196,7 +206,7 @@ LPEMirrorSymmetry::cloneD(SPObject *origin, SPObject *dest, bool live, bool root } SPShape * shape = SP_SHAPE(origin); SPPath * path = SP_PATH(dest); - if (!path && !SP_IS_GROUP(dest)) { + if (shape && !path) { Inkscape::XML::Node *dest_node = sp_selected_item_to_curved_repr(SP_ITEM(dest), 0); dest->updateRepr(xml_doc, dest_node, SP_OBJECT_WRITE_ALL); path = SP_PATH(dest); @@ -226,67 +236,69 @@ void LPEMirrorSymmetry::toMirror(Geom::Affine transform) { SPDocument * document = SP_ACTIVE_DOCUMENT; - Inkscape::XML::Document *xml_doc = document->getReprDoc(); - const char * id_origin_char = id_origin.param_getSVGValue(); - const char * elemref_id = g_strdup(Glib::ustring("mirror-").append(id_origin_char).c_str()); - items.clear(); - items.push_back(elemref_id); - SPObject *elemref= NULL; - Inkscape::XML::Node *phantom = NULL; - if (elemref = document->getObjectById(elemref_id)) { - phantom = elemref->getRepr(); - } else { - phantom = sp_lpe_item->getRepr()->duplicate(xml_doc); - std::vector<const char *> attrs; - attrs.push_back("inkscape:path-effect"); - attrs.push_back("inkscape:original-d"); - attrs.push_back("sodipodi:type"); - attrs.push_back("sodipodi:rx"); - attrs.push_back("sodipodi:ry"); - attrs.push_back("sodipodi:cx"); - attrs.push_back("sodipodi:cy"); - attrs.push_back("sodipodi:end"); - attrs.push_back("sodipodi:start"); - attrs.push_back("inkscape:flatsided"); - attrs.push_back("inkscape:randomized"); - attrs.push_back("inkscape:rounded"); - attrs.push_back("sodipodi:arg1"); - attrs.push_back("sodipodi:arg2"); - attrs.push_back("sodipodi:r1"); - attrs.push_back("sodipodi:r2"); - attrs.push_back("sodipodi:sides"); - attrs.push_back("inkscape:randomized"); - attrs.push_back("sodipodi:argument"); - attrs.push_back("sodipodi:expansion"); - attrs.push_back("sodipodi:radius"); - attrs.push_back("sodipodi:revolution"); - attrs.push_back("sodipodi:t0"); - attrs.push_back("inkscape:randomized"); - attrs.push_back("inkscape:randomized"); - attrs.push_back("inkscape:randomized"); - attrs.push_back("x"); - attrs.push_back("y"); - attrs.push_back("rx"); - attrs.push_back("ry"); - attrs.push_back("width"); - attrs.push_back("height"); - for(const char * attr : attrs) { - phantom->setAttribute(attr, NULL); + if (document) { + Inkscape::XML::Document *xml_doc = document->getReprDoc(); + const char * id_origin_char = id_origin.param_getSVGValue(); + const char * elemref_id = g_strdup(Glib::ustring("mirror-").append(id_origin_char).c_str()); + items.clear(); + items.push_back(elemref_id); + SPObject *elemref= NULL; + Inkscape::XML::Node *phantom = NULL; + if (elemref = document->getObjectById(elemref_id)) { + phantom = elemref->getRepr(); + } else { + phantom = sp_lpe_item->getRepr()->duplicate(xml_doc); + std::vector<const char *> attrs; + attrs.push_back("inkscape:path-effect"); + attrs.push_back("inkscape:original-d"); + attrs.push_back("sodipodi:type"); + attrs.push_back("sodipodi:rx"); + attrs.push_back("sodipodi:ry"); + attrs.push_back("sodipodi:cx"); + attrs.push_back("sodipodi:cy"); + attrs.push_back("sodipodi:end"); + attrs.push_back("sodipodi:start"); + attrs.push_back("inkscape:flatsided"); + attrs.push_back("inkscape:randomized"); + attrs.push_back("inkscape:rounded"); + attrs.push_back("sodipodi:arg1"); + attrs.push_back("sodipodi:arg2"); + attrs.push_back("sodipodi:r1"); + attrs.push_back("sodipodi:r2"); + attrs.push_back("sodipodi:sides"); + attrs.push_back("inkscape:randomized"); + attrs.push_back("sodipodi:argument"); + attrs.push_back("sodipodi:expansion"); + attrs.push_back("sodipodi:radius"); + attrs.push_back("sodipodi:revolution"); + attrs.push_back("sodipodi:t0"); + attrs.push_back("inkscape:randomized"); + attrs.push_back("inkscape:randomized"); + attrs.push_back("inkscape:randomized"); + attrs.push_back("x"); + attrs.push_back("y"); + attrs.push_back("rx"); + attrs.push_back("ry"); + attrs.push_back("width"); + attrs.push_back("height"); + for(const char * attr : attrs) { + phantom->setAttribute(attr, NULL); + } + } + phantom->setAttribute("id", elemref_id); + if (!elemref) { + elemref = container->appendChildRepr(phantom); + Inkscape::GC::release(phantom); + } + cloneD(SP_OBJECT(sp_lpe_item), elemref, true, true); + elemref->getRepr()->setAttribute("transform" , sp_svg_transform_write(transform)); + if (elemref->parent != container) { + Inkscape::XML::Node *copy = phantom->duplicate(xml_doc); + copy->setAttribute("id", elemref_id); + container->appendChildRepr(copy); + Inkscape::GC::release(copy); + elemref->deleteObject(); } - } - phantom->setAttribute("id", elemref_id); - if (!elemref) { - elemref = container->appendChildRepr(phantom); - Inkscape::GC::release(phantom); - } - cloneD(SP_OBJECT(sp_lpe_item), elemref, true, true); - elemref->getRepr()->setAttribute("transform" , sp_svg_transform_write(transform)); - if (elemref->parent != container) { - Inkscape::XML::Node *copy = phantom->duplicate(xml_doc); - copy->setAttribute("id", elemref_id); - container->appendChildRepr(copy); - Inkscape::GC::release(copy); - elemref->deleteObject(); } } diff --git a/src/live_effects/parameter/originalpath.cpp b/src/live_effects/parameter/originalpath.cpp index f7eb48b7a..1e78f7fe1 100644 --- a/src/live_effects/parameter/originalpath.cpp +++ b/src/live_effects/parameter/originalpath.cpp @@ -89,7 +89,7 @@ OriginalPathParam::linked_modified_callback(SPObject *linked_obj, guint /*flags* { SPCurve *curve = NULL; if (SP_IS_SHAPE(linked_obj)) { - curve = SP_SHAPE(linked_obj)->getCurveBeforeLPE(); + curve = SP_SHAPE(linked_obj)->getCurve(); } if (SP_IS_TEXT(linked_obj)) { curve = SP_TEXT(linked_obj)->getNormalizedBpath(); diff --git a/src/live_effects/parameter/originalpatharray.cpp b/src/live_effects/parameter/originalpatharray.cpp index 083abc94c..693821ed2 100644 --- a/src/live_effects/parameter/originalpatharray.cpp +++ b/src/live_effects/parameter/originalpatharray.cpp @@ -386,7 +386,7 @@ void OriginalPathArrayParam::setPathVector(SPObject *linked_obj, guint /*flags*/ } SPCurve *curve = NULL; if (SP_IS_SHAPE(linked_obj)) { - curve = SP_SHAPE(linked_obj)->getCurveBeforeLPE(); + curve = SP_SHAPE(linked_obj)->getCurve(); } if (SP_IS_TEXT(linked_obj)) { curve = SP_TEXT(linked_obj)->getNormalizedBpath(); diff --git a/src/main.cpp b/src/main.cpp index 47cf43456..8be9e2e44 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -691,18 +691,22 @@ main(int argc, char **argv) RegistryTool rt; rt.setPathInfo(); } -#elif defined(ENABLE_NLS) -# ifdef ENABLE_BINRELOC + + // disable "client side decorations" as they prevent window borders and titlebars to be drawn with native theming + // see also https://bugzilla.gnome.org/show_bug.cgi?id=778791 + g_setenv("GTK_CSD", "0", FALSE); +#endif + +#ifdef ENABLE_NLS +# ifndef WIN32 +# ifdef ENABLE_BINRELOC bindtextdomain(GETTEXT_PACKAGE, BR_LOCALEDIR("")); -# else +# else bindtextdomain(GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR); // needed by Python/Gettext g_setenv("PACKAGE_LOCALE_DIR", PACKAGE_LOCALE_DIR, TRUE); +# endif # endif -#endif - - // the bit below compiles regardless of platform -#ifdef ENABLE_NLS // Allow the user to override the locale directory by setting // the environment variable INKSCAPE_LOCALEDIR. char const *inkscape_localedir = g_getenv("INKSCAPE_LOCALEDIR"); diff --git a/src/selection-chemistry.cpp b/src/selection-chemistry.cpp index 70307579f..f2bdeba4e 100644 --- a/src/selection-chemistry.cpp +++ b/src/selection-chemistry.cpp @@ -1618,7 +1618,7 @@ void ObjectSet::applyAffine(Geom::Affine const &affine, bool set_i2d, bool compe for (auto& itm: region.children) { SPUse *use = dynamic_cast<SPUse *>(&itm); if ( use ) { - use->doWriteTransform(use->getRepr(), use->transform.inverse(), NULL, compensate); + use->doWriteTransform(use->getRepr(), item->transform.inverse(), NULL, compensate); } } } diff --git a/src/sp-namedview.cpp b/src/sp-namedview.cpp index d430f6321..166e90238 100644 --- a/src/sp-namedview.cpp +++ b/src/sp-namedview.cpp @@ -754,7 +754,7 @@ void SPNamedView::show(SPDesktop *desktop) namespace { -gint const MIN_ONSCREEN_DISTANCE = 50; +gint const MIN_ONSCREEN_DISTANCE = 100; gdouble const NEWDOC_X_SCALE = 0.75; gdouble const NEWDOC_Y_SCALE = NEWDOC_X_SCALE; @@ -774,12 +774,6 @@ Geom::Point calcAnchorPoint(gint const x, gint const y, } // namespace -void SPNamedView::writeNewGrid(SPDocument *document,int gridtype) -{ - g_assert(this->getRepr() != NULL); - Inkscape::CanvasGrid::writeNewGridToRepr(this->getRepr(),document,static_cast<Inkscape::GridType>(gridtype)); -} - /* * Restores window geometry from the document settings or defaults in prefs */ @@ -788,22 +782,42 @@ void sp_namedview_window_from_document(SPDesktop *desktop) SPNamedView *nv = desktop->namedview; Inkscape::Preferences *prefs = Inkscape::Preferences::get(); bool geometry_from_file = (1 == prefs->getInt("/options/savewindowgeometry/value", 0)); + bool geometry_from_last = (2 == prefs->getInt("/options/savewindowgeometry/value", 0)); gint default_geometry = prefs->getInt("/options/defaultwindowsize/value", 1); bool new_document = (nv->window_width <= 0) || (nv->window_height <= 0); bool show_dialogs = true; // restore window size and position stored with the document - bool sizeSet = false; - - if ((geometry_from_file && nv->window_maximized) || (new_document && (default_geometry == 2))) { + if (geometry_from_last) { + // do nothing, as we already have code for that in interface.cpp + // TODO: Probably should not do similar things in two places + } else if ((geometry_from_file && nv->window_maximized) || (new_document && (default_geometry == 2))) { Gtk::Window *win = desktop->getToplevel(); if (win) { win->maximize(); } - sizeSet = true; - } else if (geometry_from_file && !nv->window_maximized) { - gint w = MIN(gdk_screen_width(), nv->window_width); - gint h = MIN(gdk_screen_height(), nv->window_height); + } else { + // gdk_screen_width() / gdk_screen_height() return the dimensions of all displays combined + // therefore we have to get the dimensions of one monitor explicitly (currently the primary monitor) + // TODO: account for multi-monitor setups (i.e. on which monitor do we want to display Inkscape?) + gint monitor_number; + GdkRectangle monitor_geometry; + monitor_number = gdk_screen_get_primary_monitor(gdk_screen_get_default()); + gdk_screen_get_monitor_geometry(gdk_screen_get_default(), monitor_number, &monitor_geometry); + + gint w = monitor_geometry.width; + gint h = monitor_geometry.height; + bool move_to_screen = false; + if (geometry_from_file and !new_document) { + w = MIN(w, nv->window_width); + h = MIN(h, nv->window_height); + move_to_screen = true; + } else if (default_geometry == 1) { + w *= NEWDOC_X_SCALE; + h *= NEWDOC_Y_SCALE; + } else if (default_geometry == 0) { + w = h = 0; // use the smallest possible window size; could be a factor like NEWDOC_X_SCALE in future + } if ((w > 0) && (h > 0)) { #ifndef WIN32 gint dx= 0; @@ -817,22 +831,14 @@ void sp_namedview_window_from_document(SPDesktop *desktop) show_dialogs = FALSE; } #endif - Geom::Point origin = calcAnchorPoint(nv->window_x, nv->window_y, w, h, MIN_ONSCREEN_DISTANCE); desktop->setWindowSize(w, h); - desktop->setWindowPosition(origin); - sizeSet = true; + if (move_to_screen) { + Geom::Point origin = calcAnchorPoint(nv->window_x, nv->window_y, w, h, MIN_ONSCREEN_DISTANCE); + desktop->setWindowPosition(origin); + } } } - if (!sizeSet && new_document && (default_geometry == 1)) - { - gint w = gdk_screen_width() * NEWDOC_X_SCALE; - gint h = gdk_screen_height() * NEWDOC_Y_SCALE; - Geom::Point origin = calcAnchorPoint(nv->window_x, nv->window_y, w, h, MIN_ONSCREEN_DISTANCE); - desktop->setWindowSize(w, h); - desktop->setWindowPosition(origin); - } - // restore zoom and view if (nv->zoom != 0 && nv->zoom != HUGE_VAL && !IS_NAN(nv->zoom) && nv->cx != HUGE_VAL && !IS_NAN(nv->cx) @@ -850,6 +856,12 @@ void sp_namedview_window_from_document(SPDesktop *desktop) } } +void SPNamedView::writeNewGrid(SPDocument *document,int gridtype) +{ + g_assert(this->getRepr() != NULL); + Inkscape::CanvasGrid::writeNewGridToRepr(this->getRepr(),document,static_cast<Inkscape::GridType>(gridtype)); +} + bool SPNamedView::getSnapGlobal() const { return this->snap_manager.snapprefs.getSnapEnabledGlobally(); diff --git a/src/sp-object.cpp b/src/sp-object.cpp index 7807703f6..9f15935ac 100644 --- a/src/sp-object.cpp +++ b/src/sp-object.cpp @@ -1105,7 +1105,10 @@ Inkscape::XML::Node* SPObject::write(Inkscape::XML::Document *doc, Inkscape::XML * possibly we should write property attributes instead of a style * attribute. */ - sp_style_unset_property_attrs (this); + // With the changes to preserves style source this is no longer needed + // and the above comment no longer applies. I leave it here until these + // change are well tested. + // sp_style_unset_property_attrs (this); } #ifdef OBJECT_TRACE diff --git a/src/sp-path.cpp b/src/sp-path.cpp index b593b7937..c6ec5559e 100644 --- a/src/sp-path.cpp +++ b/src/sp-path.cpp @@ -292,9 +292,14 @@ Geom::Affine SPPath::set_transform(Geom::Affine const &transform) { // Transform the original-d path if this is a valid LPE this, other else the (ordinary) path if (_curve_before_lpe && hasPathEffectRecursive()) { - if (this->hasPathEffectOfType(Inkscape::LivePathEffect::CLONE_ORIGINAL) || this->hasPathEffectOfType(Inkscape::LivePathEffect::BEND_PATH)) { + if (this->hasPathEffectOfType(Inkscape::LivePathEffect::CLONE_ORIGINAL) || + this->hasPathEffectOfType(Inkscape::LivePathEffect::BEND_PATH) || + this->hasPathEffectOfType(Inkscape::LivePathEffect::FILL_BETWEEN_MANY) || + this->hasPathEffectOfType(Inkscape::LivePathEffect::FILL_BETWEEN_STROKES) ) + { // if path has the CLONE_ORIGINAL LPE applied, don't write the transform to the pathdata, but write it 'unoptimized' // also if the effect is type BEND PATH to fix bug #179842 + this->adjust_livepatheffect(transform); return transform; } else { _curve_before_lpe->transform(transform); diff --git a/src/splivarot.cpp b/src/splivarot.cpp index 531a48e44..22efe38ed 100644 --- a/src/splivarot.cpp +++ b/src/splivarot.cpp @@ -1196,13 +1196,19 @@ sp_item_path_outline(SPItem *item, SPDesktop *desktop, bool legacy) // remember old stroke style, to be set on fill SPStyle *i_style = item->style; //Stroke - and markers + gchar const *opacity; + gchar const *filter; + SPCSSAttr *ncss = 0; { ncss = sp_css_attr_from_style(i_style, SP_STYLE_FLAG_ALWAYS); gchar const *s_val = sp_repr_css_property(ncss, "stroke", NULL); gchar const *s_opac = sp_repr_css_property(ncss, "stroke-opacity", NULL); - + opacity = sp_repr_css_property(ncss, "opacity", NULL); + filter = sp_repr_css_property(ncss, "filter", NULL); sp_repr_css_set_property(ncss, "stroke", "none"); + sp_repr_css_set_property(ncss, "filter", NULL); + sp_repr_css_set_property(ncss, "opacity", NULL); sp_repr_css_set_property(ncss, "stroke-opacity", "1.0"); sp_repr_css_set_property(ncss, "fill", s_val); if ( s_opac ) { @@ -1220,6 +1226,8 @@ sp_item_path_outline(SPItem *item, SPDesktop *desktop, bool legacy) ncsf = sp_css_attr_from_style(i_style, SP_STYLE_FLAG_ALWAYS); sp_repr_css_set_property(ncsf, "stroke", "none"); sp_repr_css_set_property(ncsf, "stroke-opacity", "1.0"); + sp_repr_css_set_property(ncsf, "filter", NULL); + sp_repr_css_set_property(ncsf, "opacity", NULL); sp_repr_css_unset_property(ncsf, "marker-start"); sp_repr_css_unset_property(ncsf, "marker-mid"); sp_repr_css_unset_property(ncsf, "marker-end"); @@ -1504,7 +1512,11 @@ sp_item_path_outline(SPItem *item, SPDesktop *desktop, bool legacy) gchar const *paint_order = sp_repr_css_property(ncss, "paint-order", NULL); SPIPaintOrder temp; temp.read( paint_order ); - if (temp.layer[0] != SP_CSS_PAINT_ORDER_NORMAL && !legacy) { + bool unique = false; + if ((!fill && !markers) || (!fill && !stroke) || (!markers && !stroke)) { + unique = true; + } + if (temp.layer[0] != SP_CSS_PAINT_ORDER_NORMAL && !legacy && !unique) { if (temp.layer[0] == SP_CSS_PAINT_ORDER_FILL) { if (temp.layer[1] == SP_CSS_PAINT_ORDER_STROKE) { @@ -1574,7 +1586,7 @@ sp_item_path_outline(SPItem *item, SPDesktop *desktop, bool legacy) } } - } else { + } else if (!unique) { if ( fill ) { g_repr->appendChild(fill); } @@ -1588,35 +1600,41 @@ sp_item_path_outline(SPItem *item, SPDesktop *desktop, bool legacy) if( fill || stroke || markers ) { did = true; } + Inkscape::XML::Node *out = NULL; - if (!fill && !markers) { + if (!fill && !markers && did) { out = stroke; - parent->mergeFrom(g_repr, ""); - parent->removeChild(g_repr); - } else if (!fill && !stroke) { + } else if (!fill && !stroke && did) { out = markers; - parent->mergeFrom(g_repr, ""); - parent->removeChild(g_repr); - } else if (!markers && !stroke) { + } else if (!markers && !stroke && did) { out = fill; - parent->mergeFrom(g_repr, ""); - parent->removeChild(g_repr); - } else { + } else if(did) { out = g_repr; } - + SPCSSAttr *r_style = sp_repr_css_attr_new(); + sp_repr_css_set_property(r_style, "opacity", opacity); + sp_repr_css_set_property(r_style, "filter", filter); + sp_repr_css_change(out, r_style, "style"); + sp_repr_css_attr_unref(r_style); + if (unique) { + parent->appendChild(out); + parent->removeChild(g_repr); + out->setPosition(pos > 0 ? pos : 0); + } + out->setAttribute("transform", item->getRepr()->attribute("transform")); //bug lp:1290573 : completely destroy the old object first curve->unref(); //Check for recursive markers to path - if( selection->includes(item) ){ - selection->remove(item); - item->deleteObject(false); - selection->add(out); - } else { - item->deleteObject(false); + if (did) { + if( selection->includes(item) ){ + selection->remove(item); + item->deleteObject(false); + selection->add(out); + } else { + item->deleteObject(false); + } + Inkscape::GC::release(g_repr); } - Inkscape::GC::release(g_repr); - } } diff --git a/src/style-internal.h b/src/style-internal.h index 69b03f503..154538833 100644 --- a/src/style-internal.h +++ b/src/style-internal.h @@ -121,7 +121,7 @@ public: inherits(inherits), set(false), inherit(false), - style_src(SP_STYLE_SRC_UNSET), + style_src(SP_STYLE_SRC_STYLE_PROP), // Default to property, see bug 1662285. style(NULL) {} @@ -132,7 +132,9 @@ public: virtual void readIfUnset( gchar const *str, SPStyleSrc const &source = SP_STYLE_SRC_STYLE_PROP ) { if ( !set ) { read( str ); - style_src = source; + if ( set ) { + style_src = source; + } } } diff --git a/src/ui/dialog/aboutbox.cpp b/src/ui/dialog/aboutbox.cpp index 40b948a23..805bfb562 100644 --- a/src/ui/dialog/aboutbox.cpp +++ b/src/ui/dialog/aboutbox.cpp @@ -115,7 +115,7 @@ AboutBox::AboutBox() : Gtk::Dialog(_("About Inkscape")) { Gtk::Label *link = new Gtk::Label(); const gchar *website_link = - "<a href=\"https://www.inkscape.org\"> https://www.inkscape.org</a>"; + "<a href=\"https://www.inkscape.org\">https://www.inkscape.org</a>"; link->set_markup(website_link); link->set_alignment(Gtk::ALIGN_END); diff --git a/src/ui/dialog/cssdialog.cpp b/src/ui/dialog/cssdialog.cpp index fa266b012..a5736513f 100644 --- a/src/ui/dialog/cssdialog.cpp +++ b/src/ui/dialog/cssdialog.cpp @@ -3,8 +3,10 @@ */ /* Authors: * Kamalpreet Kaur Grewal + * Tavmjong Bah * * Copyright (C) Kamalpreet Kaur Grewal 2016 <grewalkamal005@gmail.com> + * Copyright (C) Tavmjong Bah 2017 <tavmjong@free.fr> * * Released under GNU GPL, read the file 'COPYING' for more information */ @@ -22,23 +24,6 @@ namespace UI { namespace Dialog { /** - * @brief CssDialog::_styleButton - * @param btn - * @param iconName - * @param tooltip - * This function sets the style of '+'button at the bottom of dialog. - */ -void CssDialog::_styleButton(Gtk::Button& btn, char const* iconName, - char const* tooltip) -{ - GtkWidget *child = sp_icon_new(Inkscape::ICON_SIZE_SMALL_TOOLBAR, iconName); - gtk_widget_show(child); - btn.add(*manage(Glib::wrap(child))); - btn.set_relief(Gtk::RELIEF_NONE); - btn.set_tooltip_text(tooltip); -} - -/** * 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. '-' @@ -53,18 +38,17 @@ CssDialog::CssDialog(): { set_size_request(20, 15); _mainBox.pack_start(_scrolledWindow, Gtk::PACK_EXPAND_WIDGET); - _treeView.set_headers_visible(false); + _treeView.set_headers_visible(true); _scrolledWindow.add(_treeView); _scrolledWindow.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC); _store = Gtk::ListStore::create(_cssColumns); _treeView.set_model(_store); - Inkscape::UI::Widget::AddToIcon * addRenderer = manage(new Inkscape::UI:: - Widget::AddToIcon()); + Inkscape::UI::Widget::AddToIcon * addRenderer = manage(new Inkscape::UI::Widget::AddToIcon()); addRenderer->property_active() = false; - int addCol = _treeView.append_column("Unset Property", *addRenderer) - 1; + int addCol = _treeView.append_column("", *addRenderer) - 1; Gtk::TreeViewColumn *col = _treeView.get_column(addCol); if (col) { col->add_attribute(addRenderer->property_active(), _cssColumns._colUnsetProp); @@ -72,8 +56,12 @@ CssDialog::CssDialog(): _textRenderer = Gtk::manage(new Gtk::CellRendererText()); _textRenderer->property_editable() = true; - int nameColNum = _treeView.append_column("Property", *_textRenderer) - 1; + int nameColNum = _treeView.append_column("CSS Property", *_textRenderer) - 1; _propCol = _treeView.get_column(nameColNum); + if (_propCol) { + _propCol->add_attribute(_textRenderer->property_text(), + _cssColumns._propertyLabel); + } Gtk::Button* create = manage(new Gtk::Button()); _styleButton(*create, "list-add", "Add a new property"); @@ -83,12 +71,12 @@ CssDialog::CssDialog(): _getContents()->pack_start(_mainBox, Gtk::PACK_EXPAND_WIDGET); - _targetDesktop = getDesktop(); - setDesktop(_targetDesktop); + setDesktop(getDesktop()); create->signal_clicked().connect(sigc::mem_fun(*this, &CssDialog::_addProperty)); } + /** * @brief CssDialog::~CssDialog * Class destructor @@ -98,6 +86,7 @@ CssDialog::~CssDialog() setDesktop(NULL); } + /** * @brief CssDialog::setDesktop * @param desktop @@ -108,16 +97,34 @@ void CssDialog::setDesktop(SPDesktop* desktop) _desktop = desktop; } + +/** + * @brief CssDialog::_styleButton + * @param btn + * @param iconName + * @param tooltip + * This function sets the style of '+'button at the bottom of dialog. + */ +void CssDialog::_styleButton(Gtk::Button& btn, char const* iconName, + char const* tooltip) +{ + GtkWidget *child = sp_icon_new(Inkscape::ICON_SIZE_SMALL_TOOLBAR, iconName); + gtk_widget_show(child); + btn.add(*manage(Glib::wrap(child))); + btn.set_relief(Gtk::RELIEF_NONE); + btn.set_tooltip_text(tooltip); +} + + /** * @brief CssDialog::_addProperty * This function is a slot to signal_clicked for '+' button at the bottom of CSS * panel. A new row is added, double clicking which text for new property can be - * added. _newProperty is set to true in which case the value is appended. + * added. */ void CssDialog::_addProperty() { _propRow = *(_store->append()); - _newProperty = true; } } // namespace Dialog diff --git a/src/ui/dialog/cssdialog.h b/src/ui/dialog/cssdialog.h index 3bbab5031..1f8b7ca98 100644 --- a/src/ui/dialog/cssdialog.h +++ b/src/ui/dialog/cssdialog.h @@ -3,8 +3,10 @@ */ /* Authors: * Kamalpreet Kaur Grewal + * Tavmjong Bah * * Copyright (C) Kamalpreet Kaur Grewal 2016 <grewalkamal005@gmail.com> + * Copyright (C) Tavmjong Bah 2017 <tavmjong@free.fr> * * Released under GNU GPL, read the file 'COPYING' for more information */ @@ -38,32 +40,39 @@ public: ~CssDialog(); static CssDialog &getInstance() { return *new CssDialog(); } - void setDesktop(SPDesktop* desktop); - class CssColumns : public Gtk::TreeModel::ColumnRecord - { + // Data structure + class CssColumns : public Gtk::TreeModel::ColumnRecord { public: - CssColumns() - { add(_colUnsetProp); add(_propertyLabel); } + CssColumns() { + add(_colUnsetProp); + add(_propertyLabel); + } Gtk::TreeModelColumn<bool> _colUnsetProp; Gtk::TreeModelColumn<Glib::ustring> _propertyLabel; }; - - SPDesktop* _desktop; - SPDesktop* _targetDesktop; CssColumns _cssColumns; - Gtk::VBox _mainBox; - Gtk::HBox _buttonBox; + + // TreeView Gtk::TreeView _treeView; Glib::RefPtr<Gtk::ListStore> _store; - Gtk::ScrolledWindow _scrolledWindow; Gtk::TreeModel::Row _propRow; Gtk::CellRendererText *_textRenderer; Gtk::TreeViewColumn *_propCol; - Glib::ustring _editedProp; - bool _newProperty; + // Widgets + Gtk::VBox _mainBox; + Gtk::ScrolledWindow _scrolledWindow; + Gtk::HBox _buttonBox; + + // Variables - Inkscape + SPDesktop* _desktop; + + // Helper functions + void setDesktop(SPDesktop* desktop); void _styleButton(Gtk::Button& btn, char const* iconName, char const* tooltip); + + // Signal handlers void _addProperty(); }; diff --git a/src/ui/dialog/styledialog.cpp b/src/ui/dialog/styledialog.cpp index 5246290b4..8d8ebf1b2 100644 --- a/src/ui/dialog/styledialog.cpp +++ b/src/ui/dialog/styledialog.cpp @@ -3,8 +3,10 @@ */ /* Authors: * Kamalpreet Kaur Grewal + * Tavmjong Bah * * Copyright (C) Kamalpreet Kaur Grewal 2016 <grewalkamal005@gmail.com> + * Copyright (C) Tavmjong Bah 2017 <tavmjong@free.fr> * * Released under GNU GPL, read the file 'COPYING' for more information */ @@ -16,8 +18,16 @@ #include "sp-object.h" #include "selection.h" #include "xml/attribute-record.h" +#include "xml/node-observer.h" +#include "attribute-rel-svg.h" +#include "document-undo.h" + +#include <glibmm/i18n.h> #include <glibmm/regex.h> - + +//#define DEBUG_STYLEDIALOG + +using Inkscape::DocumentUndo; using Inkscape::Util::List; using Inkscape::XML::AttributeRecord; @@ -32,23 +42,104 @@ namespace Inkscape { namespace UI { namespace Dialog { +class StyleDialog::NodeObserver : public Inkscape::XML::NodeObserver { +public: + NodeObserver(StyleDialog* styleDialog) : + _styleDialog(styleDialog) + { +#ifdef DEBUG_STYLEDIALOG + std::cout << "StyleDialog::NodeObserver: Constructor" << std::endl; +#endif + }; + + virtual void notifyContentChanged(Inkscape::XML::Node &node, + Inkscape::Util::ptr_shared<char> old_content, + Inkscape::Util::ptr_shared<char> new_content); + + StyleDialog * _styleDialog; +}; + + +void +StyleDialog::NodeObserver::notifyContentChanged( + Inkscape::XML::Node &/*node*/, + Inkscape::Util::ptr_shared<char> /*old_content*/, + Inkscape::Util::ptr_shared<char> /*new_content*/ ) { + +#ifdef DEBUG_STYLEDIALOG + std::cout << "StyleDialog::NodeObserver::notifyContentChanged" << std::endl; +#endif + + _styleDialog->_readStyleElement(); + _styleDialog->_selectRow(NULL); +} + + +StyleDialog::TreeStore::TreeStore() +{ +} + + /** - * @brief StyleDialog::_styleButton - * @param btn - * @param iconName - * @param tooltip - * This function sets the style of '+' and '-' buttons at the bottom of dialog. + * Allow dragging only selectors. */ -void StyleDialog::_styleButton(Gtk::Button& btn, char const* iconName, - char const* tooltip) +bool +StyleDialog::TreeStore::row_draggable_vfunc(const Gtk::TreeModel::Path& path) const { - GtkWidget *child = sp_icon_new(Inkscape::ICON_SIZE_SMALL_TOOLBAR, iconName); - gtk_widget_show(child); - btn.add(*manage(Glib::wrap(child))); - btn.set_relief(Gtk::RELIEF_NONE); - btn.set_tooltip_text (tooltip); +#ifdef DEBUG_STYLEDIALOG + std::cout << "StyleDialog::TreeStore::row_draggable_vfunc" << std::endl; +#endif + auto unconstThis = const_cast<StyleDialog::TreeStore*>(this); + const_iterator iter = unconstThis->get_iter(path); + if (iter) { + Gtk::TreeModel::Row row = *iter; + bool is_draggable = row[_styledialog->_mColumns._colIsSelector]; + return is_draggable; + } + return Gtk::TreeStore::row_draggable_vfunc(path); } + +/** + * Allow dropping only inbetween other selectors. + */ +bool +StyleDialog::TreeStore::row_drop_possible_vfunc(const Gtk::TreeModel::Path& dest, + const Gtk::SelectionData& selection_data) const +{ +#ifdef DEBUG_STYLEDIALOG + std::cout << "StyleDialog::TreeStore::row_drop_possible_vfunc" << std::endl; +#endif + + Gtk::TreeModel::Path dest_parent = dest; + dest_parent.up(); + return dest_parent.empty(); +} + + +// This is only here to handle updating style element after a drag and drop. +void +StyleDialog::TreeStore::on_row_deleted(const TreeModel::Path& path) +{ +#ifdef DEBUG_STYLEDIALOG + std::cout << "on_row_deleted" << std::endl; +#endif + + if (_styledialog->_updating) return; // Don't write if we deleted row (other than from DND) + + _styledialog->_writeStyleElement(); +} + + +Glib::RefPtr<StyleDialog::TreeStore> StyleDialog::TreeStore::create(StyleDialog *styledialog) +{ + StyleDialog::TreeStore * store = new StyleDialog::TreeStore(); + store->_styledialog = styledialog; + store->set_column_types( store->_styledialog->_mColumns ); + return Glib::RefPtr<StyleDialog::TreeStore>( store ); +} + + /** * Constructor * A treeview and a set of two buttons are added to the dialog. _addSelector @@ -57,42 +148,45 @@ void StyleDialog::_styleButton(Gtk::Button& btn, char const* iconName, */ StyleDialog::StyleDialog() : UI::Widget::Panel("", "/dialogs/style", SP_VERB_DIALOG_STYLE), - _desktop(0) + _desktop(0), + _updating(false), + _textNode(NULL) { - set_size_request(200, 200); - - _paned.pack1(_mainBox, Gtk::SHRINK); - _mainBox.pack_start(_scrolledWindow, Gtk::PACK_EXPAND_WIDGET); - _treeView.set_headers_visible(false); - _scrolledWindow.add(_treeView); - _scrolledWindow.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC); - - _store = Gtk::TreeStore::create(_mColumns); - _treeView.set_model(_store); +#ifdef DEBUG_STYLEDIALOG + std::cout << "StyleDialog::StyleDialog" << std::endl; +#endif + // Tree Inkscape::UI::Widget::AddToIcon * addRenderer = manage( new Inkscape::UI::Widget::AddToIcon() ); - addRenderer->property_active() = true; - int addCol = _treeView.append_column("type", *addRenderer) - 1; + _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 ); + int addCol = _treeView.append_column("", *addRenderer) - 1; Gtk::TreeViewColumn *col = _treeView.get_column(addCol); if ( col ) { - col->add_attribute( addRenderer->property_active(), _mColumns._colAddRemove ); + col->add_attribute( addRenderer->property_active(), _mColumns._colIsSelector ); } - - _treeView.append_column("Selector Name", _mColumns._selectorLabel); + _treeView.append_column("CSS Selector", _mColumns._colSelector); _treeView.set_expander_column(*(_treeView.get_column(1))); + // Pack widgets + _paned.pack1(_mainBox, Gtk::SHRINK); + _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, - &StyleDialog::_addSelector)); + create->signal_clicked().connect(sigc::mem_fun(*this, &StyleDialog::_addSelector)); del = manage( new Gtk::Button() ); _styleButton(*del, "list-remove", "Remove a CSS Selector"); - del->signal_clicked().connect(sigc::mem_fun(*this, - &StyleDialog::_delSelector)); + del->signal_clicked().connect(sigc::mem_fun(*this, &StyleDialog::_delSelector)); del->set_sensitive(false); _mainBox.pack_end(_buttonBox, Gtk::PACK_SHRINK); @@ -102,8 +196,43 @@ StyleDialog::StyleDialog() : _getContents()->pack_start(_paned, Gtk::PACK_EXPAND_WIDGET); - _targetDesktop = getDesktop(); - setDesktop(_targetDesktop); + // Dialog size request + Gtk::Requisition sreq1, sreq2; + get_preferred_size(sreq1, sreq2); + int minWidth = 200; + int minHeight = 300; + minWidth = (sreq2.width > minWidth ? sreq2.width : minWidth ); + minHeight = (sreq2.height > minHeight ? sreq2.height : minHeight); + set_size_request(minWidth, minHeight); + + // Signal handlers + _treeView.signal_button_release_event().connect( // Needs to be release, not press. + sigc::mem_fun(*this, &StyleDialog::_handleButtonEvent), + false); + + _treeView.signal_button_release_event().connect_notify( + sigc::mem_fun(*this, &StyleDialog::_buttonEventsSelectObjs), + false); + + _treeView.get_selection()->signal_changed().connect( + sigc::mem_fun(*this, &StyleDialog::_selChanged)); + + _objObserver.signal_changed().connect(sigc::mem_fun(*this, &StyleDialog::_objChanged)); + + + // Add CSS dialog + _cssPane = new CssDialog; + _paned.pack2(*_cssPane, Gtk::SHRINK); + _cssPane->show_all(); + + _cssPane->_textRenderer->signal_edited().connect( + sigc::mem_fun(*this, &StyleDialog::_handleEdited)); + _cssPane->_treeView.signal_button_release_event().connect( + sigc::mem_fun(*this, &StyleDialog::_delProperty), + false); + + // Document & Desktop TO DO: Fix this brokeness + setDesktop(getDesktop()); // Adds signal handler /** * @brief document @@ -111,444 +240,643 @@ StyleDialog::StyleDialog() : * and is then used to populate the treeview with the already existing * selectors in the style element. */ - _styleExists = false; - _document = _targetDesktop->doc(); - _selectorValue = _populateTree(_getSelectorVec()); - - _treeView.signal_button_press_event().connect(sigc::mem_fun(*this, - &StyleDialog:: - _handleButtonEvent), - false); + _document = _desktop->doc(); - _treeView.signal_button_press_event().connect_notify(sigc::mem_fun - (*this, &StyleDialog:: - _buttonEventsSelectObjs), - false); + _readStyleElement(); + _selectRow(NULL); - _cssPane = new CssDialog; + if (!_store->children().empty()) { + del->set_sensitive(true); + } - _treeView.get_selection()->signal_changed().connect(sigc::mem_fun(*this, - &StyleDialog:: - _selChanged)); } + /** * @brief StyleDialog::~StyleDialog * Class destructor */ StyleDialog::~StyleDialog() { +#ifdef DEBUG_STYLEDIALOOG + std::cout << "StyleDialog::~StyleDialog" << std::endl; +#endif setDesktop(NULL); } + /** * @brief StyleDialog::setDesktop * @param desktop - * This function sets the 'desktop' for the Style Dialog. + * Set the 'desktop' for the Style Dialog. */ void StyleDialog::setDesktop( SPDesktop* desktop ) { +#ifdef DEBUG_STYLEDIALOOG + std::cout << "StyleDialog::setDesktop" << std::endl; +#endif Panel::setDesktop(desktop); _desktop = Panel::getDesktop(); - _desktop->getSelection()->connectChanged(sigc::mem_fun(*this, &StyleDialog:: - _selectRow)); + _desktop->getSelection()->connectChanged(sigc::mem_fun(*this, &StyleDialog::_selectRow)); } + /** - * @brief StyleDialog::_addSelector - * This function is the slot to the signal emitted when '+' at the bottom of - * the dialog is clicked. + * @brief StyleDialog::_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. */ -void StyleDialog::_addSelector() +Inkscape::XML::Node* StyleDialog::_getStyleTextNode() { - Gtk::TreeModel::Row row = *(_store->append()); - /** - * On clicking '+' button, an entrybox with default text opens up. If an - * object is already selected, a selector with value in the entry - * is added to a new style element. - */ - Gtk::Dialog *textDialogPtr = new Gtk::Dialog(); - Gtk::Entry *textEditPtr = manage ( new Gtk::Entry() ); - textDialogPtr->add_button("Add", Gtk::RESPONSE_OK); - textDialogPtr->get_vbox()->pack_start(*textEditPtr, Gtk::PACK_SHRINK); + Inkscape::XML::Node *styleNode = NULL; + Inkscape::XML::Node *textNode = NULL; - /** - * By default, the entrybox contains 'Class1' as text. However, if object(s) - * is(are) selected and user clicks '+' at the bottom of dialog, the - * entrybox will have the id(s) of the selected objects as text. - */ - if (_desktop->getSelection()->isEmpty()) { - textEditPtr->set_text("Class1"); - } - else { - Inkscape::Selection* selection = _desktop->getSelection(); - std::vector<SPObject*> selected = std::vector<SPObject *>(selection - ->objects().begin(), - selection-> - objects().end()); - textEditPtr->set_text(_setClassAttribute(selected)); + Inkscape::XML::Node *root = _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 == NULL) { + // Style element found but does not contain text node! + std::cerr << "StyleDialog::_getStyleTextNode(): No text node!" << std::endl; + textNode = _document->getReprDoc()->createTextNode(""); + styleNode->appendChild(textNode); + Inkscape::GC::release(textNode); + } + } } - textDialogPtr->set_size_request(200, 100); - textDialogPtr->show_all(); - int result = textDialogPtr->run(); + if (styleNode == NULL) { + // Style element not found, create one + styleNode = _document->getReprDoc()->createElement("svg:style"); + textNode = _document->getReprDoc()->createTextNode(""); - /** - * @brief selectorName - * This string stores selector name. The text from entrybox is saved as name - * for selector. If the entrybox is empty, the text (thus selectorName) is - * set to ".Class1" - */ - if (!textEditPtr->get_text().empty()) { - _selectorName = textEditPtr->get_text(); + styleNode->appendChild(textNode); + Inkscape::GC::release(textNode); + + root->addChild(styleNode, NULL); + Inkscape::GC::release(styleNode); } - else { - _selectorName = ".Class1"; + + if (_textNode != textNode) { + _textNode = textNode; + NodeObserver *no = new NodeObserver(this); + textNode->addObserver(*no); } - del->set_sensitive(true); + return textNode; +} - /** - * The selector name objects is set to the text that the user sets in the - * entrybox. If the attribute does not exist, it is - * created. In case the attribute already has a value, the new value entered - * is appended to the values. If a style attribute does not exist, it is - * created with an empty value. Also if a class selector is added, then - * class attribute for the selected object is set too. - */ - std::vector<SPObject *> objVec; - - bool objExists = false; - if (!_desktop->getSelection()->isEmpty()) { - for (auto& obj: _desktop->getSelection()->objects()) { - objExists = true; - if (!obj->getRepr()->attribute("style")) { - obj->getRepr()->setAttribute("style", NULL); - } - if (obj->getAttribute("style") == NULL) { - _selectorValue = _selectorName + "{" + "}" + "\n"; - } - else { - _selectorValue = _selectorName + "{" - + obj->getAttribute("style") + "}" + "\n"; - } +/** + * @brief StyleDialog::_readStyleElement + * Fill the Gtk::TreeStore from the svg:style element. + */ +void StyleDialog::_readStyleElement() +{ +#ifdef DEBUG_STYLEDIALOG + std::cout << "StyleDialog::_readStyleElement: updating " << (_updating?"true":"false")<< std::endl; +#endif - if (_selectorName[0] == '.') { - if (!obj->getRepr()->attribute("class")) { - obj->getRepr()->setAttribute("class", textEditPtr->get_text() - .erase(0,1)); - } - else { - obj->getRepr()->setAttribute("class", std::string(obj-> - getRepr()-> - attribute("class")) - + " " + textEditPtr->get_text() - .erase(0,1)); - } - } - } + if (_updating) return; // Don't read if we wrote style element. + _updating = true; + + _store->clear(); + + Inkscape::XML::Node * textNode = _getStyleTextNode(); + if (textNode == NULL) { + std::cerr << "StyleDialog::_readStyleElement: No text node!" << std::endl; } - else { - _selectorValue = _selectorName + "{" + "}" + "\n"; - objExists = false; + + // 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()); + + // 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); + + // If text node is empty, return (avoids problem with negative below). + if (tokens.size() == 0) { + return; } - switch (result) { - case Gtk::RESPONSE_OK: - textDialogPtr->hide(); - row[_mColumns._selectorLabel] = _selectorName; - row[_mColumns._colAddRemove] = true; - if (objExists) { - Inkscape::Selection* selection = _desktop->getSelection(); - row[_mColumns._colObj] = std::vector<SPObject *>(selection->objects() - .begin(), selection - ->objects().end()); - objVec = row[_mColumns._colObj]; + for (unsigned i = 0; i < tokens.size()-1; i += 2) { + + Glib::ustring selector = tokens[i]; + REMOVE_SPACES(selector); // Remove leading/trailing spaces + + // Get list of objects selector matches + std::vector<SPObject *> objVec = _getObjVec( selector ); + + 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; } - break; - default: - break; - } + REMOVE_SPACES(properties); - /** - * A new style element is added to the document with value obtained - * from selectorValue above. If style element already exists, then - * the new selector's content is appended to its previous content. - */ - inkSelector._selector = _selectorName; - inkSelector._matchingObjs = objVec; - inkSelector._xmlContent = _selectorValue; - _selectorVec.push_back(inkSelector); - - if (_styleElementNode()) { - _styleChild = _styleElementNode(); - _updateStyleContent(); - } - else if (_styleExists && !_newDrawing) { - _updateStyleContent(); - } - else if (!_styleExists) { - Inkscape::XML::Node *root = _document->getReprDoc()->root(); - Inkscape::XML::Node *newChild = _document->getReprDoc() - ->createElement("svg:style"); - Inkscape::XML::Node *smallChildren = _document->getReprDoc() - ->createTextNode(_selectorValue.c_str()); - - newChild->appendChild(smallChildren); - Inkscape::GC::release(smallChildren); - - root->addChild(newChild, NULL); - Inkscape::GC::release(newChild); - _styleChild = newChild; + Gtk::TreeModel::Row row = *(_store->append()); + row[_mColumns._colSelector] = selector; + row[_mColumns._colIsSelector] = true; + row[_mColumns._colObj] = objVec; + row[_mColumns._colProperties] = properties; + + // 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._colIsSelector] = false; + childrow[_mColumns._colObj] = std::vector<SPObject *>(1, obj); + childrow[_mColumns._colProperties] = ""; // Unused + } } - _selAdd(row); + _updating = false; } + /** - * @brief StyleDialog::_updateStyleContent - * This function updates the content in style element as new selectors (or - * objects) are added/removed. + * @brief StyleDialog::_writeStyleElement + * Update the content of the style element as selectors (or objects) are added/removed. */ -void StyleDialog::_updateStyleContent() +void StyleDialog::_writeStyleElement() { - std::string styleContent = ""; - for (unsigned i = 0; i < _selectorVec.size(); ++i) { - styleContent = styleContent + _selectorVec[i]._xmlContent; + _updating = true; + + Glib::ustring styleContent; + for (auto& row: _store->children()) { + styleContent = styleContent + row[_mColumns._colSelector] + + " { " + row[_mColumns._colProperties] + " }\n"; } - _styleChild->firstChild()->setContent(styleContent.c_str()); + // We could test if styleContent is empty and then delete the style node here but there is no + // harm in keeping it around ... + + Inkscape::XML::Node *textNode = _getStyleTextNode(); + textNode->setContent(styleContent.c_str()); + + DocumentUndo::done(_document, SP_VERB_DIALOG_STYLE, _("Edited style element.")); + + _updating = false; +#ifdef DEBUG_STYLEDIALOG + std::cout << "StyleDialog::_writeStyleElement(): |" << styleContent << "|" << std::endl; +#endif } + /** - * @brief StyleDialog::_delSelector - * This function deletes selector when '-' at the bottom is clicked. The index - * of selected row is obtained and the corresponding selector and its values are - * deleted from the selector vector. If a row has no parent, it is directly - * erased from the vector along with its child rows. The style element is updated - * accordingly. + * @brief StyleDialog::_addToSelector + * @param row + * Add selected objects on the desktop to the selector corresponding to 'row'. */ -void StyleDialog::_delSelector() +void StyleDialog::_addToSelector(Gtk::TreeModel::Row row) { - Glib::RefPtr<Gtk::TreeSelection> refTreeSelection = _treeView.get_selection(); - Gtk::TreeModel::iterator iter = refTreeSelection->get_selected(); - if (iter) { - Gtk::TreeModel::Row row = *iter; - std::string sel, key, value; - std::vector<InkSelector>::iterator it; - for (it = _selectorVec.begin(); it != _selectorVec.end();) { - sel = (*it)._xmlContent; - REMOVE_SPACES(sel); - if (!sel.empty()) { - key = strtok((char*)sel.c_str(), "{"); - REMOVE_SPACES(key); - char *temp = strtok(NULL, "}"); - if (strtok(temp, "}") != NULL) { - value = strtok(temp, "}"); +#ifdef DEBUG_STYLEDIALOG + std::cout << "StyleDialog::_addToSelector: Entrance" << std::endl; +#endif + if (*row) { + + Glib::ustring selector = row[_mColumns._colSelector]; + + if (selector[0] == '#') { + // 'id' selector... add selected object's id's to list. + Inkscape::Selection* selection = _desktop->getSelection(); + for (auto& obj: selection->objects()) { + + Glib::ustring id = (obj->getId()?obj->getId():""); + + std::vector<SPObject *> objVec = row[_mColumns._colObj]; + bool found = false; + for (auto& obj: objVec) { + if (id == obj->getId()) { + found = true; + break; + } } - } - Glib::ustring selectedRowLabel = row[_mColumns._selectorLabel]; - std::string matchSelector = selectedRowLabel; - REMOVE_SPACES(matchSelector); - if (key == matchSelector) { - it = _selectorVec.erase(it); - _store->erase(row); - } - else { - ++it; + if (!found) { + // Update row + objVec.push_back(obj); // Adding to copy so need to update tree + row[_mColumns._colObj] = objVec; + row[_mColumns._colSelector] = _getIdList( objVec ); + + // Add child row + Gtk::TreeModel::Row childrow = *(_store->append(row->children())); + childrow[_mColumns._colSelector] = "#" + Glib::ustring(obj->getId()); + childrow[_mColumns._colIsSelector] = false; + childrow[_mColumns._colObj] = std::vector<SPObject *>(1, obj); + childrow[_mColumns._colProperties] = ""; // Unused + } } + } - /** - * The _stylechild is obtained which contains the style element and - * the content in style element is updated. If _selectorVec is - * empty, the style element is removed from the XML repr else - * the content is updated simply using _updateStyleContent(). - */ - _styleChild = _styleElementNode(); - if (_store->children().empty()) { - _document->getReprRoot()->removeChild(_styleChild); - _styleExists = false; - } - else { - _updateStyleContent(); + else if (selector[0] == '.') { + // 'class' selector... add value to class attribute of selected objects. + + // Get first class (split on white space or comma) + std::vector<Glib::ustring> tokens = Glib::Regex::split_simple("[,\\s]+", selector); + Glib::ustring className = tokens[0]; + className.erase(0,1); + + // Get list of objects to modify + Inkscape::Selection* selection = _desktop->getSelection(); + std::vector<SPObject *> objVec( selection->objects().begin(), + selection->objects().end() ); + + _insertClass( objVec, className ); + + row[_mColumns._colObj] = _getObjVec( selector ); + + for (auto& obj: objVec) { + // Add child row + Gtk::TreeModel::Row childrow = *(_store->append(row->children())); + childrow[_mColumns._colSelector] = "#" + Glib::ustring(obj->getId()); + childrow[_mColumns._colIsSelector] = false; + childrow[_mColumns._colObj] = std::vector<SPObject *>(1, obj); + childrow[_mColumns._colProperties] = ""; // Unused } } + + else { + // Do nothing for element selectors. + // std::cout << " Element selector... doing nothing!" << std::endl; + } } + + _writeStyleElement(); } + /** - * @brief StyleDialog::_styleElementNode - * @return - * This function returns the node containing style element. The document's - * children are iterated and the repr of the style element that occurs is - * obtained. + * @brief StyleDialog::_removeFromSelector + * @param row + * Remove the object corresponding to 'row' from the parent selector. */ -Inkscape::XML::Node* StyleDialog::_styleElementNode() +void StyleDialog::_removeFromSelector(Gtk::TreeModel::Row row) { - for (unsigned i = 0; i < _document->getReprRoot()->childCount(); ++i) { - if (std::string(_document->getReprRoot()->nthChild(i)->name()) - == "svg:style") { - _styleExists = true; - _newDrawing = true; - return _document->getReprRoot()->nthChild(i); +#ifdef DEBUG_STYLEDIALOG + std::cout << "StyleDialog::_removeFromSelector: Entrance" << std::endl; +#endif + if (*row) { + + Glib::ustring objectLabel = row[_mColumns._colSelector]; + Gtk::TreeModel::iterator iter = row->parent(); + if (iter) { + Gtk::TreeModel::Row parent = *iter; + Glib::ustring selector = parent[_mColumns._colSelector]; + + if (selector[0] == '#') { + // 'id' selector... remove selected object's id's to list. + + // Erase from selector label. + auto i = selector.find(objectLabel); + if (i != Glib::ustring::npos) { + selector.erase(i, objectLabel.length()); + } + // Erase any comma/space + if (i != Glib::ustring::npos && selector[i] == ',') { + selector.erase(i, 1); + } + if (i != Glib::ustring::npos && selector[i] == ' ') { + selector.erase(i, 1); + } + + // Update store + if (selector.empty()) { + _store->erase(parent); + } else { + // Save new selector and update object vector. + parent[_mColumns._colSelector] = selector; + parent[_mColumns._colObj] = _getObjVec( selector ); + _store->erase(row); + } + } + + else if (selector[0] == '.') { + // 'class' selector... remove value to class attribute of selected objects. + + std::vector<SPObject *> objVec = row[_mColumns._colObj]; // Just one + + // Get first class (split on white space or comma) + std::vector<Glib::ustring> tokens = Glib::Regex::split_simple("[,\\s]+", selector); + Glib::ustring className = tokens[0]; + className.erase(0,1); // Erase '.' + + // Erase class name from 'class' attribute. + Glib::ustring classAttr = objVec[0]->getRepr()->attribute("class"); + auto i = classAttr.find( className ); + if (i != Glib::ustring::npos) { + classAttr.erase(i, className.length()); + } + if (i != Glib::ustring::npos && classAttr[i] == ' ') { + classAttr.erase(i, 1); + } + objVec[0]->getRepr()->setAttribute("class", classAttr); + + parent[_mColumns._colObj] = _getObjVec( selector ); + _store->erase(row); + } + + else { + // Do nothing for element selectors. + // std::cout << " Element selector... doing nothing!" << std::endl; + } } } - return NULL; + + _writeStyleElement(); + } + /** - * @brief StyleDialog::_setClassAttribute + * @brief StyleDialog::_getIdList * @param sel - * @return This function returns the ids of objects selected which are passed - * to entrybox. + * @return This function returns a comma seperated list of ids for objects in input vector. + * It is used in creating an 'id' selector. It relies on objects having 'id's. */ -std::string StyleDialog::_setClassAttribute(std::vector<SPObject*> sel) +Glib::ustring StyleDialog::_getIdList(std::vector<SPObject*> sel) { - std::string str = ""; - for ( unsigned i = 0; i < sel.size(); ++i ) { - SPObject *obj = sel.at(i); - str = str + "#" + std::string(obj->getId()) + " "; + Glib::ustring str; + for (auto& obj: sel) { + str += "#" + Glib::ustring(obj->getId()) + ", "; + } + if (!str.empty()) { + str.erase(str.size()-1); // Remove space at end. c++11 has pop_back() but not ustring. + str.erase(str.size()-1); // Remove comma at end. } return str; } /** - * @brief StyleDialog::_getSelectorVec - * @return selVec - * This function returns a vector whose key is the style selector name and value - * is the style properties. All style selectors are extracted from svg:style - * element. _newDrawing is flag is set to false check if an existing drawing is - * opened. + * @brief StyleDialog::_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<StyleDialog::InkSelector> StyleDialog::_getSelectorVec() -{ - for (unsigned i = 0; i < _document->getReprRoot()->childCount(); ++i) { - if (std::string(_document->getReprRoot()->nthChild(i)->name()) == "svg:style") { - _styleExists = true; - _newDrawing = false; - _styleChild = _document->getReprRoot()->nthChild(i); - - // Get content from first style element. - std::string content = _styleChild->firstChild()->content(); - - // Remove end-of-lines (check it works on Windoze). - content.erase(std::remove(content.begin(), content.end(), '\n'), content.end()); - - // 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<std::string> tokens = Glib::Regex::split_simple("[}{]", content); - - for (unsigned i = 0; i < tokens.size()-1; i += 2) { - std::string selectors = tokens[i]; - REMOVE_SPACES(selectors); // Remove leading/trailing spaces - - /** Make a list of all objects that selector matches. This is - * currently limited to simple id, class, and element selectors. - * Expanding this would take integrating a true CSS parser. - */ - std::vector<SPObject *>objVec; - - // Split selector string into individual selectors (which are comma separated). - std::vector<std::string> tokens2 = Glib::Regex::split_simple - ("\\s*,\\s*", selectors ); - - for(unsigned i = 0; i < tokens2.size(); ++i) { - std::string token2 = tokens2[i]; - - // Find objects that match class selector - if (token2[0] == '.') { - token2.erase(0,1); - std::vector<SPObject *> objects = _document - ->getObjectsByClass(token2); - objVec.insert(objVec.end(), objects.begin(), objects.end()); - } +std::vector<SPObject *> StyleDialog::_getObjVec(Glib::ustring selector) { - // Find objects that match id selector - else if (token2[0] == '#') { - token2.erase(0,1); - SPObject * object = _document->getObjectById(token2); - if (object) { - objVec.push_back(object); - } - } + std::vector<SPObject *> objVec = _document->getObjectsBySelector( selector ); - // Find objects that match element selector - else { - std::vector<SPObject *> objects = _document-> - getObjectsByElement(token2); - objVec.insert(objVec.end(), objects.begin(), objects.end()); - } - } +#ifdef DEBUG_STYLEDIALOG + std::cout << "StyleDialog::_getObjVec: |" << selector << "|" << std::endl; + for (auto& obj: objVec) { + std::cout << " " << (obj->getId()?obj->getId():"null") << std::endl; + } +#endif - std::string values; - // Check to make sure we do have a value to match selector. - if ((i+1) < tokens.size()) { - values = tokens[i+1]; - } else { - std::cerr << "StyleDialog::_getSelectorVec: Missing values " - "for last selector!" << std::endl; + return objVec; +} + + +/** + * @brief StyleDialog::_insertClass + * @param objs: list of objects to insert class + * @param class: class to insert + * Insert a class name into objects' 'class' attribute. + */ +void StyleDialog::_insertClass(const std::vector<SPObject *>& objVec, const Glib::ustring& className) { + + for (auto& obj: objVec) { + + if (!obj->getRepr()->attribute("class")) { + // 'class' attribute does not exist, create it. + obj->getRepr()->setAttribute("class", className); + } else { + // 'class' attribute exists, append. + Glib::ustring classAttr = obj->getRepr()->attribute("class"); + + // Split on white space. + std::vector<Glib::ustring> tokens = Glib::Regex::split_simple("\\s+", classAttr); + bool add = true; + for (auto& token: tokens) { + if (token == className) { + add = false; // Might be useful to still add... + break; } + } + if (add) { + obj->getRepr()->setAttribute("class", classAttr + " " + className ); + } + } + } + } + + +/** + * @brief StyleDialog::_selectObjects + * @param eventX + * @param eventY + * This function selects objects in the drawing corresponding to the selector + * selected in the treeview. + */ +void StyleDialog::_selectObjects(int eventX, int eventY) +{ +#ifdef DEBUG_STYLEDIALOG + std::cout << "StyleDialog::_selectObjects: " << eventX << ", " << eventY << std::endl; +#endif - _selectorValue = selectors + "{" + values + "}\n"; - inkSelector._selector = selectors; - inkSelector._matchingObjs = objVec; - inkSelector._xmlContent = _selectorValue; - _selectorVec.push_back(inkSelector); + _desktop->selection->clear(); + Gtk::TreeViewColumn *col = _treeView.get_column(1); + Gtk::TreeModel::Path path; + int x2 = 0; + int y2 = 0; + // To do: We should be able to do this via passing in row. + if (_treeView.get_path_at_pos(eventX, eventY, path, col, x2, y2)) { + if (col == _treeView.get_column(1)) { + Gtk::TreeModel::iterator iter = _store->get_iter(path); + if (iter) { + Gtk::TreeModel::Row row = *iter; + Gtk::TreeModel::Children children = row.children(); + std::vector<SPObject *> objVec = row[_mColumns._colObj]; + for (unsigned i = 0; i < objVec.size(); ++i) { + SPObject *obj = objVec[i]; + _desktop->selection->add(obj); + } } } } - return _selectorVec; } + /** - * @brief StyleDialog::_populateTree - * @param _selVec - * This function populates the treeview with selectors available in the - * stylesheet. + * @brief StyleDialog::_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. */ -std::string StyleDialog::_populateTree(std::vector<InkSelector> _selVec) +void StyleDialog::_addSelector() { - _selectorVec = _selVec; - std::string selectorValue; +#ifdef DEBUG_STYLEDIALOG + std::cout << "StyleDialog::_addSelector: Entrance" << std::endl; +#endif - for(unsigned it = 0; it < _selectorVec.size(); ++it) { - Gtk::TreeModel::Row row = *(_store->append()); - row[_mColumns._selectorLabel] = _selectorVec[it]._selector; - row[_mColumns._colAddRemove] = true; - row[_mColumns._colObj] = _selectorVec[it]._matchingObjs; - std::string selValue = _selectorVec[it]._xmlContent; - selectorValue.append(selValue.c_str()); + // Store list of selected elements on desktop (not to be confused with selector). + Inkscape::Selection* selection = _desktop->getSelection(); + std::vector<SPObject *> objVec( selection->objects().begin(), + selection->objects().end() ); + + // ==== Create popup dialog ==== + Gtk::Dialog *textDialogPtr = new Gtk::Dialog(); + textDialogPtr->add_button(_("Cancel"), Gtk::RESPONSE_CANCEL); + textDialogPtr->add_button(_("Add"), Gtk::RESPONSE_OK); + + Gtk::Entry *textEditPtr = manage ( new Gtk::Entry() ); + textDialogPtr->get_vbox()->pack_start(*textEditPtr, Gtk::PACK_SHRINK); + + Gtk::Label *textLabelPtr = manage ( new Gtk::Label( + _("Invalid entry: Not an id (#), class (.), or element CSS selector.") + ) ); + textDialogPtr->get_vbox()->pack_start(*textLabelPtr, Gtk::PACK_SHRINK); + + /** + * By default, the entrybox contains 'Class1' as text. However, if object(s) + * is(are) selected and user clicks '+' at the bottom of dialog, the + * entrybox will have the id(s) of the selected objects as text. + */ + if (_desktop->getSelection()->isEmpty()) { + textEditPtr->set_text(".Class1"); + } else { + textEditPtr->set_text(_getIdList(objVec)); } - if (_selectorVec.size() > 0) { + Gtk::Requisition sreq1, sreq2; + textDialogPtr->get_preferred_size(sreq1, sreq2); + int minWidth = 200; + int minHeight = 100; + minWidth = (sreq2.width > minWidth ? sreq2.width : minWidth ); + minHeight = (sreq2.height > minHeight ? sreq2.height : minHeight); + textDialogPtr->set_size_request(minWidth, minHeight); + textEditPtr->show(); + textLabelPtr->hide(); + textDialogPtr->show(); + + + // ==== Get response ==== + int result = -1; + bool invalid = true; + Glib::ustring selectorValue; + + while (invalid) { + result = textDialogPtr->run(); + if (result != Gtk::RESPONSE_OK) { // Cancel, close dialog, etc. + textDialogPtr->hide(); + return; + } + /** + * @brief selectorName + * This string stores selector name. The text from entrybox is saved as name + * for selector. If the entrybox is empty, the text (thus selectorName) is + * set to ".Class1" + */ + selectorValue = textEditPtr->get_text(); + Glib::ustring firstWord = selectorValue.substr(0, selectorValue.find(" ")); + del->set_sensitive(true); + + if (selectorValue[0] == '.' || + selectorValue[0] == '#' || + selectorValue[0] == '*' || + SPAttributeRelSVG::isSVGElement( firstWord ) ) { + invalid = false; + } else { + textLabelPtr->show(); + } } + delete textDialogPtr; + + // ==== Handle response ==== - return selectorValue; + // If class selector, add selector name to class attribute for each object + if (selectorValue[0] == '.') { + + Glib::ustring className = selectorValue; + className.erase(0,1); + _insertClass(objVec, className); + } + + // Generate a new object vector (we could have an element selector, + // the user could have edited the id selector list, etc.). + objVec = _getObjVec( selectorValue ); + + // Add entry to GUI tree + Gtk::TreeModel::Row row = *(_store->append()); + row[_mColumns._colSelector] = selectorValue; + row[_mColumns._colIsSelector] = true; + row[_mColumns._colObj] = objVec; + + // 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._colIsSelector] = false; + childrow[_mColumns._colObj] = std::vector<SPObject *>(1, obj); + } + + // Add entry to style element + _writeStyleElement(); +} + +/** + * @brief StyleDialog::_delSelector + * This function deletes selector when '-' at the bottom is clicked. + * Note: If deleting a class selector, class attributes are NOT changed. + */ +void StyleDialog::_delSelector() +{ +#ifdef DEBUG_STYLEDIALOG + std::cout << "StyleDialog::_delSelector" << std::endl; +#endif + Glib::RefPtr<Gtk::TreeSelection> refTreeSelection = _treeView.get_selection(); + Gtk::TreeModel::iterator iter = refTreeSelection->get_selected(); + if (iter) { + Gtk::TreeModel::Row row = *iter; + _updating = true; + _store->erase(iter); + _updating = false; + _writeStyleElement(); + } } /** * @brief StyleDialog::_handleButtonEvent * @param event * @return - * This function handles the event when '+' button in front of a selector name - * is clicked. The selected objects (if any) is added to the selector as a child - * in the treeview. + * Handles the event when '+' button in front of a selector name is clicked or when a '-' button in + * front of a child object is clicked. In the first case, the selected objects on the desktop (if + * any) are added as children of the selector in the treeview. In the latter case, the object + * corresponding to the row is removed from the selector. */ bool StyleDialog::_handleButtonEvent(GdkEventButton *event) { - if (event->type == GDK_BUTTON_PRESS && event->button == 1) { +#ifdef DEBUG_STYLEDIALOG + std::cout << "StyleDialog::_handleButtonEvent: Entrance" << std::endl; +#endif + if (event->type == GDK_BUTTON_RELEASE && event->button == 1) { Gtk::TreeViewColumn *col = 0; Gtk::TreeModel::Path path; int x = static_cast<int>(event->x); @@ -557,119 +885,16 @@ bool StyleDialog::_handleButtonEvent(GdkEventButton *event) int y2 = 0; if (_treeView.get_path_at_pos(x, y, path, col, x2, y2)) { if (col == _treeView.get_column(0)) { - Glib::RefPtr<Gtk::TreeSelection> refTreeSelection = - _treeView.get_selection(); - Gtk::TreeModel::iterator iter = refTreeSelection-> - get_selected(); + Gtk::TreeModel::iterator iter = _store->get_iter(path); Gtk::TreeModel::Row row = *iter; - /** - * This adds child rows to selected rows. If the parent row is - * a class selector, then the class attribute of object added - * to child row is appended with class in the parent row. The - * else below deletes objects from selectors when 'delete' button - * in front of child row is clicked. The class attribute is updated - * by removing the parent row's class selector name. - */ + // Add or remove objects from a if (!row.parent()) { - _selAdd(row); - } - - else { - std::string sel, key, value; - std::vector<InkSelector>::iterator it; - Gtk::TreeModel::Row parentRow = *(row).parent(); - Glib::ustring parentKey = parentRow[_mColumns._selectorLabel]; - - for (it = _selectorVec.begin(); it != _selectorVec.end(); ++it) { - sel = (*it)._xmlContent; - REMOVE_SPACES(sel); - if (!sel.empty()) { - key = strtok((char*)sel.c_str(), "{"); - REMOVE_SPACES(key); - char *temp = strtok(NULL, "}"); - if (strtok(temp, "}") != NULL) { - value = strtok(temp, "}"); - } - } - - /** - * @brief matchSelector - * For id selectors, whenever any child row is deleted, - * the row label is updated and so is the entry for the - * selector in style element. - */ - std::string matchSelector = parentKey; - REMOVE_SPACES(matchSelector); - if (key == matchSelector) { - if (key[0] == '#') { - std::string s = parentKey; - Glib::ustring toDelRow = row[_mColumns._selectorLabel]; - std::string toDelKey = toDelRow; - std::size_t idFound = s.find(toDelKey); - if (idFound != std::string::npos) { - if (idFound == 0) { - s.erase(idFound, toDelKey.length()+1); - parentKey = s; - parentRow[_mColumns._selectorLabel] = parentKey; - (*it)._xmlContent.erase(idFound, toDelKey.length()); - } - else { - s.erase(idFound-2, toDelKey.length()+2); - parentKey = s; - parentRow[_mColumns._selectorLabel] = parentKey; - (*it)._xmlContent.erase(idFound-2, toDelKey. - length()+2); - } - } - } - } - - if (parentKey[0] == '.') { - std::vector<SPObject *> objVec = row[_mColumns._colObj]; - for (unsigned i = 0; i < objVec.size(); ++i) { - SPObject *obj = objVec[i]; - std::string classAttr = std::string(obj->getRepr() - ->attribute("class")); - std::size_t found = classAttr.find(parentKey.erase(0,1)); - if (found != std::string::npos) { - classAttr.erase(found, parentKey.length()+1); - obj->getRepr()->setAttribute("class", classAttr); - } - } - } - - if (parentKey.empty()) { - (*it)._xmlContent = ""; - } - } - - if (_styleChild) { - _updateStyleContent(); - } - else { - _styleChild = _styleElementNode(); - _updateStyleContent(); - } - - _store->erase(row); - - /** - * On continuous deletion of objects (child rows) from the - * selector (parent row), if the parent row has no child, then - * the row is erased from the _store. Further if there is no - * row left in _store, which implies there is no content in - * XML style element, then the 'svg:style' element is also - * removed. - */ - if (parentKey.empty()) { - _store->erase(parentRow); - } - - if (parentKey.empty() && _store->children().empty()) { - _document->getReprRoot()->removeChild(_styleChild); - _styleExists = false; - } + // Add selected objects to selector. + _addToSelector(row); + } else { + // Remove object from selector + _removeFromSelector(row); } } } @@ -678,274 +903,169 @@ bool StyleDialog::_handleButtonEvent(GdkEventButton *event) } /** - * @brief StyleDialog::_selAdd - * @param row - * This routine is called when an object is added to a selector by clicking on - * '+' in front of the row with selector's label. + * @brief StyleDialog::_updateCSSPanel + * Updates CSS panel according to row in Style panel. */ -void StyleDialog::_selAdd(Gtk::TreeModel::Row row) +void StyleDialog::_updateCSSPanel() { - Glib::ustring selectorName; - Gtk::TreeModel::Row childrow; - Inkscape::Selection* selection = _desktop->getSelection(); - std::vector<SPObject *> sel = std::vector<SPObject *> - (selection->objects().begin(), selection->objects().end()); - for (auto& obj: selection->objects()) { - if (*row) { - if (_selectorVec.size() != 0) { - childrow = *(_store->append(row->children())); - childrow[_mColumns._selectorLabel] = "#" + - std::string(obj->getId()); - childrow[_mColumns._colAddRemove] = false; - childrow[_mColumns._colObj] = sel; - Glib::ustring key = row[_mColumns._selectorLabel]; - if (key[0] == '.') { - if (!obj->getRepr()->attribute("class")) { - obj->setAttribute("class", key.erase(0,1)); - } - else { - if (obj->getRepr()->attribute("class") != key - .erase(0,1)) { - obj->setAttribute("class", std::string - (obj->getRepr()-> - attribute("class")) - + " " + key - .erase(0,1)); - } - } - } - } - selectorName = row[_mColumns._selectorLabel]; - } +#ifdef DEBUG_STYLEDIALOG + std::cout << "StyleDialog::_updateCSSPanel" << std::endl; +#endif + _updating = true; - /** - * If the object's parent row is a class selector, then - * there are no changes in style element except the class - * attribute is updated. For the id selector cases, XML - * content's style element is updated. - */ - REMOVE_SPACES(selectorName); - std::vector<InkSelector>::iterator it; - for (it = _selectorVec.begin(); it != _selectorVec.end(); ++it) { - std::string sel, key, value; - sel = (*it)._xmlContent; - REMOVE_SPACES(sel); - if (!sel.empty()) { - key = strtok((char*)sel.c_str(), "{"); - REMOVE_SPACES(key); - char *temp = strtok(NULL, "}"); - if (strtok(temp, "}") != NULL) { - value = strtok(temp, "}"); - } - } - - Glib::ustring selectedRowLabel = row[_mColumns._selectorLabel]; - std::string matchSelector = selectedRowLabel; - REMOVE_SPACES(matchSelector); - if (key == matchSelector) { - REMOVE_SPACES((*it)._selector); - if (selectorName[0] == '#') { - if ("#" + std::string(obj->getId()) != selectorName) { - inkSelector._selector = (*it)._selector; - inkSelector._selector.append(", #" + std::string(obj->getId())); - inkSelector._xmlContent = inkSelector._selector + "{" + value + "}\n"; - row[_mColumns._selectorLabel] = selectorName + ", " + - childrow[_mColumns._selectorLabel]; - } - } - else if (selectorName[0] == '.') { - inkSelector._xmlContent = (*it)._selector + "{" + value + "}\n"; - } + // This should probably be in a member function of CSSDialog. + _cssPane->_store->clear(); - it = _selectorVec.erase(it); - it = _selectorVec.insert(it, inkSelector); + Glib::RefPtr<Gtk::TreeSelection> refTreeSelection = _treeView.get_selection(); + Gtk::TreeModel::iterator iter = refTreeSelection->get_selected(); + if (iter) { + Gtk::TreeModel::Row row = *iter; + Glib::ustring properties; + if (row[_mColumns._colIsSelector]) { + properties = row[_mColumns._colProperties]; + _objObserver.set( NULL ); + } else { + std::vector<SPObject *> objects = row[_mColumns._colObj]; + _objObserver.set( objects[0] ); + if (objects[0] && objects[0]->getAttribute("style") != NULL) { + properties = objects[0]->getAttribute("style"); + } + } + REMOVE_SPACES(properties); // Remove leading/trailing spaces + + std::vector<Glib::ustring> tokens = + Glib::Regex::split_simple("\\s*;\\s*", properties); + for (auto& token: tokens) { + if (!token.empty()) { + _cssPane->_propRow = *(_cssPane->_store->append()); + _cssPane->_propRow[_cssPane->_cssColumns._colUnsetProp] = false; + _cssPane->_propRow[_cssPane->_cssColumns._propertyLabel] = token; } } } - if (_styleElementNode()) { - _styleChild = _styleElementNode(); - _updateStyleContent(); - } - else if (_styleExists && !_newDrawing) { - _updateStyleContent(); - } + + _updating = false; } + /** - * @brief StyleDialog::_selectObjects + * @brief StyleDialog::_buttonEventsSelectObjs * @param event - * This function detects single or double click on a selector in any row. Single - * click on a selector selects the matching objects. A double click on any - * selector selects the matching objects as well as will open CSS dialog. It - * calls _selectObjects to add objects to selection. + * 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 + * in addition open the CSS dialog. */ void StyleDialog::_buttonEventsSelectObjs(GdkEventButton* event ) { - if (event->type == GDK_BUTTON_PRESS && event->button == 1) { +#ifdef DEBUG_STYLEDIALOG + std::cout << "StyleDialog::_buttonEventsSelectObjs" << std::endl; +#endif + _updating = true; + + if (event->type == GDK_BUTTON_RELEASE && event->button == 1) { int x = static_cast<int>(event->x); int y = static_cast<int>(event->y); _selectObjects(x, y); + //} + //else if (event->type == GDK_2BUTTON_PRESS && event->button == 1) { + //int x = static_cast<int>(event->x); + //int y = static_cast<int>(event->y); + //_selectObjects(x, y); + + _updateCSSPanel(); } - else if (event->type == GDK_2BUTTON_PRESS && event->button == 1) { - int x = static_cast<int>(event->x); - int y = static_cast<int>(event->y); - _selectObjects(x, y); + _updating = false; +} - //Open CSS dialog here. - if (!_cssPane->get_visible()) { - _paned.pack2(*_cssPane, Gtk::SHRINK); - _cssPane->show_all(); - } - Glib::RefPtr<Gtk::TreeSelection> refTreeSelection = _treeView.get_selection(); - Gtk::TreeModel::iterator iter = refTreeSelection->get_selected(); - if (iter) { - Gtk::TreeModel::Row row = *iter; - std::string sel, key, value; - std::vector<InkSelector>::iterator it; - for (it = _selectorVec.begin(); it != _selectorVec.end(); ++it) { - sel = (*it)._xmlContent; - REMOVE_SPACES(sel); - if (!sel.empty()) { - key = strtok((char*)sel.c_str(), "{"); - REMOVE_SPACES(key); - char *temp = strtok(NULL, "}"); - if (strtok(temp, "}") != NULL) { - value = strtok(temp, "}"); - } - } +/** + * @brief StyleDialog::_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 StyleDialog::_selectRow(Selection */*sel*/) +{ +#ifdef DEBUG_STYLEDIALOG + std::cout << "StyleDialog::_selectRow: updating: " << (_updating?"true":"false") << std::endl; +#endif + if (_updating) return; // Avoid updating if we have set row via dialog. - Glib::ustring selectedRowLabel = row[_mColumns._selectorLabel]; - std::string matchSelector = selectedRowLabel; - REMOVE_SPACES(matchSelector); - - if (key == matchSelector) { - _cssPane->_store->clear(); - std::stringstream ss(value); - std::string token; - std::size_t found = value.find(";"); - if (found!=std::string::npos) { - while(std::getline(ss, token, ';')) { - REMOVE_SPACES(token); - if (!token.empty()) { - _cssPane->_propRow = *(_cssPane->_store->append()); - _cssPane->_propRow[_cssPane->_cssColumns._colUnsetProp] = false; - _cssPane->_propRow[_cssPane->_cssColumns._propertyLabel] = token; - _cssPane->_propCol->add_attribute(_cssPane->_textRenderer - ->property_text(), - _cssPane->_cssColumns - ._propertyLabel); - } - } - } + Inkscape::Selection* selection = _desktop->getSelection(); + if (!selection->isEmpty()) { + SPObject *obj = selection->objects().back(); + + Gtk::TreeModel::Children children = _store->children(); + for(Gtk::TreeModel::Children::iterator iter = children.begin(); + iter != children.end(); ++iter) { + + Gtk::TreeModel::Row row = *iter; + std::vector<SPObject *> objVec = row[_mColumns._colObj]; + for (unsigned i = 0; i < objVec.size(); ++i) { + if (obj->getId() == objVec[i]->getId()) { + _treeView.get_selection()->select(row); + _updateCSSPanel(); + return; } } } - else { - _cssPane->_store->clear(); - _cssPane->hide(); - } - - _cssPane->_textRenderer->signal_edited().connect(sigc::mem_fun(*this, - &StyleDialog:: - _handleEdited)); - _cssPane->_treeView.signal_button_press_event().connect(sigc::mem_fun - (*this, &StyleDialog:: - _delProperty), - false); } + + // Selection empty or no row matches. + _treeView.get_selection()->unselect_all(); + _updateCSSPanel(); } + /** * @brief StyleDialog::_selChanged - * When no row in _treeView of Style Dialog is selected, the _cssPane is hidden. */ void StyleDialog::_selChanged() { - if (_treeView.get_selection()->count_selected_rows() == 0) { - _cssPane->hide(); - } +#ifdef DEBUG_STYLEDIALOG + std::cout << "StyleDialog::_selChanged" << std::endl; +#endif +} + + +void StyleDialog::_objChanged() { +#ifdef DEBUG_STYLEDIALOG + std::cout << "StyleDialog::_objChanged" << std::endl; +#endif + if (_updating) return; + _updateCSSPanel(); } + /** * @brief StyleDialog::_handleEdited * @param path * @param new_text - * This function edits CSS properties of the selector chosen. new_text is used - * to update the property in XML repr. The value from selected selector is - * obtained and modified as per value of new_text. If a new property is added, - * value is appended with new_text. Later _updateStyleContent() is called to - * update XML repr and hence changes are reflected in the drawing too. + * Update trees when new text is entered into a CSS dialog row. */ void StyleDialog::_handleEdited(const Glib::ustring& path, const Glib::ustring& new_text) { +#ifdef DEBUG_STYLEDIALOG + std::cout << "StyleDialog::_handleEdited: path: " << path + << " new_text: " << new_text << std::endl; +#endif + Gtk::TreeModel::iterator iterCss = _cssPane->_treeView.get_model()->get_iter(path); if (iterCss) { Gtk::TreeModel::Row row = *iterCss; row[_cssPane->_cssColumns._propertyLabel] = new_text; - _cssPane->_editedProp = new_text; } - // Selected selector row is obtained here to get corresponding key and value. + Glib::ustring properties; + for (auto& crow: _cssPane->_store->children()) { + properties = properties + crow[_cssPane->_cssColumns._propertyLabel] + "; "; + } + + // Update selector data. Glib::RefPtr<Gtk::TreeSelection> refTreeSelection = _treeView.get_selection(); Gtk::TreeModel::iterator iter = refTreeSelection->get_selected(); if (iter) { Gtk::TreeModel::Row row = *iter; - std::string sel, key, value; - std::vector<InkSelector>::iterator it; - for (it = _selectorVec.begin(); it != _selectorVec.end(); ++it) { - sel = (*it)._xmlContent; - REMOVE_SPACES(sel); - if (!sel.empty()) { - key = strtok((char*)sel.c_str(), "{"); - REMOVE_SPACES(key); - char *temp = strtok(NULL, "}"); - if (strtok(temp, "}") != NULL) { - value = strtok(temp, "}"); - } - } - - Glib::ustring selectedRowLabel = row[_mColumns._selectorLabel]; - std::string matchSelector = selectedRowLabel; - REMOVE_SPACES(matchSelector); - - if (key == matchSelector) { - /** If a new property is added, existing value is appended with new - * property, else replacements in value are done in the 'else' block. - */ - if (_cssPane->_newProperty) { - if (!new_text.empty()) { - value.append((new_text + ";").c_str()); - _cssPane->_propCol->add_attribute(_cssPane->_textRenderer - ->property_text(), - _cssPane->_cssColumns - ._propertyLabel); - _cssPane->_newProperty = false; - } - } - else { - std::stringstream ss(value); - std::string token, editedToken; - std::size_t found = value.find(";"); - if (found!=std::string::npos) { - while(std::getline(ss, token, ';')) { - REMOVE_SPACES(token); - if (!token.empty()) { - if (token.substr(0, token.find(":")) == _cssPane - ->_editedProp.substr(0, _cssPane->_editedProp - .find(":"))) { - editedToken = _cssPane->_editedProp; - size_t startPos = value.find(token); - value.replace(startPos, token.length(), editedToken); - } - } - } - } - } - value.erase(std::remove(value.begin(), value.end(), '\n'), value.end()); - (*it)._xmlContent = key + "{" + value + "}\n"; - _updateStyleContent(); - } - } + row[_mColumns._colProperties] = properties; + _writeStyleElement(); } } @@ -953,14 +1073,15 @@ void StyleDialog::_handleEdited(const Glib::ustring& path, const Glib::ustring& * @brief StyleDialog::_delProperty * @param event * @return - * This function deletes property when '-' in front of property in CSS panel is - * clicked. The property row is deleted from CSS panel and XML repr is updated. - * toDelProperty is the property to be deleted which is looked in 'value' and is - * erased from 'value'. + * Delete a property from the CSS dialog and then update trees. */ bool StyleDialog::_delProperty(GdkEventButton *event) { - if (event->type == GDK_BUTTON_PRESS && event->button == 1) { +#ifdef DEBUG_STYLEDIALOG + std::cout << "StyleDialog::_delProperty" << std::endl; +#endif + + if (event->type == GDK_BUTTON_RELEASE && event->button == 1) { Gtk::TreeViewColumn *col = 0; Gtk::TreeModel::Path path; int x = static_cast<int>(event->x); @@ -971,42 +1092,33 @@ bool StyleDialog::_delProperty(GdkEventButton *event) Glib::ustring toDelProperty; if (_cssPane->_treeView.get_path_at_pos(x, y, path, col, x2, y2)) { if (col == _cssPane->_treeView.get_column(0)) { - Gtk::TreeModel::iterator cssIter = _cssPane->_treeView.get_selection() - ->get_selected(); + Gtk::TreeModel::iterator cssIter = + _cssPane->_treeView.get_selection()->get_selected(); if (cssIter) { - cssRow = *cssIter; - toDelProperty = cssRow[_cssPane->_cssColumns._propertyLabel]; - } + _cssPane->_store->erase(cssIter); - Gtk::TreeModel::iterator iter = _treeView.get_selection()->get_selected(); - if (iter) { - Gtk::TreeModel::Row row = *iter; - std::string sel, key, value; - std::vector<InkSelector>::iterator it; - for (it = _selectorVec.begin(); it != _selectorVec.end(); ++it) { - sel = (*it)._xmlContent; - REMOVE_SPACES(sel); - if (!sel.empty()) { - key = strtok((char*)sel.c_str(), "{"); - REMOVE_SPACES(key); - char *temp = strtok(NULL, "}"); - if (strtok(temp, "}") != NULL) { - value = strtok(temp, "}"); - } - } + Glib::ustring properties; + for (auto& crow: _cssPane->_store->children()) { + properties = properties + crow[_cssPane->_cssColumns._propertyLabel] + "; "; + } - Glib::ustring selectedRowLabel = row[_mColumns._selectorLabel]; - std::string matchSelector = selectedRowLabel; - REMOVE_SPACES(matchSelector); - - if (key == matchSelector) { - std::size_t found = value.find(toDelProperty); - if (found!=std::string::npos) { - if (!toDelProperty.empty()) { - value.erase(found, toDelProperty.length()+1); - (*it)._xmlContent = key + "{" + value + "}\n"; - _updateStyleContent(); - _cssPane->_store->erase(cssRow); + // Update selector data. + Glib::RefPtr<Gtk::TreeSelection> refTreeSelection = _treeView.get_selection(); + Gtk::TreeModel::iterator iter = refTreeSelection->get_selected(); + if (iter) { + Gtk::TreeModel::Row row = *iter; + row[_mColumns._colProperties] = properties; + _writeStyleElement(); + + // Update style attribute + if (!row[_mColumns._colIsSelector]) { + std::vector<SPObject *> objects = row[_mColumns._colObj]; + if (objects[0]) { + Glib::ustring properties = row[_mColumns._colProperties]; + if (properties.empty()) { + objects[0]->setAttribute("style", NULL); + } else { + objects[0]->setAttribute("style", properties); } } } @@ -1018,99 +1130,36 @@ bool StyleDialog::_delProperty(GdkEventButton *event) return false; } -/** - * @brief StyleDialog::_selectObjects - * @param eventX - * @param eventY - * This function selects objects in the drawing corresponding to the selector - * selected in the treeview. - */ -void StyleDialog::_selectObjects(int eventX, int eventY) -{ - _desktop->selection->clear(); - Gtk::TreeViewColumn *col = _treeView.get_column(1); - Gtk::TreeModel::Path path; - int x = eventX; - int y = eventY; - int x2 = 0; - int y2 = 0; - if (_treeView.get_path_at_pos(x, y, path, col, x2, y2)) { - if (col == _treeView.get_column(1)) { - Gtk::TreeModel::iterator iter = _store->get_iter(path); - if (iter) { - Gtk::TreeModel::Row row = *iter; - Gtk::TreeModel::Children children = row.children(); - std::vector<SPObject *> objVec = row[_mColumns._colObj]; - for (unsigned i = 0; i < objVec.size(); ++i) { - SPObject *obj = objVec[i]; - _desktop->selection->add(obj); - } - if (children) { - _checkAllChildren(children); - } - } - } - } -} /** - * @brief StyleDialog::_checkAllChildren - * @param children - * This function iterates children of the row selected in treeview and selects - * the objects corresponding to any selector in child rows. + * @brief StyleDialog::_styleButton + * @param btn + * @param iconName + * @param tooltip + * Set the style of '+' and '-' buttons at the bottom of dialog. */ -void StyleDialog::_checkAllChildren(Gtk::TreeModel::Children& children) +void StyleDialog::_styleButton(Gtk::Button& btn, char const* iconName, + char const* tooltip) { - for (Gtk::TreeModel::Children::iterator iter = children.begin(); - iter!= children.end(); ++iter) { - Gtk::TreeModel::Row childrow = *iter; - std::vector<SPObject *> objVec = childrow[_mColumns._colObj]; - for (unsigned i = 0; i < objVec.size(); ++i) { - SPObject *obj = objVec[i]; - _desktop->selection->add(obj); - } - } + GtkWidget *child = sp_icon_new(Inkscape::ICON_SIZE_SMALL_TOOLBAR, iconName); + gtk_widget_show(child); + btn.add(*manage(Glib::wrap(child))); + btn.set_relief(Gtk::RELIEF_NONE); + btn.set_tooltip_text (tooltip); } -/** - * @brief StyleDialog::_selectRow - * This function selects the rows in treeview corresponding to an object selected - * in the drawing. - */ -void StyleDialog::_selectRow(Selection */*sel*/) -{ - SPObject *obj = NULL; - if (!_desktop->selection->isEmpty()) { - Inkscape::Selection* selection = _desktop->getSelection(); - std::vector<SPObject*> selected = std::vector<SPObject *> - (selection->objects().begin(), selection->objects().end()); - obj = selected.back(); - } - - /** - * If obj has some SPObject, then it is added to desktop's selection. If a - * row in treeview has children, those rows are checked too against selected - * object's id. If an object which is not present in any selector is selected, - * the treeview's selections are unselected. - */ - if (obj != NULL) { - Gtk::TreeModel::Children children = _store->children(); - for(Gtk::TreeModel::Children::iterator iter = children.begin(); - iter != children.end(); ++iter) { - Gtk::TreeModel::Row row = *iter; - std::vector<SPObject *> objVec = row[_mColumns._colObj]; - for (unsigned i = 0; i < objVec.size(); ++i) { - if (obj->getId() == objVec[i]->getId()) { - _treeView.get_selection()->select(row); - } - } - } - } - else { - _treeView.get_selection()->unselect_all(); - } -} } // 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 b03a1d2e1..f21e81700 100644 --- a/src/ui/dialog/styledialog.h +++ b/src/ui/dialog/styledialog.h @@ -3,8 +3,10 @@ */ /* Authors: * Kamalpreet Kaur Grewal + * Tavmjong Bah * * Copyright (C) Kamalpreet Kaur Grewal 2016 <grewalkamal005@gmail.com> + * Copyright (C) Tavmjong Bah 2017 <tavmjong@free.fr> * * Released under GNU GPL, read the file 'COPYING' for more information */ @@ -24,94 +26,139 @@ #include "document.h" #include "ui/dialog/cssdialog.h" +#include "xml/helper-observer.h" + namespace Inkscape { namespace UI { namespace Dialog { /** * @brief The StyleDialog class - * A list of CSS selectors will show up in this dialog. This dialog allows to - * add and delete selectors. Objects can be added to and removed from the selectors - * in the dialog. Besides, selection of any selector row selects the matching - * objects in the drawing and vice-versa. + * 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. */ -typedef std::pair<std::pair<std::string, std::vector<SPObject *> >, std::string> -_selectorVecType; +class StyleDialog : public Widget::Panel { -class StyleDialog : public UI::Widget::Panel -{ public: StyleDialog(); ~StyleDialog(); static StyleDialog &getInstance() { return *new StyleDialog(); } + void setDesktop(SPDesktop* desktop); private: - void _styleButton(Gtk::Button& btn, char const* iconName, char const* tooltip); - std::string _setClassAttribute(std::vector<SPObject *>); - class InkSelector { + // Monitor <style> element for changes. + class NodeObserver; + + // Data structure + class ModelColumns : public Gtk::TreeModel::ColumnRecord { public: - std::string _selector; - std::vector<SPObject *> _matchingObjs; - std::string _xmlContent; + ModelColumns() { + add(_colSelector); + add(_colIsSelector); + add(_colObj); + add(_colProperties); + } + Gtk::TreeModelColumn<Glib::ustring> _colSelector; // Selector or matching object id. + Gtk::TreeModelColumn<bool> _colIsSelector; // Selector row or child object row. + Gtk::TreeModelColumn<std::vector<SPObject *> > _colObj; // List of matching objects. + Gtk::TreeModelColumn<Glib::ustring> _colProperties; // List of properties. }; + ModelColumns _mColumns; - InkSelector inkSelector; - std::vector<InkSelector> _selectorVec; - std::vector<InkSelector> _getSelectorVec(); - std::string _populateTree(std::vector<InkSelector>); - bool _handleButtonEvent(GdkEventButton *event); - void _buttonEventsSelectObjs(GdkEventButton *event); - void _selectObjects(int, int); - void _checkAllChildren(Gtk::TreeModel::Children& children); - Inkscape::XML::Node *_styleElementNode(); - void _updateStyleContent(); - void _selectRow(Selection *); + // Override Gtk::TreeStore to control drag-n-drop (only allow dragging and dropping of selectors). + // See: https://developer.gnome.org/gtkmm-tutorial/stable/sec-treeview-examples.html.en + // + // TreeStore implements simple drag and drop (DND) but there appears no way to know when a DND + // has been completed (other than doing the whole DND ourselves). As a hack, we use + // on_row_deleted to trigger write of style element. + class TreeStore : public Gtk::TreeStore { + protected: + TreeStore(); + bool row_draggable_vfunc(const Gtk::TreeModel::Path& path) const override; + bool row_drop_possible_vfunc(const Gtk::TreeModel::Path& path, + const Gtk::SelectionData& selection_data) const override; + void on_row_deleted(const TreeModel::Path& path) override; - class ModelColumns : public Gtk::TreeModel::ColumnRecord - { public: - ModelColumns() - { add(_selectorLabel); add(_colAddRemove); add(_colObj); } - Gtk::TreeModelColumn<Glib::ustring> _selectorLabel; - Gtk::TreeModelColumn<bool> _colAddRemove; - Gtk::TreeModelColumn<std::vector<SPObject *> > _colObj; + static Glib::RefPtr<StyleDialog::TreeStore> create(StyleDialog *styledialog); + + private: + StyleDialog *_styledialog; }; - SPDesktop* _desktop; - SPDesktop* _targetDesktop; - ModelColumns _mColumns; + // TreeView + Gtk::TreeView _treeView; + Glib::RefPtr<TreeStore> _store; + + // Widgets Gtk::VPaned _paned; Gtk::VBox _mainBox; Gtk::HBox _buttonBox; - Gtk::TreeView _treeView; - Glib::RefPtr<Gtk::TreeStore> _store; Gtk::ScrolledWindow _scrolledWindow; Gtk::Button* del; Gtk::Button* create; - SPDocument* _document; - bool _styleExists; - Inkscape::XML::Node *_styleChild; - std::string _selectorName; - std::string _selectorValue; - bool _newDrawing; CssDialog *_cssPane; - void _selAdd(Gtk::TreeModel::Row row); + + // Variables - Inkscape + SPDesktop* _desktop; // To do: use panel _desktop + SPDocument* _document; + bool _updating; // Prevent cyclic actions: read <-> write, select via dialog <-> via desktop + Inkscape::XML::Node *_textNode; + Inkscape::XML::SignalObserver _objObserver; // Track object in selected row (to update CSS panel). + + // Reading and writing the style element. + Inkscape::XML::Node *_getStyleTextNode(); + void _readStyleElement(); + void _writeStyleElement(); + + // Manipulate Tree + void _addToSelector(Gtk::TreeModel::Row row); + void _removeFromSelector(Gtk::TreeModel::Row row); + Glib::ustring _getIdList(std::vector<SPObject *>); + std::vector<SPObject *> _getObjVec(Glib::ustring selector); + void _insertClass(const std::vector<SPObject *>& objVec, const Glib::ustring& className); + void _selectObjects(int, int); + void _updateCSSPanel(); // Signal handlers void _addSelector(); void _delSelector(); + bool _handleButtonEvent(GdkEventButton *event); + void _buttonEventsSelectObjs(GdkEventButton *event); + void _selectRow(Selection *); // Select row in tree when selection changed. void _selChanged(); + void _objChanged(); - // Signal handler for CssDialog + // Signal handlers for CssDialog void _handleEdited(const Glib::ustring& path, const Glib::ustring& new_text); bool _delProperty(GdkEventButton *event); + + // GUI + void _styleButton(Gtk::Button& btn, char const* iconName, char const* tooltip); }; -} // namespace Dialog +} // namespace Dialogc } // 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/symbols.cpp b/src/ui/dialog/symbols.cpp index 4840b897b..740ff4f39 100644 --- a/src/ui/dialog/symbols.cpp +++ b/src/ui/dialog/symbols.cpp @@ -25,6 +25,7 @@ #include <gtkmm/comboboxtext.h> #include <gtkmm/iconview.h> #include <gtkmm/liststore.h> +#include <glibmm/regex.h> #include <glibmm/stringutils.h> #include <glibmm/markup.h> #include <glibmm/i18n.h> @@ -127,7 +128,7 @@ SymbolsDialog::SymbolsDialog( gchar const* prefsPath ) : sigc::connection connSet = symbolSet->signal_changed().connect( sigc::mem_fun(*this, &SymbolsDialog::rebuild)); instanceConns.push_back(connSet); - + ++row; /********************* Icon View **************************/ @@ -342,7 +343,7 @@ void SymbolsDialog::rebuild() { addSymbol->set_sensitive( true ); removeSymbol->set_sensitive( true ); } else { - addSymbol->set_sensitive( false ); + addSymbol->set_sensitive( false ); removeSymbol->set_sensitive( false ); } add_symbols( symbolDocument ); @@ -451,10 +452,21 @@ void SymbolsDialog::iconChanged() { #ifdef WITH_LIBVISIO // Read Visio stencil files -SPDocument* read_vss( gchar* fullname, gchar* filename ) { +SPDocument* read_vss( gchar* fullname, Glib::ustring name ) { + + #ifdef WIN32 + // RVNGFileStream uses fopen() internally which unfortunately only uses ANSI encoding on Windows + // therefore attempt to convert uri to the system codepage + // even if this is not possible the alternate short (8.3) file name will be used if available + fullname = g_win32_locale_filename_from_utf8(fullname); + #endif RVNGFileStream input(fullname); + #ifdef WIN32 + g_free(fullname); + #endif + if (!libvisio::VisioDocument::isSupported(&input)) { return NULL; } @@ -474,6 +486,12 @@ SPDocument* read_vss( gchar* fullname, gchar* filename ) { return NULL; } + // prepare a valid title for the symbol file + Glib::ustring title = Glib::Markup::escape_text(name); + // prepare a valid id prefix for the symbols (unfortunately libvisio doesn't give us a name) + Glib::RefPtr<Glib::Regex> regex1 = Glib::Regex::create("[^a-zA-Z0-9_-]"); + Glib::ustring id = regex1->replace(name, 0, "_", Glib::REGEX_MATCH_PARTIAL); + Glib::ustring tmpSVGOutput; tmpSVGOutput += "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n"; tmpSVGOutput += "<svg\n"; @@ -483,17 +501,10 @@ SPDocument* read_vss( gchar* fullname, gchar* filename ) { tmpSVGOutput += " version=\"1.1\"\n"; tmpSVGOutput += " style=\"fill:none;stroke:#000000;stroke-width:2\">\n"; tmpSVGOutput += " <title>"; - tmpSVGOutput += filename; + tmpSVGOutput += title; tmpSVGOutput += "</title>\n"; tmpSVGOutput += " <defs>\n"; - // Create a string we can use for the symbol id (libvisio doesn't give us a name) - std::string sanitized( filename ); - sanitized.erase( sanitized.find_last_of(".vss")-3 ); - sanitized.erase( std::remove_if( sanitized.begin(), sanitized.end(), ispunct ), sanitized.end() ); - std::replace( sanitized.begin(), sanitized.end(), ' ', '_' ); - // std::cout << filename << " |" << sanitized << "|" << std::endl; - // Each "symbol" is in it's own SVG file, we wrap with <symbol> and merge into one file. for (unsigned i=0; i<output.size(); ++i) { @@ -501,7 +512,7 @@ SPDocument* read_vss( gchar* fullname, gchar* filename ) { ss << i; tmpSVGOutput += " <symbol id=\""; - tmpSVGOutput += sanitized; + tmpSVGOutput += id; tmpSVGOutput += "_"; tmpSVGOutput += ss.str(); tmpSVGOutput += "\">\n"; @@ -521,12 +532,12 @@ SPDocument* read_vss( gchar* fullname, gchar* filename ) { tmpSVGOutput += " </defs>\n"; tmpSVGOutput += "</svg>\n"; - + return SPDocument::createNewDocFromMem( tmpSVGOutput.c_str(), strlen( tmpSVGOutput.c_str()), 0 ); } #endif - + /* Hunts preference directories for symbol files */ void SymbolsDialog::get_symbols() { @@ -565,11 +576,14 @@ void SymbolsDialog::get_symbols() { #ifdef WITH_LIBVISIO if( tag.compare( "vss" ) == 0 ) { + // strip extension from filename and use it as name for the symbol set + Glib::ustring name = Glib::ustring(filename); + name = name.erase(name.rfind('.')); - symbol_doc = read_vss( fullname, filename ); + symbol_doc = read_vss( fullname, name ); if( symbol_doc ) { - symbolSets[Glib::ustring(filename)]= symbol_doc; - symbolSet->append(filename); + symbolSets[name]= symbol_doc; + symbolSet->append(name); } } #endif @@ -630,7 +644,7 @@ GSList* SymbolsDialog::symbols_in_doc( SPDocument* symbolDocument ) { } GSList* SymbolsDialog::use_in_doc_recursive (SPObject *r, GSList *l) -{ +{ if ( dynamic_cast<SPUse *>(r) ) { l = g_slist_prepend (l, r); @@ -816,7 +830,7 @@ SPDocument* SymbolsDialog::symbols_preview_doc() " xmlns:sodipodi=\"http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd\"" " xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\"" " xmlns:xlink=\"http://www.w3.org/1999/xlink\">" -" <defs id=\"defs\">" +" <defs id=\"defs\">" " <symbol id=\"the_symbol\"/>" " </defs>" " <use id=\"the_use\" xlink:href=\"#the_symbol\"/>" diff --git a/src/widgets/text-toolbar.cpp b/src/widgets/text-toolbar.cpp index 24665be37..37497ac01 100644 --- a/src/widgets/text-toolbar.cpp +++ b/src/widgets/text-toolbar.cpp @@ -2023,7 +2023,7 @@ void sp_text_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActions, GObje g_object_set( act, "short_label", "NotUsed", NULL ); gtk_action_group_add_action( mainActions, GTK_ACTION(act) ); - g_object_set_data( holder, "TextDirectAction", act ); + g_object_set_data( holder, "TextDirectionAction", act ); ege_select_one_action_set_appearance( act, "full" ); ege_select_one_action_set_radio_action_type( act, INK_RADIO_ACTION_TYPE ); |
