Code

re-introduce isEmpty tests
[inkscape.git] / src / desktop.cpp
1 #define __SP_DESKTOP_C__
3 /** \file
4  * Editable view implementation
5  *
6  * Authors:
7  *   Lauris Kaplinski <lauris@kaplinski.com>
8  *   MenTaLguY <mental@rydia.net>
9  *   bulia byak <buliabyak@users.sf.net>
10  *   Ralf Stephan <ralf@ark.in-berlin.de>
11  *   John Bintz <jcoswell@coswellproductions.org>
12  *   Johan Engelen <j.b.c.engelen@ewi.utwente.nl>
13  *
14  * Copyright (C) 2006-2007 Johan Engelen
15  * Copyright (C) 2006 John Bintz
16  * Copyright (C) 2004 MenTaLguY
17  * Copyright (C) 1999-2002 Lauris Kaplinski
18  * Copyright (C) 2000-2001 Ximian, Inc.
19  *
20  * Released under GNU GPL, read the file 'COPYING' for more information
21  */
23 /** \class SPDesktop
24  * SPDesktop is a subclass of View, implementing an editable document
25  * canvas.  It is extensively used by many UI controls that need certain
26  * visual representations of their own.
27  *
28  * SPDesktop provides a certain set of SPCanvasItems, serving as GUI
29  * layers of different control objects. The one containing the whole
30  * document is the drawing layer. In addition to it, there are grid,
31  * guide, sketch and control layers. The sketch layer is used for
32  * temporary drawing objects, before the real objects in document are
33  * created. The control layer contains editing knots, rubberband and
34  * similar non-document UI objects.
35  *
36  * Each SPDesktop is associated with a SPNamedView node of the document
37  * tree.  Currently, all desktops are created from a single main named
38  * view, but in the future there may be support for different ones.
39  * SPNamedView serves as an in-document container for desktop-related
40  * data, like grid and guideline placement, snapping options and so on.
41  *
42  * Associated with each SPDesktop are the two most important editing
43  * related objects - SPSelection and SPEventContext.
44  *
45  * Sodipodi keeps track of the active desktop and invokes notification
46  * signals whenever it changes. UI elements can use these to update their
47  * display to the selection of the currently active editing window.
48  * (Lauris Kaplinski)
49  */
51 #ifdef HAVE_CONFIG_H
52 # include "config.h"
53 #endif
55 #include <glibmm/i18n.h>
56 #include <sigc++/functors/mem_fun.h>
57 #include <gtkmm.h>
59 #include "macros.h"
60 #include "inkscape-private.h"
61 #include "desktop.h"
62 #include "desktop-events.h"
63 #include "desktop-handles.h"
64 #include "document.h"
65 #include "message-stack.h"
66 #include "selection.h"
67 #include "select-context.h"
68 #include "sp-namedview.h"
69 #include "color.h"
70 #include "sp-item-group.h"
71 #include "prefs-utils.h"
72 #include "object-hierarchy.h"
73 #include "helper/units.h"
74 #include "display/canvas-arena.h"
75 #include "display/nr-arena.h"
76 #include "display/gnome-canvas-acetate.h"
77 #include "display/sodipodi-ctrlrect.h"
78 #include "display/sp-canvas-util.h"
79 #include "libnr/nr-matrix-div.h"
80 #include "libnr/nr-rect-ops.h"
81 #include "ui/dialog/dialog-manager.h"
82 #include "xml/repr.h"
83 #include "message-context.h"
84 #include "layer-manager.h"
85 #include "event-log.h"
87 namespace Inkscape { namespace XML { class Node; }}
89 // Callback declarations
90 static void _onSelectionChanged (Inkscape::Selection *selection, SPDesktop *desktop);
91 static gint _arena_handler (SPCanvasArena *arena, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop);
92 static void _layer_activated(SPObject *layer, SPDesktop *desktop);
93 static void _layer_deactivated(SPObject *layer, SPDesktop *desktop);
94 static void _layer_hierarchy_changed(SPObject *top, SPObject *bottom, SPDesktop *desktop);
95 static void _reconstruction_start(SPDesktop * desktop);
96 static void _reconstruction_finish(SPDesktop * desktop);
97 static void _namedview_modified (SPObject *obj, guint flags, SPDesktop *desktop);
98 static void _update_snap_distances (SPDesktop *desktop);
100 /**
101  * Return new desktop object.
102  * \pre namedview != NULL.
103  * \pre canvas != NULL.
104  */
105 SPDesktop::SPDesktop()
107     _dlg_mgr = NULL;
108     _widget = 0;
109     namedview = NULL;
110     selection = NULL;
111     acetate = NULL;
112     main = NULL;
113     grid = NULL;
114     guides = NULL;
115     drawing = NULL;
116     sketch = NULL;
117     controls = NULL;
118     event_context = 0;
119     layer_manager = 0;
121     _d2w.set_identity();
122     _w2d.set_identity();
123     _doc2dt = NR::Matrix(NR::scale(1, -1));
125     guides_active = false;
127     zooms_past = NULL;
128     zooms_future = NULL;
130     is_fullscreen = false;
131     waiting_cursor = false;
133     gr_item = NULL;
134     gr_point_type = 0;
135     gr_point_i = 0;
136     gr_fill_or_stroke = true;
138     _layer_hierarchy = NULL;
139     _active = false;
141     selection = Inkscape::GC::release (new Inkscape::Selection (this));
144 void
145 SPDesktop::init (SPNamedView *nv, SPCanvas *aCanvas)
148     _guides_message_context = new Inkscape::MessageContext(const_cast<Inkscape::MessageStack*>(messageStack()));
150     current = sp_repr_css_attr_inherited (inkscape_get_repr (INKSCAPE, "desktop"), "style");
152     namedview = nv;
153     canvas = aCanvas;
155     SPDocument *document = SP_OBJECT_DOCUMENT (namedview);
156     /* Kill flicker */
157     sp_document_ensure_up_to_date (document);
159     /* Setup Dialog Manager */
160     _dlg_mgr = new Inkscape::UI::Dialog::DialogManager();
162     dkey = sp_item_display_key_new (1);
164     /* Connect document */
165     setDocument (document);
167     number = namedview->getViewCount();
170     /* Setup Canvas */
171     g_object_set_data (G_OBJECT (canvas), "SPDesktop", this);
173     SPCanvasGroup *root = sp_canvas_root (canvas);
175     /* Setup adminstrative layers */
176     acetate = sp_canvas_item_new (root, GNOME_TYPE_CANVAS_ACETATE, NULL);
177     g_signal_connect (G_OBJECT (acetate), "event", G_CALLBACK (sp_desktop_root_handler), this);
178     main = (SPCanvasGroup *) sp_canvas_item_new (root, SP_TYPE_CANVAS_GROUP, NULL);
179     g_signal_connect (G_OBJECT (main), "event", G_CALLBACK (sp_desktop_root_handler), this);
181     table = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
182     SP_CTRLRECT(table)->setRectangle(NR::Rect(NR::Point(-80000, -80000), NR::Point(80000, 80000)));
183     SP_CTRLRECT(table)->setColor(0x00000000, true, 0x00000000);
184     sp_canvas_item_move_to_z (table, 0);
186     page = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
187     ((CtrlRect *) page)->setColor(0x00000000, FALSE, 0x00000000);
188     page_border = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL);
190     drawing = sp_canvas_item_new (main, SP_TYPE_CANVAS_ARENA, NULL);
191     g_signal_connect (G_OBJECT (drawing), "arena_event", G_CALLBACK (_arena_handler), this);
193     SP_CANVAS_ARENA (drawing)->arena->delta = prefs_get_double_attribute ("options.cursortolerance", "value", 1.0); // default is 1 px
195     if (prefs_get_int_attribute("options.startmode", "outline", 0)) {
196         // Start in outline mode
197         setDisplayModeOutline();
198     } else {
199         // Start in normal mode, default
200         setDisplayModeNormal();
201     }
203     grid = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
204     guides = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
205     sketch = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
206     controls = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL);
208     /* Push select tool to the bottom of stack */
209     /** \todo
210      * FIXME: this is the only call to this.  Everything else seems to just
211      * call "set" instead of "push".  Can we assume that there is only one
212      * context ever?
213      */
214     push_event_context (SP_TYPE_SELECT_CONTEXT, "tools.select", SP_EVENT_CONTEXT_STATIC);
216     // display rect and zoom are now handled in sp_desktop_widget_realize()
218     NR::Rect const d(NR::Point(0.0, 0.0),
219                      NR::Point(sp_document_width(document), sp_document_height(document)));
221     SP_CTRLRECT(page)->setRectangle(d);
222     SP_CTRLRECT(page_border)->setRectangle(d);
224     /* the following sets the page shadow on the canvas
225        It was originally set to 5, which is really cheesy!
226        It now is an attribute in the document's namedview. If a value of
227        0 is used, then the constructor for a shadow is not initialized.
228     */
230     if ( namedview->pageshadow != 0 && namedview->showpageshadow ) {
231         SP_CTRLRECT(page_border)->setShadow(namedview->pageshadow, 0x3f3f3fff);
232     }
235     /* Connect event for page resize */
236     _doc2dt[5] = sp_document_height (document);
237     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
239     _modified_connection = namedview->connectModified(sigc::bind<2>(sigc::ptr_fun(&_namedview_modified), this));
241     NRArenaItem *ai = sp_item_invoke_show (SP_ITEM (sp_document_root (document)),
242             SP_CANVAS_ARENA (drawing)->arena,
243             dkey,
244             SP_ITEM_SHOW_DISPLAY);
245     if (ai) {
246         nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
247         nr_arena_item_unref (ai);
248     }
250     namedview->show(this);
251     /* Ugly hack */
252     activate_guides (true);
253     /* Ugly hack */
254     _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
256 /* Set up notification of rebuilding the document, this allows
257        for saving object related settings in the document. */
258     _reconstruction_start_connection =
259         document->connectReconstructionStart(sigc::bind(sigc::ptr_fun(_reconstruction_start), this));
260     _reconstruction_finish_connection =
261         document->connectReconstructionFinish(sigc::bind(sigc::ptr_fun(_reconstruction_finish), this));
262     _reconstruction_old_layer_id = NULL;
263     
264     _commit_connection = document->connectCommit(sigc::mem_fun(*this, &SPDesktop::updateNow));
265     
266     // ?
267     // sp_active_desktop_set (desktop);
268     _inkscape = INKSCAPE;
270     _activate_connection = _activate_signal.connect(
271         sigc::bind(
272             sigc::ptr_fun(_onActivate),
273             this
274         )
275     );
276      _deactivate_connection = _deactivate_signal.connect(
277         sigc::bind(
278             sigc::ptr_fun(_onDeactivate),
279             this
280         )
281     );
283     _sel_modified_connection = selection->connectModified(
284         sigc::bind(
285             sigc::ptr_fun(&_onSelectionModified),
286             this
287         )
288     );
289     _sel_changed_connection = selection->connectChanged(
290         sigc::bind(
291             sigc::ptr_fun(&_onSelectionChanged),
292             this
293         )
294     );
297     /* setup LayerManager */
298     //   (Setting up after the connections are all in place, as it may use some of them)
299     layer_manager = new Inkscape::LayerManager( this );
303 void SPDesktop::destroy()
305     _activate_connection.disconnect();
306     _deactivate_connection.disconnect();
307     _sel_modified_connection.disconnect();
308     _sel_changed_connection.disconnect();
309     _modified_connection.disconnect();
310     _commit_connection.disconnect();
311     _reconstruction_start_connection.disconnect();
312     _reconstruction_finish_connection.disconnect();
314     g_signal_handlers_disconnect_by_func(G_OBJECT (acetate), (gpointer) G_CALLBACK(sp_desktop_root_handler), this);
315     g_signal_handlers_disconnect_by_func(G_OBJECT (main), (gpointer) G_CALLBACK(sp_desktop_root_handler), this);
316     g_signal_handlers_disconnect_by_func(G_OBJECT (drawing), (gpointer) G_CALLBACK(_arena_handler), this);
318     while (event_context) {
319         SPEventContext *ec = event_context;
320         event_context = ec->next;
321         sp_event_context_finish (ec);
322         g_object_unref (G_OBJECT (ec));
323     }
325     if (_layer_hierarchy) {
326         delete _layer_hierarchy;
327     }
329     if (_inkscape) {
330         _inkscape = NULL;
331     }
333     if (drawing) {
334         sp_item_invoke_hide (SP_ITEM (sp_document_root (doc())), dkey);
335         drawing = NULL;
336     }
338     delete _guides_message_context;
339     _guides_message_context = NULL;
341     g_list_free (zooms_past);
342     g_list_free (zooms_future);
345 SPDesktop::~SPDesktop() {}
347 //--------------------------------------------------------------------
348 /* Public methods */
350 void SPDesktop::setDisplayModeNormal()
352     SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_NORMAL;
353     canvas->rendermode = RENDERMODE_NORMAL; // canvas needs that for choosing the best buffer size
354     displayMode = RENDERMODE_NORMAL;
355     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _d2w); // redraw
356     _widget->setTitle(SP_DOCUMENT_NAME(sp_desktop_document(this)));
359 void SPDesktop::setDisplayModeOutline()
361     SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_OUTLINE;
362     canvas->rendermode = RENDERMODE_OUTLINE; // canvas needs that for choosing the best buffer size
363     displayMode = RENDERMODE_OUTLINE;
364     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _d2w); // redraw
365     _widget->setTitle(SP_DOCUMENT_NAME(sp_desktop_document(this)));
368 void SPDesktop::displayModeToggle()
370     if (displayMode == RENDERMODE_OUTLINE)
371         setDisplayModeNormal();
372     else 
373         setDisplayModeOutline();
376 /**
377  * Returns current root (=bottom) layer.
378  */
379 SPObject *SPDesktop::currentRoot() const
381     return _layer_hierarchy ? _layer_hierarchy->top() : NULL;
384 /**
385  * Returns current top layer.
386  */
387 SPObject *SPDesktop::currentLayer() const
389     return _layer_hierarchy ? _layer_hierarchy->bottom() : NULL;
392 /**
393  * Sets the current layer of the desktop.
394  * 
395  * Make \a object the top layer.
396  */
397 void SPDesktop::setCurrentLayer(SPObject *object) {
398     g_return_if_fail(SP_IS_GROUP(object));
399     g_return_if_fail( currentRoot() == object || (currentRoot() && currentRoot()->isAncestorOf(object)) );
400     // printf("Set Layer to ID: %s\n", SP_OBJECT_ID(object));
401     _layer_hierarchy->setBottom(object);
404 /**
405  * Return layer that contains \a object.
406  */
407 SPObject *SPDesktop::layerForObject(SPObject *object) {
408     g_return_val_if_fail(object != NULL, NULL);
410     SPObject *root=currentRoot();
411     object = SP_OBJECT_PARENT(object);
412     while ( object && object != root && !isLayer(object) ) {
413         object = SP_OBJECT_PARENT(object);
414     }
415     return object;
418 /**
419  * True if object is a layer.
420  */
421 bool SPDesktop::isLayer(SPObject *object) const {
422     return ( SP_IS_GROUP(object)
423              && ( SP_GROUP(object)->effectiveLayerMode(this->dkey)
424                   == SPGroup::LAYER ) );
427 /**
428  * True if desktop viewport fully contains \a item's bbox.
429  */
430 bool SPDesktop::isWithinViewport (SPItem *item) const
432     NR::Rect const viewport = get_display_area();
433     NR::Maybe<NR::Rect> const bbox = sp_item_bbox_desktop(item);
434     if (bbox) {
435         return viewport.contains(*bbox);
436     } else {
437         return true;
438     }
441 ///
442 bool SPDesktop::itemIsHidden(SPItem const *item) const {
443     return item->isHidden(this->dkey);
446 /**
447  * Set activate property of desktop; emit signal if changed.
448  */
449 void
450 SPDesktop::set_active (bool new_active)
452     if (new_active != _active) {
453         _active = new_active;
454         if (new_active) {
455             _activate_signal.emit();
456         } else {
457             _deactivate_signal.emit();
458         }
459     }
462 /**
463  * Set activate status of current desktop's named view.
464  */
465 void
466 SPDesktop::activate_guides(bool activate)
468     guides_active = activate;
469     namedview->activateGuides(this, activate);
472 /**
473  * Make desktop switch documents.
474  */
475 void
476 SPDesktop::change_document (SPDocument *theDocument)
478     g_return_if_fail (theDocument != NULL);
480     /* unselect everything before switching documents */
481     selection->clear();
483     setDocument (theDocument);
484     _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
485     _document_replaced_signal.emit (this, theDocument);
488 /**
489  * Make desktop switch event contexts.
490  */
491 void
492 SPDesktop::set_event_context (GtkType type, const gchar *config)
494     SPEventContext *ec;
495     while (event_context) {
496         ec = event_context;
497         sp_event_context_deactivate (ec);
498         event_context = ec->next;
499         sp_event_context_finish (ec);
500         g_object_unref (G_OBJECT (ec));
501     }
503     Inkscape::XML::Node *repr = (config) ? inkscape_get_repr (_inkscape, config) : NULL;
504     ec = sp_event_context_new (type, this, repr, SP_EVENT_CONTEXT_STATIC);
505     ec->next = event_context;
506     event_context = ec;
507     sp_event_context_activate (ec);
508     _event_context_changed_signal.emit (this, ec);
511 /**
512  * Push event context onto desktop's context stack.
513  */
514 void
515 SPDesktop::push_event_context (GtkType type, const gchar *config, unsigned int key)
517     SPEventContext *ref, *ec;
518     Inkscape::XML::Node *repr;
520     if (event_context && event_context->key == key) return;
521     ref = event_context;
522     while (ref && ref->next && ref->next->key != key) ref = ref->next;
523     if (ref && ref->next) {
524         ec = ref->next;
525         ref->next = ec->next;
526         sp_event_context_finish (ec);
527         g_object_unref (G_OBJECT (ec));
528     }
530     if (event_context) sp_event_context_deactivate (event_context);
531     repr = (config) ? inkscape_get_repr (INKSCAPE, config) : NULL;
532     ec = sp_event_context_new (type, this, repr, key);
533     ec->next = event_context;
534     event_context = ec;
535     sp_event_context_activate (ec);
536     _event_context_changed_signal.emit (this, ec);
539 /**
540  * Sets the coordinate status to a given point
541  */
542 void
543 SPDesktop::set_coordinate_status (NR::Point p) {
544     _widget->setCoordinateStatus(p);
547 /**
548  * \see sp_document_item_from_list_at_point_bottom()
549  */
550 SPItem *
551 SPDesktop::item_from_list_at_point_bottom (const GSList *list, NR::Point const p) const
553     g_return_val_if_fail (doc() != NULL, NULL);
554     return sp_document_item_from_list_at_point_bottom (dkey, SP_GROUP (doc()->root), list, p);
557 /**
558  * \see sp_document_item_at_point()
559  */
560 SPItem *
561 SPDesktop::item_at_point (NR::Point const p, bool into_groups, SPItem *upto) const
563     g_return_val_if_fail (doc() != NULL, NULL);
564     return sp_document_item_at_point (doc(), dkey, p, into_groups, upto);
567 /**
568  * \see sp_document_group_at_point()
569  */
570 SPItem *
571 SPDesktop::group_at_point (NR::Point const p) const
573     g_return_val_if_fail (doc() != NULL, NULL);
574     return sp_document_group_at_point (doc(), dkey, p);
577 /**
578  * \brief  Returns the mouse point in document coordinates; if mouse is
579  * outside the canvas, returns the center of canvas viewpoint
580  */
581 NR::Point
582 SPDesktop::point() const
584     NR::Point p = _widget->getPointer();
585     NR::Point pw = sp_canvas_window_to_world (canvas, p);
586     p = w2d(pw);
588     NR::Rect const r = canvas->getViewbox();
590     NR::Point r0 = w2d(r.min());
591     NR::Point r1 = w2d(r.max());
593     if (p[NR::X] >= r0[NR::X] &&
594         p[NR::X] <= r1[NR::X] &&
595         p[NR::Y] >= r1[NR::Y] &&
596         p[NR::Y] <= r0[NR::Y])
597     {
598         return p;
599     } else {
600         return (r0 + r1) / 2;
601     }
604 /**
605  * Put current zoom data in history list.
606  */
607 void
608 SPDesktop::push_current_zoom (GList **history)
610     NR::Rect const area = get_display_area();
612     NRRect *old_zoom = g_new(NRRect, 1);
613     old_zoom->x0 = area.min()[NR::X];
614     old_zoom->x1 = area.max()[NR::X];
615     old_zoom->y0 = area.min()[NR::Y];
616     old_zoom->y1 = area.max()[NR::Y];
617     if ( *history == NULL
618          || !( ( ((NRRect *) ((*history)->data))->x0 == old_zoom->x0 ) &&
619                ( ((NRRect *) ((*history)->data))->x1 == old_zoom->x1 ) &&
620                ( ((NRRect *) ((*history)->data))->y0 == old_zoom->y0 ) &&
621                ( ((NRRect *) ((*history)->data))->y1 == old_zoom->y1 ) ) )
622     {
623         *history = g_list_prepend (*history, old_zoom);
624     }
627 /**
628  * Set viewbox.
629  */
630 void
631 SPDesktop::set_display_area (double x0, double y0, double x1, double y1, double border, bool log)
633     g_assert(_widget);
635     // save the zoom
636     if (log) {
637         push_current_zoom(&zooms_past);
638         // if we do a logged zoom, our zoom-forward list is invalidated, so delete it
639         g_list_free (zooms_future);
640         zooms_future = NULL;
641     }
643     double const cx = 0.5 * (x0 + x1);
644     double const cy = 0.5 * (y0 + y1);
646     NR::Rect const viewbox = NR::expand(canvas->getViewbox(), border);
648     double scale = expansion(_d2w);
649     double newscale;
650     if (((x1 - x0) * viewbox.dimensions()[NR::Y]) > ((y1 - y0) * viewbox.dimensions()[NR::X])) {
651         newscale = viewbox.dimensions()[NR::X] / (x1 - x0);
652     } else {
653         newscale = viewbox.dimensions()[NR::Y] / (y1 - y0);
654     }
656     newscale = CLAMP(newscale, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX);
658     int clear = FALSE;
659     if (!NR_DF_TEST_CLOSE (newscale, scale, 1e-4 * scale)) {
660         /* Set zoom factors */
661         _d2w = NR::Matrix(NR::scale(newscale, -newscale));
662         _w2d = NR::Matrix(NR::scale(1/newscale, 1/-newscale));
663         sp_canvas_item_affine_absolute(SP_CANVAS_ITEM(main), _d2w);
664         clear = TRUE;
665     }
667     /* Calculate top left corner */
668     x0 = cx - 0.5 * viewbox.dimensions()[NR::X] / newscale;
669     y1 = cy + 0.5 * viewbox.dimensions()[NR::Y] / newscale;
671     /* Scroll */
672     sp_canvas_scroll_to (canvas, x0 * newscale - border, y1 * -newscale - border, clear);
674     _widget->updateRulers();
675     _widget->updateScrollbars(expansion(_d2w));
676     _widget->updateZoom();
679 void SPDesktop::set_display_area(NR::Rect const &a, NR::Coord b, bool log)
681     set_display_area(a.min()[NR::X], a.min()[NR::Y], a.max()[NR::X], a.max()[NR::Y], b, log);
684 /**
685  * Return viewbox dimensions.
686  */
687 NR::Rect SPDesktop::get_display_area() const
689     NR::Rect const viewbox = canvas->getViewbox();
691     double const scale = _d2w[0];
693     return NR::Rect(NR::Point(viewbox.min()[NR::X] / scale, viewbox.max()[NR::Y] / -scale),
694                     NR::Point(viewbox.max()[NR::X] / scale, viewbox.min()[NR::Y] / -scale));
697 /**
698  * Revert back to previous zoom if possible.
699  */
700 void
701 SPDesktop::prev_zoom()
703     if (zooms_past == NULL) {
704         messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No previous zoom."));
705         return;
706     }
708     // push current zoom into forward zooms list
709     push_current_zoom (&zooms_future);
711     // restore previous zoom
712     set_display_area (((NRRect *) zooms_past->data)->x0,
713             ((NRRect *) zooms_past->data)->y0,
714             ((NRRect *) zooms_past->data)->x1,
715             ((NRRect *) zooms_past->data)->y1,
716             0, false);
718     // remove the just-added zoom from the past zooms list
719     zooms_past = g_list_remove (zooms_past, ((NRRect *) zooms_past->data));
722 /**
723  * Set zoom to next in list.
724  */
725 void
726 SPDesktop::next_zoom()
728     if (zooms_future == NULL) {
729         this->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No next zoom."));
730         return;
731     }
733     // push current zoom into past zooms list
734     push_current_zoom (&zooms_past);
736     // restore next zoom
737     set_display_area (((NRRect *) zooms_future->data)->x0,
738             ((NRRect *) zooms_future->data)->y0,
739             ((NRRect *) zooms_future->data)->x1,
740             ((NRRect *) zooms_future->data)->y1,
741             0, false);
743     // remove the just-used zoom from the zooms_future list
744     zooms_future = g_list_remove (zooms_future, ((NRRect *) zooms_future->data));
747 /**
748  * Zoom to point with absolute zoom factor.
749  */
750 void
751 SPDesktop::zoom_absolute_keep_point (double cx, double cy, double px, double py, double zoom)
753     zoom = CLAMP (zoom, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX);
755     // maximum or minimum zoom reached, but there's no exact equality because of rounding errors;
756     // this check prevents "sliding" when trying to zoom in at maximum zoom;
757     /// \todo someone please fix calculations properly and remove this hack
758     if (fabs(expansion(_d2w) - zoom) < 0.0001*zoom && (fabs(SP_DESKTOP_ZOOM_MAX - zoom) < 0.01 || fabs(SP_DESKTOP_ZOOM_MIN - zoom) < 0.000001))
759         return;
761     NR::Rect const viewbox = canvas->getViewbox();
763     double const width2 = viewbox.dimensions()[NR::X] / zoom;
764     double const height2 = viewbox.dimensions()[NR::Y] / zoom;
766     set_display_area(cx - px * width2,
767                      cy - py * height2,
768                      cx + (1 - px) * width2,
769                      cy + (1 - py) * height2,
770                      0.0);
773 /**
774  * Zoom to center with absolute zoom factor.
775  */
776 void
777 SPDesktop::zoom_absolute (double cx, double cy, double zoom)
779     zoom_absolute_keep_point (cx, cy, 0.5, 0.5, zoom);
782 /**
783  * Zoom to point with relative zoom factor.
784  */
785 void
786 SPDesktop::zoom_relative_keep_point (double cx, double cy, double zoom)
788     NR::Rect const area = get_display_area();
790     if (cx < area.min()[NR::X]) {
791         cx = area.min()[NR::X];
792     }
793     if (cx > area.max()[NR::X]) {
794         cx = area.max()[NR::X];
795     }
796     if (cy < area.min()[NR::Y]) {
797         cy = area.min()[NR::Y];
798     }
799     if (cy > area.max()[NR::Y]) {
800         cy = area.max()[NR::Y];
801     }
803     gdouble const scale = expansion(_d2w) * zoom;
804     double const px = (cx - area.min()[NR::X]) / area.dimensions()[NR::X];
805     double const py = (cy - area.min()[NR::Y]) / area.dimensions()[NR::Y];
807     zoom_absolute_keep_point(cx, cy, px, py, scale);
810 /**
811  * Zoom to center with relative zoom factor.
812  */
813 void
814 SPDesktop::zoom_relative (double cx, double cy, double zoom)
816     gdouble scale = expansion(_d2w) * zoom;
817     zoom_absolute (cx, cy, scale);
820 /**
821  * Set display area to origin and current document dimensions.
822  */
823 void
824 SPDesktop::zoom_page()
826     NR::Rect d(NR::Point(0, 0),
827                NR::Point(sp_document_width(doc()), sp_document_height(doc())));
829     if (d.isEmpty(1.0)) {
830         return;
831     }
833     set_display_area(d, 10);
836 /**
837  * Set display area to current document width.
838  */
839 void
840 SPDesktop::zoom_page_width()
842     NR::Rect const a = get_display_area();
844     if (sp_document_width(doc()) < 1.0) {
845         return;
846     }
848     NR::Rect d(NR::Point(0, a.midpoint()[NR::Y]),
849                NR::Point(sp_document_width(doc()), a.midpoint()[NR::Y]));
851     set_display_area(d, 10);
854 /**
855  * Zoom to selection.
856  */
857 void
858 SPDesktop::zoom_selection()
860     NR::Maybe<NR::Rect> const d = selection->bounds();
862     if ( !d || d->isEmpty(0.1) ) {
863         return;
864     }
866     set_display_area(*d, 10);
869 /**
870  * Tell widget to let zoom widget grab keyboard focus.
871  */
872 void
873 SPDesktop::zoom_grab_focus()
875     _widget->letZoomGrabFocus();
878 /**
879  * Zoom to whole drawing.
880  */
881 void
882 SPDesktop::zoom_drawing()
884     g_return_if_fail (doc() != NULL);
885     SPItem *docitem = SP_ITEM (sp_document_root (doc()));
886     g_return_if_fail (docitem != NULL);
888     NR::Maybe<NR::Rect> d = sp_item_bbox_desktop(docitem);
890     /* Note that the second condition here indicates that
891     ** there are no items in the drawing.
892     */
893     if ( !d || d->isEmpty(1.0) ) {
894         return;
895     }
897     set_display_area(*d, 10);
900 /**
901  * Scroll canvas by specific coordinate amount.
902  */
903 void
904 SPDesktop::scroll_world (double dx, double dy, bool is_scrolling)
906     g_assert(_widget);
908     NR::Rect const viewbox = canvas->getViewbox();
910     sp_canvas_scroll_to(canvas, viewbox.min()[NR::X] - dx, viewbox.min()[NR::Y] - dy, FALSE, is_scrolling);
912     _widget->updateRulers();
913     _widget->updateScrollbars(expansion(_d2w));
916 bool
917 SPDesktop::scroll_to_point (NR::Point const *p, gdouble autoscrollspeed)
919     gdouble autoscrolldistance = (gdouble) prefs_get_int_attribute_limited ("options.autoscrolldistance", "value", 0, -1000, 10000);
921     // autoscrolldistance is in screen pixels, but the display area is in document units
922     autoscrolldistance /= expansion(_d2w);
923     NR::Rect const dbox = NR::expand(get_display_area(), -autoscrolldistance);
925     if (!((*p)[NR::X] > dbox.min()[NR::X] && (*p)[NR::X] < dbox.max()[NR::X]) ||
926         !((*p)[NR::Y] > dbox.min()[NR::Y] && (*p)[NR::Y] < dbox.max()[NR::Y])   ) {
928         NR::Point const s_w( (*p) * _d2w );
930         gdouble x_to;
931         if ((*p)[NR::X] < dbox.min()[NR::X])
932             x_to = dbox.min()[NR::X];
933         else if ((*p)[NR::X] > dbox.max()[NR::X])
934             x_to = dbox.max()[NR::X];
935         else
936             x_to = (*p)[NR::X];
938         gdouble y_to;
939         if ((*p)[NR::Y] < dbox.min()[NR::Y])
940             y_to = dbox.min()[NR::Y];
941         else if ((*p)[NR::Y] > dbox.max()[NR::Y])
942             y_to = dbox.max()[NR::Y];
943         else
944             y_to = (*p)[NR::Y];
946         NR::Point const d_dt(x_to, y_to);
947         NR::Point const d_w( d_dt * _d2w );
948         NR::Point const moved_w( d_w - s_w );
950         if (autoscrollspeed == 0)
951             autoscrollspeed = prefs_get_double_attribute_limited ("options.autoscrollspeed", "value", 1, 0, 10);
953         if (autoscrollspeed != 0)
954             scroll_world (autoscrollspeed * moved_w);
956         return true;
957     }
958     return false;
961 void
962 SPDesktop::fullscreen()
964     _widget->setFullscreen();
967 void
968 SPDesktop::getWindowGeometry (gint &x, gint &y, gint &w, gint &h)
970     _widget->getGeometry (x, y, w, h);
973 void
974 SPDesktop::setWindowPosition (NR::Point p)
976     _widget->setPosition (p);
979 void
980 SPDesktop::setWindowSize (gint w, gint h)
982     _widget->setSize (w, h);
985 void
986 SPDesktop::setWindowTransient (void *p, int transient_policy)
988     _widget->setTransient (p, transient_policy);
991 void
992 SPDesktop::presentWindow()
994     _widget->present();
997 bool
998 SPDesktop::warnDialog (gchar *text)
1000     return _widget->warnDialog (text);
1003 void
1004 SPDesktop::toggleRulers()
1006     _widget->toggleRulers();
1009 void
1010 SPDesktop::toggleScrollbars()
1012     _widget->toggleScrollbars();
1015 void
1016 SPDesktop::layoutWidget()
1018     _widget->layout();
1021 void
1022 SPDesktop::destroyWidget()
1024     _widget->destroy();
1027 bool
1028 SPDesktop::shutdown()
1030     return _widget->shutdown();
1033 void
1034 SPDesktop::setToolboxFocusTo (gchar const *label)
1036     _widget->setToolboxFocusTo (label);
1039 void
1040 SPDesktop::setToolboxAdjustmentValue (gchar const* id, double val)
1042     _widget->setToolboxAdjustmentValue (id, val);
1045 bool
1046 SPDesktop::isToolboxButtonActive (gchar const *id)
1048     return _widget->isToolboxButtonActive (id);
1051 void
1052 SPDesktop::emitToolSubselectionChanged(gpointer data)
1054         _tool_subselection_changed.emit(data);
1055         inkscape_subselection_changed (this);
1058 void
1059 SPDesktop::updateNow()
1061   sp_canvas_update_now(canvas);
1064 void
1065 SPDesktop::enableInteraction()
1067   _widget->enableInteraction();
1070 void SPDesktop::disableInteraction()
1072   _widget->disableInteraction();
1075 void SPDesktop::setWaitingCursor()
1077     GdkCursor *waiting = gdk_cursor_new(GDK_WATCH);
1078     gdk_window_set_cursor(GTK_WIDGET(sp_desktop_canvas(this))->window, waiting);
1079     gdk_cursor_unref(waiting);
1080     waiting_cursor = true;
1082     // Stupidly broken GDK cannot just set the new cursor right now - it needs some main loop iterations for that
1083     // Since setting waiting_cursor is usually immediately followed by some Real Work, we must run the iterations here
1084     // CAUTION: iterations may redraw, and redraw may be interrupted, so you cannot assume that anything is the same
1085     // after the call to setWaitingCursor as it was before
1086     while( Gtk::Main::events_pending() )
1087        Gtk::Main::iteration();
1090 //----------------------------------------------------------------------
1091 // Callback implementations. The virtual ones are connected by the view.
1093 void
1094 SPDesktop::onPositionSet (double x, double y)
1096     _widget->viewSetPosition (NR::Point(x,y));
1099 void
1100 SPDesktop::onResized (double x, double y)
1102    // Nothing called here
1105 /**
1106  * Redraw callback; queues Gtk redraw; connected by View.
1107  */
1108 void
1109 SPDesktop::onRedrawRequested ()
1111     if (main) {
1112         _widget->requestCanvasUpdate();
1113     }
1116 void
1117 SPDesktop::updateCanvasNow()
1119   _widget->requestCanvasUpdateAndWait();
1122 /**
1123  * Associate document with desktop.
1124  */
1125 /// \todo fixme: refactor SPDesktop::init to use setDocument
1126 void
1127 SPDesktop::setDocument (SPDocument *doc)
1129     if (this->doc() && doc) {
1130         namedview->hide(this);
1131         sp_item_invoke_hide (SP_ITEM (sp_document_root (this->doc())), dkey);
1132     }
1134     if (_layer_hierarchy) {
1135         _layer_hierarchy->clear();
1136         delete _layer_hierarchy;
1137     }
1138     _layer_hierarchy = new Inkscape::ObjectHierarchy(NULL);
1139     _layer_hierarchy->connectAdded(sigc::bind(sigc::ptr_fun(_layer_activated), this));
1140     _layer_hierarchy->connectRemoved(sigc::bind(sigc::ptr_fun(_layer_deactivated), this));
1141     _layer_hierarchy->connectChanged(sigc::bind(sigc::ptr_fun(_layer_hierarchy_changed), this));
1142     _layer_hierarchy->setTop(SP_DOCUMENT_ROOT(doc));
1144     /* setup EventLog */
1145     event_log = new Inkscape::EventLog(doc);
1146     doc->addUndoObserver(*event_log);
1148     _commit_connection.disconnect();
1149     _commit_connection = doc->connectCommit(sigc::mem_fun(*this, &SPDesktop::updateNow));
1151     /// \todo fixme: This condition exists to make sure the code
1152     /// inside is called only once on initialization. But there
1153     /// are surely more safe methods to accomplish this.
1154     if (drawing) {
1155         NRArenaItem *ai;
1157         namedview = sp_document_namedview (doc, NULL);
1158         _modified_connection = namedview->connectModified(sigc::bind<2>(sigc::ptr_fun(&_namedview_modified), this));
1159         number = namedview->getViewCount();
1161         ai = sp_item_invoke_show (SP_ITEM (sp_document_root (doc)),
1162                 SP_CANVAS_ARENA (drawing)->arena,
1163                 dkey,
1164                 SP_ITEM_SHOW_DISPLAY);
1165         if (ai) {
1166             nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL);
1167             nr_arena_item_unref (ai);
1168         }
1169         namedview->show(this);
1170         /* Ugly hack */
1171         activate_guides (true);
1172         /* Ugly hack */
1173         _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this);
1174     }
1176     _document_replaced_signal.emit (this, doc);
1178     View::setDocument (doc);
1181 void
1182 SPDesktop::onStatusMessage
1183 (Inkscape::MessageType type, gchar const *message)
1185     if (_widget) {
1186         _widget->setMessage(type, message);
1187     }
1190 void
1191 SPDesktop::onDocumentURISet (gchar const* uri)
1193     _widget->setTitle(uri);
1196 /**
1197  * Resized callback.
1198  */
1199 void
1200 SPDesktop::onDocumentResized (gdouble width, gdouble height)
1202     _doc2dt[5] = height;
1203     sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt);
1204     NR::Rect const a(NR::Point(0, 0), NR::Point(width, height));
1205     SP_CTRLRECT(page)->setRectangle(a);
1206     SP_CTRLRECT(page_border)->setRectangle(a);
1210 void
1211 SPDesktop::_onActivate (SPDesktop* dt)
1213     if (!dt->_widget) return;
1214     dt->_widget->activateDesktop();
1217 void
1218 SPDesktop::_onDeactivate (SPDesktop* dt)
1220     if (!dt->_widget) return;
1221     dt->_widget->deactivateDesktop();
1224 void
1225 SPDesktop::_onSelectionModified
1226 (Inkscape::Selection *selection, guint flags, SPDesktop *dt)
1228     if (!dt->_widget) return;
1229     dt->_widget->updateScrollbars (expansion(dt->_d2w));
1232 static void
1233 _onSelectionChanged
1234 (Inkscape::Selection *selection, SPDesktop *desktop)
1236     /** \todo
1237      * only change the layer for single selections, or what?
1238      * This seems reasonable -- for multiple selections there can be many
1239      * different layers involved.
1240      */
1241     SPItem *item=selection->singleItem();
1242     if (item) {
1243         SPObject *layer=desktop->layerForObject(item);
1244         if ( layer && layer != desktop->currentLayer() ) {
1245             desktop->setCurrentLayer(layer);
1246         }
1247     }
1250 /**
1251  * Calls event handler of current event context.
1252  * \param arena Unused
1253  * \todo fixme
1254  */
1255 static gint
1256 _arena_handler (SPCanvasArena *arena, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop)
1258     if (ai) {
1259         SPItem *spi = (SPItem*)NR_ARENA_ITEM_GET_DATA (ai);
1260         return sp_event_context_item_handler (desktop->event_context, spi, event);
1261     } else {
1262         return sp_event_context_root_handler (desktop->event_context, event);
1263     }
1266 static void
1267 _layer_activated(SPObject *layer, SPDesktop *desktop) {
1268     g_return_if_fail(SP_IS_GROUP(layer));
1269     SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::LAYER);
1272 /// Callback
1273 static void
1274 _layer_deactivated(SPObject *layer, SPDesktop *desktop) {
1275     g_return_if_fail(SP_IS_GROUP(layer));
1276     SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::GROUP);
1279 /// Callback
1280 static void
1281 _layer_hierarchy_changed(SPObject *top, SPObject *bottom,
1282                                          SPDesktop *desktop)
1284     desktop->_layer_changed_signal.emit (bottom);
1287 /// Called when document is starting to be rebuilt.
1288 static void
1289 _reconstruction_start (SPDesktop * desktop)
1291     // printf("Desktop, starting reconstruction\n");
1292     desktop->_reconstruction_old_layer_id = g_strdup(SP_OBJECT_ID(desktop->currentLayer()));
1293     desktop->_layer_hierarchy->setBottom(desktop->currentRoot());
1295     /*
1296     GSList const * selection_objs = desktop->selection->list();
1297     for (; selection_objs != NULL; selection_objs = selection_objs->next) {
1299     }
1300     */
1301     desktop->selection->clear();
1303     // printf("Desktop, starting reconstruction end\n");
1306 /// Called when document rebuild is finished.
1307 static void
1308 _reconstruction_finish (SPDesktop * desktop)
1310     // printf("Desktop, finishing reconstruction\n");
1311     if (desktop->_reconstruction_old_layer_id == NULL)
1312         return;
1314     SPObject * newLayer = SP_OBJECT_DOCUMENT(desktop->namedview)->getObjectById(desktop->_reconstruction_old_layer_id);
1315     if (newLayer != NULL)
1316         desktop->setCurrentLayer(newLayer);
1318     g_free(desktop->_reconstruction_old_layer_id);
1319     desktop->_reconstruction_old_layer_id = NULL;
1320     // printf("Desktop, finishing reconstruction end\n");
1321     return;
1324 /**
1325  * Namedview_modified callback.
1326  */
1327 static void
1328 _namedview_modified (SPObject *obj, guint flags, SPDesktop *desktop)
1330     SPNamedView *nv=SP_NAMEDVIEW(obj);
1332     if (flags & SP_OBJECT_MODIFIED_FLAG) {
1334         /* Recalculate snap distances */
1335         /* FIXME: why is the desktop getting involved in setting up something
1336         ** that is entirely to do with the namedview?
1337         */
1338         _update_snap_distances (desktop);
1340         /* Show/hide page background */
1341         if (nv->pagecolor & 0xff) {
1342             sp_canvas_item_show (desktop->table);
1343             ((CtrlRect *) desktop->table)->setColor(0x00000000, true, nv->pagecolor);
1344             sp_canvas_item_move_to_z (desktop->table, 0);
1345         } else {
1346             sp_canvas_item_hide (desktop->table);
1347         }
1349         /* Show/hide page border */
1350         if (nv->showborder) {
1351             // show
1352             sp_canvas_item_show (desktop->page_border);
1353             // set color and shadow
1354             ((CtrlRect *) desktop->page_border)->setColor(nv->bordercolor, false, 0x00000000);
1355             if (nv->pageshadow) {
1356                 ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1357             }
1358             // place in the z-order stack
1359             if (nv->borderlayer == SP_BORDER_LAYER_BOTTOM) {
1360                  sp_canvas_item_move_to_z (desktop->page_border, 2);
1361             } else {
1362                 int order = sp_canvas_item_order (desktop->page_border);
1363                 int morder = sp_canvas_item_order (desktop->drawing);
1364                 if (morder > order) sp_canvas_item_raise (desktop->page_border,
1365                                     morder - order);
1366             }
1367         } else {
1368                 sp_canvas_item_hide (desktop->page_border);
1369                 if (nv->pageshadow) {
1370                     ((CtrlRect *) desktop->page)->setShadow(0, 0x00000000);
1371                 }
1372         }
1373         
1374         /* Show/hide page shadow */
1375         if (nv->showpageshadow && nv->pageshadow) {
1376             ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor);
1377         } else {
1378             ((CtrlRect *) desktop->page_border)->setShadow(0, 0x00000000);
1379         }
1381         if (SP_RGBA32_A_U(nv->pagecolor) < 128 ||
1382             (SP_RGBA32_R_U(nv->pagecolor) +
1383              SP_RGBA32_G_U(nv->pagecolor) +
1384              SP_RGBA32_B_U(nv->pagecolor)) >= 384) {
1385             // the background color is light or transparent, use black outline
1386             SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = prefs_get_int_attribute("options.wireframecolors", "onlight", 0xff);
1387         } else { // use white outline
1388             SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = prefs_get_int_attribute("options.wireframecolors", "ondark", 0xffffffff);
1389         }
1390     }
1393 /**
1394  * Callback to reset snapper's distances.
1395  */
1396 static void
1397 _update_snap_distances (SPDesktop *desktop)
1399     SPUnit const &px = sp_unit_get_by_id(SP_UNIT_PX);
1401     SPNamedView &nv = *desktop->namedview;
1403     nv.snap_manager.grid.setDistance(sp_convert_distance_full(nv.gridtolerance,
1404                                                                       *nv.gridtoleranceunit,
1405                                                                       px));
1406     nv.snap_manager.axonomgrid.setDistance(sp_convert_distance_full(nv.gridtolerance,
1407                                                                       *nv.gridtoleranceunit,
1408                                                                       px));
1409     nv.snap_manager.guide.setDistance(sp_convert_distance_full(nv.guidetolerance,
1410                                                                        *nv.guidetoleranceunit,
1411                                                                        px));
1412     nv.snap_manager.object.setDistance(sp_convert_distance_full(nv.objecttolerance,
1413                                                                         *nv.objecttoleranceunit,
1414                                                                         px));
1418 NR::Matrix SPDesktop::w2d() const
1420     return _w2d;
1423 NR::Point SPDesktop::w2d(NR::Point const &p) const
1425     return p * _w2d;
1428 NR::Point SPDesktop::d2w(NR::Point const &p) const
1430     return p * _d2w;
1433 NR::Matrix SPDesktop::doc2dt() const
1435     return _doc2dt;
1438 NR::Point SPDesktop::doc2dt(NR::Point const &p) const
1440     return p * _doc2dt;
1443 NR::Point SPDesktop::dt2doc(NR::Point const &p) const
1445     return p / _doc2dt;
1449 /**
1450  * Pop event context from desktop's context stack. Never used.
1451  */
1452 // void
1453 // SPDesktop::pop_event_context (unsigned int key)
1454 // {
1455 //    SPEventContext *ec = NULL;
1456 //
1457 //    if (event_context && event_context->key == key) {
1458 //        g_return_if_fail (event_context);
1459 //        g_return_if_fail (event_context->next);
1460 //        ec = event_context;
1461 //        sp_event_context_deactivate (ec);
1462 //        event_context = ec->next;
1463 //        sp_event_context_activate (event_context);
1464 //        _event_context_changed_signal.emit (this, ec);
1465 //    }
1466 //
1467 //    SPEventContext *ref = event_context;
1468 //    while (ref && ref->next && ref->next->key != key)
1469 //        ref = ref->next;
1470 //
1471 //    if (ref && ref->next) {
1472 //        ec = ref->next;
1473 //        ref->next = ec->next;
1474 //    }
1475 //
1476 //    if (ec) {
1477 //        sp_event_context_finish (ec);
1478 //        g_object_unref (G_OBJECT (ec));
1479 //    }
1480 // }
1482 /*
1483   Local Variables:
1484   mode:c++
1485   c-file-style:"stroustrup"
1486   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1487   indent-tabs-mode:nil
1488   fill-column:99
1489   End:
1490 */
1491 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :