Code

Replacing old multifunction widget with separate widget & model
authorjoncruz <joncruz@users.sourceforge.net>
Thu, 5 Apr 2007 00:35:22 +0000 (00:35 +0000)
committerjoncruz <joncruz@users.sourceforge.net>
Thu, 5 Apr 2007 00:35:22 +0000 (00:35 +0000)
src/Makefile_insert
src/ege-select-one-action.cpp [new file with mode: 0644]
src/ege-select-one-action.h [new file with mode: 0644]
src/helper/Makefile_insert
src/helper/unit-tracker.cpp [new file with mode: 0644]
src/helper/unit-tracker.h [new file with mode: 0644]
src/widgets/select-toolbar.cpp

index edf63b7b8774bbd4cd2f9678f6cfe57f0bc799ee..77bd4e7e5bfc5384031c5d4d4306fd76bf9162c4 100644 (file)
@@ -275,6 +275,8 @@ libinkpost_a_SOURCES =      \
        ege-adjustment-action.h \
        ege-output-action.cpp   \
        ege-output-action.h     \
+       ege-select-one-action.cpp       \
+       ege-select-one-action.h \
        fill-or-stroke.h        \
        filter-chemistry.cpp filter-chemistry.h \
        fixes.cpp \
diff --git a/src/ege-select-one-action.cpp b/src/ege-select-one-action.cpp
new file mode 100644 (file)
index 0000000..b059308
--- /dev/null
@@ -0,0 +1,407 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is EGE Select One Action.
+ *
+ * The Initial Developer of the Original Code is
+ * Jon A. Cruz.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/* Note: this file should be kept compilable as both .cpp and .c */
+
+#include <string.h>
+
+#include <gtk/gtkhbox.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtktoolitem.h>
+#include <gtk/gtkcombobox.h>
+#include <gtk/gtkcellrenderertext.h>
+#include <gtk/gtkcelllayout.h>
+#include <gtk/gtkradiomenuitem.h>
+
+#include "ege-select-one-action.h"
+
+enum {
+    CHANGED = 0,
+    LAST_SIGNAL};
+
+
+static void ege_select_one_action_class_init( EgeSelectOneActionClass* klass );
+static void ege_select_one_action_init( EgeSelectOneAction* action );
+static void ege_select_one_action_get_property( GObject* obj, guint propId, GValue* value, GParamSpec * pspec );
+static void ege_select_one_action_set_property( GObject* obj, guint propId, const GValue *value, GParamSpec* pspec );
+
+static void resync_active( EgeSelectOneAction* act, gint active );
+static void combo_changed_cb( GtkComboBox* widget, gpointer user_data );
+static void menu_toggled_cb( GtkWidget* obj, gpointer data );
+
+static GtkWidget* create_menu_item( GtkAction* action );
+static GtkWidget* create_tool_item( GtkAction* action );
+static void connect_proxy( GtkAction *action, GtkWidget *proxy );
+static void disconnect_proxy( GtkAction *action, GtkWidget *proxy );
+
+static GtkActionClass* gParentClass = 0;
+static guint signals[LAST_SIGNAL] = {0};
+static GQuark gDataName = 0;
+
+
+struct _EgeSelectOneActionPrivate
+{
+    GtkTreeModel* model;
+    gint active;
+    gint column;
+};
+
+#define EGE_SELECT_ONE_ACTION_GET_PRIVATE( o ) ( G_TYPE_INSTANCE_GET_PRIVATE( (o), EGE_SELECT_ONE_ACTION_TYPE, EgeSelectOneActionPrivate ) )
+
+enum {
+    PROP_MODEL = 1,
+    PROP_ACTIVE,
+    PROP_COLUMN
+};
+
+GType ege_select_one_action_get_type( void )
+{
+    static GType myType = 0;
+    if ( !myType ) {
+        static const GTypeInfo myInfo = {
+            sizeof( EgeSelectOneActionClass ),
+            NULL, /* base_init */
+            NULL, /* base_finalize */
+            (GClassInitFunc)ege_select_one_action_class_init,
+            NULL, /* class_finalize */
+            NULL, /* class_data */
+            sizeof( EgeSelectOneAction ),
+            0, /* n_preallocs */
+            (GInstanceInitFunc)ege_select_one_action_init,
+            NULL
+        };
+
+        myType = g_type_register_static( GTK_TYPE_ACTION, "EgeSelectOneAction", &myInfo, (GTypeFlags)0 );
+    }
+
+    return myType;
+}
+
+void ege_select_one_action_class_init( EgeSelectOneActionClass* klass )
+{
+    if ( klass ) {
+        gParentClass = GTK_ACTION_CLASS( g_type_class_peek_parent( klass ) );
+        GObjectClass* objClass = G_OBJECT_CLASS( klass );
+
+        gDataName = g_quark_from_string("ege-select1-action");
+
+        objClass->get_property = ege_select_one_action_get_property;
+        objClass->set_property = ege_select_one_action_set_property;
+
+        klass->parent_class.create_menu_item = create_menu_item;
+        klass->parent_class.create_tool_item = create_tool_item;
+        klass->parent_class.connect_proxy = connect_proxy;
+        klass->parent_class.disconnect_proxy = disconnect_proxy;
+
+        g_object_class_install_property( objClass,
+                                         PROP_MODEL,
+                                         g_param_spec_object( "model",
+                                                              "Tree Model",
+                                                              "Tree model of possible items",
+                                                              GTK_TYPE_TREE_MODEL,
+                                                              (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
+
+        g_object_class_install_property( objClass,
+                                         PROP_ACTIVE,
+                                         g_param_spec_int( "active",
+                                                           "Active Selection",
+                                                           "The index of the selected item",
+                                                           0, 20, 0,
+                                                           (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
+
+        g_object_class_install_property( objClass,
+                                         PROP_COLUMN,
+                                         g_param_spec_int( "column",
+                                                           "Display Column",
+                                                           "The column of the model that holds display strings",
+                                                           0, 20, 0,
+                                                           (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
+
+        signals[CHANGED] = g_signal_new( "changed",
+                                         G_TYPE_FROM_CLASS(klass),
+                                         G_SIGNAL_RUN_FIRST,
+                                         G_STRUCT_OFFSET(EgeSelectOneActionClass, changed),
+                                         NULL, NULL,
+                                         g_cclosure_marshal_VOID__VOID,
+                                         G_TYPE_NONE, 0);
+
+        g_type_class_add_private( klass, sizeof(EgeSelectOneActionClass) );
+    }
+}
+
+
+void ege_select_one_action_init( EgeSelectOneAction* action )
+{
+    action->private_data = EGE_SELECT_ONE_ACTION_GET_PRIVATE( action );
+    action->private_data->model = 0;
+
+/*     g_signal_connect( action, "notify", G_CALLBACK( fixup_labels ), NULL ); */
+}
+
+EgeSelectOneAction* ege_select_one_action_new( const gchar *name,
+                                               const gchar *label,
+                                               const gchar *tooltip,
+                                               const gchar *stock_id,
+                                               GtkTreeModel* model )
+{
+    GObject* obj = (GObject*)g_object_new( EGE_SELECT_ONE_ACTION_TYPE,
+                                           "name", name,
+                                           "label", label,
+                                           "tooltip", tooltip,
+                                           "stock_id", stock_id,
+                                           "model", model,
+                                           "active", 0,
+                                           NULL );
+
+    EgeSelectOneAction* action = EGE_SELECT_ONE_ACTION( obj );
+
+    return action;
+}
+
+
+gint ege_select_one_action_get_active( EgeSelectOneAction* action )
+{
+    g_return_val_if_fail( IS_EGE_SELECT_ONE_ACTION(action), 0 );
+    return action->private_data->active;
+}
+
+void ege_select_one_action_set_active( EgeSelectOneAction* action, gint val )
+{
+    g_object_set( G_OBJECT(action), "active", val, NULL );
+}
+
+gint ege_select_one_action_get_label_column( EgeSelectOneAction* action )
+{
+    g_return_val_if_fail( IS_EGE_SELECT_ONE_ACTION(action), 0 );
+    return action->private_data->column;
+}
+
+void ege_select_one_action_set_label_column( EgeSelectOneAction* action, gint col )
+{
+    g_object_set( G_OBJECT(action), "column", col, NULL );
+}
+
+
+void ege_select_one_action_get_property( GObject* obj, guint propId, GValue* value, GParamSpec * pspec )
+{
+    EgeSelectOneAction* action = EGE_SELECT_ONE_ACTION( obj );
+    switch ( propId ) {
+        case PROP_MODEL:
+            g_value_set_object( value, action->private_data->model );
+            break;
+
+        case PROP_ACTIVE:
+            g_value_set_int( value, action->private_data->active );
+            break;
+
+        case PROP_COLUMN:
+            g_value_set_int( value, action->private_data->column );
+            break;
+
+        default:
+            G_OBJECT_WARN_INVALID_PROPERTY_ID( obj, propId, pspec );
+    }
+}
+
+void ege_select_one_action_set_property( GObject* obj, guint propId, const GValue *value, GParamSpec* pspec )
+{
+    EgeSelectOneAction* action = EGE_SELECT_ONE_ACTION( obj );
+    switch ( propId ) {
+        case PROP_MODEL:
+        {
+            action->private_data->model = GTK_TREE_MODEL( g_value_get_object( value ) );
+        }
+        break;
+
+        case PROP_ACTIVE:
+        {
+            resync_active( action, g_value_get_int( value ) );
+        }
+        break;
+
+        case PROP_COLUMN:
+        {
+            action->private_data->column = g_value_get_int( value );
+        }
+        break;
+
+        default:
+            G_OBJECT_WARN_INVALID_PROPERTY_ID( obj, propId, pspec );
+    }
+}
+
+GtkWidget* create_menu_item( GtkAction* action )
+{
+    GtkWidget* item = 0;
+
+    if ( IS_EGE_SELECT_ONE_ACTION(action) ) {
+        EgeSelectOneAction* act = EGE_SELECT_ONE_ACTION( action );
+        gchar*  sss = 0;
+        gboolean valid = FALSE;
+        gint index = 0;
+        GtkTreeIter iter;
+        GSList* group = 0;
+        GtkWidget* subby = gtk_menu_new();
+
+        g_object_get( G_OBJECT(action), "label", &sss, NULL );
+
+        item = gtk_menu_item_new_with_label( sss );
+
+        valid = gtk_tree_model_get_iter_first( act->private_data->model, &iter );
+        while ( valid ) {
+            gchar* str = 0;
+            gtk_tree_model_get( act->private_data->model, &iter,
+                                act->private_data->column, &str,
+                                -1 );
+
+            GtkWidget *item = gtk_radio_menu_item_new_with_label( group, str );
+            group = gtk_radio_menu_item_get_group( GTK_RADIO_MENU_ITEM(item) );
+            gtk_menu_shell_append( GTK_MENU_SHELL(subby), item );
+            g_object_set_qdata( G_OBJECT(item), gDataName, act );
+
+            gtk_check_menu_item_set_active( GTK_CHECK_MENU_ITEM(item), index == act->private_data->active );
+
+            g_free(str);
+
+            g_signal_connect( G_OBJECT(item), "toggled", G_CALLBACK(menu_toggled_cb), GINT_TO_POINTER(index) );
+
+            index++;
+            valid = gtk_tree_model_iter_next( act->private_data->model, &iter );
+        }
+
+        gtk_menu_item_set_submenu( GTK_MENU_ITEM(item), subby );
+        gtk_widget_show_all( subby );
+
+        g_free(sss);
+    } else {
+        item = gParentClass->create_menu_item( action );
+    }
+
+    return item;
+}
+
+GtkWidget* create_tool_item( GtkAction* action )
+{
+    GtkWidget* item = 0;
+
+    if ( IS_EGE_SELECT_ONE_ACTION(action) && EGE_SELECT_ONE_ACTION(action)->private_data->model )
+    {
+        EgeSelectOneAction* act = EGE_SELECT_ONE_ACTION(action);
+        item = GTK_WIDGET( gtk_tool_item_new() );
+
+        GtkWidget* normal = gtk_combo_box_new_with_model( act->private_data->model );
+
+        GtkCellRenderer * renderer = gtk_cell_renderer_text_new();
+        gtk_cell_layout_pack_start( GTK_CELL_LAYOUT(normal), renderer, TRUE );
+        gtk_cell_layout_set_attributes( GTK_CELL_LAYOUT(normal), renderer, "text", act->private_data->column, (gchar*)0);
+
+        gtk_combo_box_set_active( GTK_COMBO_BOX(normal), act->private_data->active );
+
+        g_signal_connect( G_OBJECT(normal), "changed", G_CALLBACK(combo_changed_cb), action );
+
+        gtk_container_add( GTK_CONTAINER(item), normal );
+
+        gtk_widget_show_all( item );
+    } else {
+        item = gParentClass->create_tool_item( action );
+    }
+
+    return item;
+}
+
+void connect_proxy( GtkAction *action, GtkWidget *proxy )
+{
+    gParentClass->connect_proxy( action, proxy );
+}
+
+void disconnect_proxy( GtkAction *action, GtkWidget *proxy )
+{
+    gParentClass->disconnect_proxy( action, proxy );
+}
+
+
+void resync_active( EgeSelectOneAction* act, gint active )
+{
+    if ( act->private_data->active != active ) {
+        act->private_data->active = active;
+        GSList* proxies = gtk_action_get_proxies( GTK_ACTION(act) );
+        while ( proxies ) {
+            if ( GTK_IS_TOOL_ITEM(proxies->data) ) {
+                /* Search for the things we built up in create_tool_item() */
+                GList* children = gtk_container_get_children( GTK_CONTAINER(proxies->data) );
+                if ( children && children->data ) {
+                    GtkComboBox* combo = GTK_COMBO_BOX(children->data);
+                    if ( gtk_combo_box_get_active(combo) != active ) {
+                        gtk_combo_box_set_active( combo, active );
+                    }
+                }
+            } else if ( GTK_IS_MENU_ITEM(proxies->data) ) {
+                GtkWidget* subMenu = gtk_menu_item_get_submenu( GTK_MENU_ITEM(proxies->data) );
+                GList* children = gtk_container_get_children( GTK_CONTAINER(subMenu) );
+                if ( children && (g_list_length(children) > (guint)active) ) {
+                    gpointer data = g_list_nth_data( children, active );
+                    gtk_check_menu_item_set_active( GTK_CHECK_MENU_ITEM(data), TRUE );
+                }
+            }
+
+            proxies = g_slist_next( proxies );
+        }
+
+        g_signal_emit( G_OBJECT(act), signals[CHANGED], 0);
+    }
+}
+
+void combo_changed_cb( GtkComboBox* widget, gpointer user_data )
+{
+    EgeSelectOneAction* act = EGE_SELECT_ONE_ACTION(user_data);
+    gint newActive = gtk_combo_box_get_active(widget);
+    if (newActive != act->private_data->active) {
+        g_object_set( G_OBJECT(act), "active", newActive, NULL );
+    }
+}
+
+void menu_toggled_cb( GtkWidget* obj, gpointer data )
+{
+    GtkCheckMenuItem* item = GTK_CHECK_MENU_ITEM(obj);
+    EgeSelectOneAction* act = (EgeSelectOneAction*)g_object_get_qdata( G_OBJECT(obj), gDataName );
+    gint newActive = GPOINTER_TO_INT(data);
+    if ( gtk_check_menu_item_get_active(item) && (newActive != act->private_data->active) ) {
+        g_object_set( G_OBJECT(act), "active", newActive, NULL );
+    }
+}
diff --git a/src/ege-select-one-action.h b/src/ege-select-one-action.h
new file mode 100644 (file)
index 0000000..f617cc5
--- /dev/null
@@ -0,0 +1,91 @@
+#ifndef SEEN_EGE_SELECT_ONE_ACTION
+#define SEEN_EGE_SELECT_ONE_ACTION
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is EGE Select One Action.
+ *
+ * The Initial Developer of the Original Code is
+ * Jon A. Cruz.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/* Note: this file should be kept compilable as both .cpp and .c */
+
+#include <glib.h>
+#include <gtk/gtkaction.h>
+#include <gtk/gtktreemodel.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+
+#define EGE_SELECT_ONE_ACTION_TYPE                ( ege_select_one_action_get_type() )
+#define EGE_SELECT_ONE_ACTION( obj )              ( G_TYPE_CHECK_INSTANCE_CAST( (obj), EGE_SELECT_ONE_ACTION_TYPE, EgeSelectOneAction) )
+#define EGE_SELECT_ONE_ACTION_CLASS( klass )      ( G_TYPE_CHECK_CLASS_CAST( (klass), EGE_SELECT_ONE_ACTION_TYPE, EgeSelectOneActionClass) )
+#define IS_EGE_SELECT_ONE_ACTION( obj )           ( G_TYPE_CHECK_INSTANCE_TYPE( (obj), EGE_SELECT_ONE_ACTION_TYPE) )
+#define IS_EGE_SELECT_ONE_ACTION_CLASS( klass )   ( G_TYPE_CHECK_CLASS_TYPE( (klass), EGE_SELECT_ONE_ACTION_TYPE) )
+#define EGE_SELECT_ONE_ACTION_GET_CLASS( obj )    ( G_TYPE_INSTANCE_GET_CLASS( (obj), EGE_SELECT_ONE_ACTION_TYPE, EgeSelectOneActionClass) )
+
+typedef struct _EgeSelectOneAction      EgeSelectOneAction;
+typedef struct _EgeSelectOneActionClass EgeSelectOneActionClass;
+typedef struct _EgeSelectOneActionPrivate EgeSelectOneActionPrivate;
+
+struct _EgeSelectOneAction
+{
+    GtkAction action;
+    EgeSelectOneActionPrivate *private_data;
+};
+
+struct _EgeSelectOneActionClass
+{
+    GtkActionClass parent_class;
+    void (*changed) (EgeSelectOneAction* action);
+};
+
+GType ege_select_one_action_get_type( void );
+
+EgeSelectOneAction* ege_select_one_action_new( const gchar *name,
+                                               const gchar *label,
+                                               const gchar *tooltip,
+                                               const gchar *stock_id,
+                                               GtkTreeModel* model );
+
+gint ege_select_one_action_get_active( EgeSelectOneAction* action );
+void ege_select_one_action_set_active( EgeSelectOneAction* action, gint val );
+
+gint ege_select_one_action_get_label_column( EgeSelectOneAction* action );
+void ege_select_one_action_set_label_column( EgeSelectOneAction* action, gint col );
+
+G_END_DECLS
+
+#endif /* SEEN_EGE_SELECT_ONE_ACTION */
index 9f061c090f3d41e191d73de01d91e54eea81ac68..f781332d00ebfe37d63fe30e15738be008ff50dd 100644 (file)
@@ -26,6 +26,8 @@ helper_libspchelp_a_SOURCES = \
        helper/stlport.h        \
        helper/unit-menu.cpp    \
        helper/unit-menu.h      \
+       helper/unit-tracker.cpp \
+       helper/unit-tracker.h   \
        helper/units.cpp        \
        helper/units.h  \
        helper/window.cpp       \
diff --git a/src/helper/unit-tracker.cpp b/src/helper/unit-tracker.cpp
new file mode 100644 (file)
index 0000000..989be24
--- /dev/null
@@ -0,0 +1,264 @@
+/*
+ * Inkscape::UnitTracker - Simple mediator to synchronize changes to a set
+ *   of possible units
+ *
+ * Authors:
+ *   Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2007 Jon A. Cruz
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include <gtk/gtkliststore.h>
+
+#include "unit-tracker.h"
+#include "ege-select-one-action.h"
+
+namespace Inkscape {
+
+enum {
+    COLUMN_STRING,
+    COLUMN_SPUNIT,
+    N_COLUMNS
+};
+
+UnitTracker::UnitTracker( guint bases ) :
+    _active(0),
+    _isUpdating(false),
+    _activeUnit(0),
+    _store(0),
+    _unitList(0),
+    _actionList(0),
+    _adjList(0),
+    _priorValues()
+{
+    _store = gtk_list_store_new( N_COLUMNS, G_TYPE_STRING, G_TYPE_POINTER );
+    setBase( bases );
+}
+
+UnitTracker::~UnitTracker()
+{
+    if ( _unitList ) {
+        sp_unit_free_list( _unitList );
+    }
+
+    // Unhook weak references to GtkActions
+    while ( _actionList ) {
+        g_signal_handlers_disconnect_by_func( G_OBJECT(_actionList->data), (gpointer)_unitChangedCB, this );
+        g_object_weak_unref( G_OBJECT(_actionList->data), _actionFinalizedCB, this );
+        _actionList = g_slist_delete_link( _actionList, _actionList );
+    }
+
+    // Unhook wek references to GtkAdjustments
+    while ( _adjList ) {
+        g_object_weak_unref( G_OBJECT(_adjList->data), _adjustmentFinalizedCB, this );
+        _adjList = g_slist_delete_link( _adjList, _adjList );
+    }
+}
+
+void UnitTracker::setBase( guint bases )
+{
+    GtkTreeIter iter;
+    _unitList = sp_unit_get_list( bases );
+    for ( GSList* cur = _unitList; cur; cur = g_slist_next(cur) ) {
+        SPUnit* unit = static_cast<SPUnit*>(cur->data);
+        gtk_list_store_append( _store, &iter );
+        gtk_list_store_set( _store, &iter, COLUMN_STRING, unit->abbr, COLUMN_SPUNIT, unit, -1 );
+    }
+    gint count = gtk_tree_model_iter_n_children( GTK_TREE_MODEL(_store), 0 );
+    if ( (count > 0) && (_active > count) ) {
+        _setActive( count - 1 );
+    }
+}
+
+void UnitTracker::addUnit( SPUnitId id, gint index )
+{
+    GtkTreeIter iter;
+    const SPUnit* percentUnit = &sp_unit_get_by_id( id );
+    gtk_list_store_insert( _store, &iter, index );
+    gtk_list_store_set( _store, &iter, COLUMN_STRING, percentUnit->abbr, COLUMN_SPUNIT, percentUnit, -1 );
+}
+
+bool UnitTracker::isUpdating() const
+{
+    return _isUpdating;
+}
+
+SPUnit const* UnitTracker::getActiveUnit() const
+{
+    return _activeUnit;
+}
+
+void UnitTracker::setActiveUnit( SPUnit const *unit )
+{
+    if ( unit ) {
+        GtkTreeIter iter;
+        int index = 0;
+        gboolean found = gtk_tree_model_get_iter_first( GTK_TREE_MODEL(_store), &iter );
+        while ( found ) {
+            SPUnit* storedUnit = 0;
+            gtk_tree_model_get( GTK_TREE_MODEL(_store), &iter, COLUMN_SPUNIT, &storedUnit, -1 );
+            if ( storedUnit && (storedUnit->unit_id == unit->unit_id) ) {
+                _setActive(index);
+                break;
+            }
+
+            found = gtk_tree_model_iter_next( GTK_TREE_MODEL(_store), &iter );
+            index++;
+        }
+    }
+}
+
+void UnitTracker::addAdjustment( GtkAdjustment* adj )
+{
+    if ( !g_slist_find( _adjList, adj ) ) {
+        g_object_weak_ref( G_OBJECT(adj), _adjustmentFinalizedCB, this );
+        _adjList = g_slist_append( _adjList, adj );
+    }
+}
+
+void UnitTracker::setFullVal( GtkAdjustment* adj, gdouble val )
+{
+    _priorValues[adj] = val;
+}
+
+GtkAction* UnitTracker::createAction( gchar const* name, gchar const* label, gchar const* tooltip )
+{
+    EgeSelectOneAction* act1 = ege_select_one_action_new( name, label, tooltip, NULL, GTK_TREE_MODEL(_store) );
+    ege_select_one_action_set_label_column( act1, COLUMN_STRING );
+    if ( _active ) {
+        ege_select_one_action_set_active( act1, _active );
+    }
+
+    g_object_weak_ref( G_OBJECT(act1), _actionFinalizedCB, this );
+    g_signal_connect( G_OBJECT(act1), "changed", G_CALLBACK( _unitChangedCB ), this );
+    _actionList = g_slist_append( _actionList, act1 );
+
+    return GTK_ACTION(act1);
+}
+
+void UnitTracker::_unitChangedCB( GtkAction* action, gpointer data )
+{
+    if ( action && data ) {
+        EgeSelectOneAction* act = EGE_SELECT_ONE_ACTION(action);
+        gint active = ege_select_one_action_get_active( act );
+        UnitTracker* self = reinterpret_cast<UnitTracker*>(data);
+        self->_setActive(active);
+    }
+}
+
+void UnitTracker::_actionFinalizedCB( gpointer data, GObject *where_the_object_was )
+{
+    if ( data && where_the_object_was ) {
+        UnitTracker* self = reinterpret_cast<UnitTracker*>(data);
+        self->_actionFinalized( where_the_object_was );
+    }
+}
+
+void UnitTracker::_adjustmentFinalizedCB( gpointer data, GObject *where_the_object_was )
+{
+    if ( data && where_the_object_was ) {
+        UnitTracker* self = reinterpret_cast<UnitTracker*>(data);
+        self->_adjustmentFinalized( where_the_object_was );
+    }
+}
+
+void UnitTracker::_actionFinalized( GObject *where_the_object_was )
+{
+    GSList* target = g_slist_find( _actionList, where_the_object_was );
+    if ( target ) {
+        _actionList = g_slist_remove( _actionList, where_the_object_was );
+    } else {
+        g_warning("Received a finalization callback for unknown object %p", where_the_object_was );
+    }
+}
+
+void UnitTracker::_adjustmentFinalized( GObject *where_the_object_was )
+{
+    GSList* target = g_slist_find( _adjList, where_the_object_was );
+    if ( target ) {
+        _adjList = g_slist_remove( _adjList, where_the_object_was );
+    } else {
+        g_warning("Received a finalization callback for unknown object %p", where_the_object_was );
+    }
+}
+
+void UnitTracker::_setActive( gint active )
+{
+    if ( active != _active ) {
+        gint oldActive = _active;
+
+        GtkTreeIter iter;
+        gboolean found = gtk_tree_model_iter_nth_child( GTK_TREE_MODEL(_store), &iter, NULL, oldActive );
+        if ( found ) {
+            SPUnit* unit = 0;
+            gtk_tree_model_get( GTK_TREE_MODEL(_store), &iter, COLUMN_SPUNIT, &unit, -1 );
+
+            found = gtk_tree_model_iter_nth_child( GTK_TREE_MODEL(_store), &iter, NULL, active );
+            if ( found ) {
+                SPUnit* newUnit = 0;
+                gtk_tree_model_get( GTK_TREE_MODEL(_store), &iter, COLUMN_SPUNIT, &newUnit, -1 );
+                _activeUnit = newUnit;
+
+                if ( _adjList ) {
+                    _fixupAdjustments( unit, newUnit );
+                }
+
+            } else {
+                g_warning("Did not find new unit");
+            }
+        } else {
+            g_warning("Did not find old unit");
+        }
+
+        _active = active;
+
+        for ( GSList* cur = _actionList; cur; cur = g_slist_next(cur) ) {
+            if ( IS_EGE_SELECT_ONE_ACTION( cur->data ) ) {
+                EgeSelectOneAction* act = EGE_SELECT_ONE_ACTION( cur->data );
+                ege_select_one_action_set_active( act, active );
+            }
+        }
+    }
+}
+
+void UnitTracker::_fixupAdjustments( SPUnit const* oldUnit, SPUnit const *newUnit )
+{
+    _isUpdating = true;
+    for ( GSList* cur = _adjList; cur; cur = g_slist_next(cur) ) {
+        GtkAdjustment* adj = GTK_ADJUSTMENT(cur->data);
+        gdouble oldVal = gtk_adjustment_get_value(adj);
+        gdouble val = oldVal;
+
+        if ((oldUnit->base == SP_UNIT_ABSOLUTE || oldUnit->base == SP_UNIT_DEVICE)
+            && (newUnit->base == SP_UNIT_DIMENSIONLESS))
+        {
+            val = 1.0 / newUnit->unittobase;
+            _priorValues[adj] = sp_units_get_pixels( oldVal, *oldUnit );
+        } else if ((oldUnit->base == SP_UNIT_DIMENSIONLESS)
+                   && (newUnit->base == SP_UNIT_ABSOLUTE || newUnit->base == SP_UNIT_DEVICE)) {
+            if ( _priorValues.find(adj) != _priorValues.end() ) {
+                val = sp_pixels_get_units( _priorValues[adj], *newUnit );
+            }
+        } else {
+            val = sp_convert_distance_full( oldVal, *oldUnit, *newUnit );
+        }
+
+        gtk_adjustment_set_value( adj, val );
+    }
+    _isUpdating = false;
+}
+
+}
+
+/*
+  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:encoding=utf-8:textwidth=99 :
diff --git a/src/helper/unit-tracker.h b/src/helper/unit-tracker.h
new file mode 100644 (file)
index 0000000..1832430
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * Inkscape::UnitTracker - Simple mediator to synchronize changes to a set
+ *   of possible units
+ *
+ * Authors:
+ *   Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2007 Jon A. Cruz
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifndef SEEN_INKSCAPE_UNIT_TRACKER_H
+#define SEEN_INKSCAPE_UNIT_TRACKER_H
+
+#include <map>
+
+#include <gtk/gtkaction.h>
+
+#include "helper/units.h"
+
+namespace Inkscape {
+
+class UnitTracker
+{
+public:
+    UnitTracker( guint bases = (SP_UNIT_ABSOLUTE | SP_UNIT_DEVICE) );
+    ~UnitTracker();
+
+    void setBase( guint bases ); // SP_UNIT_ABSOLUTE | SP_UNIT_DEVICE
+    void addUnit( SPUnitId id, gint index );
+
+    bool isUpdating() const;
+
+    void setActiveUnit( SPUnit const *unit );
+    SPUnit const* getActiveUnit() const;
+
+    void addAdjustment( GtkAdjustment* adj );
+    void setFullVal( GtkAdjustment* adj, gdouble val );
+
+    GtkAction* createAction( gchar const* name, gchar const* label, gchar const* tooltip );
+
+private:
+    static void _unitChangedCB( GtkAction* action, gpointer data );
+    static void _actionFinalizedCB( gpointer data, GObject *where_the_object_was );
+    static void _adjustmentFinalizedCB( gpointer data, GObject *where_the_object_was );
+    void _setActive( gint index );
+    void _fixupAdjustments( SPUnit const* oldUnit, SPUnit const *newUnit );
+    void _actionFinalized( GObject *where_the_object_was );
+    void _adjustmentFinalized( GObject *where_the_object_was );
+
+    gint _active;
+    bool _isUpdating;
+    SPUnit* _activeUnit;
+    GtkListStore* _store;
+    GSList* _unitList;
+    GSList* _actionList;
+    GSList* _adjList;
+    std::map <GtkAdjustment*, gdouble> _priorValues;
+};
+
+}
+
+#endif // SEEN_INKSCAPE_UNIT_TRACKER_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:encoding=utf-8:textwidth=99 :
index 0d64409cc1ec959fba58b8d7ef4d63f528c58b02..30ce00ca214f210a529142aa0c950b0c1688b758 100644 (file)
@@ -4,6 +4,7 @@
  * Authors:
  *   Lauris Kaplinski <lauris@kaplinski.com>
  *   bulia byak <buliabyak@users.sf.net>
+ *   Jon A. Cruz <jon@joncruz.org>
  *
  * Copyright (C) 2003-2005 authors
  *
@@ -15,6 +16,7 @@
 #endif
 
 #include <gtk/gtk.h>
+#include <gtk/gtkaction.h>
 
 #include "widgets/button.h"
 #include "widgets/spw-utilities.h"
 #include "sp-item-transform.h"
 #include "message-stack.h"
 #include "display/sp-canvas.h"
+#include "ege-select-one-action.h"
+#include "helper/unit-tracker.h"
+
+using Inkscape::UnitTracker;
 
 static void
 sp_selection_layout_widget_update(SPWidget *spw, Inkscape::Selection *sel)
@@ -60,23 +66,24 @@ sp_selection_layout_widget_update(SPWidget *spw, Inkscape::Selection *sel)
     if ( sel && !sel->isEmpty() ) {
         NR::Maybe<NR::Rect> const bbox(sel->bounds());
         if ( bbox && !bbox->isEmpty() ) {
-            GtkWidget *us = (GtkWidget *) gtk_object_get_data(GTK_OBJECT(spw), "units");
-            SPUnit const &unit = *sp_unit_selector_get_unit(SP_UNIT_SELECTOR(us));
+            UnitTracker *tracker = reinterpret_cast<UnitTracker*>(gtk_object_get_data(GTK_OBJECT(spw), "tracker"));
+            SPUnit const &unit = *tracker->getActiveUnit();
+
+            struct { char const *key; double val; } const keyval[] = {
+                { "X", bbox->min()[X] },
+                { "Y", bbox->min()[Y] },
+                { "width", bbox->extent(X) },
+                { "height", bbox->extent(Y) }
+            };
 
             if (unit.base == SP_UNIT_DIMENSIONLESS) {
-                char const * const keys[] = {"X", "Y", "width", "height"};
                 double const val = 1. / unit.unittobase;
-                for (unsigned i = 0; i < G_N_ELEMENTS(keys); ++i) {
-                    GtkAdjustment *a = (GtkAdjustment *) gtk_object_get_data(GTK_OBJECT(spw), keys[i]);
+                for (unsigned i = 0; i < G_N_ELEMENTS(keyval); ++i) {
+                    GtkAdjustment *a = (GtkAdjustment *) gtk_object_get_data(GTK_OBJECT(spw), keyval[i].key);
                     gtk_adjustment_set_value(a, val);
+                    tracker->setFullVal( a, keyval[i].val );
                 }
             } else {
-                struct { char const *key; double val; } const keyval[] = {
-                    { "X", bbox->min()[X] },
-                    { "Y", bbox->min()[Y] },
-                    { "width", bbox->extent(X) },
-                    { "height", bbox->extent(Y) }
-                };
                 for (unsigned i = 0; i < G_N_ELEMENTS(keyval); ++i) {
                     GtkAdjustment *a = (GtkAdjustment *) gtk_object_get_data(GTK_OBJECT(spw), keyval[i].key);
                     gtk_adjustment_set_value(a, sp_pixels_get_units(keyval[i].val, unit));
@@ -123,9 +130,8 @@ sp_object_layout_any_value_changed(GtkAdjustment *adj, SPWidget *spw)
         return;
     }
 
-    GtkWidget *us = (GtkWidget *) gtk_object_get_data(GTK_OBJECT(spw), "units");
-    SPUnit const &unit = *sp_unit_selector_get_unit(SP_UNIT_SELECTOR(us));
-    if (sp_unit_selector_update_test(SP_UNIT_SELECTOR(us))) {
+    UnitTracker *tracker = reinterpret_cast<UnitTracker*>(gtk_object_get_data(GTK_OBJECT(spw), "tracker"));
+    if ( !tracker || tracker->isUpdating() ) {
         /*
          * When only units are being changed, don't treat changes
          * to adjuster values as object changes.
@@ -145,34 +151,33 @@ sp_object_layout_any_value_changed(GtkAdjustment *adj, SPWidget *spw)
         return;
     }
 
-    gdouble x0, y0, x1, y1, xrel, yrel;
-    GtkAdjustment *a_w;
-    GtkAdjustment *a_h;
+    gdouble x0 = 0;
+    gdouble y0 = 0;
+    gdouble x1 = 0;
+    gdouble y1 = 0;
+    gdouble xrel = 0;
+    gdouble yrel = 0;
+    SPUnit const &unit = *tracker->getActiveUnit();
+
+    GtkAdjustment* a_x = (GtkAdjustment *)gtk_object_get_data( GTK_OBJECT(spw), "X" );
+    GtkAdjustment* a_y = (GtkAdjustment *)gtk_object_get_data( GTK_OBJECT(spw), "Y" );
+    GtkAdjustment* a_w = (GtkAdjustment *)gtk_object_get_data( GTK_OBJECT(spw), "width" );
+    GtkAdjustment* a_h = (GtkAdjustment *)gtk_object_get_data( GTK_OBJECT(spw), "height" );
 
     if (unit.base == SP_UNIT_ABSOLUTE || unit.base == SP_UNIT_DEVICE) {
-        GtkAdjustment *a;
-        a = (GtkAdjustment *) gtk_object_get_data(GTK_OBJECT(spw), "X");
-        x0 = sp_units_get_pixels (a->value, unit);
-        a = (GtkAdjustment *) gtk_object_get_data(GTK_OBJECT(spw), "Y");
-        y0 = sp_units_get_pixels (a->value, unit);
-        a_w = (GtkAdjustment *) gtk_object_get_data(GTK_OBJECT(spw), "width");
+        x0 = sp_units_get_pixels (a_x->value, unit);
+        y0 = sp_units_get_pixels (a_y->value, unit);
         x1 = x0 + sp_units_get_pixels (a_w->value, unit);
         xrel = sp_units_get_pixels (a_w->value, unit) / bbox->extent(NR::X);
-        a_h = (GtkAdjustment *) gtk_object_get_data(GTK_OBJECT(spw), "height");
         y1 = y0 + sp_units_get_pixels (a_h->value, unit);
         yrel = sp_units_get_pixels (a_h->value, unit) / bbox->extent(NR::Y);
     } else {
-        GtkAdjustment *a;
-        a = (GtkAdjustment *) gtk_object_get_data(GTK_OBJECT(spw), "X");
-        double const x0_propn = a->value * unit.unittobase;
+        double const x0_propn = a_x->value * unit.unittobase;
         x0 = bbox->min()[NR::X] * x0_propn;
-        a = (GtkAdjustment *) gtk_object_get_data(GTK_OBJECT(spw), "Y");
-        double const y0_propn = a->value * unit.unittobase;
+        double const y0_propn = a_y->value * unit.unittobase;
         y0 = y0_propn * bbox->min()[NR::Y];
-        a_w = (GtkAdjustment *) gtk_object_get_data(GTK_OBJECT(spw), "width");
         xrel = a_w->value * unit.unittobase;
         x1 = x0 + xrel * bbox->extent(NR::X);
-        a_h = (GtkAdjustment *) gtk_object_get_data(GTK_OBJECT(spw), "height");
         yrel = a_h->value * unit.unittobase;
         y1 = y0 + yrel * bbox->extent(NR::Y);
     }
@@ -206,9 +211,9 @@ sp_object_layout_any_value_changed(GtkAdjustment *adj, SPWidget *spw)
     // the value was changed by the user, the difference will be at least that much; otherwise it's
     // just rounding difference between the spinbox value and actual value, so no action is
     // performed
-    char const * const actionkey = ( mh > 5e-4 ? "selector:toolbar:move:horizontal" : 
-                                     sh > 5e-4 ? "selector:toolbar:scale:horizontal" : 
-                                     mv > 5e-4 ? "selector:toolbar:move:vertical" : 
+    char const * const actionkey = ( mh > 5e-4 ? "selector:toolbar:move:horizontal" :
+                                     sh > 5e-4 ? "selector:toolbar:scale:horizontal" :
+                                     mv > 5e-4 ? "selector:toolbar:move:vertical" :
                                      sv > 5e-4 ? "selector:toolbar:scale:vertical" : NULL );
 
     if (actionkey != NULL) {
@@ -222,7 +227,7 @@ sp_object_layout_any_value_changed(GtkAdjustment *adj, SPWidget *spw)
         NR::Matrix scaler = get_scale_transform_with_stroke (*bbox, strokewidth, transform_stroke, x0, y0, x1, y1);
 
         sp_selection_apply_affine(selection, scaler);
-        sp_document_maybe_done (document, actionkey, SP_VERB_CONTEXT_SELECT, 
+        sp_document_maybe_done (document, actionkey, SP_VERB_CONTEXT_SELECT,
                                 _("Transform by toolbar"));
 
         // defocus spinbuttons by moving focus to the canvas, unless "stay" is on
@@ -236,7 +241,7 @@ sp_object_layout_any_value_changed(GtkAdjustment *adj, SPWidget *spw)
 }
 
 GtkWidget *
-sp_select_toolbox_spinbutton(gchar *label, gchar *data, float lower_limit, GtkWidget *us, GtkWidget *spw, gchar *tooltip, gboolean altx)
+sp_select_toolbox_spinbutton(gchar *label, gchar *data, float lower_limit, UnitTracker* tracker, GtkWidget *spw, gchar *tooltip, gboolean altx)
 {
     GtkTooltips *tt = gtk_tooltips_new();
 
@@ -248,7 +253,9 @@ sp_select_toolbox_spinbutton(gchar *label, gchar *data, float lower_limit, GtkWi
     gtk_container_add(GTK_CONTAINER(hb), l);
 
     GtkObject *a = gtk_adjustment_new(0.0, lower_limit, 1e6, SPIN_STEP, SPIN_PAGE_STEP, SPIN_PAGE_STEP);
-    sp_unit_selector_add_adjustment(SP_UNIT_SELECTOR(us), GTK_ADJUSTMENT(a));
+    if ( tracker ) {
+        tracker->addAdjustment( GTK_ADJUSTMENT(a) );
+    }
     gtk_object_set_data(GTK_OBJECT(spw), data, a);
 
     GtkWidget *sb = gtk_spin_button_new(GTK_ADJUSTMENT(a), SPIN_STEP, 3);
@@ -268,79 +275,6 @@ sp_select_toolbox_spinbutton(gchar *label, gchar *data, float lower_limit, GtkWi
     return hb;
 }
 
-static gboolean aux_set_unit(SPUnitSelector *,
-                             SPUnit const *old,
-                             SPUnit const *new_units,
-                             GObject *dlg)
-{
-    SPDesktop *desktop = SP_ACTIVE_DESKTOP;
-
-    if (!desktop) {
-        return FALSE;
-    }
-
-    Inkscape::Selection *selection = sp_desktop_selection(desktop);
-
-    if (selection->isEmpty())
-        return FALSE;
-
-    if ((old->base == SP_UNIT_ABSOLUTE || old->base == SP_UNIT_DEVICE)
-        && (new_units->base == SP_UNIT_DIMENSIONLESS))
-    {
-
-        NR::Maybe<NR::Rect> bbox = selection->bounds();
-        if (!bbox) {
-            return FALSE;
-        }
-
-        /* Absolute to percentage */
-        g_object_set_data(dlg, "update", GUINT_TO_POINTER(TRUE));
-
-        GtkAdjustment *ax = GTK_ADJUSTMENT(g_object_get_data(dlg, "X"));
-        GtkAdjustment *ay = GTK_ADJUSTMENT(g_object_get_data(dlg, "Y"));
-        GtkAdjustment *aw = GTK_ADJUSTMENT(g_object_get_data(dlg, "width"));
-        GtkAdjustment *ah = GTK_ADJUSTMENT(g_object_get_data(dlg, "height"));
-
-        double const x = sp_units_get_pixels (ax->value, *old);
-        double const y = sp_units_get_pixels (ay->value, *old);
-        double const w = sp_units_get_pixels (aw->value, *old);
-        double const h = sp_units_get_pixels (ah->value, *old);
-
-        gtk_adjustment_set_value(ax, fabs(bbox->min()[NR::X]) > 1e-6? 100.0 * x / bbox->min()[NR::X] : 100.0);
-        gtk_adjustment_set_value(ay, fabs(bbox->min()[NR::Y]) > 1e-6? 100.0 * y / bbox->min()[NR::Y] : 100.0);
-        gtk_adjustment_set_value(aw, fabs(bbox->extent(NR::X)) > 1e-6? 100.0 * w / bbox->extent(NR::X) : 100.0);
-        gtk_adjustment_set_value(ah, fabs(bbox->extent(NR::Y)) > 1e-6? 100.0 * h / bbox->extent(NR::Y) : 100.0);
-
-        g_object_set_data(dlg, "update", GUINT_TO_POINTER(FALSE));
-        return TRUE;
-    } else if ((old->base == SP_UNIT_DIMENSIONLESS)
-               && (new_units->base == SP_UNIT_ABSOLUTE || new_units->base == SP_UNIT_DEVICE)) {
-
-        NR::Maybe<NR::Rect> bbox = selection->bounds();
-        if (!bbox) {
-            return FALSE;
-        }
-
-        /* Percentage to absolute */
-        g_object_set_data(dlg, "update", GUINT_TO_POINTER(TRUE));
-
-        GtkAdjustment *ax = GTK_ADJUSTMENT(g_object_get_data(dlg, "X"));
-        GtkAdjustment *ay = GTK_ADJUSTMENT(g_object_get_data(dlg, "Y"));
-        GtkAdjustment *aw = GTK_ADJUSTMENT(g_object_get_data(dlg, "width"));
-        GtkAdjustment *ah = GTK_ADJUSTMENT(g_object_get_data(dlg, "height"));
-
-        gtk_adjustment_set_value(ax, sp_pixels_get_units(0.01 * ax->value * bbox->min()[NR::X], *new_units));
-        gtk_adjustment_set_value(ay, sp_pixels_get_units(0.01 * ay->value * bbox->min()[NR::Y], *new_units));
-        gtk_adjustment_set_value(aw, sp_pixels_get_units(0.01 * aw->value * bbox->extent(NR::X), *new_units));
-        gtk_adjustment_set_value(ah, sp_pixels_get_units(0.01 * ah->value * bbox->extent(NR::Y), *new_units));
-
-        g_object_set_data(dlg, "update", GUINT_TO_POINTER(FALSE));
-        return TRUE;
-    }
-
-    return FALSE;
-}
-
 // toggle button callbacks and updaters
 
 static void toggle_stroke (GtkWidget *button, gpointer data) {
@@ -399,6 +333,15 @@ static void toggle_lock (GtkWidget *button, gpointer data) {
     }
 }
 
+static void destroy_tracker( GtkObject* obj, gpointer /*user_data*/ )
+{
+    UnitTracker *tracker = reinterpret_cast<UnitTracker*>(gtk_object_get_data(obj, "tracker"));
+    if ( tracker ) {
+        delete tracker;
+        gtk_object_set_data( obj, "tracker", 0 );
+    }
+}
+
 GtkWidget *
 sp_select_toolbox_new(SPDesktop *desktop)
 {
@@ -432,29 +375,31 @@ sp_select_toolbox_new(SPDesktop *desktop)
     gtk_object_set_data(GTK_OBJECT(spw), "frame", vb);
 
     // Create the units menu.
-    GtkWidget *us = sp_unit_selector_new(SP_UNIT_ABSOLUTE | SP_UNIT_DEVICE);
-    sp_unit_selector_setsize(us, AUX_OPTION_MENU_WIDTH, AUX_OPTION_MENU_HEIGHT);
-    sp_unit_selector_add_unit(SP_UNIT_SELECTOR(us), &sp_unit_get_by_id(SP_UNIT_PERCENT), 0);
-    sp_unit_selector_set_unit (SP_UNIT_SELECTOR(us), sp_desktop_namedview(desktop)->doc_units);
-    g_signal_connect(G_OBJECT(us), "set_unit", G_CALLBACK(aux_set_unit), spw);
+    UnitTracker* tracker = new UnitTracker( SP_UNIT_ABSOLUTE | SP_UNIT_DEVICE );
+    tracker->addUnit( SP_UNIT_PERCENT, 0 );
+    tracker->setActiveUnit( sp_desktop_namedview(desktop)->doc_units );
+
+    gtk_object_set_data( GTK_OBJECT(spw), "tracker", tracker );
+    g_signal_connect( G_OBJECT(spw), "destroy", G_CALLBACK(destroy_tracker), spw );
+
 
     // four spinbuttons
 
     gtk_container_add(GTK_CONTAINER(vb),
-                      //TRANSLATORS: only translate "string" in "context|string". 
+                      //TRANSLATORS: only translate "string" in "context|string".
                       // For more details, see http://developer.gnome.org/doc/API/2.0/glib/glib-I18N.html#Q-:CAPS
-                      sp_select_toolbox_spinbutton(_("select_toolbar|X"), "X", -1e6, us, spw, _("Horizontal coordinate of selection"), TRUE));
+                      sp_select_toolbox_spinbutton(_("select_toolbar|X"), "X", -1e6, tracker, spw, _("Horizontal coordinate of selection"), TRUE));
     aux_toolbox_space(vb, AUX_BETWEEN_SPINBUTTONS);
     gtk_container_add(GTK_CONTAINER(vb),
-                      //TRANSLATORS: only translate "string" in "context|string". 
+                      //TRANSLATORS: only translate "string" in "context|string".
                       // For more details, see http://developer.gnome.org/doc/API/2.0/glib/glib-I18N.html#Q-:CAPS
-                      sp_select_toolbox_spinbutton(_("select_toolbar|Y"), "Y", -1e6, us, spw, _("Vertical coordinate of selection"), FALSE));
+                      sp_select_toolbox_spinbutton(_("select_toolbar|Y"), "Y", -1e6, tracker, spw, _("Vertical coordinate of selection"), FALSE));
     aux_toolbox_space(vb, AUX_BETWEEN_BUTTON_GROUPS);
 
     gtk_container_add(GTK_CONTAINER(vb),
-                      //TRANSLATORS: only translate "string" in "context|string". 
+                      //TRANSLATORS: only translate "string" in "context|string".
                       // For more details, see http://developer.gnome.org/doc/API/2.0/glib/glib-I18N.html#Q-:CAPS
-                      sp_select_toolbox_spinbutton(_("select_toolbar|W"), "width", 1e-3, us, spw, _("Width of selection"), FALSE));
+                      sp_select_toolbox_spinbutton(_("select_toolbar|W"), "width", 1e-3, tracker, spw, _("Width of selection"), FALSE));
 
     // lock toggle
     GtkWidget *lockbox = gtk_vbox_new(TRUE, 0);
@@ -470,16 +415,20 @@ sp_select_toolbox_new(SPDesktop *desktop)
     g_signal_connect_after (G_OBJECT (lock), "clicked", G_CALLBACK (toggle_lock), desktop);
 
     gtk_container_add(GTK_CONTAINER(vb),
-                      //TRANSLATORS: only translate "string" in "context|string". 
+                      //TRANSLATORS: only translate "string" in "context|string".
                       // For more details, see http://developer.gnome.org/doc/API/2.0/glib/glib-I18N.html#Q-:CAPS
-                      sp_select_toolbox_spinbutton(_("select_toolbar|H"), "height", 1e-3, us, spw, _("Height of selection"), FALSE));
+                      sp_select_toolbox_spinbutton(_("select_toolbar|H"), "height", 1e-3, tracker, spw, _("Height of selection"), FALSE));
 
     aux_toolbox_space(vb, 2);
 
     // Add the units menu.
-    gtk_widget_show(us);
-    gtk_container_add(GTK_CONTAINER(vb), us);
-    gtk_object_set_data(GTK_OBJECT(spw), "units", us);
+    {
+        GtkAction* act = tracker->createAction( "UnitAction", _("Units"), _("") );
+
+        GtkWidget* normal = gtk_action_create_tool_item( act );
+        gtk_widget_show( normal );
+        gtk_container_add( GTK_CONTAINER(vb), normal );
+    }
 
     // Set font size.
     sp_set_font_size_smaller (vb);