Code

New widget helperclass for Gtk:Entry
[inkscape.git] / src / ui / widget / selected-style.cpp
index c808289b21b6133f6f494cd555b988990f07d7ba..9cf5aa32b39606a9d866f567275f29f83ed6ed05 100644 (file)
@@ -13,6 +13,7 @@
 # include <config.h>
 #endif
 
+#include <gtk/gtkdnd.h>
 
 #include "selected-style.h"
 
 #include "document.h"
 #include "widgets/widget-sizes.h"
 #include "widgets/spinbutton-events.h"
-#include "svg/svg.h"
+#include "svg/svg-color.h"
 #include "svg/css-ostringstream.h"
 #include "helper/units.h"
+#include "verbs.h"
+#include <display/sp-canvas.h>
 
 static gdouble const _sw_presets[]     = { 32 ,  16 ,  10 ,  8 ,  6 ,  4 ,  3 ,  2 ,  1.5 ,  1 ,  0.75 ,  0.5 ,  0.25 ,  0.1 };
 static gchar* const _sw_presets_str[] = {"32", "16", "10", "8", "6", "4", "3", "2", "1.5", "1", "0.75", "0.5", "0.25", "0.1"};
@@ -61,6 +64,25 @@ namespace Inkscape {
 namespace UI {
 namespace Widget {
 
+
+typedef struct {
+    SelectedStyle* parent;
+    int item;
+} DropTracker;
+
+/* Drag and Drop */
+typedef enum {
+    APP_X_COLOR
+} ui_drop_target_info;
+
+static GtkTargetEntry ui_drop_target_entries [] = {
+    {"application/x-color", 0, APP_X_COLOR}
+};
+
+#define ENTRIES_SIZE(n) sizeof(n)/sizeof(n[0])
+static guint nui_drop_target_entries = ENTRIES_SIZE(ui_drop_target_entries);
+
+
 SelectedStyle::SelectedStyle(bool layout)
     : _desktop (NULL),
 
@@ -75,8 +97,8 @@ SelectedStyle::SelectedStyle(bool layout)
       _stroke_flag_place (),
 
       _opacity_place (),
-      _opacity_adjustment (1.0, 0.0, 1.0, 0.01, 0.1),
-      _opacity_sb (0.02, 2),
+      _opacity_adjustment (100, 0.0, 100, 1.0, 10.0),
+      _opacity_sb (0.02, 0),
 
       _stroke (),
       _stroke_width (""),
@@ -90,7 +112,11 @@ SelectedStyle::SelectedStyle(bool layout)
       _sw_unit(NULL),
 
       _tooltips ()
+
 {
+    _drop[0] = _drop[1] = 0;
+    _dropEnabled[0] = _dropEnabled[1] = false;
+    
     _fill_label.set_alignment(0.0, 0.5);
     _fill_label.set_padding(0, 0);
     _stroke_label.set_alignment(0.0, 0.5);
@@ -307,6 +333,24 @@ SelectedStyle::SelectedStyle(bool layout)
     sp_set_font_size_smaller (GTK_WIDGET(_stroke_width.gobj()));
     sp_set_font_size_smaller (GTK_WIDGET(_fill_label.gobj()));
     sp_set_font_size_smaller (GTK_WIDGET(_stroke_label.gobj()));
+
+    _drop[SS_FILL] = new DropTracker();
+    ((DropTracker*)_drop[SS_FILL])->parent = this;
+    ((DropTracker*)_drop[SS_FILL])->item = SS_FILL;
+
+    _drop[SS_STROKE] = new DropTracker();
+    ((DropTracker*)_drop[SS_STROKE])->parent = this;
+    ((DropTracker*)_drop[SS_STROKE])->item = SS_STROKE;
+
+    g_signal_connect(_stroke_place.gobj(),
+                     "drag_data_received",
+                     G_CALLBACK(dragDataReceived),
+                     _drop[SS_STROKE]);
+
+    g_signal_connect(_fill_place.gobj(),
+                     "drag_data_received",
+                     G_CALLBACK(dragDataReceived),
+                     _drop[SS_FILL]);
 }
 
 SelectedStyle::~SelectedStyle()
@@ -321,6 +365,9 @@ SelectedStyle::~SelectedStyle()
     for (int i = SS_FILL; i <= SS_STROKE; i++) {
         delete _color_preview[i];
     }
+
+    delete (DropTracker*)_drop[SS_FILL];
+    delete (DropTracker*)_drop[SS_STROKE];
 }
 
 void
@@ -329,7 +376,7 @@ SelectedStyle::setDesktop(SPDesktop *desktop)
     _desktop = desktop;
     gtk_object_set_data (GTK_OBJECT(_opacity_sb.gobj()), "dtw", _desktop->canvas);
 
-    Inkscape::Selection *selection = SP_DT_SELECTION (desktop);
+    Inkscape::Selection *selection = sp_desktop_selection (desktop);
 
     selection_changed_connection = new sigc::connection (selection->connectChanged(
         sigc::bind (
@@ -347,39 +394,81 @@ SelectedStyle::setDesktop(SPDesktop *desktop)
             this )
     ));
 
-    //_sw_unit = (SPUnit *) SP_DT_NAMEDVIEW(desktop)->doc_units;
+    //_sw_unit = (SPUnit *) sp_desktop_namedview(desktop)->doc_units;
+}
+
+void SelectedStyle::dragDataReceived( GtkWidget *widget,
+                                      GdkDragContext *drag_context,
+                                      gint x, gint y,
+                                      GtkSelectionData *data,
+                                      guint info,
+                                      guint event_time,
+                                      gpointer user_data )
+{
+    DropTracker* tracker = (DropTracker*)user_data;
+
+    switch ( (int)tracker->item ) {
+        case SS_FILL:
+        case SS_STROKE:
+        {
+            if ( data->length == 8 ) {
+                gchar c[64];
+                // Careful about endian issues.
+                guint16* dataVals = (guint16*)data->data;
+                sp_svg_write_color( c, 64,
+                                    SP_RGBA32_U_COMPOSE(
+                                        0x0ff & (dataVals[0] >> 8),
+                                        0x0ff & (dataVals[1] >> 8),
+                                        0x0ff & (dataVals[2] >> 8),
+                                        0xff // can't have transparency in the color itself
+                                        //0x0ff & (data->data[3] >> 8),
+                                        ));
+                SPCSSAttr *css = sp_repr_css_attr_new();
+                sp_repr_css_set_property( css, (tracker->item == SS_FILL) ? "fill":"stroke", c );
+                sp_desktop_set_style( tracker->parent->_desktop, css );
+                sp_repr_css_attr_unref( css );
+                sp_document_done( sp_desktop_document(tracker->parent->_desktop) , SP_VERB_NONE, 
+                                  _("Drop color"));
+            }
+        }
+        break;
+    }
 }
 
 void SelectedStyle::on_fill_remove() {
     SPCSSAttr *css = sp_repr_css_attr_new ();
     sp_repr_css_set_property (css, "fill", "none");
-    sp_desktop_set_style (_desktop, css, true, false); // do not write to current, to preserve current color
+    sp_desktop_set_style (_desktop, css, true, true); 
     sp_repr_css_attr_unref (css);
-    sp_document_done (SP_DT_DOCUMENT(_desktop));
+    sp_document_done (sp_desktop_document(_desktop), SP_VERB_DIALOG_FILL_STROKE,
+                      _("Remove fill"));
 }
 
 void SelectedStyle::on_stroke_remove() {
     SPCSSAttr *css = sp_repr_css_attr_new ();
     sp_repr_css_set_property (css, "stroke", "none");
-    sp_desktop_set_style (_desktop, css, true, false); // do not write to current, to preserve current color
+    sp_desktop_set_style (_desktop, css, true, true); 
     sp_repr_css_attr_unref (css);
-    sp_document_done (SP_DT_DOCUMENT(_desktop));
+    sp_document_done (sp_desktop_document(_desktop), SP_VERB_DIALOG_FILL_STROKE,
+                      _("Remove stroke"));
 }
 
 void SelectedStyle::on_fill_unset() {
     SPCSSAttr *css = sp_repr_css_attr_new ();
     sp_repr_css_unset_property (css, "fill");
-    sp_desktop_set_style (_desktop, css, true, false); // do not write to current, to preserve current color
+    sp_desktop_set_style (_desktop, css, true, true); 
     sp_repr_css_attr_unref (css);
-    sp_document_done (SP_DT_DOCUMENT(_desktop));
+    sp_document_done (sp_desktop_document(_desktop), SP_VERB_DIALOG_FILL_STROKE,
+                      _("Unset fill"));
 }
 
 void SelectedStyle::on_stroke_unset() {
     SPCSSAttr *css = sp_repr_css_attr_new ();
     sp_repr_css_unset_property (css, "stroke");
-    sp_desktop_set_style (_desktop, css, true, false); // do not write to current, to preserve current color
+    sp_desktop_set_style (_desktop, css, true, true);
     sp_repr_css_attr_unref (css);
-    sp_document_done (SP_DT_DOCUMENT(_desktop));
+    sp_document_done (sp_desktop_document(_desktop), SP_VERB_DIALOG_FILL_STROKE,
+                      _("Unset stroke"));
 }
 
 void SelectedStyle::on_fill_opaque() {
@@ -387,7 +476,8 @@ void SelectedStyle::on_fill_opaque() {
     sp_repr_css_set_property (css, "fill-opacity", "1");
     sp_desktop_set_style (_desktop, css, true);
     sp_repr_css_attr_unref (css);
-    sp_document_done (SP_DT_DOCUMENT(_desktop));
+    sp_document_done (sp_desktop_document(_desktop), SP_VERB_DIALOG_FILL_STROKE,
+                      _("Make fill opaque"));
 }
 
 void SelectedStyle::on_stroke_opaque() {
@@ -395,7 +485,8 @@ void SelectedStyle::on_stroke_opaque() {
     sp_repr_css_set_property (css, "stroke-opacity", "1");
     sp_desktop_set_style (_desktop, css, true);
     sp_repr_css_attr_unref (css);
-    sp_document_done (SP_DT_DOCUMENT(_desktop));
+    sp_document_done (sp_desktop_document(_desktop), SP_VERB_DIALOG_FILL_STROKE,
+                      _("Make fill opaque"));
 }
 
 void SelectedStyle::on_fill_lastused() {
@@ -406,7 +497,8 @@ void SelectedStyle::on_fill_lastused() {
     sp_repr_css_set_property (css, "fill", c);
     sp_desktop_set_style (_desktop, css);
     sp_repr_css_attr_unref (css);
-    sp_document_done (SP_DT_DOCUMENT(_desktop));
+    sp_document_done (sp_desktop_document(_desktop), SP_VERB_DIALOG_FILL_STROKE, 
+                      _("Apply last set color to fill"));
 }
 
 void SelectedStyle::on_stroke_lastused() {
@@ -417,7 +509,8 @@ void SelectedStyle::on_stroke_lastused() {
     sp_repr_css_set_property (css, "stroke", c);
     sp_desktop_set_style (_desktop, css);
     sp_repr_css_attr_unref (css);
-    sp_document_done (SP_DT_DOCUMENT(_desktop));
+    sp_document_done (sp_desktop_document(_desktop), SP_VERB_DIALOG_FILL_STROKE, 
+                      _("Apply last set color to stroke"));
 }
 
 void SelectedStyle::on_fill_lastselected() {
@@ -427,7 +520,8 @@ void SelectedStyle::on_fill_lastselected() {
     sp_repr_css_set_property (css, "fill", c);
     sp_desktop_set_style (_desktop, css);
     sp_repr_css_attr_unref (css);
-    sp_document_done (SP_DT_DOCUMENT(_desktop));
+    sp_document_done (sp_desktop_document(_desktop), SP_VERB_DIALOG_FILL_STROKE,
+                      _("Apply last selected color to fill"));
 }
 
 void SelectedStyle::on_stroke_lastselected() {
@@ -437,7 +531,8 @@ void SelectedStyle::on_stroke_lastselected() {
     sp_repr_css_set_property (css, "stroke", c);
     sp_desktop_set_style (_desktop, css);
     sp_repr_css_attr_unref (css);
-    sp_document_done (SP_DT_DOCUMENT(_desktop));
+    sp_document_done (sp_desktop_document(_desktop), SP_VERB_DIALOG_FILL_STROKE, 
+                      _("Apply last selected color to stroke"));
 }
 
 void SelectedStyle::on_fill_invert() {
@@ -456,7 +551,8 @@ void SelectedStyle::on_fill_invert() {
     sp_repr_css_set_property (css, "fill", c);
     sp_desktop_set_style (_desktop, css);
     sp_repr_css_attr_unref (css);
-    sp_document_done (SP_DT_DOCUMENT(_desktop));
+    sp_document_done (sp_desktop_document(_desktop), SP_VERB_DIALOG_FILL_STROKE,
+                      _("Invert fill"));
 }
 
 void SelectedStyle::on_stroke_invert() {
@@ -475,7 +571,8 @@ void SelectedStyle::on_stroke_invert() {
     sp_repr_css_set_property (css, "stroke", c);
     sp_desktop_set_style (_desktop, css);
     sp_repr_css_attr_unref (css);
-    sp_document_done (SP_DT_DOCUMENT(_desktop));
+    sp_document_done (sp_desktop_document(_desktop), SP_VERB_DIALOG_FILL_STROKE,
+                      _("Invert stroke"));
 } 
 
 void SelectedStyle::on_fill_white() {
@@ -486,7 +583,8 @@ void SelectedStyle::on_fill_white() {
     sp_repr_css_set_property (css, "fill-opacity", "1");
     sp_desktop_set_style (_desktop, css);
     sp_repr_css_attr_unref (css);
-    sp_document_done (SP_DT_DOCUMENT(_desktop));
+    sp_document_done (sp_desktop_document(_desktop), SP_VERB_DIALOG_FILL_STROKE,
+                      _("White fill"));
 }
 
 void SelectedStyle::on_stroke_white() {
@@ -497,7 +595,8 @@ void SelectedStyle::on_stroke_white() {
     sp_repr_css_set_property (css, "stroke-opacity", "1");
     sp_desktop_set_style (_desktop, css);
     sp_repr_css_attr_unref (css);
-    sp_document_done (SP_DT_DOCUMENT(_desktop));
+    sp_document_done (sp_desktop_document(_desktop), SP_VERB_DIALOG_FILL_STROKE, 
+                      _("White stroke"));
 }
 
 void SelectedStyle::on_fill_black() {
@@ -508,7 +607,8 @@ void SelectedStyle::on_fill_black() {
     sp_repr_css_set_property (css, "fill-opacity", "1.0");
     sp_desktop_set_style (_desktop, css);
     sp_repr_css_attr_unref (css);
-    sp_document_done (SP_DT_DOCUMENT(_desktop));
+    sp_document_done (sp_desktop_document(_desktop), SP_VERB_DIALOG_FILL_STROKE, 
+                      _("Black fill"));
 }
 
 void SelectedStyle::on_stroke_black() {
@@ -519,7 +619,8 @@ void SelectedStyle::on_stroke_black() {
     sp_repr_css_set_property (css, "stroke-opacity", "1.0");
     sp_desktop_set_style (_desktop, css);
     sp_repr_css_attr_unref (css);
-    sp_document_done (SP_DT_DOCUMENT(_desktop));
+    sp_document_done (sp_desktop_document(_desktop), SP_VERB_DIALOG_FILL_STROKE, 
+                      _("Black stroke"));
 }
 
 void SelectedStyle::on_fill_copy() {
@@ -561,7 +662,8 @@ void SelectedStyle::on_fill_paste() {
         sp_repr_css_set_property (css, "fill", text.c_str());
         sp_desktop_set_style (_desktop, css);
         sp_repr_css_attr_unref (css);
-        sp_document_done (SP_DT_DOCUMENT(_desktop));
+        sp_document_done (sp_desktop_document(_desktop), SP_VERB_DIALOG_FILL_STROKE, 
+                      _("Paste fill"));
     }
 }
 
@@ -578,7 +680,8 @@ void SelectedStyle::on_stroke_paste() {
         sp_repr_css_set_property (css, "stroke", text.c_str());
         sp_desktop_set_style (_desktop, css);
         sp_repr_css_attr_unref (css);
-        sp_document_done (SP_DT_DOCUMENT(_desktop));
+        sp_document_done (sp_desktop_document(_desktop), SP_VERB_DIALOG_FILL_STROKE,
+                      _("Paste stroke"));
     }
 }
 
@@ -631,7 +734,8 @@ void SelectedStyle::on_fillstroke_swap() {
 
     sp_desktop_set_style (_desktop, css);
     sp_repr_css_attr_unref (css);
-    sp_document_done (SP_DT_DOCUMENT(_desktop));
+    sp_document_done (sp_desktop_document(_desktop), SP_VERB_DIALOG_FILL_STROKE, 
+                      _("Swap fill and stroke"));
 }
 
 void SelectedStyle::on_fill_edit() {
@@ -693,12 +797,13 @@ bool
 SelectedStyle::on_opacity_click(GdkEventButton *event)
 {
     if (event->button == 2) { // middle click
-        const char* opacity = _opacity_sb.get_value() < 0.5? "0.5" : (_opacity_sb.get_value() == 1? "0" : "1");
+        const char* opacity = _opacity_sb.get_value() < 50? "0.5" : (_opacity_sb.get_value() == 100? "0" : "1");
         SPCSSAttr *css = sp_repr_css_attr_new ();
         sp_repr_css_set_property (css, "opacity", opacity);
         sp_desktop_set_style (_desktop, css);
         sp_repr_css_attr_unref (css);
-        sp_document_done (SP_DT_DOCUMENT (_desktop));
+        sp_document_done (sp_desktop_document (_desktop), SP_VERB_DIALOG_FILL_STROKE,
+                      _("Change opacity"));
         return true;
     }
 
@@ -729,9 +834,11 @@ void SelectedStyle::on_popup_preset(int i) {
     Inkscape::CSSOStringStream os;
     os << w;
     sp_repr_css_set_property (css, "stroke-width", os.str().c_str());
+    // FIXME: update dash patterns!
     sp_desktop_set_style (_desktop, css, true);
     sp_repr_css_attr_unref (css);
-    sp_document_done (SP_DT_DOCUMENT(_desktop));
+    sp_document_done (sp_desktop_document(_desktop), SP_VERB_DIALOG_SWATCHES,
+                      _("Change stroke width"));
 }
 
 void
@@ -766,10 +873,22 @@ SelectedStyle::update()
             place->add(_na[i]);
             _tooltips.set_tip(*place, __na[i]);
             _mode[i] = SS_NA;
+            if ( _dropEnabled[i] ) {
+                gtk_drag_dest_unset( GTK_WIDGET((i==SS_FILL) ? _fill_place.gobj():_stroke_place.gobj()) );
+                _dropEnabled[i] = false;
+            }
             break;
         case QUERY_STYLE_SINGLE:
         case QUERY_STYLE_MULTIPLE_AVERAGED:
         case QUERY_STYLE_MULTIPLE_SAME: 
+            if ( !_dropEnabled[i] ) {
+                gtk_drag_dest_set( GTK_WIDGET( (i==SS_FILL) ? _fill_place.gobj():_stroke_place.gobj()),
+                                   GTK_DEST_DEFAULT_ALL,
+                                   ui_drop_target_entries,
+                                   nui_drop_target_entries,
+                                   GdkDragAction(GDK_ACTION_COPY | GDK_ACTION_MOVE) );
+                _dropEnabled[i] = true;
+            }
             SPIPaint *paint;
             if (i == SS_FILL) {
                 paint = &(query->fill);
@@ -792,24 +911,27 @@ SelectedStyle::update()
 
             } else if (paint->set && paint->type == SP_PAINT_TYPE_PAINTSERVER) {
                 SPPaintServer *server = (i == SS_FILL)? SP_STYLE_FILL_SERVER (query) : SP_STYLE_STROKE_SERVER (query);
-
-                Inkscape::XML::Node *srepr = SP_OBJECT_REPR(server);
-                _paintserver_id[i] += "url(#";
-                _paintserver_id[i] += srepr->attribute("id");
-                _paintserver_id[i] += ")";
-
-                if (SP_IS_LINEARGRADIENT (server)) {
-                    place->add(_lgradient[i]);
-                    _tooltips.set_tip(*place, __lgradient[i]);
-                    _mode[i] = SS_LGRADIENT;
-                } else if (SP_IS_RADIALGRADIENT (server)) {
-                    place->add(_rgradient[i]);
-                    _tooltips.set_tip(*place, __rgradient[i]);
-                    _mode[i] = SS_RGRADIENT;
-                } else if (SP_IS_PATTERN (server)) {
-                    place->add(_pattern[i]);
-                    _tooltips.set_tip(*place, __pattern[i]);
-                    _mode[i] = SS_PATTERN;
+                if ( server ) {
+                    Inkscape::XML::Node *srepr = SP_OBJECT_REPR(server);
+                    _paintserver_id[i] += "url(#";
+                    _paintserver_id[i] += srepr->attribute("id");
+                    _paintserver_id[i] += ")";
+
+                    if (SP_IS_LINEARGRADIENT (server)) {
+                        place->add(_lgradient[i]);
+                        _tooltips.set_tip(*place, __lgradient[i]);
+                        _mode[i] = SS_LGRADIENT;
+                    } else if (SP_IS_RADIALGRADIENT (server)) {
+                        place->add(_rgradient[i]);
+                        _tooltips.set_tip(*place, __rgradient[i]);
+                        _mode[i] = SS_RGRADIENT;
+                    } else if (SP_IS_PATTERN (server)) {
+                        place->add(_pattern[i]);
+                        _tooltips.set_tip(*place, __pattern[i]);
+                        _mode[i] = SS_PATTERN;
+                    }
+                } else {
+                    g_warning ("file %s: line %d: Unknown paint server", __FILE__, __LINE__);
                 }
 
             } else if (paint->set && paint->type == SP_PAINT_TYPE_NONE) {
@@ -841,21 +963,25 @@ SelectedStyle::update()
 
 // Now query opacity
     _tooltips.unset_tip(_opacity_place);
+    _tooltips.unset_tip(_opacity_sb);
 
     int result = sp_desktop_query_style (_desktop, query, QUERY_STYLE_PROPERTY_MASTEROPACITY);
 
     switch (result) {
     case QUERY_STYLE_NOTHING:
         _tooltips.set_tip(_opacity_place, _("Nothing selected"));
+        _tooltips.set_tip(_opacity_sb, _("Nothing selected"));
         _opacity_sb.set_sensitive(false);
         break;
     case QUERY_STYLE_SINGLE:
     case QUERY_STYLE_MULTIPLE_AVERAGED:
     case QUERY_STYLE_MULTIPLE_SAME:
-        _tooltips.set_tip(_opacity_place, _("Master opacity"));
+        _tooltips.set_tip(_opacity_place, _("Master opacity, %"));
+        _tooltips.set_tip(_opacity_sb, _("Master opacity, %"));
+        if (_opacity_blocked) break;
         _opacity_blocked = true;
         _opacity_sb.set_sensitive(true);
-        _opacity_adjustment.set_value(SP_SCALE24_TO_FLOAT(query->opacity.value));
+        _opacity_adjustment.set_value(SP_SCALE24_TO_FLOAT(query->opacity.value) * 100);
         _opacity_blocked = false;
         break;
     }
@@ -900,10 +1026,10 @@ SelectedStyle::update()
 }
 
 void SelectedStyle::opacity_0(void) {_opacity_sb.set_value(0);}
-void SelectedStyle::opacity_025(void) {_opacity_sb.set_value(0.25);}
-void SelectedStyle::opacity_05(void) {_opacity_sb.set_value(0.5);}
-void SelectedStyle::opacity_075(void) {_opacity_sb.set_value(0.75);}
-void SelectedStyle::opacity_1(void) {_opacity_sb.set_value(1.0);}
+void SelectedStyle::opacity_025(void) {_opacity_sb.set_value(25);}
+void SelectedStyle::opacity_05(void) {_opacity_sb.set_value(50);}
+void SelectedStyle::opacity_075(void) {_opacity_sb.set_value(75);}
+void SelectedStyle::opacity_1(void) {_opacity_sb.set_value(100);}
 
 void SelectedStyle::on_opacity_menu (Gtk::Menu *menu) {
 
@@ -920,25 +1046,25 @@ void SelectedStyle::on_opacity_menu (Gtk::Menu *menu) {
     }
     {
         Gtk::MenuItem *item = new Gtk::MenuItem;
-        item->add(*(new Gtk::Label("0.25", 0, 0)));
+        item->add(*(new Gtk::Label("25%", 0, 0)));
         item->signal_activate().connect(sigc::mem_fun(*this, &SelectedStyle::opacity_025 ));
         menu->add(*item);
     }
     {
         Gtk::MenuItem *item = new Gtk::MenuItem;
-        item->add(*(new Gtk::Label("0.5", 0, 0)));
+        item->add(*(new Gtk::Label("50%", 0, 0)));
         item->signal_activate().connect(sigc::mem_fun(*this, &SelectedStyle::opacity_05 ));
         menu->add(*item);
     }
     {
         Gtk::MenuItem *item = new Gtk::MenuItem;
-        item->add(*(new Gtk::Label("0.75", 0, 0)));
+        item->add(*(new Gtk::Label("75%", 0, 0)));
         item->signal_activate().connect(sigc::mem_fun(*this, &SelectedStyle::opacity_075 ));
         menu->add(*item);
     }
     {
         Gtk::MenuItem *item = new Gtk::MenuItem;
-        item->add(*(new Gtk::Label(_("1.0 (opaque)"), 0, 0)));
+        item->add(*(new Gtk::Label(_("100% (opaque)"), 0, 0)));
         item->signal_activate().connect(sigc::mem_fun(*this, &SelectedStyle::opacity_1 ));
         menu->add(*item);
     }
@@ -952,11 +1078,22 @@ void SelectedStyle::on_opacity_changed () {
     _opacity_blocked = true;
     SPCSSAttr *css = sp_repr_css_attr_new ();
     Inkscape::CSSOStringStream os;
-    os << CLAMP (_opacity_adjustment.get_value(), 0.0, 1.0);
+    os << CLAMP ((_opacity_adjustment.get_value() / 100), 0.0, 1.0);
     sp_repr_css_set_property (css, "opacity", os.str().c_str());
+    // FIXME: workaround for GTK breakage: display interruptibility sometimes results in GTK
+    // sending multiple value-changed events. As if when Inkscape interrupts redraw for main loop
+    // iterations, GTK discovers that this callback hasn't finished yet, and for some weird reason
+    // decides to add yet another value-changed event to the queue. Totally braindead if you ask
+    // me. As a result, scrolling the spinbutton once results in runaway change until it hits 1.0
+    // or 0.0. (And no, this is not a race with ::update, I checked that.)
+    // Sigh. So we disable interruptibility while we're setting the new value.
+    sp_canvas_force_full_redraw_after_interruptions(sp_desktop_canvas(_desktop), 0);
     sp_desktop_set_style (_desktop, css);
     sp_repr_css_attr_unref (css);
-    sp_document_maybe_done (SP_DT_DOCUMENT (_desktop), "fillstroke:opacity");
+    sp_document_maybe_done (sp_desktop_document (_desktop), "fillstroke:opacity", SP_VERB_DIALOG_FILL_STROKE,
+                      _("Change opacity"));
+    // resume interruptibility
+    sp_canvas_end_forced_full_redraws(sp_desktop_canvas(_desktop));
     spinbutton_defocus(GTK_OBJECT(_opacity_sb.gobj()));
     _opacity_blocked = false;
 }