control-point.cpp revision a255d33e74f9e0bcc36575cbded6e8d6408b0e70
/**
* @file
* Desktop-bound visual control object - implementation.
*/
/* Authors:
* Krzysztof KosiĆski <tweenk.pl@gmail.com>
* Jon A. Cruz <jon@joncruz.org>
*
* Copyright (C) 2009 Authors
* Released under GNU GPL, read the file 'COPYING' for more information
*/
#include <iostream>
#include <gdkmm.h>
#include <gtkmm.h>
#include "desktop.h"
#include "desktop-handles.h"
#include "display/sp-canvas.h"
#include "display/snap-indicator.h"
#include "event-context.h"
#include "message-context.h"
#include "preferences.h"
#include "ui/tool/control-point.h"
#include "ui/tool/event-utils.h"
#include "ui/tool/transform-handle-set.h"
namespace Inkscape {
namespace UI {
// class and member documentation goes here...
/**
* @class ControlPoint
* Draggable point, the workhorse of on-canvas editing.
*
* Control points (formerly known as knots) are graphical representations of some significant
* point in the drawing. The drawing can be changed by dragging the point and the things that are
* attached to it with the mouse. Example things that could be edited with draggable points
* are gradient stops, the place where text is attached to a path, text kerns, nodes and handles
* in a path, and many more.
*
* @par Control point event handlers
* @par
* The control point has several virtual methods which allow you to react to things that
* happen to it. The most important ones are the grabbed, dragged, ungrabbed and moved functions.
* When a drag happens, the order of calls is as follows:
* - <tt>grabbed()</tt>
* - <tt>dragged()</tt>
* - <tt>dragged()</tt>
* - <tt>dragged()</tt>
* - ...
* - <tt>dragged()</tt>
* - <tt>ungrabbed()</tt>
*
* The control point can also respond to clicks and double clicks. On a double click,
* clicked() is called, followed by doubleclicked(). When deriving from SelectableControlPoint,
* you need to manually call the superclass version at the appropriate point in your handler.
*
* @par Which method to override?
* @par
* You might wonder which hook to use when you want to do things when the point is relocated.
* Here are some tips:
* - If the point is used to edit an object, override the move() method.
* - If the point can usually be dragged wherever you like but can optionally be constrained
* to axes or the like, add a handler for <tt>signal_dragged</tt> that modifies its new
* position argument.
* - If the point has additional canvas items tied to it (like handle lines), override
* the setPosition() method.
*/
/**
* @enum ControlPoint::State
* Enumeration representing the possible states of the control point, used to determine
* its appearance.
* @var ControlPoint::STATE_NORMAL
* Normal state
* @var ControlPoint::STATE_MOUSEOVER
* Mouse is hovering over the control point
* @var ControlPoint::STATE_CLICKED
* First mouse button pressed over the control point
*/
// Default colors for control points
{0xffffff00, 0x01000000}, // normal fill, stroke
{0xff0000ff, 0x01000000}, // mouseover fill, stroke
{0x0000ffff, 0x01000000} // clicked fill, stroke
};
/** Holds the currently mouseovered control point. */
/** Emitted when the mouseovered point changes. The parameter is the new mouseovered point.
* When a point ceases to be mouseovered, the parameter will be NULL. */
/** Stores the window point over which the cursor was during the last mouse button press */
/** Stores the desktop point from which the last drag was initiated */
/** Events which should be captured when a handle is being dragged. */
bool ControlPoint::_drag_initiated = false;
bool ControlPoint::_event_grab = false;
/** A color set which you can use to create an invisible control that can still receive events.
* @relates ControlPoint */
{0x00000000, 0x00000000},
{0x00000000, 0x00000000},
{0x00000000, 0x00000000}
};
/**
* Create a regular control point.
* Derive to have constructors with a reasonable number of parameters.
*
* @param d Desktop for this control
* @param initial_pos Initial position of the control point in desktop coordinates
* @param anchor Where is the control point rendered relative to its desktop coordinates
* @param shape Shape of the control point: square, diamond, circle...
* @param size Pixel size of the visual representation
* @param cset Colors of the point
* @param group The canvas group the point's canvas item should be created in
*/
: _desktop (d)
, _canvas_item (NULL)
, _state (STATE_NORMAL)
, _position (initial_pos)
{
_commonInit();
}
/**
* Create a control point with a pixbuf-based visual representation.
*
* @param d Desktop for this control
* @param initial_pos Initial position of the control point in desktop coordinates
* @param anchor Where is the control point rendered relative to its desktop coordinates
* @param pixbuf Pixbuf to be used as the visual representation
* @param cset Colors of the point
* @param group The canvas group the point's canvas item should be created in
*/
: _desktop (d)
, _canvas_item (NULL)
, _position (initial_pos)
{
_commonInit();
}
{
// avoid storing invalid points in mouseovered_point
if (this == mouseovered_point) {
}
//sp_canvas_item_hide(_canvas_item);
}
void ControlPoint::_commonInit()
{
G_CALLBACK(_event_handler), this);
}
/** Relocate the control point without side effects.
* Overload this method only if there is an additional graphical representation
* that must be updated (like the lines that connect handles to nodes). If you override it,
* you must also call the superclass implementation of the method.
* @todo Investigate whether this method should be protected */
{
}
/** Move the control point to new position with side effects.
* This is called after each drag. Override this method if only some positions make sense
* for a control point (like a point that must always be on a path and can't modify it),
* or when moving a control point changes the positions of other points. */
{
}
/** Apply an arbitrary affine transformation to a control point. This is used
* by ControlPointSelection, and is important for things like nodes with handles.
* The default implementation simply moves the point according to the transform. */
}
bool ControlPoint::visible() const
{
return sp_canvas_item_is_visible(_canvas_item);
}
/** Set the visibility of the control point. An invisible point is not drawn on the canvas
* and cannot receive any events. If you want to have an invisible point that can respond
* to events, use <tt>invisible_cset</tt> as its color set. */
void ControlPoint::setVisible(bool v)
{
if (v) sp_canvas_item_show(_canvas_item);
else sp_canvas_item_hide(_canvas_item);
}
{
return ret;
}
unsigned int ControlPoint::_size() const
{
double ret;
return static_cast<unsigned int>(ret);
}
{
return ret;
}
{
return ret;
}
{
}
// Same for setters.
{
}
{
}
{
}
{
}
// re-routes events into the virtual function
{
}
// main event callback, which emits all other callbacks.
{
// NOTE the static variables below are shared for all points!
// TODO handle clicks and drags from other buttons too
// offset from the pointer hotspot to the center of the grabbed knot in desktop coords
// number of last doubleclicked button
static unsigned next_release_doubleclick = 0;
{
case GDK_BUTTON_PRESS:
// 1st mouse button click. internally, start dragging, but do not emit signals
// or change position until drag tolerance is exceeded.
_drag_initiated = false;
// route all events to this handler
_event_grab = true;
return true;
}
return _event_grab;
case GDK_2BUTTON_PRESS:
// store the button number for next release
return true;
case GDK_MOTION_NOTIFY:
bool transferred = false;
if (!_drag_initiated) {
if (t) return true;
// if we are here, it means the tolerance was just exceeded.
// _drag_initiated might change during the above virtual call
if (!_drag_initiated) {
// this guarantees smooth redraws while dragging
_drag_initiated = true;
}
}
if (!transferred) {
// dragging in progress
// the new position is passed by reference and can be changed in the handlers.
}
return true;
}
break;
case GDK_BUTTON_RELEASE:
// If we have any pending snap event, then invoke it now!
// (This is needed because we might not have snapped on the latest GDK_MOTION_NOTIFY event
// if the mouse speed was too high. This is inherent to the snap-delay mechanism.
// We must snap at some point in time though, and this is our last chance)
// PS: For other contexts this is handled already in sp_event_context_item_handler or
// sp_event_context_root_handler
}
_event_grab = false;
if (_drag_initiated) {
// it is the end of a drag
_drag_initiated = false;
return true;
} else {
// it is the end of a click
if (next_release_doubleclick) {
} else {
}
}
}
break;
case GDK_ENTER_NOTIFY:
return true;
case GDK_LEAVE_NOTIFY:
return true;
case GDK_GRAB_BROKEN:
{
if (_drag_initiated)
}
_event_grab = false;
_drag_initiated = false;
return true;
}
break;
// update tips on modifier state change
// TODO add ESC keybinding as drag cancel
case GDK_KEY_PRESS:
{
case GDK_Escape: {
// ignore Escape if this is not a drag
if (!_drag_initiated) return false;
// Do not process delayed snap events - we might snap to a different place than we were initially
// make a fake event for dragging
// ASSUMPTION: dragging a point without modifiers will never prevent us from moving it
// to its original position
// TODO: pass correct values for x, y, x_root and y_root
fake.x = 0; // not used in handlers atm
fake.y = 0; // not used in handlers atm
_clearMouseover(); // this will also reset state to normal
_event_grab = false;
_drag_initiated = false;
} return true;
case GDK_Tab:
{// Downcast from ControlPoint to TransformHandle, if possible
// This is an ugly hack; we should have the transform handle intercept the keystrokes itself
if (th) {
th->getNextClosestPoint(false);
return true;
}
break;
}
case GDK_ISO_Left_Tab:
{// Downcast from ControlPoint to TransformHandle, if possible
// This is an ugly hack; we should have the transform handle intercept the keystrokes itself
if (th) {
th->getNextClosestPoint(true);
return true;
}
break;
}
default:
break;
}
// Do not break here, to allow for updating tooltips and such
case GDK_KEY_RELEASE:
if (mouseovered_point != this) return false;
if (_drag_initiated) {
return true; // this prevents the tool from overwriting the drag tip
} else {
// we need to return true if there was a tip available, otherwise the tool's
// handler will process this event and set the tool's message, overwriting
// the point's message
return _updateTip(state);
}
}
break;
default: break;
}
// do not propagate events during grab - it might cause problems
return _event_grab;
}
{
if (visible) { // invisible points shouldn't get mouseovered
p->_setState(STATE_MOUSEOVER);
}
p->_updateTip(state);
if (visible && mouseovered_point != p) {
mouseovered_point = p;
}
}
{
return true;
} else {
return false;
}
}
{
if (!_hasDragTips()) return false;
return true;
} else {
return false;
}
}
void ControlPoint::_clearMouseover()
{
if (mouseovered_point) {
mouseovered_point = 0;
}
}
/** Transfer the grab to another point. This method allows one to create a draggable point
* that should be dragged instead of the one that received the grabbed signal.
* This is used to implement dragging out handles in the new node tool, for example.
*
* This method will NOT emit the ungrab signal of @c prev_point, because this would complicate
* using it with selectable control points. If you use this method while dragging, you must emit
* the ungrab signal yourself.
*
* Note that this will break horribly if you try to transfer grab between points in different
* desktops, which doesn't make much sense anyway. */
{
if (!_event_grab) return;
if (!_drag_initiated) {
_drag_initiated = true;
}
}
/**
* Change the state of the knot.
* Alters the appearance of the knot to match one of the states: normal, mouseover
* or clicked.
*/
{
ColorEntry current = {0, 0};
switch(state) {
case STATE_NORMAL:
case STATE_MOUSEOVER:
case STATE_CLICKED:
};
}
{
}
// dummy implementations for handlers
// they are here to avoid unused param warnings
} // namespace UI
} // namespace Inkscape
/*
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 :