diff options
| author | MenTaLguY <mental@rydia.net> | 2006-01-16 02:36:01 +0000 |
|---|---|---|
| committer | mental <mental@users.sourceforge.net> | 2006-01-16 02:36:01 +0000 |
| commit | 179fa413b047bede6e32109e2ce82437c5fb8d34 (patch) | |
| tree | a5a6ac2c1708bd02288fbd8edb2ff500ff2e0916 /src/desktop.cpp | |
| download | inkscape-179fa413b047bede6e32109e2ce82437c5fb8d34.tar.gz inkscape-179fa413b047bede6e32109e2ce82437c5fb8d34.zip | |
moving trunk for module inkscape
(bzr r1)
Diffstat (limited to 'src/desktop.cpp')
| -rw-r--r-- | src/desktop.cpp | 1400 |
1 files changed, 1400 insertions, 0 deletions
diff --git a/src/desktop.cpp b/src/desktop.cpp new file mode 100644 index 000000000..9a2274bea --- /dev/null +++ b/src/desktop.cpp @@ -0,0 +1,1400 @@ +#define __SP_DESKTOP_C__ + +/** \file + * Editable view implementation + * + * Authors: + * Lauris Kaplinski <lauris@kaplinski.com> + * MenTaLguY <mental@rydia.net> + * bulia byak <buliabyak@users.sf.net> + * Ralf Stephan <ralf@ark.in-berlin.de> + * + * Copyright (C) 2004 MenTaLguY + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +/** \class SPDesktop + * SPDesktop is a subclass of View, implementing an editable document + * canvas. It is extensively used by many UI controls that need certain + * visual representations of their own. + * + * SPDesktop provides a certain set of SPCanvasItems, serving as GUI + * layers of different control objects. The one containing the whole + * document is the drawing layer. In addition to it, there are grid, + * guide, sketch and control layers. The sketch layer is used for + * temporary drawing objects, before the real objects in document are + * created. The control layer contains editing knots, rubberband and + * similar non-document UI objects. + * + * Each SPDesktop is associated with a SPNamedView node of the document + * tree. Currently, all desktops are created from a single main named + * view, but in the future there may be support for different ones. + * SPNamedView serves as an in-document container for desktop-related + * data, like grid and guideline placement, snapping options and so on. + * + * Associated with each SPDesktop are the two most important editing + * related objects - SPSelection and SPEventContext. + * + * Sodipodi keeps track of the active desktop and invokes notification + * signals whenever it changes. UI elements can use these to update their + * display to the selection of the currently active editing window. + * (Lauris Kaplinski) + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <glibmm/i18n.h> + +#include "macros.h" +#include "inkscape-private.h" +#include "desktop.h" +#include "desktop-events.h" +#include "desktop-handles.h" +#include "document.h" +#include "message-stack.h" +#include "selection.h" +#include "select-context.h" +#include "sp-namedview.h" +#include "color.h" +#include "sp-item-group.h" +#include "prefs-utils.h" +#include "object-hierarchy.h" +#include "helper/units.h" +#include "display/canvas-arena.h" +#include "display/nr-arena.h" +#include "display/gnome-canvas-acetate.h" +#include "display/sodipodi-ctrlrect.h" +#include "display/sp-canvas-util.h" +#include "libnr/nr-matrix-div.h" +#include "libnr/nr-rect-ops.h" +#include "ui/dialog/dialog-manager.h" +#include "xml/repr.h" +#include "message-context.h" + +#ifdef WITH_INKBOARD +#include "jabber_whiteboard/session-manager.h" +#endif + +namespace Inkscape { namespace XML { class Node; }} + +// Callback declarations +static void _onSelectionChanged (Inkscape::Selection *selection, SPDesktop *desktop); +static gint _arena_handler (SPCanvasArena *arena, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop); +static void _layer_activated(SPObject *layer, SPDesktop *desktop); +static void _layer_deactivated(SPObject *layer, SPDesktop *desktop); +static void _layer_hierarchy_changed(SPObject *top, SPObject *bottom, SPDesktop *desktop); +static void _reconstruction_start(SPDesktop * desktop); +static void _reconstruction_finish(SPDesktop * desktop); +static void _namedview_modified (SPNamedView *nv, guint flags, SPDesktop *desktop); +static void _update_snap_distances (SPDesktop *desktop); + +/** + * Return new desktop object. + * \pre namedview != NULL. + * \pre canvas != NULL. + */ +SPDesktop::SPDesktop() +{ + _dlg_mgr = NULL; + _widget = 0; + namedview = NULL; + selection = NULL; + acetate = NULL; + main = NULL; + grid = NULL; + guides = NULL; + drawing = NULL; + sketch = NULL; + controls = NULL; + event_context = 0; + + _d2w.set_identity(); + _w2d.set_identity(); + _doc2dt = NR::Matrix(NR::scale(1, -1)); + + guides_active = false; + + zooms_past = NULL; + zooms_future = NULL; + + is_fullscreen = false; + + gr_item = NULL; + gr_point_num = 0; + gr_fill_or_stroke = true; + + _layer_hierarchy = NULL; + _active = false; + + selection = Inkscape::GC::release (new Inkscape::Selection (this)); +} + +void +SPDesktop::init (SPNamedView *nv, SPCanvas *aCanvas) + +{ + _guides_message_context = new Inkscape::MessageContext(const_cast<Inkscape::MessageStack*>(messageStack())); + + current = sp_repr_css_attr_inherited (inkscape_get_repr (INKSCAPE, "desktop"), "style"); + + namedview = nv; + canvas = aCanvas; + + SPDocument *document = SP_OBJECT_DOCUMENT (namedview); + /* Kill flicker */ + sp_document_ensure_up_to_date (document); + + /* Setup Dialog Manager */ + _dlg_mgr = new Inkscape::UI::Dialog::DialogManager(); + + dkey = sp_item_display_key_new (1); + + /* Connect document */ + setDocument (document); + + number = namedview->getViewCount(); + + + /* Setup Canvas */ + g_object_set_data (G_OBJECT (canvas), "SPDesktop", this); + + SPCanvasGroup *root = sp_canvas_root (canvas); + + /* Setup adminstrative layers */ + acetate = sp_canvas_item_new (root, GNOME_TYPE_CANVAS_ACETATE, NULL); + g_signal_connect (G_OBJECT (acetate), "event", G_CALLBACK (sp_desktop_root_handler), this); + main = (SPCanvasGroup *) sp_canvas_item_new (root, SP_TYPE_CANVAS_GROUP, NULL); + g_signal_connect (G_OBJECT (main), "event", G_CALLBACK (sp_desktop_root_handler), this); + + table = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL); + SP_CTRLRECT(table)->setRectangle(NR::Rect(NR::Point(-80000, -80000), NR::Point(80000, 80000))); + SP_CTRLRECT(table)->setColor(0x00000000, true, 0x00000000); + sp_canvas_item_move_to_z (table, 0); + + page = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL); + ((CtrlRect *) page)->setColor(0x00000000, FALSE, 0x00000000); + page_border = sp_canvas_item_new (main, SP_TYPE_CTRLRECT, NULL); + + drawing = sp_canvas_item_new (main, SP_TYPE_CANVAS_ARENA, NULL); + g_signal_connect (G_OBJECT (drawing), "arena_event", G_CALLBACK (_arena_handler), this); + + SP_CANVAS_ARENA (drawing)->arena->delta = prefs_get_double_attribute ("options.cursortolerance", "value", 1.0); // default is 1 px + + // Start always in normal mode + SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_NORMAL; + canvas->rendermode = RENDERMODE_NORMAL; // canvas needs that for choosing the best buffer size + + grid = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL); + guides = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL); + sketch = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL); + controls = (SPCanvasGroup *) sp_canvas_item_new (main, SP_TYPE_CANVAS_GROUP, NULL); + + /* Push select tool to the bottom of stack */ + /** \todo + * FIXME: this is the only call to this. Everything else seems to just + * call "set" instead of "push". Can we assume that there is only one + * context ever? + */ + push_event_context (SP_TYPE_SELECT_CONTEXT, "tools.select", SP_EVENT_CONTEXT_STATIC); + + // display rect and zoom are now handled in sp_desktop_widget_realize() + + NR::Rect const d(NR::Point(0.0, 0.0), + NR::Point(sp_document_width(document), sp_document_height(document))); + + SP_CTRLRECT(page)->setRectangle(d); + SP_CTRLRECT(page_border)->setRectangle(d); + + /* the following sets the page shadow on the canvas + It was originally set to 5, which is really cheesy! + It now is an attribute in the document's namedview. If a value of + 0 is used, then the constructor for a shadow is not initialized. + */ + + if ( namedview->pageshadow != 0 && namedview->showpageshadow ) { + SP_CTRLRECT(page_border)->setShadow(namedview->pageshadow, 0x3f3f3fff); + } + + + /* Connect event for page resize */ + _doc2dt[5] = sp_document_height (document); + sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt); + + g_signal_connect (G_OBJECT (namedview), "modified", G_CALLBACK (_namedview_modified), this); + + + NRArenaItem *ai = sp_item_invoke_show (SP_ITEM (sp_document_root (document)), + SP_CANVAS_ARENA (drawing)->arena, + dkey, + SP_ITEM_SHOW_DISPLAY); + if (ai) { + nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL); + nr_arena_item_unref (ai); + } + + namedview->show(this); + /* Ugly hack */ + activate_guides (true); + /* Ugly hack */ + _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this); + + /* Construct SessionManager + * + * SessionManager construction needs to be done after document connection + */ +#ifdef WITH_INKBOARD + _whiteboard_session_manager = new Inkscape::Whiteboard::SessionManager(this); +#endif + +/* Set up notification of rebuilding the document, this allows + for saving object related settings in the document. */ + _reconstruction_start_connection = + document->connectReconstructionStart(sigc::bind(sigc::ptr_fun(_reconstruction_start), this)); + _reconstruction_finish_connection = + document->connectReconstructionFinish(sigc::bind(sigc::ptr_fun(_reconstruction_finish), this)); + _reconstruction_old_layer_id = NULL; + + // ? + // sp_active_desktop_set (desktop); + _inkscape = INKSCAPE; + + _activate_connection = _activate_signal.connect( + sigc::bind( + sigc::ptr_fun(_onActivate), + this + ) + ); + _deactivate_connection = _deactivate_signal.connect( + sigc::bind( + sigc::ptr_fun(_onDeactivate), + this + ) + ); + + _sel_modified_connection = selection->connectModified( + sigc::bind( + sigc::ptr_fun(&_onSelectionModified), + this + ) + ); + _sel_changed_connection = selection->connectChanged( + sigc::bind( + sigc::ptr_fun(&_onSelectionChanged), + this + ) + ); + +} + + +void SPDesktop::destroy() +{ + _activate_connection.disconnect(); + _deactivate_connection.disconnect(); + _sel_modified_connection.disconnect(); + _sel_changed_connection.disconnect(); + + while (event_context) { + SPEventContext *ec = event_context; + event_context = ec->next; + sp_event_context_finish (ec); + g_object_unref (G_OBJECT (ec)); + } + + if (_layer_hierarchy) { + delete _layer_hierarchy; + } + + if (_inkscape) { + _inkscape = NULL; + } + + if (drawing) { + sp_item_invoke_hide (SP_ITEM (sp_document_root (doc())), dkey); + drawing = NULL; + } + +#ifdef WITH_INKBOARD + if (_whiteboard_session_manager) { + delete _whiteboard_session_manager; + } +#endif + + delete _guides_message_context; + _guides_message_context = NULL; + + sp_signal_disconnect_by_data (G_OBJECT (namedview), this); + + g_list_free (zooms_past); + g_list_free (zooms_future); +} + +SPDesktop::~SPDesktop() {} + +//-------------------------------------------------------------------- +/* Public methods */ + +void SPDesktop::setDisplayModeNormal() +{ + SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_NORMAL; + canvas->rendermode = RENDERMODE_NORMAL; // canvas needs that for choosing the best buffer size + sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _d2w); // redraw +} + +void SPDesktop::setDisplayModeOutline() +{ + SP_CANVAS_ARENA (drawing)->arena->rendermode = RENDERMODE_OUTLINE; + canvas->rendermode = RENDERMODE_OUTLINE; // canvas needs that for choosing the best buffer size + sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (main), _d2w); // redraw +} + +/** + * Returns current root (=bottom) layer. + */ +SPObject *SPDesktop::currentRoot() const +{ + return _layer_hierarchy ? _layer_hierarchy->top() : NULL; +} + +/** + * Returns current top layer. + */ +SPObject *SPDesktop::currentLayer() const +{ + return _layer_hierarchy ? _layer_hierarchy->bottom() : NULL; +} + +/** + * Make \a object the top layer. + */ +void SPDesktop::setCurrentLayer(SPObject *object) { + g_return_if_fail(SP_IS_GROUP(object)); + g_return_if_fail( currentRoot() == object || currentRoot()->isAncestorOf(object)); + // printf("Set Layer to ID: %s\n", SP_OBJECT_ID(object)); + _layer_hierarchy->setBottom(object); +} + +/** + * Return layer that contains \a object. + */ +SPObject *SPDesktop::layerForObject(SPObject *object) { + g_return_val_if_fail(object != NULL, NULL); + + SPObject *root=currentRoot(); + object = SP_OBJECT_PARENT(object); + while ( object && object != root && !isLayer(object) ) { + object = SP_OBJECT_PARENT(object); + } + return object; +} + +/** + * True if object is a layer. + */ +bool SPDesktop::isLayer(SPObject *object) const { + return ( SP_IS_GROUP(object) + && ( SP_GROUP(object)->effectiveLayerMode(this->dkey) + == SPGroup::LAYER ) ); +} + +/** + * True if desktop viewport fully contains \a item's bbox. + */ +bool SPDesktop::isWithinViewport (SPItem *item) const +{ + NR::Rect const viewport = get_display_area(); + NR::Rect const bbox = sp_item_bbox_desktop(item); + return viewport.contains(bbox); +} + +/// +bool SPDesktop::itemIsHidden(SPItem const *item) const { + return item->isHidden(this->dkey); +} + +/** + * Set activate property of desktop; emit signal if changed. + */ +void +SPDesktop::set_active (bool new_active) +{ + if (new_active != _active) { + _active = new_active; + if (new_active) { + _activate_signal.emit(); + } else { + _deactivate_signal.emit(); + } + } +} + +/** + * Set activate status of current desktop's named view. + */ +void +SPDesktop::activate_guides(bool activate) +{ + guides_active = activate; + namedview->activateGuides(this, activate); +} + +/** + * Make desktop switch documents. + */ +void +SPDesktop::change_document (SPDocument *theDocument) +{ + g_return_if_fail (theDocument != NULL); + + /* unselect everything before switching documents */ + selection->clear(); + + setDocument (theDocument); + _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this); + _document_replaced_signal.emit (this, theDocument); +} + +/** + * Make desktop switch event contexts. + */ +void +SPDesktop::set_event_context (GtkType type, const gchar *config) +{ + SPEventContext *ec; + while (event_context) { + ec = event_context; + sp_event_context_deactivate (ec); + event_context = ec->next; + sp_event_context_finish (ec); + g_object_unref (G_OBJECT (ec)); + } + + Inkscape::XML::Node *repr = (config) ? inkscape_get_repr (_inkscape, config) : NULL; + ec = sp_event_context_new (type, this, repr, SP_EVENT_CONTEXT_STATIC); + ec->next = event_context; + event_context = ec; + sp_event_context_activate (ec); + _event_context_changed_signal.emit (this, ec); +} + +/** + * Push event context onto desktop's context stack. + */ +void +SPDesktop::push_event_context (GtkType type, const gchar *config, unsigned int key) +{ + SPEventContext *ref, *ec; + Inkscape::XML::Node *repr; + + if (event_context && event_context->key == key) return; + ref = event_context; + while (ref && ref->next && ref->next->key != key) ref = ref->next; + if (ref && ref->next) { + ec = ref->next; + ref->next = ec->next; + sp_event_context_finish (ec); + g_object_unref (G_OBJECT (ec)); + } + + if (event_context) sp_event_context_deactivate (event_context); + repr = (config) ? inkscape_get_repr (INKSCAPE, config) : NULL; + ec = sp_event_context_new (type, this, repr, key); + ec->next = event_context; + event_context = ec; + sp_event_context_activate (ec); + _event_context_changed_signal.emit (this, ec); +} + +void +SPDesktop::set_coordinate_status (NR::Point p) { + _widget->setCoordinateStatus(p); +} + + +SPItem * +SPDesktop::item_from_list_at_point_bottom (const GSList *list, NR::Point const p) const +{ + g_return_val_if_fail (doc() != NULL, NULL); + return sp_document_item_from_list_at_point_bottom (dkey, SP_GROUP (doc()->root), list, p); +} + +SPItem * +SPDesktop::item_at_point (NR::Point const p, bool into_groups, SPItem *upto) const +{ + g_return_val_if_fail (doc() != NULL, NULL); + return sp_document_item_at_point (doc(), dkey, p, into_groups, upto); +} + +SPItem * +SPDesktop::group_at_point (NR::Point const p) const +{ + g_return_val_if_fail (doc() != NULL, NULL); + return sp_document_group_at_point (doc(), dkey, p); +} + +/** + * \brief Returns the mouse point in document coordinates; if mouse is + * outside the canvas, returns the center of canvas viewpoint + */ +NR::Point +SPDesktop::point() const +{ + NR::Point p = _widget->getPointer(); + NR::Point pw = sp_canvas_window_to_world (canvas, p); + p = w2d(pw); + + NR::Rect const r = canvas->getViewbox(); + + NR::Point r0 = w2d(r.min()); + NR::Point r1 = w2d(r.max()); + + if (p[NR::X] >= r0[NR::X] && + p[NR::X] <= r1[NR::X] && + p[NR::Y] >= r1[NR::Y] && + p[NR::Y] <= r0[NR::Y]) + { + return p; + } else { + return (r0 + r1) / 2; + } +} + +/** + * Put current zoom data in history list. + */ +void +SPDesktop::push_current_zoom (GList **history) +{ + NR::Rect const area = get_display_area(); + + NRRect *old_zoom = g_new(NRRect, 1); + old_zoom->x0 = area.min()[NR::X]; + old_zoom->x1 = area.max()[NR::X]; + old_zoom->y0 = area.min()[NR::Y]; + old_zoom->y1 = area.max()[NR::Y]; + if ( *history == NULL + || !( ( ((NRRect *) ((*history)->data))->x0 == old_zoom->x0 ) && + ( ((NRRect *) ((*history)->data))->x1 == old_zoom->x1 ) && + ( ((NRRect *) ((*history)->data))->y0 == old_zoom->y0 ) && + ( ((NRRect *) ((*history)->data))->y1 == old_zoom->y1 ) ) ) + { + *history = g_list_prepend (*history, old_zoom); + } +} + +/** + * Set viewbox. + */ +void +SPDesktop::set_display_area (double x0, double y0, double x1, double y1, double border, bool log) +{ + g_assert(_widget); + + // save the zoom + if (log) { + push_current_zoom(&zooms_past); + // if we do a logged zoom, our zoom-forward list is invalidated, so delete it + g_list_free (zooms_future); + zooms_future = NULL; + } + + double const cx = 0.5 * (x0 + x1); + double const cy = 0.5 * (y0 + y1); + + NR::Rect const viewbox = NR::expand(canvas->getViewbox(), border); + + double scale = expansion(_d2w); + double newscale; + if (((x1 - x0) * viewbox.dimensions()[NR::Y]) > ((y1 - y0) * viewbox.dimensions()[NR::X])) { + newscale = viewbox.dimensions()[NR::X] / (x1 - x0); + } else { + newscale = viewbox.dimensions()[NR::Y] / (y1 - y0); + } + + newscale = CLAMP(newscale, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX); + + int clear = FALSE; + if (!NR_DF_TEST_CLOSE (newscale, scale, 1e-4 * scale)) { + /* Set zoom factors */ + _d2w = NR::Matrix(NR::scale(newscale, -newscale)); + _w2d = NR::Matrix(NR::scale(1/newscale, 1/-newscale)); + sp_canvas_item_affine_absolute(SP_CANVAS_ITEM(main), _d2w); + clear = TRUE; + } + + /* Calculate top left corner */ + x0 = cx - 0.5 * viewbox.dimensions()[NR::X] / newscale; + y1 = cy + 0.5 * viewbox.dimensions()[NR::Y] / newscale; + + /* Scroll */ + sp_canvas_scroll_to (canvas, x0 * newscale - border, y1 * -newscale - border, clear); + + _widget->updateRulers(); + _widget->updateScrollbars(expansion(_d2w)); + _widget->updateZoom(); +} + +void SPDesktop::set_display_area(NR::Rect const &a, NR::Coord b, bool log) +{ + set_display_area(a.min()[NR::X], a.min()[NR::Y], a.max()[NR::X], a.max()[NR::Y], b, log); +} + +/** + * Return viewbox dimensions. + */ +NR::Rect SPDesktop::get_display_area() const +{ + NR::Rect const viewbox = canvas->getViewbox(); + + double const scale = _d2w[0]; + + return NR::Rect(NR::Point(viewbox.min()[NR::X] / scale, viewbox.max()[NR::Y] / -scale), + NR::Point(viewbox.max()[NR::X] / scale, viewbox.min()[NR::Y] / -scale)); +} + +/** + * Revert back to previous zoom if possible. + */ +void +SPDesktop::prev_zoom() +{ + if (zooms_past == NULL) { + messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No previous zoom.")); + return; + } + + // push current zoom into forward zooms list + push_current_zoom (&zooms_future); + + // restore previous zoom + set_display_area (((NRRect *) zooms_past->data)->x0, + ((NRRect *) zooms_past->data)->y0, + ((NRRect *) zooms_past->data)->x1, + ((NRRect *) zooms_past->data)->y1, + 0, false); + + // remove the just-added zoom from the past zooms list + zooms_past = g_list_remove (zooms_past, ((NRRect *) zooms_past->data)); +} + +/** + * Set zoom to next in list. + */ +void +SPDesktop::next_zoom() +{ + if (zooms_future == NULL) { + this->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No next zoom.")); + return; + } + + // push current zoom into past zooms list + push_current_zoom (&zooms_past); + + // restore next zoom + set_display_area (((NRRect *) zooms_future->data)->x0, + ((NRRect *) zooms_future->data)->y0, + ((NRRect *) zooms_future->data)->x1, + ((NRRect *) zooms_future->data)->y1, + 0, false); + + // remove the just-used zoom from the zooms_future list + zooms_future = g_list_remove (zooms_future, ((NRRect *) zooms_future->data)); +} + +/** + * Zoom to point with absolute zoom factor. + */ +void +SPDesktop::zoom_absolute_keep_point (double cx, double cy, double px, double py, double zoom) +{ + zoom = CLAMP (zoom, SP_DESKTOP_ZOOM_MIN, SP_DESKTOP_ZOOM_MAX); + + // maximum or minimum zoom reached, but there's no exact equality because of rounding errors; + // this check prevents "sliding" when trying to zoom in at maximum zoom; + /// \todo someone please fix calculations properly and remove this hack + if (fabs(expansion(_d2w) - zoom) < 0.0001*zoom && (fabs(SP_DESKTOP_ZOOM_MAX - zoom) < 0.01 || fabs(SP_DESKTOP_ZOOM_MIN - zoom) < 0.000001)) + return; + + NR::Rect const viewbox = canvas->getViewbox(); + + double const width2 = viewbox.dimensions()[NR::X] / zoom; + double const height2 = viewbox.dimensions()[NR::Y] / zoom; + + set_display_area(cx - px * width2, + cy - py * height2, + cx + (1 - px) * width2, + cy + (1 - py) * height2, + 0.0); +} + +/** + * Zoom to center with absolute zoom factor. + */ +void +SPDesktop::zoom_absolute (double cx, double cy, double zoom) +{ + zoom_absolute_keep_point (cx, cy, 0.5, 0.5, zoom); +} + +/** + * Zoom to point with relative zoom factor. + */ +void +SPDesktop::zoom_relative_keep_point (double cx, double cy, double zoom) +{ + NR::Rect const area = get_display_area(); + + if (cx < area.min()[NR::X]) { + cx = area.min()[NR::X]; + } + if (cx > area.max()[NR::X]) { + cx = area.max()[NR::X]; + } + if (cy < area.min()[NR::Y]) { + cy = area.min()[NR::Y]; + } + if (cy > area.max()[NR::Y]) { + cy = area.max()[NR::Y]; + } + + gdouble const scale = expansion(_d2w) * zoom; + double const px = (cx - area.min()[NR::X]) / area.dimensions()[NR::X]; + double const py = (cy - area.min()[NR::Y]) / area.dimensions()[NR::Y]; + + zoom_absolute_keep_point(cx, cy, px, py, scale); +} + +/** + * Zoom to center with relative zoom factor. + */ +void +SPDesktop::zoom_relative (double cx, double cy, double zoom) +{ + gdouble scale = expansion(_d2w) * zoom; + zoom_absolute (cx, cy, scale); +} + +/** + * Set display area to origin and current document dimensions. + */ +void +SPDesktop::zoom_page() +{ + NR::Rect d(NR::Point(0, 0), + NR::Point(sp_document_width(doc()), sp_document_height(doc()))); + + if (d.dimensions()[NR::X] < 1.0 || d.dimensions()[NR::Y] < 1.0) { + return; + } + + set_display_area(d, 10); +} + +/** + * Set display area to current document width. + */ +void +SPDesktop::zoom_page_width() +{ + NR::Rect const a = get_display_area(); + + if (sp_document_width(doc()) < 1.0) { + return; + } + + NR::Rect d(NR::Point(0, a.midpoint()[NR::Y]), + NR::Point(sp_document_width(doc()), a.midpoint()[NR::Y])); + + set_display_area(d, 10); +} + +/** + * Zoom to selection. + */ +void +SPDesktop::zoom_selection() +{ + NR::Rect const d = selection->bounds(); + + if (d.dimensions()[NR::X] < 0.1 || d.dimensions()[NR::Y] < 0.1) { + return; + } + + set_display_area(d, 10); +} + +/** + * Tell widget to let zoom widget grab keyboard focus. + */ +void +SPDesktop::zoom_grab_focus() +{ + _widget->letZoomGrabFocus(); +} + +/** + * Zoom to whole drawing. + */ +void +SPDesktop::zoom_drawing() +{ + g_return_if_fail (doc() != NULL); + SPItem *docitem = SP_ITEM (sp_document_root (doc())); + g_return_if_fail (docitem != NULL); + + NR::Rect d = sp_item_bbox_desktop(docitem); + + /* Note that the second condition here indicates that + ** there are no items in the drawing. + */ + if ( d.dimensions()[NR::X] < 1.0 || d.dimensions()[NR::Y] < 1.0 ) { + return; + } + + set_display_area(d, 10); +} + +/** + * Scroll canvas by specific coordinate amount. + */ +void +SPDesktop::scroll_world (double dx, double dy) +{ + g_assert(_widget); + + NR::Rect const viewbox = canvas->getViewbox(); + + sp_canvas_scroll_to(canvas, viewbox.min()[NR::X] - dx, viewbox.min()[NR::Y] - dy, FALSE); + + _widget->updateRulers(); + _widget->updateScrollbars(expansion(_d2w)); +} + +bool +SPDesktop::scroll_to_point (NR::Point const *p, gdouble autoscrollspeed) +{ + gdouble autoscrolldistance = (gdouble) prefs_get_int_attribute_limited ("options.autoscrolldistance", "value", 0, -1000, 10000); + + // autoscrolldistance is in screen pixels, but the display area is in document units + autoscrolldistance /= expansion(_d2w); + NR::Rect const dbox = NR::expand(get_display_area(), -autoscrolldistance); + + if (!((*p)[NR::X] > dbox.min()[NR::X] && (*p)[NR::X] < dbox.max()[NR::X]) || + !((*p)[NR::Y] > dbox.min()[NR::Y] && (*p)[NR::Y] < dbox.max()[NR::Y]) ) { + + NR::Point const s_w( (*p) * _d2w ); + + gdouble x_to; + if ((*p)[NR::X] < dbox.min()[NR::X]) + x_to = dbox.min()[NR::X]; + else if ((*p)[NR::X] > dbox.max()[NR::X]) + x_to = dbox.max()[NR::X]; + else + x_to = (*p)[NR::X]; + + gdouble y_to; + if ((*p)[NR::Y] < dbox.min()[NR::Y]) + y_to = dbox.min()[NR::Y]; + else if ((*p)[NR::Y] > dbox.max()[NR::Y]) + y_to = dbox.max()[NR::Y]; + else + y_to = (*p)[NR::Y]; + + NR::Point const d_dt(x_to, y_to); + NR::Point const d_w( d_dt * _d2w ); + NR::Point const moved_w( d_w - s_w ); + + if (autoscrollspeed == 0) + autoscrollspeed = prefs_get_double_attribute_limited ("options.autoscrollspeed", "value", 1, 0, 10); + + if (autoscrollspeed != 0) + scroll_world (autoscrollspeed * moved_w); + + return true; + } + return false; +} + +void +SPDesktop::fullscreen() +{ + _widget->setFullscreen(); +} + +void +SPDesktop::getWindowGeometry (gint &x, gint &y, gint &w, gint &h) +{ + _widget->getGeometry (x, y, w, h); +} + +void +SPDesktop::setWindowPosition (NR::Point p) +{ + _widget->setPosition (p); +} + +void +SPDesktop::setWindowSize (gint w, gint h) +{ + _widget->setSize (w, h); +} + +void +SPDesktop::setWindowTransient (void *p, int transient_policy) +{ + _widget->setTransient (p, transient_policy); +} + +void +SPDesktop::presentWindow() +{ + _widget->present(); +} + +bool +SPDesktop::warnDialog (gchar *text) +{ + return _widget->warnDialog (text); +} + +void +SPDesktop::toggleRulers() +{ + _widget->toggleRulers(); +} + +void +SPDesktop::toggleScrollbars() +{ + _widget->toggleScrollbars(); +} + +void +SPDesktop::layoutWidget() +{ + _widget->layout(); +} + +void +SPDesktop::destroyWidget() +{ + _widget->destroy(); +} + +bool +SPDesktop::shutdown() +{ + return _widget->shutdown(); +} + +void +SPDesktop::setToolboxFocusTo (gchar const *label) +{ + _widget->setToolboxFocusTo (label); +} + +void +SPDesktop::setToolboxAdjustmentValue (gchar const* id, double val) +{ + _widget->setToolboxAdjustmentValue (id, val); +} + +bool +SPDesktop::isToolboxButtonActive (gchar const *id) +{ + return _widget->isToolboxButtonActive (id); +} + +void +SPDesktop::emitToolSubselectionChanged(gpointer data) +{ + _tool_subselection_changed.emit(data); + inkscape_subselection_changed (this); +} + +//---------------------------------------------------------------------- +// Callback implementations. The virtual ones are connected by the view. + +void +SPDesktop::onPositionSet (double x, double y) +{ + _widget->viewSetPosition (NR::Point(x,y)); +} + +void +SPDesktop::onResized (double x, double y) +{ + // Nothing called here +} + +/** + * Redraw callback; queues Gtk redraw; connected by View. + */ +void +SPDesktop::onRedrawRequested () +{ + if (main) { + _widget->requestCanvasUpdate(); + } +} + +/** + * Associate document with desktop. + */ +void +SPDesktop::setDocument (SPDocument *doc) +{ + if (this->doc() && doc) { + namedview->hide(this); + sp_item_invoke_hide (SP_ITEM (sp_document_root (this->doc())), dkey); + } + + if (_layer_hierarchy) { + _layer_hierarchy->clear(); + delete _layer_hierarchy; + } + _layer_hierarchy = new Inkscape::ObjectHierarchy(NULL); + _layer_hierarchy->connectAdded(sigc::bind(sigc::ptr_fun(_layer_activated), this)); + _layer_hierarchy->connectRemoved(sigc::bind(sigc::ptr_fun(_layer_deactivated), this)); + _layer_hierarchy->connectChanged(sigc::bind(sigc::ptr_fun(_layer_hierarchy_changed), this)); + _layer_hierarchy->setTop(SP_DOCUMENT_ROOT(doc)); + + /// \todo fixme: This condition exists to make sure the code + /// inside is called only once on initialization. But there + /// are surely more safe methods to accomplish this. + if (drawing) { + NRArenaItem *ai; + + namedview = sp_document_namedview (doc, NULL); + g_signal_connect (G_OBJECT (namedview), "modified", G_CALLBACK (_namedview_modified), this); + number = namedview->getViewCount(); + + ai = sp_item_invoke_show (SP_ITEM (sp_document_root (doc)), + SP_CANVAS_ARENA (drawing)->arena, + dkey, + SP_ITEM_SHOW_DISPLAY); + if (ai) { + nr_arena_item_add_child (SP_CANVAS_ARENA (drawing)->root, ai, NULL); + nr_arena_item_unref (ai); + } + namedview->show(this); + /* Ugly hack */ + activate_guides (true); + /* Ugly hack */ + _namedview_modified (namedview, SP_OBJECT_MODIFIED_FLAG, this); + } + + _document_replaced_signal.emit (this, doc); + + View::setDocument (doc); +} + +void +SPDesktop::onStatusMessage +(Inkscape::MessageType type, gchar const *message) +{ + if (_widget) { + _widget->setMessage(type, message); + } +} + +void +SPDesktop::onDocumentURISet (gchar const* uri) +{ + _widget->setTitle(uri); +} + +/** + * Resized callback. + */ +void +SPDesktop::onDocumentResized (gdouble width, gdouble height) +{ + _doc2dt[5] = height; + sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (drawing), _doc2dt); + NR::Rect const a(NR::Point(0, 0), NR::Point(width, height)); + SP_CTRLRECT(page)->setRectangle(a); + SP_CTRLRECT(page_border)->setRectangle(a); +} + + +void +SPDesktop::_onActivate (SPDesktop* dt) +{ + if (!dt->_widget) return; + dt->_widget->activateDesktop(); +} + +void +SPDesktop::_onDeactivate (SPDesktop* dt) +{ + if (!dt->_widget) return; + dt->_widget->deactivateDesktop(); +} + +void +SPDesktop::_onSelectionModified +(Inkscape::Selection *selection, guint flags, SPDesktop *dt) +{ + if (!dt->_widget) return; + dt->_widget->updateScrollbars (expansion(dt->_d2w)); +} + +static void +_onSelectionChanged +(Inkscape::Selection *selection, SPDesktop *desktop) +{ + /** \todo + * only change the layer for single selections, or what? + * This seems reasonable -- for multiple selections there can be many + * different layers involved. + */ + SPItem *item=selection->singleItem(); + if (item) { + SPObject *layer=desktop->layerForObject(item); + if ( layer && layer != desktop->currentLayer() ) { + desktop->setCurrentLayer(layer); + } + } +} + +/** + * Calls event handler of current event context. + * \param arena Unused + * \todo fixme + */ +static gint +_arena_handler (SPCanvasArena *arena, NRArenaItem *ai, GdkEvent *event, SPDesktop *desktop) +{ + if (ai) { + SPItem *spi = (SPItem*)NR_ARENA_ITEM_GET_DATA (ai); + return sp_event_context_item_handler (desktop->event_context, spi, event); + } else { + return sp_event_context_root_handler (desktop->event_context, event); + } +} + +static void +_layer_activated(SPObject *layer, SPDesktop *desktop) { + g_return_if_fail(SP_IS_GROUP(layer)); + SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::LAYER); +} + +/// Callback +static void +_layer_deactivated(SPObject *layer, SPDesktop *desktop) { + g_return_if_fail(SP_IS_GROUP(layer)); + SP_GROUP(layer)->setLayerDisplayMode(desktop->dkey, SPGroup::GROUP); +} + +/// Callback +static void +_layer_hierarchy_changed(SPObject *top, SPObject *bottom, + SPDesktop *desktop) +{ + desktop->_layer_changed_signal.emit (bottom); +} + +/// Called when document is starting to be rebuilt. +static void +_reconstruction_start (SPDesktop * desktop) +{ + // printf("Desktop, starting reconstruction\n"); + desktop->_reconstruction_old_layer_id = g_strdup(SP_OBJECT_ID(desktop->currentLayer())); + desktop->_layer_hierarchy->setBottom(desktop->currentRoot()); + + /* + GSList const * selection_objs = desktop->selection->list(); + for (; selection_objs != NULL; selection_objs = selection_objs->next) { + + } + */ + desktop->selection->clear(); + + // printf("Desktop, starting reconstruction end\n"); +} + +/// Called when document rebuild is finished. +static void +_reconstruction_finish (SPDesktop * desktop) +{ + // printf("Desktop, finishing reconstruction\n"); + if (desktop->_reconstruction_old_layer_id == NULL) + return; + + SPObject * newLayer = SP_OBJECT_DOCUMENT(desktop->namedview)->getObjectById(desktop->_reconstruction_old_layer_id); + if (newLayer != NULL) + desktop->setCurrentLayer(newLayer); + + g_free(desktop->_reconstruction_old_layer_id); + desktop->_reconstruction_old_layer_id = NULL; + // printf("Desktop, finishing reconstruction end\n"); + return; +} + +/** + * Namedview_modified callback. + */ +static void +_namedview_modified (SPNamedView *nv, guint flags, SPDesktop *desktop) +{ + if (flags & SP_OBJECT_MODIFIED_FLAG) { + + /* Recalculate snap distances */ + _update_snap_distances (desktop); + + /* Show/hide page background */ + if (nv->pagecolor & 0xff) { + sp_canvas_item_show (desktop->table); + ((CtrlRect *) desktop->table)->setColor(0x00000000, true, nv->pagecolor); + sp_canvas_item_move_to_z (desktop->table, 0); + } else { + sp_canvas_item_hide (desktop->table); + } + + /* Show/hide page border */ + if (nv->showborder) { + // show + sp_canvas_item_show (desktop->page_border); + // set color and shadow + ((CtrlRect *) desktop->page_border)->setColor(nv->bordercolor, false, 0x00000000); + if (nv->pageshadow) { + ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor); + } + // place in the z-order stack + if (nv->borderlayer == SP_BORDER_LAYER_BOTTOM) { + sp_canvas_item_move_to_z (desktop->page_border, 2); + } else { + int order = sp_canvas_item_order (desktop->page_border); + int morder = sp_canvas_item_order (desktop->drawing); + if (morder > order) sp_canvas_item_raise (desktop->page_border, + morder - order); + } + } else { + sp_canvas_item_hide (desktop->page_border); + if (nv->pageshadow) { + ((CtrlRect *) desktop->page)->setShadow(0, 0x00000000); + } + } + + /* Show/hide page shadow */ + if (nv->showpageshadow && nv->pageshadow) { + ((CtrlRect *) desktop->page_border)->setShadow(nv->pageshadow, nv->bordercolor); + } else { + ((CtrlRect *) desktop->page_border)->setShadow(0, 0x00000000); + } + + if (SP_RGBA32_A_U(nv->pagecolor) < 128 || + (SP_RGBA32_R_U(nv->pagecolor) + + SP_RGBA32_G_U(nv->pagecolor) + + SP_RGBA32_B_U(nv->pagecolor)) >= 384) { + // the background color is light or transparent, use black outline + SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = 0xff; + } else { // use white outline + SP_CANVAS_ARENA (desktop->drawing)->arena->outlinecolor = 0xffffffff; + } + } +} + +/** + * Callback to reset snapper's distances. + */ +static void +_update_snap_distances (SPDesktop *desktop) +{ + SPUnit const &px = sp_unit_get_by_id(SP_UNIT_PX); + + SPNamedView &nv = *desktop->namedview; + + nv.grid_snapper.setDistance(sp_convert_distance_full(nv.gridtolerance, + *nv.gridtoleranceunit, + px)); + nv.guide_snapper.setDistance(sp_convert_distance_full(nv.guidetolerance, + *nv.guidetoleranceunit, + px)); + nv.object_snapper.setDistance(sp_convert_distance_full(nv.objecttolerance, + *nv.objecttoleranceunit, + px)); +} + + +NR::Matrix SPDesktop::w2d() const +{ + return _w2d; +} + +NR::Point SPDesktop::w2d(NR::Point const &p) const +{ + return p * _w2d; +} + +NR::Point SPDesktop::d2w(NR::Point const &p) const +{ + return p * _d2w; +} + +NR::Matrix SPDesktop::doc2dt() const +{ + return _doc2dt; +} + +NR::Point SPDesktop::doc2dt(NR::Point const &p) const +{ + return p * _doc2dt; +} + +NR::Point SPDesktop::dt2doc(NR::Point const &p) const +{ + return p / _doc2dt; +} + + +/** + * Pop event context from desktop's context stack. Never used. + */ +// void +// SPDesktop::pop_event_context (unsigned int key) +// { +// SPEventContext *ec = NULL; +// +// if (event_context && event_context->key == key) { +// g_return_if_fail (event_context); +// g_return_if_fail (event_context->next); +// ec = event_context; +// sp_event_context_deactivate (ec); +// event_context = ec->next; +// sp_event_context_activate (event_context); +// _event_context_changed_signal.emit (this, ec); +// } +// +// SPEventContext *ref = event_context; +// while (ref && ref->next && ref->next->key != key) +// ref = ref->next; +// +// if (ref && ref->next) { +// ec = ref->next; +// ref->next = ec->next; +// } +// +// if (ec) { +// sp_event_context_finish (ec); +// g_object_unref (G_OBJECT (ec)); +// } +// } + +/* + 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 : |
