Code

Rough pass of Fill-n-Stroke swatch conversion.
[inkscape.git] / src / widgets / fill-style.cpp
index 5e9d30bcdbe6cd651eb6f3aecde17cac628f1c2c..f1342f3de3374d47c14ee64b32eb166c5b315288 100644 (file)
@@ -5,9 +5,11 @@
  *   Lauris Kaplinski <lauris@kaplinski.com>
  *   Frank Felfe <innerspace@iname.com>
  *   bulia byak <buliabyak@users.sf.net>
+ *   Jon A. Cruz <jon@joncruz.org>
  *
  * Copyright (C) 1999-2005 authors
  * Copyright (C) 2001-2002 Ximian, Inc.
+ * Copyright (C) 2010 Jon A. Cruz
  *
  * Released under GNU GPL, read the file 'COPYING' for more information
  */
 #endif
 
 #include <glibmm/i18n.h>
+#include <gtkmm/box.h>
+#include <gtk/gtkvbox.h>
 
+#include "desktop.h"
+#include "selection.h"
 #include "desktop-handles.h"
 #include "desktop-style.h"
 #include "display/sp-canvas.h"
 #include "sp-radial-gradient.h"
 #include "style.h"
 #include "widgets/paint-selector.h"
-#include "widgets/sp-widget.h"
 #include "xml/repr.h"
 
-#include "widgets/fill-style.h"
+#include "fill-style.h"
+#include "fill-n-stroke-factory.h"
 
 
 // These can be deleted once we sort out the libart dependence.
 
 #define ART_WIND_RULE_NONZERO 0
 
-static void sp_fill_style_widget_construct          ( SPWidget *spw,
-                                                      SPPaintSelector *psel );
-
-static void sp_fill_style_widget_modify_selection   ( SPWidget *spw,
-                                                      Inkscape::Selection *selection,
-                                                      guint flags,
-                                                      SPPaintSelector *psel );
-
-static void sp_fill_style_widget_change_subselection ( Inkscape::Application *inkscape, SPDesktop *desktop, SPWidget *spw );
-
-static void sp_fill_style_widget_change_selection   ( SPWidget *spw,
-                                                      Inkscape::Selection *selection,
-                                                      SPPaintSelector *psel );
+/* Fill */
 
-static void sp_fill_style_widget_update (SPWidget *spw);
 
-static void sp_fill_style_widget_paint_mode_changed ( SPPaintSelector *psel,
-                                                      SPPaintSelectorMode mode,
-                                                      SPWidget *spw );
-static void sp_fill_style_widget_fillrule_changed ( SPPaintSelector *psel,
-                                          SPPaintSelectorFillRule mode,
-                                                    SPWidget *spw );
-
-static void sp_fill_style_widget_paint_dragged (SPPaintSelector *psel, SPWidget *spw );
-static void sp_fill_style_widget_paint_changed (SPPaintSelector *psel, SPWidget *spw );
-
-GtkWidget *
-sp_fill_style_widget_new (void)
+Gtk::Widget *sp_fill_style_widget_new(void)
 {
-    GtkWidget *spw = sp_widget_new_global (INKSCAPE);
+    return Inkscape::Widgets::createStyleWidget( FILL );
+}
 
-    GtkWidget *vb = gtk_vbox_new (FALSE, 0);
-    gtk_widget_show (vb);
-    gtk_container_add (GTK_CONTAINER (spw), vb);
 
-    GtkWidget *psel = sp_paint_selector_new (true); // with fillrule selector
-    gtk_widget_show (psel);
-    gtk_box_pack_start (GTK_BOX (vb), psel, TRUE, TRUE, 0);
-    g_object_set_data (G_OBJECT (spw), "paint-selector", psel);
+namespace Inkscape {
 
-    g_signal_connect ( G_OBJECT (psel), "mode_changed",
-                       G_CALLBACK (sp_fill_style_widget_paint_mode_changed),
-                       spw );
+class FillNStroke : public Gtk::VBox
+{
+public:
+    FillNStroke( FillOrStroke kind );
+    ~FillNStroke();
 
-    g_signal_connect ( G_OBJECT (psel), "dragged",
-                       G_CALLBACK (sp_fill_style_widget_paint_dragged),
-                       spw );
+    void setFillrule( SPPaintSelector::FillRule mode );
 
-    g_signal_connect ( G_OBJECT (psel), "changed",
-                       G_CALLBACK (sp_fill_style_widget_paint_changed),
-                       spw );
+    void setDesktop(SPDesktop *desktop);
 
-    g_signal_connect ( G_OBJECT (psel), "fillrule_changed",
-                       G_CALLBACK (sp_fill_style_widget_fillrule_changed),
-                       spw );
+private:
+    static void paintModeChangeCB(SPPaintSelector *psel, SPPaintSelector::Mode mode, FillNStroke *self);
+    static void paintChangedCB(SPPaintSelector *psel, FillNStroke *self);
+    static void paintDraggedCB(SPPaintSelector *psel, FillNStroke *self);
+    static gboolean dragDelayCB(gpointer data);
 
+    static void fillruleChangedCB( SPPaintSelector *psel, SPPaintSelector::FillRule mode, FillNStroke *self );
 
-    g_signal_connect ( G_OBJECT (spw), "construct",
-                       G_CALLBACK (sp_fill_style_widget_construct), psel);
+    void selectionModifiedCB(guint flags);
 
-//FIXME: switch these from spw signals to global inkscape object signals; spw just retranslates
-//those anyway; then eliminate spw
-    g_signal_connect ( G_OBJECT (spw), "modify_selection",
-                       G_CALLBACK (sp_fill_style_widget_modify_selection), psel);
+    void dragFromPaint();
+    void updateFromPaint();
 
-    g_signal_connect ( G_OBJECT (spw), "change_selection",
-                       G_CALLBACK (sp_fill_style_widget_change_selection), psel);
+    void performUpdate();
 
-    g_signal_connect (INKSCAPE, "change_subselection", G_CALLBACK (sp_fill_style_widget_change_subselection), spw);
+    FillOrStroke kind;
+    SPDesktop *desktop;
+    SPPaintSelector *psel;
+    guint32 lastDrag;
+    guint dragId;
+    bool update;
+    sigc::connection selectChangedConn;
+    sigc::connection subselChangedConn;
+    sigc::connection selectModifiedConn;
+};
 
-    sp_fill_style_widget_update (SP_WIDGET (spw));
+} // namespace Inkscape
 
-    return spw;
+void sp_fill_style_widget_set_desktop(Gtk::Widget *widget, SPDesktop *desktop)
+{
+    Inkscape::FillNStroke *fs = dynamic_cast<Inkscape::FillNStroke*>(widget);
+    if (fs) {
+        fs->setDesktop(desktop);
+    }
+}
 
-} // end of sp_fill_style_widget_new()
+namespace Inkscape {
 
+/**
+ * Create the fill or stroke style widget, and hook up all the signals.
+ */
+Gtk::Widget *Inkscape::Widgets::createStyleWidget( FillOrStroke kind )
+{
+    FillNStroke *filler = new FillNStroke(kind);
 
+    return filler;
+}
 
-static void
-sp_fill_style_widget_construct( SPWidget *spw, SPPaintSelector */*psel*/ )
+FillNStroke::FillNStroke( FillOrStroke kind ) :
+    Gtk::VBox(),
+    kind(kind),
+    desktop(0),
+    psel(0),
+    lastDrag(0),
+    dragId(0),
+    update(false),
+    selectChangedConn(),
+    subselChangedConn(),
+    selectModifiedConn()
 {
-#ifdef SP_FS_VERBOSE
-    g_print ( "Fill style widget constructed: inkscape %p repr %p\n",
-              spw->inkscape, spw->repr );
-#endif
-    if (spw->inkscape) {
-        sp_fill_style_widget_update (spw);
+    // Add and connect up the paint selector widget:
+    psel = sp_paint_selector_new(kind);
+    gtk_widget_show(GTK_WIDGET(psel));
+    gtk_container_add(GTK_CONTAINER(gobj()), GTK_WIDGET(psel));
+    g_signal_connect( G_OBJECT(psel), "mode_changed",
+                      G_CALLBACK(paintModeChangeCB),
+                      this );
+
+    g_signal_connect( G_OBJECT(psel), "dragged",
+                      G_CALLBACK(paintDraggedCB),
+                      this );
+
+    g_signal_connect( G_OBJECT(psel), "changed",
+                      G_CALLBACK(paintChangedCB),
+                      this );
+    if (kind == FILL) {
+        g_signal_connect( G_OBJECT(psel), "fillrule_changed",
+                          G_CALLBACK(fillruleChangedCB),
+                          this );
     }
 
-} // end of sp_fill_style_widget_construct()
+    performUpdate();
+}
 
-static void
-sp_fill_style_widget_modify_selection( SPWidget *spw,
-                                       Inkscape::Selection */*selection*/,
-                                       guint flags,
-                                       SPPaintSelector */*psel*/ )
+FillNStroke::~FillNStroke()
 {
-    if (flags & ( SP_OBJECT_MODIFIED_FLAG |
-                  SP_OBJECT_PARENT_MODIFIED_FLAG |
-                  SP_OBJECT_STYLE_MODIFIED_FLAG) )
-    {
-        sp_fill_style_widget_update (spw);
+    if (dragId) {
+        g_source_remove(dragId);
+        dragId = 0;
     }
+    psel = 0;
+    selectModifiedConn.disconnect();
+    subselChangedConn.disconnect();
+    selectChangedConn.disconnect();
 }
 
-static void
-sp_fill_style_widget_change_subselection( Inkscape::Application */*inkscape*/,
-                                          SPDesktop */*desktop*/,
-                                          SPWidget *spw )
+/**
+ * On signal modified, invokes an update of the fill or stroke style paint object.
+ */
+void FillNStroke::selectionModifiedCB( guint flags )
 {
-    sp_fill_style_widget_update (spw);
+    if (flags & ( SP_OBJECT_MODIFIED_FLAG |
+                   SP_OBJECT_PARENT_MODIFIED_FLAG |
+                   SP_OBJECT_STYLE_MODIFIED_FLAG) ) {
+#ifdef SP_FS_VERBOSE
+        g_message("selectionModifiedCB(%d) on %p", flags, this);
+#endif
+        performUpdate();
+    }
 }
 
-static void
-sp_fill_style_widget_change_selection( SPWidget *spw,
-                                       Inkscape::Selection */*selection*/,
-                                       SPPaintSelector */*psel*/ )
+void FillNStroke::setDesktop(SPDesktop *desktop)
 {
-    sp_fill_style_widget_update (spw);
+    if (this->desktop != desktop) {
+        if (dragId) {
+            g_source_remove(dragId);
+            dragId = 0;
+        }
+        if (this->desktop) {
+            selectModifiedConn.disconnect();
+            subselChangedConn.disconnect();
+            selectChangedConn.disconnect();
+        }
+        this->desktop = desktop;
+        if (desktop && desktop->selection) {
+            selectChangedConn = desktop->selection->connectChanged(sigc::hide(sigc::mem_fun(*this, &FillNStroke::performUpdate)));
+            subselChangedConn = desktop->connectToolSubselectionChanged(sigc::hide(sigc::mem_fun(*this, &FillNStroke::performUpdate)));
+
+            // Must check flags, so can't call performUpdate() directly.
+            selectModifiedConn = desktop->selection->connectModified(sigc::hide<0>(sigc::mem_fun(*this, &FillNStroke::selectionModifiedCB)));
+        }
+        performUpdate();
+    }
 }
 
 /**
-* \param sel Selection to use, or NULL.
-*/
-static void
-sp_fill_style_widget_update (SPWidget *spw)
+ * Gets the active fill or stroke style property, then sets the appropriate
+ * color, alpha, gradient, pattern, etc. for the paint-selector.
+ *
+ * @param sel Selection to use, or NULL.
+ */
+void FillNStroke::performUpdate()
 {
-    if (g_object_get_data (G_OBJECT (spw), "update"))
+    if ( update || !desktop ) {
         return;
+    }
 
-    if (g_object_get_data (G_OBJECT (spw), "local")) {
-        g_object_set_data (G_OBJECT (spw), "local", GINT_TO_POINTER (FALSE)); // local change; do nothing, but reset the flag
+    if ( dragId ) {
+        // local change; do nothing, but reset the flag
+        g_source_remove(dragId);
+        dragId = 0;
         return;
     }
 
-    g_object_set_data (G_OBJECT (spw), "update", GINT_TO_POINTER (TRUE));
-
-    SPPaintSelector *psel = SP_PAINT_SELECTOR (g_object_get_data (G_OBJECT (spw), "paint-selector"));
+    update = true;
 
     // create temporary style
-    SPStyle *query = sp_style_new (SP_ACTIVE_DOCUMENT);
+    SPStyle *query = sp_style_new(desktop->doc());
+
     // query style from desktop into it. This returns a result flag and fills query with the style of subselection, if any, or selection
-    int result = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_FILL); 
+    int result = sp_desktop_query_style(desktop, query, (kind == FILL) ? QUERY_STYLE_PROPERTY_FILL : QUERY_STYLE_PROPERTY_STROKE);
+
+    SPIPaint &targPaint = (kind == FILL) ? query->fill : query->stroke;
+    SPIScale24 &targOpacity = (kind == FILL) ? query->fill_opacity : query->stroke_opacity;
 
     switch (result) {
         case QUERY_STYLE_NOTHING:
         {
             /* No paint at all */
-            sp_paint_selector_set_mode (psel, SP_PAINT_SELECTOR_MODE_EMPTY);
+            psel->setMode(SPPaintSelector::MODE_EMPTY);
             break;
         }
 
         case QUERY_STYLE_SINGLE:
         case QUERY_STYLE_MULTIPLE_AVERAGED: // TODO: treat this slightly differently, e.g. display "averaged" somewhere in paint selector
-        case QUERY_STYLE_MULTIPLE_SAME: 
+        case QUERY_STYLE_MULTIPLE_SAME:
         {
-            SPPaintSelectorMode pselmode = sp_style_determine_paint_selector_mode (query, true);
-            sp_paint_selector_set_mode (psel, pselmode);
-
-            sp_paint_selector_set_fillrule (psel, query->fill_rule.computed == ART_WIND_RULE_NONZERO? 
-                                     SP_PAINT_SELECTOR_FILLRULE_NONZERO : SP_PAINT_SELECTOR_FILLRULE_EVENODD);
-
-            if (query->fill.set && query->fill.isColor()) {
-                sp_paint_selector_set_color_alpha (psel, &query->fill.value.color, SP_SCALE24_TO_FLOAT (query->fill_opacity.value));
-            } else if (query->fill.set && query->fill.isPaintserver()) {
-
-                SPPaintServer *server = SP_STYLE_FILL_SERVER (query);
-
-                if (SP_IS_LINEARGRADIENT (server)) {
-                    SPGradient *vector = sp_gradient_get_vector (SP_GRADIENT (server), FALSE);
-                    sp_paint_selector_set_gradient_linear (psel, vector);
-
-                    SPLinearGradient *lg = SP_LINEARGRADIENT (server);
-                    sp_paint_selector_set_gradient_properties (psel,
-                                                       SP_GRADIENT_UNITS (lg),
-                                                       SP_GRADIENT_SPREAD (lg));
-                } else if (SP_IS_RADIALGRADIENT (server)) {
-                    SPGradient *vector = sp_gradient_get_vector (SP_GRADIENT (server), FALSE);
-                    sp_paint_selector_set_gradient_radial (psel, vector);
-
-                    SPRadialGradient *rg = SP_RADIALGRADIENT (server);
-                    sp_paint_selector_set_gradient_properties (psel,
-                                                       SP_GRADIENT_UNITS (rg),
-                                                       SP_GRADIENT_SPREAD (rg));
-                } else if (SP_IS_PATTERN (server)) {
-                    SPPattern *pat = pattern_getroot (SP_PATTERN (server));
-                    sp_update_pattern_list (psel, pat);
+            SPPaintSelector::Mode pselmode = SPPaintSelector::getModeForStyle(*query, kind);
+            psel->setMode(pselmode);
+
+            if (kind == FILL) {
+                psel->setFillrule(query->fill_rule.computed == ART_WIND_RULE_NONZERO?
+                                  SPPaintSelector::FILLRULE_NONZERO : SPPaintSelector::FILLRULE_EVENODD);
+            }
+
+            if (targPaint.set && targPaint.isColor()) {
+                psel->setColorAlpha(targPaint.value.color, SP_SCALE24_TO_FLOAT(targOpacity.value));
+            } else if (targPaint.set && targPaint.isPaintserver()) {
+
+                SPPaintServer *server = (kind == FILL) ? query->getFillPaintServer() : query->getStrokePaintServer();
+
+                if (server && SP_IS_GRADIENT(server) && SP_GRADIENT(server)->getVector()->isSwatch()) {
+                    SPGradient *vector = SP_GRADIENT(server)->getVector();
+                    psel->setSwatch( vector );
+                } else if (SP_IS_LINEARGRADIENT(server)) {
+                    SPGradient *vector = SP_GRADIENT(server)->getVector();
+                    psel->setGradientLinear( vector );
+
+                    SPLinearGradient *lg = SP_LINEARGRADIENT(server);
+                    psel->setGradientProperties( lg->getUnits(),
+                                                 lg->getSpread() );
+                } else if (SP_IS_RADIALGRADIENT(server)) {
+                    SPGradient *vector = SP_GRADIENT(server)->getVector();
+                    psel->setGradientRadial( vector );
+
+                    SPRadialGradient *rg = SP_RADIALGRADIENT(server);
+                    psel->setGradientProperties( rg->getUnits(),
+                                                 rg->getSpread() );
+                } else if (SP_IS_PATTERN(server)) {
+                    SPPattern *pat = pattern_getroot(SP_PATTERN(server));
+                    psel->updatePatternList( pat );
                 }
             }
             break;
@@ -236,105 +287,149 @@ sp_fill_style_widget_update (SPWidget *spw)
 
         case QUERY_STYLE_MULTIPLE_DIFFERENT:
         {
-            sp_paint_selector_set_mode (psel, SP_PAINT_SELECTOR_MODE_MULTIPLE);
+            psel->setMode(SPPaintSelector::MODE_MULTIPLE);
             break;
         }
     }
 
     sp_style_unref(query);
 
-    g_object_set_data (G_OBJECT (spw), "update", GINT_TO_POINTER (FALSE));
-
+    update = false;
 }
 
-
-static void
-sp_fill_style_widget_paint_mode_changed ( SPPaintSelector *psel,
-                                          SPPaintSelectorMode /*mode*/,
-                                          SPWidget *spw )
+/**
+ * When the mode is changed, invoke a regular changed handler.
+ */
+void FillNStroke::paintModeChangeCB( SPPaintSelector * /*psel*/,
+                                     SPPaintSelector::Mode /*mode*/,
+                                     FillNStroke *self )
 {
-    if (g_object_get_data (G_OBJECT (spw), "update"))
-        return;
+#ifdef SP_FS_VERBOSE
+    g_message("paintModeChangeCB(psel, mode, self:%p)", self);
+#endif
+    if (self && !self->update) {
+        self->updateFromPaint();
+    }
+}
 
-    /* TODO: Does this work? */
-    /* TODO: Not really, here we have to get old color back from object */
-    /* Instead of relying on paint widget having meaningful colors set */
-    sp_fill_style_widget_paint_changed (psel, spw);
+void FillNStroke::fillruleChangedCB( SPPaintSelector * /*psel*/,
+                                     SPPaintSelector::FillRule mode,
+                                     FillNStroke *self )
+{
+    if (self) {
+        self->setFillrule(mode);
+    }
 }
 
-static void
-sp_fill_style_widget_fillrule_changed ( SPPaintSelector */*psel*/,
-                                          SPPaintSelectorFillRule mode,
-                                          SPWidget *spw )
+void FillNStroke::setFillrule( SPPaintSelector::FillRule mode )
 {
-    if (g_object_get_data (G_OBJECT (spw), "update"))
-        return;
+    if (!update && desktop) {
+        SPCSSAttr *css = sp_repr_css_attr_new();
+        sp_repr_css_set_property(css, "fill-rule", (mode == SPPaintSelector::FILLRULE_EVENODD) ? "evenodd":"nonzero");
+
+        sp_desktop_set_style(desktop, css);
 
-    SPDesktop *desktop = SP_ACTIVE_DESKTOP;
+        sp_repr_css_attr_unref(css);
+        css = 0;
 
-    SPCSSAttr *css = sp_repr_css_attr_new ();
-    sp_repr_css_set_property (css, "fill-rule", mode == SP_PAINT_SELECTOR_FILLRULE_EVENODD? "evenodd":"nonzero");
+        sp_document_done(desktop->doc(), SP_VERB_DIALOG_FILL_STROKE,
+                         _("Change fill rule"));
+    }
+}
 
-    sp_desktop_set_style (desktop, css);
+static gchar const *undo_F_label_1 = "fill:flatcolor:1";
+static gchar const *undo_F_label_2 = "fill:flatcolor:2";
 
-    sp_repr_css_attr_unref (css);
+static gchar const *undo_S_label_1 = "stroke:flatcolor:1";
+static gchar const *undo_S_label_2 = "stroke:flatcolor:2";
 
-    sp_document_done (SP_ACTIVE_DOCUMENT, SP_VERB_DIALOG_FILL_STROKE, 
-                      _("Change fill rule"));
+static gchar const *undo_F_label = undo_F_label_1;
+static gchar const *undo_S_label = undo_S_label_1;
+
+
+void FillNStroke::paintDraggedCB(SPPaintSelector * /*psel*/, FillNStroke *self)
+{
+#ifdef SP_FS_VERBOSE
+    g_message("paintDraggedCB(psel, spw:%p)", self);
+#endif
+    if (self && !self->update) {
+        self->dragFromPaint();
+    }
 }
 
-static gchar const *undo_label_1 = "fill:flatcolor:1";
-static gchar const *undo_label_2 = "fill:flatcolor:2";
-static gchar const *undo_label = undo_label_1;
+
+gboolean FillNStroke::dragDelayCB(gpointer data)
+{
+    gboolean keepGoing = TRUE;
+    if (data) {
+        FillNStroke *self = reinterpret_cast<FillNStroke*>(data);
+        if (!self->update) {
+            if (self->dragId) {
+                g_source_remove(self->dragId);
+                self->dragId = 0;
+
+                self->dragFromPaint();
+                self->performUpdate();
+            }
+            keepGoing = FALSE;
+        }
+    } else {
+        keepGoing = FALSE;
+    }
+    return keepGoing;
+}
 
 /**
-This is called repeatedly while you are dragging a color slider, only for flat color
-modes. Previously it set the color in style but did not update the repr for efficiency, however
-this was flakey and didn't buy us almost anything. So now it does the same as _changed, except
-lumps all its changes for undo.
+ * This is called repeatedly while you are dragging a color slider, only for flat color
+ * modes. Previously it set the color in style but did not update the repr for efficiency, however
+ * this was flakey and didn't buy us almost anything. So now it does the same as _changed, except
+ * lumps all its changes for undo.
  */
-static void
-sp_fill_style_widget_paint_dragged (SPPaintSelector *psel, SPWidget *spw)
+void FillNStroke::dragFromPaint()
 {
-    if (!spw->inkscape) {
+    if (!desktop || update) {
         return;
     }
 
-    if (g_object_get_data (G_OBJECT (spw), "update")) {
-        return;
+    guint32 when = gtk_get_current_event_time();
+
+    // Don't attempt too many updates per second.
+    // Assume a base 15.625ms resolution on the timer.
+    if (!dragId && lastDrag && when && ((when - lastDrag) < 32)) {
+        // local change, do not update from selection
+        dragId = g_timeout_add_full(G_PRIORITY_DEFAULT, 33, dragDelayCB, this, 0);
     }
 
-    if (g_object_get_data (G_OBJECT (spw), "local")) {
-        // previous local flag not cleared yet; 
-        // this means dragged events come too fast, so we better skip this one to speed up display 
+    if (dragId) {
+        // previous local flag not cleared yet;
+        // this means dragged events come too fast, so we better skip this one to speed up display
         // (it's safe to do this in any case)
         return;
     }
+    lastDrag = when;
 
-    g_object_set_data (G_OBJECT (spw), "update", GINT_TO_POINTER (TRUE));
+    update = true;
 
     switch (psel->mode) {
-
-        case SP_PAINT_SELECTOR_MODE_COLOR_RGB:
-        case SP_PAINT_SELECTOR_MODE_COLOR_CMYK:
+        case SPPaintSelector::MODE_COLOR_RGB:
+        case SPPaintSelector::MODE_COLOR_CMYK:
         {
-            sp_paint_selector_set_flat_color (psel, SP_ACTIVE_DESKTOP, "fill", "fill-opacity");
-            sp_document_maybe_done (sp_desktop_document(SP_ACTIVE_DESKTOP), undo_label, SP_VERB_DIALOG_FILL_STROKE, 
-                                    _("Set fill color"));
-            g_object_set_data (G_OBJECT (spw), "local", GINT_TO_POINTER (TRUE)); // local change, do not update from selection
+            // local change, do not update from selection
+            dragId = g_timeout_add_full(G_PRIORITY_DEFAULT, 100, dragDelayCB, this, 0);
+            psel->setFlatColor( desktop, (kind == FILL) ? "fill" : "stroke", (kind == FILL) ? "fill-opacity" : "stroke-opacity" );
+            sp_document_maybe_done(desktop->doc(), (kind == FILL) ? undo_F_label : undo_S_label, SP_VERB_DIALOG_FILL_STROKE,
+                                   (kind == FILL) ? _("Set fill color") : _("Set stroke color"));
             break;
         }
 
         default:
-            g_warning ( "file %s: line %d: Paint %d should not emit 'dragged'",
-                        __FILE__, __LINE__, psel->mode );
+            g_warning( "file %s: line %d: Paint %d should not emit 'dragged'",
+                       __FILE__, __LINE__, psel->mode );
             break;
-
     }
-    g_object_set_data (G_OBJECT (spw), "update", GINT_TO_POINTER (FALSE));
+    update = false;
 }
 
-
 /**
 This is called (at least) when:
 1  paint selector mode is switched (e.g. flat color -> gradient)
@@ -342,94 +437,113 @@ This is called (at least) when:
 3  you changed a gradient selector parameter (e.g. spread)
 Must update repr.
  */
-static void
-sp_fill_style_widget_paint_changed ( SPPaintSelector *psel,
-                                     SPWidget *spw )
+void FillNStroke::paintChangedCB( SPPaintSelector * /*psel*/, FillNStroke *self )
 {
-    if (g_object_get_data (G_OBJECT (spw), "update")) {
-        return;
+#ifdef SP_FS_VERBOSE
+    g_message("paintChangedCB(psel, spw:%p)", self);
+#endif
+    if (self && !self->update) {
+        self->updateFromPaint();
     }
-    g_object_set_data (G_OBJECT (spw), "update", GINT_TO_POINTER (TRUE));
+}
 
-    SPDesktop *desktop = SP_ACTIVE_DESKTOP;
+void FillNStroke::updateFromPaint()
+{
     if (!desktop) {
         return;
     }
-    SPDocument *document = sp_desktop_document (desktop);
-    Inkscape::Selection *selection = sp_desktop_selection (desktop);
+    update = true;
+
+    SPDocument *document = sp_desktop_document(desktop);
+    Inkscape::Selection *selection = sp_desktop_selection(desktop);
 
     GSList const *items = selection->itemList();
 
     switch (psel->mode) {
-
-        case SP_PAINT_SELECTOR_MODE_EMPTY:
+        case SPPaintSelector::MODE_EMPTY:
             // This should not happen.
-            g_warning ( "file %s: line %d: Paint %d should not emit 'changed'",
-                        __FILE__, __LINE__, psel->mode);
+            g_warning( "file %s: line %d: Paint %d should not emit 'changed'",
+                       __FILE__, __LINE__, psel->mode);
             break;
-        case SP_PAINT_SELECTOR_MODE_MULTIPLE:
+        case SPPaintSelector::MODE_MULTIPLE:
             // This happens when you switch multiple objects with different gradients to flat color;
             // nothing to do here.
             break;
 
-        case SP_PAINT_SELECTOR_MODE_NONE:
+        case SPPaintSelector::MODE_NONE:
         {
-            SPCSSAttr *css = sp_repr_css_attr_new ();
-            sp_repr_css_set_property (css, "fill", "none");
+            SPCSSAttr *css = sp_repr_css_attr_new();
+            sp_repr_css_set_property(css, (kind == FILL) ? "fill" : "stroke", "none");
 
-            sp_desktop_set_style (desktop, css);
+            sp_desktop_set_style(desktop, css);
 
-            sp_repr_css_attr_unref (css);
+            sp_repr_css_attr_unref(css);
+            css = 0;
 
-            sp_document_done (document, SP_VERB_DIALOG_FILL_STROKE, 
-                              _("Remove fill"));
+            sp_document_done(document, SP_VERB_DIALOG_FILL_STROKE,
+                             (kind == FILL) ? _("Remove fill") : _("Remove stroke"));
             break;
         }
 
-        case SP_PAINT_SELECTOR_MODE_COLOR_RGB:
-        case SP_PAINT_SELECTOR_MODE_COLOR_CMYK:
+        case SPPaintSelector::MODE_COLOR_RGB:
+        case SPPaintSelector::MODE_COLOR_CMYK:
         {
-            // FIXME: fix for GTK breakage, see comment in SelectedStyle::on_opacity_changed; here it results in losing release events
-            sp_canvas_force_full_redraw_after_interruptions(sp_desktop_canvas(desktop), 0);
+            if (kind == FILL) {
+                // FIXME: fix for GTK breakage, see comment in SelectedStyle::on_opacity_changed; here it results in losing release events
+                sp_canvas_force_full_redraw_after_interruptions(sp_desktop_canvas(desktop), 0);
+            }
 
-            sp_paint_selector_set_flat_color (psel, desktop, "fill", "fill-opacity");
-            sp_document_maybe_done (sp_desktop_document(desktop), undo_label, SP_VERB_DIALOG_FILL_STROKE,
-                                    _("Set fill color"));
-            // resume interruptibility
-            sp_canvas_end_forced_full_redraws(sp_desktop_canvas(desktop));
+            psel->setFlatColor( desktop,
+                                (kind == FILL) ? "fill" : "stroke",
+                                (kind == FILL) ? "fill-opacity" : "stroke-opacity" );
+            sp_document_maybe_done(sp_desktop_document(desktop), (kind == FILL) ? undo_F_label : undo_S_label, SP_VERB_DIALOG_FILL_STROKE,
+                                   (kind == FILL) ? _("Set fill color") : _("Set stroke color"));
+
+            if (kind == FILL) {
+                // resume interruptibility
+                sp_canvas_end_forced_full_redraws(sp_desktop_canvas(desktop));
+            }
 
             // on release, toggle undo_label so that the next drag will not be lumped with this one
-            if (undo_label == undo_label_1)
-                undo_label = undo_label_2;
-            else
-                undo_label = undo_label_1;
+            if (undo_F_label == undo_F_label_1) {
+                undo_F_label = undo_F_label_2;
+                undo_S_label = undo_S_label_2;
+            } else {
+                undo_F_label = undo_F_label_1;
+                undo_S_label = undo_S_label_1;
+            }
 
             break;
         }
 
-        case SP_PAINT_SELECTOR_MODE_GRADIENT_LINEAR:
-        case SP_PAINT_SELECTOR_MODE_GRADIENT_RADIAL:
+        case SPPaintSelector::MODE_GRADIENT_LINEAR:
+        case SPPaintSelector::MODE_GRADIENT_RADIAL:
+        case SPPaintSelector::MODE_SWATCH:
             if (items) {
-                SPGradientType const gradient_type = ( psel->mode == SP_PAINT_SELECTOR_MODE_GRADIENT_LINEAR
+                SPGradientType const gradient_type = ( psel->mode != SPPaintSelector::MODE_GRADIENT_RADIAL
                                                        ? SP_GRADIENT_TYPE_LINEAR
                                                        : SP_GRADIENT_TYPE_RADIAL );
 
-                // HACK: reset fill-opacity - that 0.75 is annoying; BUT remove this when we have an opacity slider for all tabs
-                SPCSSAttr *css = sp_repr_css_attr_new();
-                sp_repr_css_set_property(css, "fill-opacity", "1.0");
+                SPCSSAttr *css = 0;
+                if (kind == FILL) {
+                    // HACK: reset fill-opacity - that 0.75 is annoying; BUT remove this when we have an opacity slider for all tabs
+                    css = sp_repr_css_attr_new();
+                    sp_repr_css_set_property(css, "fill-opacity", "1.0");
+                }
 
-                SPGradient *vector = sp_paint_selector_get_gradient_vector(psel);
+                SPGradient *vector = psel->getGradientVector();
                 if (!vector) {
                     /* No vector in paint selector should mean that we just changed mode */
 
-                    SPStyle *query = sp_style_new (SP_ACTIVE_DOCUMENT);
-                    int result = objects_query_fillstroke ((GSList *) items, query, true);
+                    SPStyle *query = sp_style_new(desktop->doc());
+                    int result = objects_query_fillstroke(const_cast<GSList *>(items), query, kind == FILL);
+                    SPIPaint &targPaint = (kind == FILL) ? query->fill : query->stroke;
                     guint32 common_rgb = 0;
                     if (result == QUERY_STYLE_MULTIPLE_SAME) {
-                        if (!query->fill.isColor()) {
-                            common_rgb = sp_desktop_get_color(desktop, true);
+                        if (!targPaint.isColor()) {
+                            common_rgb = sp_desktop_get_color(desktop, kind == FILL);
                         } else {
-                            common_rgb = query->fill.value.color.toRGBA32( 0xff );
+                            common_rgb = targPaint.value.color.toRGBA32( 0xff );
                         }
                         vector = sp_document_default_gradient_vector(document, common_rgb);
                     }
@@ -437,41 +551,52 @@ sp_fill_style_widget_paint_changed ( SPPaintSelector *psel,
 
                     for (GSList const *i = items; i != NULL; i = i->next) {
                         //FIXME: see above
-                        sp_repr_css_change_recursive(SP_OBJECT_REPR(i->data), css, "style");
+                        if (kind == FILL) {
+                            sp_repr_css_change_recursive(SP_OBJECT_REPR(i->data), css, "style");
+                        }
 
                         if (!vector) {
+                            SPGradient *gr = sp_gradient_vector_for_object(document, desktop, SP_OBJECT(i->data), kind == FILL);
+                            if ( gr && (psel->mode == SPPaintSelector::MODE_SWATCH) ) {
+                                gr->setSwatch();
+                            }
                             sp_item_set_gradient(SP_ITEM(i->data),
-                                                 sp_gradient_vector_for_object(document, desktop, SP_OBJECT(i->data), true),
-                                                 gradient_type, true);
+                                                 gr,
+                                                 gradient_type, kind == FILL);
                         } else {
-                            sp_item_set_gradient(SP_ITEM(i->data), vector, gradient_type, true);
+                            sp_item_set_gradient(SP_ITEM(i->data), vector, gradient_type, kind == FILL);
                         }
                     }
                 } else {
-                    /* We have changed from another gradient type, or modified spread/units within
-                     * this gradient type. */
-                    vector = sp_gradient_ensure_vector_normalized (vector);
+                    // We have changed from another gradient type, or modified spread/units within
+                    // this gradient type.
+                    vector = sp_gradient_ensure_vector_normalized(vector);
                     for (GSList const *i = items; i != NULL; i = i->next) {
                         //FIXME: see above
-                        sp_repr_css_change_recursive (SP_OBJECT_REPR (i->data), css, "style");
+                        if (kind == FILL) {
+                            sp_repr_css_change_recursive(SP_OBJECT_REPR(i->data), css, "style");
+                        }
 
-                        SPGradient *gr = sp_item_set_gradient(SP_ITEM(i->data), vector, gradient_type, true);
-                        sp_gradient_selector_attrs_to_gradient (gr, psel);
+                        SPGradient *gr = sp_item_set_gradient(SP_ITEM(i->data), vector, gradient_type, kind == FILL);
+                        psel->pushAttrsToGradient( gr );
                     }
                 }
 
-                sp_repr_css_attr_unref (css);
+                if (css) {
+                    sp_repr_css_attr_unref(css);
+                    css = 0;
+                }
 
-                sp_document_done (document, SP_VERB_DIALOG_FILL_STROKE, 
-                                  _("Set gradient on fill"));
+                sp_document_done(document, SP_VERB_DIALOG_FILL_STROKE,
+                                 (kind == FILL) ? _("Set gradient on fill") : _("Set gradient on stroke"));
             }
             break;
 
-        case SP_PAINT_SELECTOR_MODE_PATTERN:
+        case SPPaintSelector::MODE_PATTERN:
 
             if (items) {
 
-                SPPattern *pattern = sp_paint_selector_get_pattern (psel);
+                SPPattern *pattern = psel->getPattern();
                 if (!pattern) {
 
                     /* No Pattern in paint selector should mean that we just
@@ -480,65 +605,92 @@ sp_fill_style_widget_paint_changed ( SPPaintSelector *psel,
 
                 } else {
                     Inkscape::XML::Node *patrepr = SP_OBJECT_REPR(pattern);
-                    SPCSSAttr *css = sp_repr_css_attr_new ();
-                    gchar *urltext = g_strdup_printf ("url(#%s)", patrepr->attribute("id"));
-                    sp_repr_css_set_property (css, "fill", urltext);
+                    SPCSSAttr *css = sp_repr_css_attr_new();
+                    gchar *urltext = g_strdup_printf("url(#%s)", patrepr->attribute("id"));
+                    sp_repr_css_set_property(css, (kind == FILL) ? "fill" : "stroke", urltext);
 
                     // HACK: reset fill-opacity - that 0.75 is annoying; BUT remove this when we have an opacity slider for all tabs
-                    sp_repr_css_set_property(css, "fill-opacity", "1.0");
+                    if (kind == FILL) {
+                        sp_repr_css_set_property(css, "fill-opacity", "1.0");
+                    }
 
                     // cannot just call sp_desktop_set_style, because we don't want to touch those
                     // objects who already have the same root pattern but through a different href
                     // chain. FIXME: move this to a sp_item_set_pattern
                     for (GSList const *i = items; i != NULL; i = i->next) {
-                         SPObject *selobj = SP_OBJECT (i->data);
-
-                         SPStyle *style = SP_OBJECT_STYLE (selobj);
-                         if (style && style->fill.isPaintserver()) {
-                             SPObject *server = SP_OBJECT_STYLE_FILL_SERVER (selobj);
-                             if (SP_IS_PATTERN (server) && pattern_getroot (SP_PATTERN(server)) == pattern)
+                        Inkscape::XML::Node *selrepr = SP_OBJECT_REPR(i->data);
+                        if ( (kind == STROKE) && !selrepr) {
+                            continue;
+                        }
+                        SPObject *selobj = SP_OBJECT(i->data);
+
+                        SPStyle *style = SP_OBJECT_STYLE(selobj);
+                        if (style && ((kind == FILL) ? style->fill : style->stroke).isPaintserver()) {
+                            SPObject *server = (kind == FILL) ?
+                                SP_OBJECT_STYLE_FILL_SERVER(selobj) :
+                                SP_OBJECT_STYLE_STROKE_SERVER(selobj);
+                            if (SP_IS_PATTERN(server) && pattern_getroot(SP_PATTERN(server)) == pattern)
                                 // only if this object's pattern is not rooted in our selected pattern, apply
-                                 continue;
-                         }
+                                continue;
+                        }
 
-                         sp_desktop_apply_css_recursive (selobj, css, true);
-                     }
+                        if (kind == FILL) {
+                            sp_desktop_apply_css_recursive(selobj, css, true);
+                        } else {
+                            sp_repr_css_change_recursive(selrepr, css, "style");
+                        }
+                    }
 
-                    sp_repr_css_attr_unref (css);
-                    g_free (urltext);
+                    sp_repr_css_attr_unref(css);
+                    css = 0;
+                    g_free(urltext);
 
                 } // end if
 
-                sp_document_done (document, SP_VERB_DIALOG_FILL_STROKE, 
-                                  _("Set pattern on fill"));
-
+                sp_document_done(document, SP_VERB_DIALOG_FILL_STROKE,
+                                 (kind == FILL) ? _("Set pattern on fill") :
+                                 _("Set pattern on stroke"));
             } // end if
 
             break;
 
-        case SP_PAINT_SELECTOR_MODE_UNSET:
+        case SPPaintSelector::MODE_UNSET:
             if (items) {
-                    SPCSSAttr *css = sp_repr_css_attr_new ();
-                    sp_repr_css_unset_property (css, "fill");
+                SPCSSAttr *css = sp_repr_css_attr_new();
+                if (kind == FILL) {
+                    sp_repr_css_unset_property(css, "fill");
+                } else {
+                    sp_repr_css_unset_property(css, "stroke");
+                    sp_repr_css_unset_property(css, "stroke-opacity");
+                    sp_repr_css_unset_property(css, "stroke-width");
+                    sp_repr_css_unset_property(css, "stroke-miterlimit");
+                    sp_repr_css_unset_property(css, "stroke-linejoin");
+                    sp_repr_css_unset_property(css, "stroke-linecap");
+                    sp_repr_css_unset_property(css, "stroke-dashoffset");
+                    sp_repr_css_unset_property(css, "stroke-dasharray");
+                }
 
-                    sp_desktop_set_style (desktop, css);
-                    sp_repr_css_attr_unref (css);
+                sp_desktop_set_style(desktop, css);
+                sp_repr_css_attr_unref(css);
+                css = 0;
 
-                    sp_document_done (document, SP_VERB_DIALOG_FILL_STROKE, 
-                                      _("Unset fill"));
+                sp_document_done(document, SP_VERB_DIALOG_FILL_STROKE,
+                                 (kind == FILL) ? _("Unset fill") : _("Unset stroke"));
             }
             break;
 
         default:
-            g_warning ( "file %s: line %d: Paint selector should not be in "
-                        "mode %d",
-                        __FILE__, __LINE__, psel->mode );
+            g_warning( "file %s: line %d: Paint selector should not be in "
+                       "mode %d",
+                       __FILE__, __LINE__,
+                       psel->mode );
             break;
     }
 
-    g_object_set_data (G_OBJECT (spw), "update", GINT_TO_POINTER (FALSE));
+    update = false;
 }
 
+} // namespace Inkscape
 
 /*
   Local Variables: