Code

Merge from trunk.
authorJon A. Cruz <jon@joncruz.org>
Sat, 18 Dec 2010 06:03:56 +0000 (22:03 -0800)
committerJon A. Cruz <jon@joncruz.org>
Sat, 18 Dec 2010 06:03:56 +0000 (22:03 -0800)
22 files changed:
share/extensions/dxf_input.py
share/extensions/gcodetools_all_in_one.inx
share/extensions/gcodetools_area.inx
share/extensions/measure.inx
src/dialogs/text-edit.cpp
src/dialogs/xml-tree.cpp
src/ui/dialog/dialog-manager.cpp
src/ui/dialog/fill-and-stroke.cpp
src/ui/tool/control-point-selection.cpp
src/ui/tool/multi-path-manipulator.cpp
src/ui/tool/node.cpp
src/ui/tool/node.h
src/ui/tool/path-manipulator.cpp
src/ui/tool/path-manipulator.h
src/ui/tool/transform-handle-set.cpp
src/ui/widget/filter-effect-chooser.cpp
src/ui/widget/object-composite-settings.cpp
src/util/Makefile_insert
src/util/ege-appear-time-tracker.cpp [new file with mode: 0644]
src/util/ege-appear-time-tracker.h [new file with mode: 0644]
src/verbs.cpp
src/widgets/desktop-widget.cpp

index b46477de16bf581366e880e4caa08cb948d7f7de..ee52b8c30b447a061776e5bce298c2a17c4e2c3a 100644 (file)
@@ -88,6 +88,10 @@ def export_SPLINE():
             path = 'M %f,%f Q %f,%f %f,%f' % (vals[groups['10']][0], vals[groups['20']][0], vals[groups['10']][1], vals[groups['20']][1], vals[groups['10']][2], vals[groups['20']][2])
             attribs = {'d': path, 'style': style}
             inkex.etree.SubElement(layer, 'path', attribs)
+        if not (vals[groups['70']][0] & 3) and len(vals[groups['10']]) == 5 and len(vals[groups['20']]) == 5:
+            path = 'M %f,%f Q %f,%f %f,%f Q %f,%f %f,%f' % (vals[groups['10']][0], vals[groups['20']][0], vals[groups['10']][1], vals[groups['20']][1], vals[groups['10']][2], vals[groups['20']][2], vals[groups['10']][3], vals[groups['20']][3], vals[groups['10']][4], vals[groups['20']][4])
+            attribs = {'d': path, 'style': style}
+            inkex.etree.SubElement(layer, 'path', attribs)
 
 def export_CIRCLE():
     # mandatory group codes : (10, 20, 40) (x, y, radius)
@@ -322,7 +326,7 @@ parser.add_option("--font", action="store", type="string", dest="font", default=
 parser.add_option("--tab", action="store", type="string", dest="tab", default="Options")
 parser.add_option("--inputhelp", action="store", type="string", dest="inputhelp", default="")
 (options, args) = parser.parse_args(inkex.sys.argv[1:])
-doc = inkex.etree.parse(StringIO('<svg xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"></svg>'))
+doc = inkex.etree.parse(StringIO('<svg xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" width="%s" height="%s"></svg>' % (210*90/25.4, 297*90/25.4)))
 desc = inkex.etree.SubElement(doc.getroot(), 'desc', {})
 defs = inkex.etree.SubElement(doc.getroot(), 'defs', {})
 marker = inkex.etree.SubElement(defs, 'marker', {'id': 'DistanceX', 'orient': 'auto', 'refX': '0.0', 'refY': '0.0', 'style': 'overflow:visible'})
index 0f566fc7b257f6367edee579519921bf2c391329..85d7cfbee79d3c98c1017d82848ffc4d9ad4a909 100644 (file)
@@ -15,7 +15,7 @@ The segment will be split into two segments if the distance between path's segme
 </_param>
                </page>
 
-               <page name='area' _gui-text='Area'>     
+               <page name='area' msgctxt="gcodetools extension" _gui-text='Area'>      
                        <param name="max-area-curves" type="int" min="0" max="1000" _gui-text="Maximum area cutting curves:">100</param>
                        <param name="area-inkscape-radius" type="float" min="-1000" max="1000" _gui-text="Area width:">-10</param>
 
index 718ae0d9f6cccea69b47db277829ac3e25540dea..53ceeafaa4e2875bbd6e7f5a47fbb43230a63dc7 100644 (file)
@@ -6,7 +6,7 @@
        <dependency type="executable" location="extensions">inkex.py</dependency>
        <param name='active-tab' type="notebook">
 
-               <page name='area' _gui-text='Area'>     
+               <page name='area' msgctxt="gcodetools extension" _gui-text='Area'>      
                        <param name="max-area-curves" type="int" min="0" max="1000" _gui-text="Maximum area cutting curves:">100</param>
                        <param name="area-inkscape-radius" type="float" min="-1000" max="1000" _gui-text="Area width:">-10</param>
 
index 264e33ab667693dcf81c1eda498ab3a247869434..63a919806d6ef38b1d6602bcf78aee02e23903da 100644 (file)
@@ -8,7 +8,7 @@
                <page name="measure" _gui-text="Measure">
                        <param name="type" type="enum" _gui-text="Measurement Type: ">
                                <_item value="length">Length</_item>
-                               <_item value="area">Area</_item>
+                               <_item msgctxt="measure extension" value="area">Area</_item>
                        </param>
                        <param name="fontsize" type="int" min="1" max="1000" _gui-text="Font size (px):">12</param>
                        <param name="offset" type="float" min="-10000" max="10000" _gui-text="Offset (px):">-6</param>
index 61f56e3f7fec8ac12aa32c4d3e1434937700173a..d741e2de096a98bda3b7b5a9d3254cd7f3bae644 100644 (file)
@@ -49,8 +49,10 @@ extern "C" {
 #include "svg/css-ostringstream.h"
 #include "widgets/icon.h"
 #include <xml/repr.h>
+#include "util/ege-appear-time-tracker.h"
 
 using Inkscape::DocumentUndo;
+using ege::AppearTimeTracker;
 
 #define VB_MARGIN 4
 #define MIN_ONSCREEN_DISTANCE 50
@@ -139,6 +141,9 @@ text_view_focus_out (GtkWidget */*w*/, GdkEventKey */*event*/, gpointer data)
 void
 sp_text_edit_dialog (void)
 {
+    bool wantTiming = Inkscape::Preferences::get()->getBool("/dialogs/debug/trackAppear", false);
+    GTimer *timer = wantTiming ? g_timer_new() : 0;
+
     if (!dlg) {
 
         gchar title[500];
@@ -457,6 +462,13 @@ sp_text_edit_dialog (void)
         sp_text_edit_dialog_read_selection (dlg, TRUE, TRUE);
     }
 
+    if ( wantTiming ) {
+        // Time tracker takes ownership of the timer.
+        AppearTimeTracker *tracker = new AppearTimeTracker(timer, GTK_WIDGET(dlg), "DialogText");
+        tracker->setAutodelete(true);
+        timer = 0;
+    }
+
     gtk_window_present ((GtkWindow *) dlg);
 
 } // end of sp_text_edit_dialog()
index c90cde490ba02dfd0812b1c57a19ddadfbe2934f..d25bdad723550b097117e8b346b2014b93241f8d 100644 (file)
 #include "../widgets/sp-xmlview-attr-list.h"
 #include "../widgets/sp-xmlview-content.h"
 #include "../widgets/sp-xmlview-tree.h"
+#include "util/ege-appear-time-tracker.h"
 
 using Inkscape::DocumentUndo;
+using ege::AppearTimeTracker;
 
 #define MIN_ONSCREEN_DISTANCE 50
 
@@ -178,11 +180,13 @@ void attr_reset_context(gint attr)
 void sp_xml_tree_dialog()
 {
     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
-
     if (!desktop) {
         return;
     }
 
+    bool wantTiming = Inkscape::Preferences::get()->getBool("/dialogs/debug/trackAppear", false);
+    GTimer *timer = wantTiming ? g_timer_new() : 0;
+
     if (dlg == NULL)
     { // very long block
 
@@ -604,6 +608,13 @@ void sp_xml_tree_dialog()
         tree_reset_context();
     } // end of if (dlg == NULL)
 
+    if ( wantTiming ) {
+        // Time tracker takes ownership of the timer.
+        AppearTimeTracker *tracker = new AppearTimeTracker(timer, GTK_WIDGET(dlg), "DialogXMLEditor");
+        tracker->setAutodelete(true);
+        timer = 0;
+    }
+
     gtk_window_present((GtkWindow *) dlg);
 
     g_assert(desktop != NULL);
index ff31c91c4910178b544ec84608ff7f76fa59037e..0c49690cce5b70d585cf003335271f410491b267 100644 (file)
@@ -42,6 +42,7 @@
 #include "ui/dialog/floating-behavior.h"
 #include "ui/dialog/dock-behavior.h"
 //#include "ui/dialog/print-colors-preview-dialog.h"
+#include "util/ege-appear-time-tracker.h"
 #include "preferences.h"
 
 #ifdef ENABLE_SVG_FONTS
@@ -232,10 +233,23 @@ void DialogManager::showDialog(gchar const *name) {
  * Shows the named dialog, creating it if necessary.
  */
 void DialogManager::showDialog(GQuark name) {
-    Dialog *dialog=getDialog(name);
-    if (dialog) {
+    bool wantTiming = Inkscape::Preferences::get()->getBool("/dialogs/debug/trackAppear", false);
+    GTimer *timer = (wantTiming) ? g_timer_new() : 0; // if needed, must be created/started before getDialog()
+    Dialog *dialog = getDialog(name);
+    if ( dialog ) {
+        if ( wantTiming ) {
+            gchar const * nameStr = g_quark_to_string(name);
+            ege::AppearTimeTracker *tracker = new ege::AppearTimeTracker(timer, dialog->gobj(), nameStr);
+            tracker->setAutodelete(true);
+            timer = 0;
+        }
         dialog->present();
     }
+
+    if ( timer ) {
+        g_timer_destroy(timer);
+        timer = 0;
+    }
 }
 
 } // namespace Dialog
index 0c234003e210397eab05d96b5237bd28e00d599e..19bcadc0056fe4e3a8e4521057319efaacb1fd2e 100644 (file)
@@ -54,7 +54,7 @@ FillAndStroke::FillAndStroke()
 
     contents->pack_start(_notebook, true, true);
 
-    _notebook.append_page(_page_fill, _createPageTabLabel(_("Fill"), INKSCAPE_ICON_OBJECT_FILL));
+    _notebook.append_page(_page_fill, _createPageTabLabel(_("_Fill"), INKSCAPE_ICON_OBJECT_FILL));
     _notebook.append_page(_page_stroke_paint, _createPageTabLabel(_("Stroke _paint"), INKSCAPE_ICON_OBJECT_STROKE));
     _notebook.append_page(_page_stroke_style, _createPageTabLabel(_("Stroke st_yle"), INKSCAPE_ICON_OBJECT_STROKE_STYLE));
 
index 91e0bc2c242d367a8b1fc12496593f779f012b22..baa53f76ea920b8e4d208393b25184e4f11e679b 100644 (file)
@@ -290,7 +290,7 @@ void ControlPointSelection::_pointDragged(Geom::Point &new_pos, GdkEventMotion *
 {
     Geom::Point abs_delta = new_pos - _original_positions[_grabbed_point];
     double fdist = Geom::distance(_original_positions[_grabbed_point], _original_positions[_farthest_point]);
-    if (held_alt(*event) && fdist > 0) {
+    if (held_only_alt(*event) && fdist > 0) {
         // Sculpting
         for (iterator i = _points.begin(); i != _points.end(); ++i) {
             SelectableControlPoint *cur = (*i);
index ef1c764bbb35c0f35161a42093fa59e2c0c1dad8..082ac194b962e85a2501f99120814896689fbd27 100644 (file)
@@ -210,7 +210,78 @@ void MultiPathManipulator::selectSubpaths()
 
 void MultiPathManipulator::shiftSelection(int dir)
 {
-    invokeForAll(&PathManipulator::shiftSelection, dir);
+    if (empty()) return;
+
+    // 1. find last selected node
+    // 2. select the next node; if the last node or nothing is selected,
+    //    select first node
+    MapType::iterator last_i;
+    SubpathList::iterator last_j;
+    NodeList::iterator last_k;
+    bool anything_found = false;
+
+    for (MapType::iterator i = _mmap.begin(); i != _mmap.end(); ++i) {
+        SubpathList &sp = i->second->subpathList();
+        for (SubpathList::iterator j = sp.begin(); j != sp.end(); ++j) {
+            for (NodeList::iterator k = (*j)->begin(); k != (*j)->end(); ++k) {
+                if (k->selected()) {
+                    last_i = i;
+                    last_j = j;
+                    last_k = k;
+                    anything_found = true;
+                    // when tabbing backwards, we want the first node
+                    if (dir == -1) goto exit_loop;
+                }
+            }
+        }
+    }
+    exit_loop:
+
+    // NOTE: we should not assume the _selection contains only nodes
+    // in future it might also contain handles and other types of control points
+    // this is why we use a flag instead in the loop above, instead of calling
+    // selection.empty()
+    if (!anything_found) {
+        // select first / last node
+        // this should never fail because there must be at least 1 non-empty manipulator
+        if (dir == 1) {
+            _selection.insert((*_mmap.begin()->second->subpathList().begin())->begin().ptr());
+        } else {
+            _selection.insert((--(*--(--_mmap.end())->second->subpathList().end())->end()).ptr());
+        }
+        return;
+    }
+
+    // three levels deep - w00t!
+    if (dir == 1) {
+        if (++last_k == (*last_j)->end()) {
+            // here, last_k points to the node to be selected
+            ++last_j;
+            if (last_j == last_i->second->subpathList().end()) {
+                ++last_i;
+                if (last_i == _mmap.end()) {
+                    last_i = _mmap.begin();
+                }
+                last_j = last_i->second->subpathList().begin();
+            }
+            last_k = (*last_j)->begin();
+        }
+    } else {
+        if (!last_k || last_k == (*last_j)->begin()) {
+            if (last_j == last_i->second->subpathList().begin()) {
+                if (last_i == _mmap.begin()) {
+                    last_i = _mmap.end();
+                }
+                --last_i;
+                last_j = last_i->second->subpathList().end();
+            }
+            --last_j;
+            last_k = (*last_j)->end();
+        }
+        --last_k;
+    }
+    _selection.clear();
+    _selection.insert(last_k.ptr());
 }
 
 void MultiPathManipulator::invertSelectionInSubpaths()
@@ -695,6 +766,14 @@ void MultiPathManipulator::_commit(CommitEvent cps)
         reason = _("Scale nodes vertically");
         key = "node:scale:y";
         break;
+    case COMMIT_MOUSE_SKEW_X:
+        reason = _("Skew nodes horizontally");
+        key = "node:skew:x";
+        break;
+    case COMMIT_MOUSE_SKEW_Y:
+        reason = _("Skew nodes vertically");
+        key = "node:skew:y";
+        break;
     case COMMIT_FLIP_X:
         reason = _("Flip nodes horizontally");
         break;
index 12d04dd2b74541e3e4b804c73e600000f4ae16a2..fea02d399f64c01cbd30bfcee4948736eadaa1b4 100644 (file)
@@ -113,12 +113,29 @@ void Handle::move(Geom::Point const &new_pos)
     Handle *towards_second = node_towards ? node_towards->handleToward(_parent) : NULL;
 
     if (Geom::are_near(new_pos, _parent->position())) {
-        // The handle becomes degenerate. If the segment between it and the node
+        // The handle becomes degenerate.
+        // Adjust node type as necessary.
+        if (other->isDegenerate()) {
+            // If both handles become degenerate, convert to parent cusp node
+            _parent->setType(NODE_CUSP, false);
+        } else {
+            // Only 1 handle becomes degenerate
+            switch (_parent->type()) {
+            case NODE_AUTO:
+            case NODE_SYMMETRIC:
+                _parent->setType(NODE_SMOOTH, false);
+                break;
+            default:
+                // do nothing for other node types
+                break;
+            }
+        }
+        // If the segment between the handle and the node
         // in its direction becomes linear and there are smooth nodes
         // at its ends, make their handles colinear with the segment
-        if (towards && towards->isDegenerate()) {
+        if (towards && towards_second->isDegenerate()) {
             if (node_towards->type() == NODE_SMOOTH) {
-                towards_second->setDirection(*_parent, *node_towards);
+                towards->setDirection(*_parent, *node_towards);
             }
             if (_parent->type() == NODE_SMOOTH) {
                 other->setDirection(*node_towards, *_parent);
@@ -160,6 +177,7 @@ void Handle::move(Geom::Point const &new_pos)
 
 void Handle::setPosition(Geom::Point const &p)
 {
+    Geom::Point old_pos = position();
     ControlPoint::setPosition(p);
     sp_ctrlline_set_coords(SP_CTRLLINE(_handle_line), _parent->position(), position());
 
@@ -167,15 +185,12 @@ void Handle::setPosition(Geom::Point const &p)
     if (Geom::are_near(position(), _parent->position()))
         _degenerate = true;
     else _degenerate = false;
+
     if (_parent->_handles_shown && _parent->visible() && !_degenerate) {
         setVisible(true);
     } else {
         setVisible(false);
     }
-    // If both handles become degenerate, convert to parent cusp node
-    if (_parent->isDegenerate()) {
-        _parent->setType(NODE_CUSP, false);
-    }
 }
 
 void Handle::setLength(double len)
@@ -187,7 +202,7 @@ void Handle::setLength(double len)
 
 void Handle::retract()
 {
-    setPosition(_parent->position());
+    move(_parent->position());
 }
 
 void Handle::setDirection(Geom::Point const &from, Geom::Point const &to)
@@ -212,6 +227,35 @@ char const *Handle::handle_type_to_localized_string(NodeType type)
     }
 }
 
+bool Handle::_eventHandler(GdkEvent *event)
+{
+    switch (event->type)
+    {
+    case GDK_KEY_PRESS:
+        switch (shortcut_key(event->key))
+        {
+        case GDK_s:
+        case GDK_S:
+            if (held_only_shift(event->key) && _parent->_type == NODE_CUSP) {
+                // when Shift+S is pressed when hovering over a handle belonging to a cusp node,
+                // hold this handle in place; otherwise process normally
+                // this handle is guaranteed not to be degenerate
+                other()->move(_parent->position() - (position() - _parent->position()));
+                _parent->setType(NODE_SMOOTH, false);
+                _parent->_pm().update(); // magic triple combo to add undo event
+                _parent->_pm().writeXML();
+                _parent->_pm()._commit(_("Change node type"));
+                return true;
+            }
+            break;
+        default: break;
+        }
+    default: break;
+    }
+
+    return ControlPoint::_eventHandler(event);
+}
+
 bool Handle::grabbed(GdkEventMotion *)
 {
     _saved_other_pos = other()->position();
@@ -226,6 +270,7 @@ void Handle::dragged(Geom::Point &new_pos, GdkEventMotion *event)
     Geom::Point origin = _last_drag_origin();
     SnapManager &sm = _desktop->namedview->snap_manager;
     bool snap = sm.someSnapperMightSnap();
+    boost::optional<Inkscape::Snapper::SnapConstraint> ctrl_constraint;
 
     // with Alt, preserve length
     if (held_alt(*event)) {
@@ -241,16 +286,23 @@ void Handle::dragged(Geom::Point &new_pos, GdkEventMotion *event)
         // note: if snapping to the original position is only desired in the original
         // direction of the handle, change to Ray instead of Line
         Geom::Line original_line(parent_pos, origin);
+        Geom::Line perp_line(parent_pos, parent_pos + Geom::rot90(origin - parent_pos));
         Geom::Point snap_pos = parent_pos + Geom::constrain_angle(
             Geom::Point(0,0), new_pos - parent_pos, snaps, Geom::Point(1,0));
         Geom::Point orig_pos = original_line.pointAt(original_line.nearestPoint(new_pos));
+        Geom::Point perp_pos = perp_line.pointAt(perp_line.nearestPoint(new_pos));
 
-        if (Geom::distance(snap_pos, new_pos) < Geom::distance(orig_pos, new_pos)) {
-            new_pos = snap_pos;
-        } else {
-            new_pos = orig_pos;
+        Geom::Point result = snap_pos;
+        ctrl_constraint = Inkscape::Snapper::SnapConstraint(parent_pos, parent_pos - snap_pos);
+        if (Geom::distance(orig_pos, new_pos) < Geom::distance(result, new_pos)) {
+            result = orig_pos;
+            ctrl_constraint = Inkscape::Snapper::SnapConstraint(parent_pos, parent_pos - orig_pos);
         }
-        snap = false;
+        if (Geom::distance(perp_pos, new_pos) < Geom::distance(result, new_pos)) {
+            result = perp_pos;
+            ctrl_constraint = Inkscape::Snapper::SnapConstraint(parent_pos, parent_pos - perp_pos);
+        }
+        new_pos = result;
     }
 
     std::vector<Inkscape::SnapCandidatePoint> unselected;
@@ -264,13 +316,20 @@ void Handle::dragged(Geom::Point &new_pos, GdkEventMotion *event)
         }
         sm.setupIgnoreSelection(_desktop, true, &unselected);
 
-        Node *node_away = (this == &_parent->_front ? _parent->_prev() : _parent->_next());
+        Node *node_away = _parent->nodeAwayFrom(this);
         if (_parent->type() == NODE_SMOOTH && Node::_is_line_segment(_parent, node_away)) {
             Inkscape::Snapper::SnapConstraint cl(_parent->position(),
                 _parent->position() - node_away->position());
             Inkscape::SnappedPoint p;
             p = sm.constrainedSnap(Inkscape::SnapCandidatePoint(new_pos, SNAPSOURCE_NODE_HANDLE), cl);
             new_pos = p.getPoint();
+        } else if (ctrl_constraint) {
+            // NOTE: this is subtly wrong.
+            // We should get all possible constraints and snap along them using
+            // multipleConstrainedSnaps, instead of first snapping to angle and the to objects
+            Inkscape::SnappedPoint p;
+            p = sm.constrainedSnap(Inkscape::SnapCandidatePoint(new_pos, SNAPSOURCE_NODE_HANDLE), *ctrl_constraint);
+            new_pos = p.getPoint();
         } else {
             sm.freeSnapReturnByRef(new_pos, SNAPSOURCE_NODE_HANDLE);
         }
index 0194f5053176936f7ee0ef62543cb02fa32d2403..b5d4d88f2045c424edfa7882ba427386434cd1fa 100644 (file)
@@ -102,6 +102,7 @@ public:
 protected:
     Handle(NodeSharedData const &data, Geom::Point const &initial_pos, Node *parent);
 
+    virtual bool _eventHandler(GdkEvent *event);
     virtual void dragged(Geom::Point &, GdkEventMotion *);
     virtual bool grabbed(GdkEventMotion *);
     virtual void ungrabbed(GdkEventButton *);
index 5ae9c4137255c7ab9cc1db66f7cb700045190448..ea7f3412d0de9fae0f60e6f434972fe4c8330525 100644 (file)
@@ -215,7 +215,7 @@ void PathManipulator::clear()
 /** Select all nodes in subpaths that have something selected. */
 void PathManipulator::selectSubpaths()
 {
-    for (std::list<SubpathPtr>::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
+    for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
         NodeList::iterator sp_start = (*i)->begin(), sp_end = (*i)->end();
         for (NodeList::iterator j = sp_start; j != sp_end; ++j) {
             if (j->selected()) {
@@ -229,63 +229,6 @@ void PathManipulator::selectSubpaths()
     }
 }
 
-/** Move the selection forward or backward by one node in each subpath, based on the sign
- * of the parameter. */
-void PathManipulator::shiftSelection(int dir)
-{
-    if (dir == 0) return;
-    if (_num_selected == 0) {
-        // select the first node of the path.
-        SubpathList::iterator s = _subpaths.begin();
-        if (s == _subpaths.end()) return;
-        NodeList::iterator n = (*s)->begin();
-        if (n != (*s)->end())
-            _selection.insert(n.ptr());
-        return;
-    }
-    // We cannot do any tricks here, like iterating in different directions based on
-    // the sign and only setting the selection of nodes behind us, because it would break
-    // for closed paths.
-    for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
-        std::deque<bool> sels; // I hope this is specialized for bools!
-        unsigned num = 0;
-        
-        for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
-            sels.push_back(j->selected());
-            _selection.erase(j.ptr());
-            ++num;
-        }
-        if (num == 0) continue; // should never happen! zero-node subpaths are not allowed
-
-        num = 0;
-        // In closed subpath, shift the selection cyclically. In an open one,
-        // let the selection 'slide into nothing' at ends.
-        if (dir > 0) {
-            if ((*i)->closed()) {
-                bool last = sels.back();
-                sels.pop_back();
-                sels.push_front(last);
-            } else {
-                sels.push_front(false);
-            }
-        } else {
-            if ((*i)->closed()) {
-                bool first = sels.front();
-                sels.pop_front();
-                sels.push_back(first);
-            } else {
-                sels.push_back(false);
-                num = 1;
-            }
-        }
-
-        for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
-            if (sels[num]) _selection.insert(j.ptr());
-            ++num;
-        }
-    }
-}
-
 /** Invert selection in the selected subpaths. */
 void PathManipulator::invertSelectionInSubpaths()
 {
@@ -332,22 +275,31 @@ void PathManipulator::duplicateNodes()
                 NodeList::iterator k = j.next();
                 Node *n = new Node(_multi_path_manipulator._path_data.node_data, *j);
 
-                // Move the new node to the bottom of the Z-order. This way you can drag all
-                // nodes that were selected before this operation without deselecting
-                // everything because there is a new node above.
-                n->sink();
+                if (k) {
+                    // Move the new node to the bottom of the Z-order. This way you can drag all
+                    // nodes that were selected before this operation without deselecting
+                    // everything because there is a new node above.
+                    n->sink();
+                }
 
                 n->front()->setPosition(*j->front());
                 j->front()->retract();
                 j->setType(NODE_CUSP, false);
                 (*i)->insert(k, n);
 
-                // We need to manually call the selection change callback to refresh
-                // the handle display correctly.
-                // This call changes num_selected, but we call this once for a selected node
-                // and once for an unselected node, so in the end the number stays correct.
-                _selectionChanged(j.ptr(), true);
-                _selectionChanged(n, false);
+                if (k) {
+                    // We need to manually call the selection change callback to refresh
+                    // the handle display correctly.
+                    // This call changes num_selected, but we call this once for a selected node
+                    // and once for an unselected node, so in the end the number stays correct.
+                    _selectionChanged(j.ptr(), true);
+                    _selectionChanged(n, false);
+                } else {
+                    // select the new end node instead of the node just before it
+                    _selection.erase(j.ptr());
+                    _selection.insert(n);
+                    break; // this was the end node, nothing more to do
+                }
             }
         }
     }
index 87b88fc77495bfb4c141bb2a936eec1f1d968d53..8a0167e595111ff38785cce37c9fe1bbc8e859d5 100644 (file)
@@ -65,7 +65,6 @@ public:
     SPPath *item() { return _path; }
 
     void selectSubpaths();
-    void shiftSelection(int dir);
     void invertSelectionInSubpaths();
 
     void insertNodes();
@@ -94,6 +93,9 @@ public:
     NodeList::iterator extremeNode(NodeList::iterator origin, bool search_selected,
         bool search_unselected, bool closest);
 
+    // this is necessary for Tab-selection in MultiPathManipulator
+    SubpathList &subpathList() { return _subpaths; }
+
     static bool is_item_type(void *item);
 private:
     typedef NodeList Subpath;
index ef93a3767c32777722c67854f5fd6d19c66e7a14..cafd592a3622017f42f62fafdd7253bd1b17f461 100644 (file)
@@ -18,6 +18,8 @@
 #include "desktop-handles.h"
 #include "display/sodipodi-ctrlrect.h"
 #include "preferences.h"
+#include "snap.h"
+#include "sp-namedview.h"
 #include "ui/tool/commit-events.h"
 #include "ui/tool/control-point.h"
 #include "ui/tool/event-utils.h"
@@ -473,7 +475,23 @@ public:
     }
 
 protected:
-
+    virtual void dragged(Geom::Point &new_pos, GdkEventMotion *event) {
+        SnapManager &sm = _desktop->namedview->snap_manager;
+        sm.setup(_desktop);
+        bool snap = !held_shift(*event) && sm.someSnapperMightSnap();
+        if (held_control(*event)) {
+            // constrain to axes
+            Geom::Point origin = _last_drag_origin();
+            std::vector<Inkscape::Snapper::SnapConstraint> constraints;
+            constraints.push_back(Inkscape::Snapper::SnapConstraint(origin, Geom::Point(1, 0)));
+            constraints.push_back(Inkscape::Snapper::SnapConstraint(origin, Geom::Point(0, 1)));
+            new_pos = sm.multipleConstrainedSnaps(Inkscape::SnapCandidatePoint(new_pos,
+                SNAPSOURCE_ROTATION_CENTER), constraints, held_shift(*event)).getPoint();
+        } else if (snap) {
+            sm.freeSnapReturnByRef(new_pos, SNAPSOURCE_ROTATION_CENTER);
+        }
+        sm.unSetup();
+    }
     virtual Glib::ustring _getTip(unsigned /*state*/) {
         return C_("Transform handle tip",
             "<b>Rotation center</b>: drag to change the origin of transforms");
index 3097306009a3fb36751c8b9aec216fabb49cfb0a..aba3a18e82ee9428f0f523c6926873fcfc52a3fc 100644 (file)
@@ -23,7 +23,7 @@ namespace Widget {
 
 SimpleFilterModifier::SimpleFilterModifier(int flags)
     : _lb_blend(_("_Blend mode:")),
-      _lb_blur(_("Blur:"), Gtk::ALIGN_LEFT),
+      _lb_blur(_("_Blur:"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, true),
       _blend(BlendModeConverter, SP_ATTR_INVALID, false),
       _blur(0, 0, 100, 1, 0.01, 1)
 {
index 1de425da376b97856680a119645773f65f6017ea..997c2f0dc87acf371af7715e3536de0ae38059f0 100644 (file)
@@ -60,7 +60,7 @@ ObjectCompositeSettings::ObjectCompositeSettings(unsigned int verb_code, char co
   _opacity_tag(Glib::ustring(history_prefix) + ":opacity"),
   _opacity_vbox(false, 0),
   _opacity_label_box(false, 0),
-  _opacity_label(_("Opacity (%):"), 0.0, 1.0, true),
+  _opacity_label(_("_Opacity (%):"), 0.0, 1.0, true),
   _opacity_adjustment(100.0, 0.0, 100.0, 1.0, 1.0, 0.0),
   _opacity_hscale(_opacity_adjustment),
   _opacity_spin_button(_opacity_adjustment, 0.01, 1),
@@ -86,6 +86,7 @@ ObjectCompositeSettings::ObjectCompositeSettings(unsigned int verb_code, char co
     _opacity_hscale.set_draw_value(false);
     _opacity_hscale.set_update_policy(Gtk::UPDATE_DELAYED);
     _opacity_adjustment.signal_value_changed().connect(sigc::mem_fun(*this, &ObjectCompositeSettings::_opacityValueChanged));
+       _opacity_label.set_mnemonic_widget(_opacity_hscale);
 
     show_all_children();
 
index d2f0055315e0155baa22d2b1bbf2f574a6f62039..deff951d44f4f06bdf2d39ecb43c3861360a36d7 100644 (file)
@@ -5,6 +5,8 @@ ink_common_sources += \
        util/compose.hpp        \
        util/copy.h \
        util/enums.h \
+       util/ege-appear-time-tracker.cpp        \
+       util/ege-appear-time-tracker.h  \
        util/ege-tags.h \
        util/ege-tags.cpp \
        util/filter-list.h \
diff --git a/src/util/ege-appear-time-tracker.cpp b/src/util/ege-appear-time-tracker.cpp
new file mode 100644 (file)
index 0000000..d53e867
--- /dev/null
@@ -0,0 +1,162 @@
+/* ***** 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 Appear Time Tracker.
+ *
+ * The Initial Developer of the Original Code is
+ * Jon A. Cruz.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * 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 ***** */
+
+
+#include "ege-appear-time-tracker.h"
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+
+namespace ege
+{
+
+namespace {
+
+void unhookHandler( gulong &id, GtkWidget *obj )
+{
+   if ( id ) {
+        if ( obj ) {
+            g_signal_handler_disconnect( G_OBJECT(obj), id );
+        }
+        id = 0;
+    }
+}
+
+} // namespace
+
+
+AppearTimeTracker::AppearTimeTracker(GTimer *timer, GtkWidget *widget, gchar const* name) : 
+    _name(name ? name : ""),
+    _timer(timer),
+    _widget(widget),
+    _topMost(widget),
+    _autodelete(false),
+    _mapId(0),
+    _realizeId(0),
+    _hierarchyId(0)
+
+{
+    while (_topMost->parent) {
+        _topMost = _topMost->parent;
+    }
+    _mapId = g_signal_connect( G_OBJECT(_topMost), "map-event", G_CALLBACK(mapCB), this );
+    _realizeId = g_signal_connect( G_OBJECT(_topMost), "realize", G_CALLBACK(realizeCB), this );
+    _hierarchyId = g_signal_connect( G_OBJECT(_widget), "hierarchy-changed", G_CALLBACK(hierarchyCB), this );
+}
+
+AppearTimeTracker::~AppearTimeTracker()
+{
+    if ( _timer ) {
+        g_timer_destroy(_timer);
+        _timer = 0;
+    }
+
+    unhookHandler( _mapId, _topMost );
+    unhookHandler( _realizeId, _topMost );
+    unhookHandler( _hierarchyId, _widget );
+}
+
+void AppearTimeTracker::stop() {
+    if (_timer) {
+        g_timer_stop(_timer);
+    }
+}
+
+void AppearTimeTracker::setAutodelete(bool autodelete)
+{
+    if ( autodelete != _autodelete ) {
+        _autodelete = autodelete;
+    }
+}
+
+void AppearTimeTracker::report(gchar const* msg)
+{
+    gulong msCount = 0;
+    gdouble secs = g_timer_elapsed( _timer, &msCount );
+    g_message("Time ended at %2.3f with [%s] on [%s]", secs, msg, _name.c_str());
+}
+
+void AppearTimeTracker::handleHierarchyChange( GtkWidget * /*prevTop*/ )
+{
+    GtkWidget *newTop = _widget;
+    while (newTop->parent) {
+        newTop = newTop->parent;
+    }
+
+    if ( newTop != _topMost ) {
+        unhookHandler( _mapId, _topMost );
+        unhookHandler( _realizeId, _topMost );
+
+        _topMost = newTop;
+        _mapId = g_signal_connect( G_OBJECT(_topMost), "map-event", G_CALLBACK(mapCB), this );
+        _realizeId = g_signal_connect( G_OBJECT(_topMost), "realize", G_CALLBACK(realizeCB), this );
+    }
+}
+
+gboolean AppearTimeTracker::mapCB(GtkWidget * /*widget*/, GdkEvent * /*event*/, gpointer userData)
+{
+    AppearTimeTracker *tracker = reinterpret_cast<AppearTimeTracker*>(userData);
+    tracker->report("MAP");
+    if ( tracker->_autodelete ) {
+        delete tracker;
+    }
+    return FALSE;
+}
+
+void AppearTimeTracker::realizeCB(GtkWidget * /*widget*/, gpointer userData)
+{
+    AppearTimeTracker *tracker = reinterpret_cast<AppearTimeTracker*>(userData);
+    tracker->report("REALIZE");
+}
+
+void AppearTimeTracker::hierarchyCB(GtkWidget * /*widget*/, GtkWidget *prevTop, gpointer userData)
+{
+    AppearTimeTracker *tracker = reinterpret_cast<AppearTimeTracker*>(userData);
+    tracker->handleHierarchyChange( prevTop );
+}
+
+} // namespace ege
+
+/*
+  Local Variables:
+  mode:c++
+  c-file-style:"stroustrup"
+  c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+  indent-tabs-mode:nil
+  fill-column:99
+  End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/util/ege-appear-time-tracker.h b/src/util/ege-appear-time-tracker.h
new file mode 100644 (file)
index 0000000..b5ea8b5
--- /dev/null
@@ -0,0 +1,89 @@
+#ifndef SEEN_APPEAR_TIME_TRACKER_H
+#define SEEN_APPEAR_TIME_TRACKER_H
+
+/* ***** 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 Appear Time Tracker.
+ *
+ * The Initial Developer of the Original Code is
+ * Jon A. Cruz.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * 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 ***** */
+
+#include <glib/gtimer.h>
+#include <glibmm/ustring.h>
+
+typedef union _GdkEvent GdkEvent;
+typedef struct _GtkWidget GtkWidget;
+
+namespace ege
+{
+
+class AppearTimeTracker {
+public:
+    AppearTimeTracker(GTimer *timer, GtkWidget *widget, gchar const* name);
+    ~AppearTimeTracker();
+
+    void stop();
+
+    bool isAutodelete() const { return _autodelete; }
+    void setAutodelete(bool autodelete);
+
+private:
+    Glib::ustring _name;
+    GTimer *_timer;
+    GtkWidget *_widget;
+    GtkWidget *_topMost;
+    bool _autodelete;
+    gulong _mapId;
+    gulong _realizeId;
+    gulong _hierarchyId;
+
+    static gboolean mapCB(GtkWidget *widget, GdkEvent *event, gpointer userData);
+    static void realizeCB(GtkWidget *widget, gpointer userData);
+    static void hierarchyCB(GtkWidget *widget, GtkWidget *prevTop, gpointer userData);
+
+    void report(gchar const* msg);
+    void handleHierarchyChange( GtkWidget *prevTop );
+};
+
+} // namespace ege
+
+#endif // SEEN_APPEAR_TIME_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:fileencoding=utf-8:textwidth=99 :
index c1726ecd412b5bbc46f9b78a0272a85262cefab4..00a82624bddac847137f697289181fa21b99f4f6 100644 (file)
@@ -2437,7 +2437,7 @@ Verb *Verb::_base_verbs[] = {
                   N_("Raise the current layer"), INKSCAPE_ICON_LAYER_RAISE),
     new LayerVerb(SP_VERB_LAYER_LOWER, "LayerLower", N_("_Lower Layer"),
                   N_("Lower the current layer"), INKSCAPE_ICON_LAYER_LOWER),
-    new LayerVerb(SP_VERB_LAYER_DUPLICATE, "LayerDuplicate", N_("Duplicate Current Layer"),
+    new LayerVerb(SP_VERB_LAYER_DUPLICATE, "LayerDuplicate", N_("D_uplicate Current Layer"),
                   N_("Duplicate an existing layer"), NULL),
     new LayerVerb(SP_VERB_LAYER_DELETE, "LayerDelete", N_("_Delete Current Layer"),
                   N_("Delete the current layer"), INKSCAPE_ICON_LAYER_DELETE),
index 57e58cd5e4d4795f98c8eb8c9bd3017b7b7353aa..4620b15328e0137603f11dcf50078cf0426513a5 100644 (file)
@@ -56,6 +56,7 @@
 #include "ui/widget/layer-selector.h"
 #include "ui/widget/selected-style.h"
 #include "ui/uxmanager.h"
+#include "util/ege-appear-time-tracker.h"
 
 // We're in the "widgets" directory, so no need to explicitly prefix these:
 #include "button.h"
@@ -73,6 +74,7 @@ using Inkscape::round;
 using Inkscape::UnitTracker;
 using Inkscape::UI::UXManager;
 using Inkscape::UI::ToolboxFactory;
+using ege::AppearTimeTracker;
 
 #ifdef WITH_INKBOARD
 #endif
@@ -267,6 +269,8 @@ SPDesktopWidget::window_get_pointer()
     return Geom::Point(x,y);
 }
 
+static GTimer *overallTimer = 0;
+
 /**
  * Registers SPDesktopWidget class and returns its type number.
  */
@@ -288,6 +292,8 @@ GType SPDesktopWidget::getType(void)
             0 // value_table
         };
         type = g_type_register_static(SP_TYPE_VIEW_WIDGET, "SPDesktopWidget", &info, static_cast<GTypeFlags>(0));
+        // Begin a timer to watch for the first desktop to appear on-screen
+        overallTimer = g_timer_new();
     }
     return type;
 }
@@ -592,29 +598,19 @@ void SPDesktopWidget::init( SPDesktopWidget *dtw )
 
     gtk_widget_grab_focus (GTK_WIDGET(dtw->canvas));
 
-    {
-        g_message("FIRE UP");
-
-        gtk_widget_add_events( eventbox, GDK_STRUCTURE_MASK );
-
-        gboolean fooMap(GtkWidget *widget, GdkEvent *event, gpointer userData);
-        g_signal_connect( G_OBJECT(eventbox), "map-event", G_CALLBACK(fooMap), dtw );
-
-        void fooReal(GtkWidget *widget, gpointer userData);
-        g_signal_connect( G_OBJECT(eventbox), "map-event", G_CALLBACK(fooReal), dtw );
+    // If this is the first desktop created, report the time it takes to show up
+    if ( overallTimer ) {
+        if ( prefs->getBool("/dialogs/debug/trackAppear", false) ) {
+            // Time tracker takes ownership of the timer.
+            AppearTimeTracker *tracker = new AppearTimeTracker(overallTimer, GTK_WIDGET(dtw), "first SPDesktopWidget");
+            tracker->setAutodelete(true);
+        } else {
+            g_timer_destroy(overallTimer);
+        }
+        overallTimer = 0;
     }
 }
 
-gboolean fooMap(GtkWidget *widget, GdkEvent *event, gpointer userData) {
-    checkTime( "MAP");
-    return FALSE;
-}
-
-void fooReal(GtkWidget *widget, gpointer userData)
-{
-    checkTime("REALIZE");
-}
-
 /**
  * Called before SPDesktopWidget destruction.
  */