Logo Search packages:      
Sourcecode: k3d version File versions

viewport_control.cpp

Go to the documentation of this file.
// K-3D
// Copyright (c) 1995-2004, Timothy M. Shead
//
// Contact: tshead@k-3d.com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public
// License along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

/** \file
            \brief Implements the k3d::viewport namespace, which provides a MVC UI for viewing the document
            \author Tim Shead (tshead@k-3d.com)
*/

#include "context_menu.h"
#include "gtkml.h"
#include "k3ddialog.h"
#include "keyboard.h"
#include "viewport_control.h"

#include <k3dsdk/algebra.h>
#include <k3dsdk/color.h>
#include <k3dsdk/frames.h>
#include <k3dsdk/geometry.h>
#include <k3dsdk/glutility.h>
#include <k3dsdk/high_res_timer.h>
#include <k3dsdk/icommand_tree.h>
#include <k3dsdk/idag.h>
#include <k3dsdk/idocument.h>
#include <k3dsdk/ihandle.h>
#include <k3dsdk/iobject_collection.h>
#include <k3dsdk/ioptions.h>
#include <k3dsdk/iplugin_factory.h>
#include <k3dsdk/iprojection.h>
#include <k3dsdk/iuser_interface.h>
#include <k3dsdk/iviewport.h>
#include <k3dsdk/iviewport_host.h>
#include <k3dsdk/mesh.h>
#include <k3dsdk/mouse_event_observer.h>
#include <k3dsdk/objects.h>
#include <k3dsdk/plugins.h>
#include <k3dsdk/property.h>
#include <k3dsdk/property_collection.h>
#include <k3dsdk/selection.h>
#include <k3dsdk/state_change_set.h>
#include <k3dsdk/string_modifiers.h>
#include <k3dsdk/time_source.h>
#include <k3dsdk/transform.h>
#include <k3dsdk/viewport.h>

#include <sdpgtk/sdpgtkevents.h>
#include <sdpgtk/sdpgtkmouseinput.h>
#include <sdpgtk/sdpgtkopengldrawingarea.h>
#include <sdpgtk/sdpgtkutility.h>

#include <boost/any.hpp>
#include <boost/mem_fn.hpp>

#include <iomanip>

#ifdef      WIN32
#include <GL/glext.h>
#endif      //WIN32

namespace
{

const std::string control_frame("frame");
const std::string control_mousemove = "mousemove";
const std::string control_lbuttondown = "lbuttondown";
const std::string control_lbuttonclick = "lbuttonclick";
const std::string control_lbuttonup = "lbuttonup";
const std::string control_lbuttondoubleclick = "lbuttondoubleclick";
const std::string control_lbuttonstartdrag = "lbuttonstartdrag";
const std::string control_lbuttondrag = "lbuttondrag";
const std::string control_lbuttonenddrag = "lbuttonenddrag";
const std::string control_mbuttondown = "mbuttondown";
const std::string control_mbuttonclick = "mbuttonclick";
const std::string control_mbuttonup = "mbuttonup";
const std::string control_mbuttondoubleclick = "mbuttondoubleclick";
const std::string control_mbuttonstartdrag = "mbuttonstartdrag";
const std::string control_mbuttondrag = "mbuttondrag";
const std::string control_mbuttonenddrag = "mbuttonenddrag";
const std::string control_rbuttondown = "rbuttondown";
const std::string control_rbuttonclick = "rbuttonclick";
const std::string control_rbuttonup = "rbuttonup";
const std::string control_rbuttondoubleclick = "rbuttondoubleclick";
const std::string control_rbuttonstartdrag = "rbuttonstartdrag";
const std::string control_rbuttondrag = "rbuttondrag";
const std::string control_rbuttonenddrag = "rbuttonenddrag";
const std::string control_lrbuttonstartdrag = "lrbuttonstartdrag";
const std::string control_lrbuttondrag = "lrbuttondrag";
const std::string control_lrbuttonenddrag = "lrbuttonenddrag";

/// Defines the color used to XOR selection rubber-bands and lassos into the viewport
const k3d::color g_selection_color(0.215, 0.215, 0.8);

/// Defines storage for a collection of OpenGL hit records
00109 typedef std::vector<GLuint> gl_selection_buffer_t;

/// Wrapper class for OpenGL hit records - designed to resemble an STL container
class hit_record
{
public:
      explicit hit_record(GLuint* const Storage) :
            m_storage(Storage)
      {
            assert(m_storage);
      }

      /// Returns the minimum Z depth of the hit
      GLuint zmin() const
      {
            return *(m_storage+1);
      }

      /// Returns the maximum Z depth of the hit
      GLuint zmax() const
      {
            return *(m_storage+2);
      }

      /// Returns true iff the hit was empty (i.e. doesn't contain any names)
      bool empty() const
      {
            return 0 == size();
      }

      /// Returns the number of names contained in the hit
      unsigned int size() const
      {
            return *m_storage;
      }

      /// Defines an iterator type for accessing hit names
      typedef GLuint* const_name_iterator;

      /// Returns an iterator designating the beginning of the range of hit names
      const_name_iterator name_begin() const
      {
            return m_storage+3;
      }

      /// Returns an iterator designating one-past-the-end of the range of hit names
      const_name_iterator name_end() const
      {
            return m_storage+3+size();
      }

      /// Defines a strict ordering for non-empty hit records based on minimum Z depth, since we typically want to find the "closest" hit
      friend bool operator<(const hit_record& LHS, const hit_record& RHS)
      {
            if(LHS.empty())
                  return false;

            return LHS.zmin() < RHS.zmin();
      }

      /// hit_record serialization, mainly intended for debugging
      friend std::ostream& operator<<(std::ostream& Stream, const hit_record& RHS)
      {
            Stream << RHS.zmin() << " " << RHS.zmax() << " ";
            for(hit_record::const_name_iterator name = RHS.name_begin(); name != RHS.name_end(); )
                  {
                        k3d::iunknown* const unknown = k3d::glGetName(name);
                        Stream << typeid(*unknown).name() << " ";
/*
                        // If we hit a "handle", we short-circuit the rest of the process and "grab" it ...
                        k3d::ihandle* const handle = dynamic_cast<k3d::ihandle*>(unknown);
                        if(handle)
                              {
                                    handle->grab();

                                    k3d::viewport::redraw_all(m_document, k3d::iviewport::ASYNCHRONOUS);
                                    return true;
                              }

                        // Nope, so see if we hit something selectable ...
                        k3d::iselectable* const selectable = dynamic_cast<k3d::iselectable*>(unknown);
                        if(!selectable)
                              continue;

                        // Handle different selection modes ...
                        if(select_objects && dynamic_cast<k3d::iobject*>(unknown))
                              selection = selectable;
                        else if(select_objects && dynamic_cast<k3d::mesh*>(unknown))
                              selection = selectable;
                        else if(select_polygons && dynamic_cast<k3d::point_group*>(unknown))
                              selection = selectable;
                        else if(select_polygons && dynamic_cast<k3dIPath*>(unknown))
                              selection = selectable;
                        else if(select_polygons && dynamic_cast<k3d::split_edge*>(unknown))
                              selection = selectable;
                        else if(select_polygons && dynamic_cast<k3d::face*>(unknown))
                              selection = selectable;
                        else if(select_polygons && dynamic_cast<k3d::bilinear_patch*>(unknown))
                              selection = selectable;
                        else if(select_polygons && dynamic_cast<k3d::bicubic_patch*>(unknown))
                              selection = selectable;
                        else if(select_polygons && dynamic_cast<k3d::nucurve*>(unknown))
                              selection = selectable;
                        else if(select_points && dynamic_cast<k3dIPoint*>(unknown))
                              selection = selectable;
                        else if(select_points && dynamic_cast<k3d::point*>(unknown))
                              selection = selectable;
*/
                  }

            return Stream;
      }

private:
      GLuint* const m_storage;
};

/// Input iterator that extracts objects of type hit_record from a flat buffer
class hit_iterator
{
public:
      hit_iterator() :
            m_current(0),
            m_remaining(0)
      {
      }

      hit_iterator(gl_selection_buffer_t& Buffer, const unsigned int HitCount) :
            m_current(HitCount ? &Buffer[0] : 0),
            m_remaining(HitCount)
      {
      }

      hit_record operator*() const
      {
            return hit_record(m_current);
      }

      hit_record operator->() const
      {
            return hit_record(m_current);
      }

      hit_iterator& operator++()
      {
            if(m_remaining)
                  {
                        if(0 == --m_remaining)
                              m_current = 0;
                        else
                              m_current += (3 + hit_record(m_current).size());
                  }

            return *this;
      }

      hit_iterator operator++(int)
      {
            hit_iterator temp(*this);
            this->operator++();

            return temp;
      }

      friend bool operator == (const hit_iterator& LHS, const hit_iterator& RHS)
      {
            return LHS.m_current == RHS.m_current;
      }

      friend bool operator != (const hit_iterator& LHS, const hit_iterator& RHS)
      {
            return !(LHS == RHS);
      }

private:
      GLuint* m_current;
      unsigned int m_remaining;
};

/// Creates an XOR GC in the special selection color
00289 GdkGC* selection_gc(sdpGtkWidget& Widget)
{
      // Sanity checks ...
      return_val_if_fail(Widget.Attached(), 0);

      // Create the rubber-band color ...
      GdkColor color;
      color.red = static_cast<unsigned short>(g_selection_color.red * 0xffff);
      color.green = static_cast<unsigned short>(g_selection_color.green * 0xffff);
      color.blue = static_cast<unsigned short>(g_selection_color.blue * 0xffff);
      gdk_color_alloc(gdk_colormap_get_system(), &color);

      // Create an XOR gc for drawing ...
      GdkGC* gc = gdk_gc_new(static_cast<GtkWidget*>(Widget)->window);
      gdk_gc_set_foreground(gc, &color);
      gdk_gc_set_function(gc, GDK_XOR);

      return gc;
}

/// XORs a rubber-band rectangle into a widget
00310 void draw_rubber_band(sdpGtkWidget& Widget, k3d::rectangle Rectangle)
{
      // Sanity checks ...
      return_if_fail(Widget.Attached());

      // Get our GC ...
      GdkGC* const gc = selection_gc(Widget);

      // Normalize coordinates ...
      Rectangle.Normalize();

      // Draw the rectangle ...
      gdk_draw_rectangle(static_cast<GtkWidget*>(Widget)->window, gc, 0, int(Rectangle.Left()), int(Rectangle.Top()), int(Rectangle.Width()), int(Rectangle.Height()));

      // Cleanup ...
      gdk_gc_destroy(gc);
}

/*

class CEditorEngine::manipulator
{
public:
      virtual ~manipulator() {}

      virtual void render(k3dIEditorEngine& Engine) = 0;

protected:
      manipulator() {}
      manipulator(const manipulator&) {}
      manipulator& operator=(const manipulator&) { return *this; }
};

namespace
{

/// Ripped-off from GLUT
void draw_box(GLfloat size, GLenum type)
{
      static GLfloat n[6][3] = {
            {-1.0, 0.0, 0.0},
            {0.0, 1.0, 0.0},
            {1.0, 0.0, 0.0},
            {0.0, -1.0, 0.0},
            {0.0, 0.0, 1.0},
            {0.0, 0.0, -1.0}
      };

      static GLint faces[6][4] = {
            {0, 1, 2, 3},
            {3, 2, 6, 7},
            {7, 6, 5, 4},
            {4, 5, 1, 0},
            {5, 6, 2, 1},
            {7, 4, 0, 3}
      };

      GLfloat v[8][3];

      v[0][0] = v[1][0] = v[2][0] = v[3][0] = -size / 2;
      v[4][0] = v[5][0] = v[6][0] = v[7][0] = size / 2;
      v[0][1] = v[1][1] = v[4][1] = v[5][1] = -size / 2;
      v[2][1] = v[3][1] = v[6][1] = v[7][1] = size / 2;
      v[0][2] = v[3][2] = v[4][2] = v[7][2] = -size / 2;
      v[1][2] = v[2][2] = v[5][2] = v[6][2] = size / 2;

      for(GLint i = 5; i >= 0; i--)
            {
                  glBegin(type);
                  glNormal3fv(&n[i][0]);
                  glVertex3fv(&v[faces[i][0]][0]);
                  glVertex3fv(&v[faces[i][1]][0]);
                  glVertex3fv(&v[faces[i][2]][0]);
                  glVertex3fv(&v[faces[i][3]][0]);
                  glEnd();
            }
}

class handle :
      public k3d::ihandle,
      public k3d::mouse_event_observer
{
      typedef k3d::mouse_event_observer base;

public:
      virtual ~handle()
      {
            if(m_document.mouse_focus() == this)
                  {
                        m_document.set_mouse_focus(0);
                        m_released_signal.emit();
                  }
      }

      void grab()
      {
            m_document.set_mouse_focus(this);
            m_grabbed_signal.emit();
      }

      bool OnLButtonUp(k3dICamera& Camera, const k3d::key_modifiers Modifiers, const k3d::vector2 Current)
      {
            release();
            return true;
      }

      bool OnLButtonEndDrag(k3dICamera& Camera, const k3d::key_modifiers Modifiers, const k3d::vector2 Current, const k3d::vector2 Last, const k3d::vector2 Start)
      {
            return true;
      }

      grabbed_signal_t& grabbed_signal()
      {
            return m_grabbed_signal;
      }

      released_signal_t& released_signal()
      {
            return m_released_signal;
      }

      virtual void render(k3dIEditorEngine* Engine) = 0;

protected:
      handle(k3d::idocument& Document, const std::string& Message) :
            base(Message),
            m_document(Document)
      {
      }

      void release()
      {
            m_document.set_mouse_focus(0);
            m_released_signal.emit();
            k3d::viewport::redraw_all(m_document, k3d::iviewport::ASYNCHRONOUS);
      }

private:
      k3d::idocument& m_document;
      grabbed_signal_t m_grabbed_signal;
      released_signal_t m_released_signal;

      handle(const handle&);
      handle& operator=(const handle&);
};

class position_handle_1d :
      public handle
{
      typedef handle base;

public:
      position_handle_1d(k3d::iobject& Object, const std::string& Message, const k3d::angle_axis Direction, const double Radius, const k3d::color Color) :
            base(Object.m_document, Message),
            m_object(Object),
            m_document(Object.m_document),
            m_direction(Direction),
            m_radius(Radius),
            m_quadrics(gluNewQuadric())
      {
            gluQuadricDrawStyle(m_quadrics, GLenum(GLU_FILL));
            gluQuadricNormals(m_quadrics, GLenum(GLU_SMOOTH));

            m_color.push_back(Color.red);
            m_color.push_back(Color.green);
            m_color.push_back(Color.blue);
            m_color.push_back(1);

            m_emissive_color.push_back(Color.red * 0.5);
            m_emissive_color.push_back(Color.green * 0.5);
            m_emissive_color.push_back(Color.blue * 0.5);
            m_emissive_color.push_back(1);
      }

      ~position_handle_1d()
      {
            if(m_quadrics)
                  gluDeleteQuadric(m_quadrics);
      }

      bool OnLButtonDrag(k3dICamera& Camera, const k3d::key_modifiers Modifiers, const k3d::vector2 Current, const k3d::vector2 Last, const k3d::vector2 Start)
      {
            // Sanity checks ...
            return_val_if_fail(Camera.Width(), false);
            return_val_if_fail(Camera.Height(), false);

            // If the mouse didn't really move, we're done ...
            if(Current == Last)
                  return true;

            // Turn mouse coordinates into a vector in world coordinates ...
            k3d::vector3 current_origin, current_direction;
            k3d::mouse_to_world_ray(Camera, Current, current_origin, current_direction);

            k3d::vector3 last_origin, last_direction;
            k3d::mouse_to_world_ray(Camera, Last, last_origin, last_direction);

            // Get the object origin in world coordinates ...
            const k3d::vector3 world_position = k3d::world_position(m_object);
            // Create a screen-aligned plane through the object's origin ...
            const k3d::plane screen_plane(k3d::camera_to_world_matrix(Camera) * k3d::vector3(0, 0, 1), world_position);

            // Calculate intersections with the screen plane ...
            k3d::vector3 current_intersection;
            k3d::PlaneLineIntersection(screen_plane, current_origin, current_direction, current_intersection);

            k3d::vector3 last_intersection;
            k3d::PlaneLineIntersection(screen_plane, last_origin, last_direction, last_intersection);

            // Calculate the delta along the screen plane ...
            const k3d::vector3 screen_delta = current_intersection - last_intersection;

            // Calculate a direction vector for this handle ...
            const k3d::vector3 direction_vector = k3d::rotation3D(m_direction) * k3d::vector3(0, 0, 1);

            // Use the scalar projection of the screen delta onto the direction vector to calculate our final position
            const k3d::vector3 new_position = world_position + (direction_vector * (screen_delta * direction_vector));

            // Make it happen!
            assert_warning(k3d::set_property_value(m_object, "position", new_position));

            k3d::viewport::redraw_all(m_document, k3d::iviewport::ASYNCHRONOUS);
            return true;
      }

      void render(k3dIEditorEngine* Engine)
      {
            sdpgl::store_attributes attributes;

            glMatrixMode(GL_MODELVIEW);
            glPushMatrix();
            k3dPushOpenGLMatrix(k3d::object_to_world_matrix(m_object));
            glRotated(k3d::degrees(m_direction.angle), m_direction.axis[0], m_direction.axis[1], m_direction.axis[2]);

            // Draw the arrow ...
            glEnable(GL_LIGHTING);
            static GLfloat white[4] = { 1, 1, 1, 1 };
            static GLfloat black[4] = { 0, 0, 0, 1 };
            glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, black);
            glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, &m_color[0]);
            glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, white);
            glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, &m_emissive_color[0]);

            k3d::glPushName(this);

            gluCylinder(m_quadrics, m_radius * 0.025, m_radius * 0.025, m_radius, 8, 1);

            // Draw the head of the arrow ...
            glPushMatrix();
            glTranslated(0, 0, m_radius);
            gluCylinder(m_quadrics, m_radius * 0.05, m_radius * 0.025, 0, 8, 1);
            gluCylinder(m_quadrics, m_radius * 0.05, m_radius * 0.001, m_radius * 0.15, 8, 1);
            glPopMatrix();

            k3d::glPopName();

            glPopMatrix();
      }

private:
      k3d::iobject& m_object;
      k3d::idocument& m_document;
      const k3d::angle_axis m_direction;
      const double m_radius;

      std::vector<GLfloat> m_color;
      std::vector<GLfloat> m_emissive_color;

      GLUquadricObj*    const m_quadrics;
};

class position_handle_2d :
      public handle
{
      typedef handle base;

public:
      position_handle_2d(k3d::iobject& Object, const std::string& Message, const double Radius) :
            base(Object.m_document, Message),
            m_object(Object),
            m_document(Object.m_document),
            m_radius(Radius),
            m_quadrics(gluNewQuadric())
      {
            gluQuadricDrawStyle(m_quadrics, GLenum(GLU_FILL));
            gluQuadricNormals(m_quadrics, GLenum(GLU_SMOOTH));
      }

      ~position_handle_2d()
      {
            if(m_quadrics)
                  gluDeleteQuadric(m_quadrics);
      }

      bool OnLButtonDrag(k3dICamera& Camera, const k3d::key_modifiers Modifiers, const k3d::vector2 Current, const k3d::vector2 Last, const k3d::vector2 Start)
      {
            // Sanity checks ...
            return_val_if_fail(Camera.Width(), false);
            return_val_if_fail(Camera.Height(), false);

            // If the mouse didn't really move, we're done ...
            if(Current == Last)
                  return true;

            // Turn mouse coordinates into a vector in world coordinates ...
            k3d::vector3 current_origin, current_direction;
            k3d::mouse_to_world_ray(Camera, Current, current_origin, current_direction);

            k3d::vector3 last_origin, last_direction;
            k3d::mouse_to_world_ray(Camera, Last, last_origin, last_direction);

            // Get the object origin in world coordinates ...
            const k3d::vector3 world_position = k3d::world_position(m_object);
            // Create a screen-aligned plane through the object's origin ...
            const k3d::plane screen_plane(k3d::camera_to_world_matrix(Camera) * k3d::vector3(0, 0, 1), world_position);

            // Calculate intersections with the screen plane ...
            k3d::vector3 current_intersection;
            k3d::PlaneLineIntersection(screen_plane, current_origin, current_direction, current_intersection);

            k3d::vector3 last_intersection;
            k3d::PlaneLineIntersection(screen_plane, last_origin, last_direction, last_intersection);

            // Calculate the delta along the screen plane ...
            const k3d::vector3 screen_delta = current_intersection - last_intersection;

            // Calculate our final position in screen space ...
            const k3d::vector3 new_position = world_position + screen_delta;

            // Make it happen!
            assert_warning(k3d::set_property_value(m_object, "position", new_position));

            k3d::viewport::redraw_all(m_document, k3d::iviewport::ASYNCHRONOUS);
            return true;
      }

      void render(k3dIEditorEngine* Engine)
      {
            sdpgl::store_attributes attributes;
            glEnable(GL_LIGHTING);

            glMatrixMode(GL_MODELVIEW);
            glPushMatrix();
            k3dPushOpenGLMatrix(k3d::object_to_world_matrix(m_object));

            // Draw ourselves ...
            static GLfloat color[4] = { 1, 1, 1, 1 };
            static GLfloat white[4] = { 1, 1, 1, 1 };
            static GLfloat grey[4] = { 0.5, 0.5, 0.5, 1 };
            static GLfloat black[4] = { 0, 0, 0, 1 };
            glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, black);
            glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, color);
            glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, white);
            glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, grey);

            k3d::glPushName(this);
            gluSphere(m_quadrics, m_radius * 0.1, 16, 8);
            k3d::glPopName();

            glPopMatrix();
      }

private:
      k3d::iobject& m_object;
      k3d::idocument& m_document;
      const double m_radius;

      GLUquadricObj*    const m_quadrics;
};

k3d::vector3 trackball(const k3d::vector2 ScreenCoords)
{
      // Convert screen coordinates to trackball coordinates ...
      k3d::vector3 result(ScreenCoords, 0);
      const double radius = result.Length();
      if(radius < 1)
            result[2] = -sqrt(1 - radius * radius);
      else
            result.Normalize();

      return result;
}

class orientation_handle_1d :
      public handle
{
      typedef handle base;

public:
      orientation_handle_1d(k3d::iobject& Object, const std::string& Message, const k3d::angle_axis Orientation, const double Radius, const k3d::color Color) :
            base(Object.m_document, Message),
            m_object(Object),
            m_document(Object.m_document),
            m_orientation(Orientation),
            m_radius(Radius),
            m_color(Color),
            m_quadrics(gluNewQuadric())
      {
            gluQuadricDrawStyle(m_quadrics, GLenum(GLU_FILL));
      }

      ~orientation_handle_1d()
      {
            if(m_quadrics)
                  gluDeleteQuadric(m_quadrics);
      }

      bool OnLButtonDrag(k3dICamera& Camera, const k3d::key_modifiers Modifiers, const k3d::vector2 Current, const k3d::vector2 Last, const k3d::vector2 Start)
      {
assert_warning(0);
            // Sanity checks ...
            return_val_if_fail(Camera.Width(), true);
            return_val_if_fail(Camera.Height(), true);

            const k3d::vector2 CurrentMouse(Current);
            const k3d::vector2 LastMouse(Last);

            // If the mouse didn't really move, we're done ...
            if(LastMouse == CurrentMouse)
                  return true;

            k3d::vector2 lastmouse, currentmouse;
            const double width = Camera.Width();
            const double height = Camera.Height();

            if(width > height) {
                  lastmouse = k3d::transform_mouse_coordinates(Camera, LastMouse,
                                                -width / height, width / height, 1, -1, 2);
                  currentmouse = k3d::transform_mouse_coordinates(Camera, CurrentMouse,
                                          -width / height, width / height, 1, -1, 2);
            } else {
                  lastmouse = k3d::transform_mouse_coordinates(Camera, LastMouse, -1, 1,
                                          height / width, -height / width, 2);
                  currentmouse = k3d::transform_mouse_coordinates(Camera, CurrentMouse, -
                                          1, 1, height / width, -height / width, 2);
            }

            // Convert screen coordinates to "trackball" coordinates ...
            static const double trackballsize = 1 / 0.9;
            k3d::vector3 last = trackball(lastmouse * trackballsize);
            k3d::vector3 current = trackball(currentmouse * trackballsize);

            // Transform "trackball" coordinates to model coordinates ...
            k3d::iobject* parent = m_object.m_document.hierarchy().ObjectParent(&m_object);
            k3d::matrix4 cameratoworld = parent ? k3d::camera_to_object_matrix(Camera, *parent) : k3d::camera_to_world_matrix(Camera);
            last = (cameratoworld * last) - (cameratoworld * k3d::vector3(0, 0, 0));
            current = (cameratoworld * current) - (cameratoworld * k3d::vector3(0, 0, 0));

            // Convert the change in mouse position to a quaternion ...
            k3d::quaternion delta(last * current, last ^ current);

            if(Modifiers.control()) {
                  double w = k3d::Clamp(-1.0, delta.w, 1.0);
                  double phi = acos(w);

                  switch(Camera.ActiveAxis()) {
                  case k3d::X:
                        if(delta.v.n[0] >= 0)
                              delta.v = k3d::vector3(sin(phi), 0, 0);
                        else
                              delta.v = k3d::vector3(-sin(phi), 0, 0);
                        break;
                  case k3d::Y:
                        if(delta.v.n[1] >= 0)
                              delta.v = k3d::vector3(0, sin(phi), 0);
                        else
                              delta.v = k3d::vector3(0, -sin(phi), 0);
                        break;
                  case k3d::Z:
                        if(delta.v.n[2] >= 0)
                              delta.v = k3d::vector3(0, 0, sin(phi));
                        else
                              delta.v = k3d::vector3(0, 0, -sin(phi));
                        break;
                  }
            }

            k3d::itransform* xform = dynamic_cast<k3d::itransform*>(&m_object);
            if(xform) {
                  const k3d::angle_axis currentrotation = xform->orientation();
                        xform->set_orientation(k3d::angle_axis(delta * k3d::quaternion(currentrotation)));
            }

            k3d::viewport::redraw_all(m_document, k3d::iviewport::ASYNCHRONOUS);
            return true;
      }

      void render(k3dIEditorEngine* Engine)
      {
            sdpgl::store_attributes attributes;
            glDisable(GL_LIGHTING);

            glMatrixMode(GL_MODELVIEW);
            glPushMatrix();
            k3dPushOpenGLMatrix(k3d::object_to_world_matrix(m_object));
            glRotated(k3d::degrees(m_orientation.angle), m_orientation.axis[0], m_orientation.axis[1], m_orientation.axis[2]);

            // Draw the disk ...
            glColor3d(m_color.red, m_color.green, m_color.blue);

            k3d::glPushName(this);
            gluCylinder(m_quadrics, m_radius * 1.1, m_radius * 0.9, 0, 36, 1);
            k3d::glPopName();

            glPopMatrix();
      }

private:
      k3d::iobject& m_object;
      k3d::idocument& m_document;
      const k3d::angle_axis m_orientation;
      const double m_radius;
      const k3d::color m_color;

      GLUquadricObj*    const m_quadrics;
};

class scale_handle_1d :
      public handle
{
      typedef handle base;

public:
      scale_handle_1d(k3d::iobject& Object, const std::string& Message, const k3d::angle_axis Direction, const double Radius, const k3d::color Color) :
            base(Object.m_document, Message),
            m_object(Object),
            m_document(Object.m_document),
            m_direction(Direction),
            m_radius(Radius),
            m_quadrics(gluNewQuadric())
      {
            gluQuadricDrawStyle(m_quadrics, GLenum(GLU_FILL));
            gluQuadricNormals(m_quadrics, GLenum(GLU_SMOOTH));

            m_color.push_back(Color.red);
            m_color.push_back(Color.green);
            m_color.push_back(Color.blue);
            m_color.push_back(1);

            m_emissive_color.push_back(Color.red * 0.5);
            m_emissive_color.push_back(Color.green * 0.5);
            m_emissive_color.push_back(Color.blue * 0.5);
            m_emissive_color.push_back(1);
      }

      ~scale_handle_1d()
      {
            if(m_quadrics)
                  gluDeleteQuadric(m_quadrics);
      }

      bool OnLButtonDrag(k3dICamera& Camera, const k3d::key_modifiers Modifiers, const k3d::vector2 Current, const k3d::vector2 Last, const k3d::vector2 Start)
      {
            // Sanity checks ...
            return_val_if_fail(Camera.Width(), true);
            return_val_if_fail(Camera.Height(), true);

            const k3d::vector2 CurrentMouse(Current);
            const k3d::vector2 LastMouse(Last);

            // If the mouse didn't really move, we're done ...
            if(LastMouse == CurrentMouse)
                  return true;

            // Convert mouse coordinates to screen coordinates ...
            k3d::vector2 lastmouse, currentmouse;
            const double width = Camera.Width();
            const double height = Camera.Height();
            if(width > height) {
                  lastmouse = k3d::transform_mouse_coordinates(Camera, LastMouse, -width / height, width / height, 1, -1);
                  currentmouse = k3d::transform_mouse_coordinates(Camera, CurrentMouse, -width / height, width / height, 1, -1);
            } else {
                  lastmouse = k3d::transform_mouse_coordinates(Camera, LastMouse, -1, 1, height / width, -height / width);
                  currentmouse = k3d::transform_mouse_coordinates(Camera, CurrentMouse, -1, 1, height / width, -height / width);
            }

            k3d::vector2 deltamouse = currentmouse - lastmouse;

            k3d::vector3 delta;
            if(Modifiers.control()) {
                  switch(Camera.ActiveAxis()) {
                  case k3d::X:
                        delta = k3d::vector3(deltamouse[0], 0, 0);
                        break;
                  case k3d::Y:
                        delta = k3d::vector3(0, deltamouse[1], 0);
                        break;
                  case k3d::Z:
                        delta = k3d::vector3(0, 0, deltamouse[0]);
                        break;
                  default:
                        return_val_if_fail(0, true);
                  }
            }else{
                  double length = deltamouse.Length();
                  double angle = k3d::degrees(deltamouse.Angle());
                  if(angle < -45.0  || angle > 135.0) {
                        length = -length;
                  }
                  delta = k3d::vector3(length, length, length);
            }

assert_warning(0);
            k3d::itransform* const xform = dynamic_cast<k3d::itransform*>(&m_object);
            if(xform)
                  xform->set_scale(xform->scale() + delta);

            k3d::viewport::redraw_all(m_document, k3d::iviewport::ASYNCHRONOUS);
            return true;
      }

      void render(k3dIEditorEngine* Engine)
      {
            sdpgl::store_attributes attributes;

            glMatrixMode(GL_MODELVIEW);
            glPushMatrix();
            k3dPushOpenGLMatrix(k3d::object_to_world_matrix(m_object));
            glRotated(k3d::degrees(m_direction.angle), m_direction.axis[0], m_direction.axis[1], m_direction.axis[2]);

            // Draw the arrow ...
            glEnable(GL_LIGHTING);
            static GLfloat white[4] = { 1, 1, 1, 1 };
            static GLfloat black[4] = { 0, 0, 0, 1 };
            glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, black);
            glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, &m_color[0]);
            glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, white);
            glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, &m_emissive_color[0]);

            k3d::glPushName(this);

            gluCylinder(m_quadrics, m_radius * 0.025, m_radius * 0.025, m_radius, 8, 1);

            // Draw the head of the arrow ...
            glPushMatrix();
            glTranslated(0, 0, m_radius);
            draw_box(m_radius * 0.15, GL_QUADS);
            glPopMatrix();

            k3d::glPopName();

            glPopMatrix();
      }

private:
      k3d::iobject& m_object;
      k3d::idocument& m_document;
      const k3d::angle_axis m_direction;
      const double m_radius;

      std::vector<GLfloat> m_color;
      std::vector<GLfloat> m_emissive_color;

      GLUquadricObj*    const m_quadrics;
};

class scale_handle_3d :
      public handle
{
      typedef handle base;

public:
      scale_handle_3d(k3d::iobject& Object, const std::string& Message, const double Radius) :
            base(Object.m_document, Message),
            m_object(Object),
            m_document(Object.m_document),
            m_radius(Radius)
      {
      }

      bool OnLButtonDrag(k3dICamera& Camera, const k3d::key_modifiers Modifiers, const k3d::vector2 Current, const k3d::vector2 Last, const k3d::vector2 Start)
      {
            // Sanity checks ...
            return_val_if_fail(Camera.Width(), true);
            return_val_if_fail(Camera.Height(), true);

            const k3d::vector2 CurrentMouse(Current);
            const k3d::vector2 LastMouse(Last);

            // If the mouse didn't really move, we're done ...
            if(LastMouse == CurrentMouse)
                  return true;

            // Convert mouse coordinates to screen coordinates ...
            k3d::vector2 lastmouse, currentmouse;
            const double width = Camera.Width();
            const double height = Camera.Height();
            if(width > height) {
                  lastmouse = k3d::transform_mouse_coordinates(Camera, LastMouse, -width / height, width / height, 1, -1);
                  currentmouse = k3d::transform_mouse_coordinates(Camera, CurrentMouse, -width / height, width / height, 1, -1);
            } else {
                  lastmouse = k3d::transform_mouse_coordinates(Camera, LastMouse, -1, 1, height / width, -height / width);
                  currentmouse = k3d::transform_mouse_coordinates(Camera, CurrentMouse, -1, 1, height / width, -height / width);
            }

            const k3d::vector2 deltamouse = currentmouse - lastmouse;
            const double length = deltamouse.Length();
            const double angle = k3d::degrees(deltamouse.Angle());


            const double zoomfactor = (angle < -45.0  || angle > 135.0) ? pow(0.5, length) : pow(2.0, length);

assert_warning(0);
            k3d::itransform* const xform = dynamic_cast<k3d::itransform*>(&m_object);
            if(xform)
                  xform->set_scale(xform->scale() * zoomfactor);

            k3d::viewport::redraw_all(m_document, k3d::iviewport::ASYNCHRONOUS);
            return true;
      }

      void render(k3dIEditorEngine* Engine)
      {
            sdpgl::store_attributes attributes;

            glMatrixMode(GL_MODELVIEW);
            glPushMatrix();
            k3dPushOpenGLMatrix(k3d::object_to_world_matrix(m_object));

            glEnable(GL_LIGHTING);
            static GLfloat white[4] = { 1, 1, 1, 1 };
            static GLfloat grey[4] = { 0.5, 0.5, 0.5, 1 };
            static GLfloat black[4] = { 0, 0, 0, 1 };
            glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, black);
            glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, white);
            glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, white);
            glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, grey);

            k3d::glPushName(this);
            draw_box(m_radius * 0.25, GL_QUADS);
            k3d::glPopName();

            glPopMatrix();
      }

private:
      k3d::iobject& m_object;
      k3d::idocument& m_document;
      const double m_radius;
};

class basic_manipulator :
      public CEditorEngine::manipulator,
      public SigC::Object
{
public:
      basic_manipulator() :
            m_active_handle(0)
      {
      }

protected:
      void register_handle(handle* const Handle)
      {
            assert(Handle);

            m_handles.push_back(Handle);
            Handle->grabbed_signal().connect(SigC::bind(SigC::slot(*this, &basic_manipulator::on_grab_handle), Handle));
            Handle->released_signal().connect(SigC::bind(SigC::slot(*this, &basic_manipulator::on_grab_handle), static_cast<handle*>(0)));
      }

      ~basic_manipulator()
      {
            std::for_each(m_handles.begin(), m_handles.end(), k3d::delete_object());
      }

private:
      void on_grab_handle(handle* const Handle)
      {
            m_active_handle = Handle;
      }

      void render(k3dIEditorEngine& Engine)
      {
            if(m_active_handle)
                  m_active_handle->render(&Engine);
            else
                  std::for_each(m_handles.begin(), m_handles.end(), std::bind2nd(std::mem_fun(&handle::render), &Engine));
      }

      typedef std::vector<handle*> handles_t;
      handles_t m_handles;

      handle* m_active_handle;
};

class position_manipulator :
      public basic_manipulator
{
public:
      position_manipulator(k3d::iobject& Object, const k3d::bounding_box Bounds)
      {
            const double radius = Bounds.max_distance() * 1.2;

            register_handle(new position_handle_1d(Object, "LMB drag along world X axis", k3d::angle_axis(k3d::radians(90.0), k3d::vector3(0, 1, 0)), radius, k3d::color(1, 0, 0)));
            register_handle(new position_handle_1d(Object, "LMB drag along world X axis", k3d::angle_axis(k3d::radians(-90.0), k3d::vector3(0, 1, 0)), radius, k3d::color(1, 0, 0)));
            register_handle(new position_handle_1d(Object, "LMB drag along world Y axis", k3d::angle_axis(k3d::radians(-90.0), k3d::vector3(1, 0, 0)), radius, k3d::color(1, 1, 0)));
            register_handle(new position_handle_1d(Object, "LMB drag along world Y axis", k3d::angle_axis(k3d::radians(90.0), k3d::vector3(1, 0, 0)), radius, k3d::color(1, 1, 0)));
            register_handle(new position_handle_1d(Object, "LMB drag along world Z axis", k3d::angle_axis(k3d::radians(0.0), k3d::vector3(0, 1, 0)), radius, k3d::color(0, 1, 0)));
            register_handle(new position_handle_1d(Object, "LMB drag along world Z axis", k3d::angle_axis(k3d::radians(180.0), k3d::vector3(0, 1, 0)), radius, k3d::color(0, 1, 0)));
            register_handle(new position_handle_2d(Object, "LMB drag in screen space", radius));
      }
};

class orientation_manipulator :
      public basic_manipulator
{
public:
      orientation_manipulator(k3d::iobject& Object, const k3d::bounding_box Bounds)
      {
            const double radius = Bounds.max_distance();

            register_handle(new orientation_handle_1d(Object, "LMB drag around world X axis", k3d::angle_axis(k3d::radians(90.0), k3d::vector3(0, 1, 0)), radius, k3d::color(1, 0, 0)));
            register_handle(new orientation_handle_1d(Object, "LMB drag around world Y axis", k3d::angle_axis(k3d::radians(90.0), k3d::vector3(1, 0, 0)), radius, k3d::color(1, 1, 0)));
            register_handle(new orientation_handle_1d(Object, "LMB drag around world Z axis", k3d::angle_axis(k3d::radians(0.0), k3d::vector3(0, 0, 1)), radius, k3d::color(0, 1, 0)));
      }
};

class scale_manipulator :
      public basic_manipulator
{
public:
      scale_manipulator(k3d::iobject& Object, const k3d::bounding_box Bounds)
      {
            const double radius = Bounds.max_distance() * 1.2;

            register_handle(new scale_handle_1d(Object, "LMB drag to scale along local X axis", k3d::angle_axis(k3d::radians(90.0), k3d::vector3(0, 1, 0)), radius, k3d::color(1, 0, 0)));
            register_handle(new scale_handle_1d(Object, "LMB drag to scale along local X axis", k3d::angle_axis(k3d::radians(-90.0), k3d::vector3(0, 1, 0)), radius, k3d::color(1, 0, 0)));
            register_handle(new scale_handle_1d(Object, "LMB drag to scale along local Y axis", k3d::angle_axis(k3d::radians(-90.0), k3d::vector3(1, 0, 0)), radius, k3d::color(1, 1, 0)));
            register_handle(new scale_handle_1d(Object, "LMB drag to scale along local Y axis", k3d::angle_axis(k3d::radians(90.0), k3d::vector3(1, 0, 0)), radius, k3d::color(1, 1, 0)));
            register_handle(new scale_handle_1d(Object, "LMB drag to scale along local Z axis", k3d::angle_axis(k3d::radians(0.0), k3d::vector3(0, 1, 0)), radius, k3d::color(0, 1, 0)));
            register_handle(new scale_handle_1d(Object, "LMB drag to scale along local Z axis", k3d::angle_axis(k3d::radians(180.0), k3d::vector3(0, 1, 0)), radius, k3d::color(0, 1, 0)));
            register_handle(new scale_handle_3d(Object, "LMB drag to scale", radius));
      }
};
*/

} // namespace

namespace k3d
{

namespace viewport
{

////////////////////////////////////////////////////////////////////////////
// control::implementation

class control::implementation :
      public k3dDialog,
      public mouse_event_observer,
      public property_collection,
      private sdpGtkMouseInput
{
      typedef k3dDialog base;

public:
      implementation(k3d::idocument& Document, k3d::iunknown& ParentCommandNode, const sdpGtkWidget& Owner) :
            base(&ParentCommandNode, "viewport_control", 0),
            mouse_event_observer("LMB drag to select, RMB drag to pan/tilt"),
            property_collection(Document.dag()),
            m_document(Document),
            m_viewport(0),
            m_last_pick(0),
            m_active_axis(k3d::init_name("active_axis") + k3d::init_description("Active Axis [enumeration]") + k3d::init_value(k3d::Z) + k3d::init_document(Document) + k3d::init_enumeration(k3d::axis_values())),
            m_navigation_mode(k3d::init_name("navigation_mode") + k3d::init_description("Navigation Mode [enumeration]") + k3d::init_value(MODELING) + k3d::init_document(Document) + k3d::init_enumeration(navigation_mode_values())),
            m_select_objects(k3d::init_name("select_objects") + k3d::init_description("Select Objects [boolean]") + k3d::init_value(true) + k3d::init_document(Document)),
            m_select_meshes(k3d::init_name("select_meshes") + k3d::init_description("Select Meshes [boolean]") + k3d::init_value(false) + k3d::init_document(Document)),
            m_select_edges(k3d::init_name("select_edges") + k3d::init_description("Select Edges [boolean]") + k3d::init_value(false) + k3d::init_document(Document)),
            m_select_faces(k3d::init_name("select_faces") + k3d::init_description("Select Faces [boolean]") + k3d::init_value(false) + k3d::init_document(Document)),
            m_select_curves(k3d::init_name("select_curves") + k3d::init_description("Select Curves [boolean]") + k3d::init_value(false) + k3d::init_document(Document)),
            m_select_patches(k3d::init_name("select_patches") + k3d::init_description("Select Patches [boolean]") + k3d::init_value(false) + k3d::init_document(Document)),
            m_select_point_groups(k3d::init_name("select_point_groups") + k3d::init_description("Select Point Groups [boolean]") + k3d::init_value(false) + k3d::init_document(Document)),
            m_select_points(k3d::init_name("select_points") + k3d::init_description("Select Points [boolean]") + k3d::init_value(false) + k3d::init_document(Document)),
            m_context_menu(Document)
      {
            // Sanity checks ...
            assert_warning(Owner.Attached());

            MapEvent("configure-event", "configure_1", false, Owner, true);

            // Create and load our UI template ...
            std::istringstream uitemplate(
                  "<gtkml>"
                        "<eventbox>"
                              "<event signal=\"size-allocate\" name=\"size_allocate\"/>"
                              "<aspectframe name=\"frame\" shadowtype=\"none\"/>"
                        "</eventbox>"
                  "</gtkml>\n");

            return_if_fail(load_gtkml(uitemplate, "viewport control builtin template", *this));

            // Attach ourselves to the parent widget ...
            sdpGtkContainer(GTK_CONTAINER(Owner.Object())).Attach(RootWidget());

            // Create the OpenGL drawing area ...
            sdpGtkAspectFrame aspect_frame(AspectFrame(control_frame));
            
            if(!m_drawing_area.Create(aspect_frame, true, true, 8, 8, 8, 16))
                  {
                        if(!m_drawing_area.Create(aspect_frame, true, true, 5, 5, 5, 16))
                              {
                                    if(!m_drawing_area.Create(aspect_frame, false, false, 4, 4, 4, 16))
                                          {
                                                std::cerr << __PRETTY_FUNCTION__ << ": Could not find usable OpenGL visual" << std::endl;
                                          }
                              }
                  }

            if(m_drawing_area.Initialized())
                  {
                        return_if_fail(m_drawing_area.InitializeFont());

                        // Setup some events ...
                        MapEvent("expose-event", "expose_drawing_area", false, m_drawing_area, true);
                        MapEvent("motion-notify-event", "mousemove", false, m_drawing_area, true);
                        MapEvent("button-press-event", "buttondown", false, m_drawing_area, true);
                        MapEvent("button-release-event", "buttonup", false, m_drawing_area, true);
                  }

            register_property(m_active_axis);
            register_property(m_navigation_mode);
            register_property(m_select_objects);
            register_property(m_select_meshes);
            register_property(m_select_edges);
            register_property(m_select_faces);
            register_property(m_select_curves);
            register_property(m_select_patches);
            register_property(m_select_point_groups);
            register_property(m_select_points);
            
            Show();
      
            gdk_window_set_cursor(static_cast<GtkWidget*>(AspectFrame(control_frame))->window, gdk_cursor_new(GDK_PLUS));
      }

      ~implementation()
      {
            // No more events from this point forward ...
            DisconnectAllEvents();

            // Clean-up the GTK+ tree ...
            if(Root())
                  RootWidget().Destroy();

            // Get rid of our widget pointers ...
            Clear();
      }

      /// Attaches this control to a viewport (may be called zero-to-many times)
      bool attach(k3d::iviewport& Viewport)
      {
            m_viewport_deleted_connection.disconnect();
            m_viewport_host_changed_connection.disconnect();
            m_viewport_redraw_request_connection.disconnect();
            m_viewport_aspect_ratio_changed_connection.disconnect();
            
            m_viewport = &Viewport;
            
            k3d::iobject* const viewport_object = dynamic_cast<k3d::iobject*>(&Viewport);
            if(viewport_object)
                  m_viewport_deleted_connection = viewport_object->deleted_signal().connect(SigC::slot(*this, &implementation::on_viewport_deleted));
            m_viewport_host_changed_connection = Viewport.host_changed_signal().connect(SigC::slot(*this, &implementation::on_viewport_host_changed));
            m_viewport_redraw_request_connection = Viewport.redraw_request_signal().connect(SigC::slot(*this, &implementation::on_redraw_request));
            m_viewport_aspect_ratio_changed_connection = Viewport.aspect_ratio_changed_signal().connect(SigC::slot(*this, &implementation::on_viewport_aspect_ratio_changed));
      
            m_viewport_changed_signal.emit(m_viewport);

            on_size_allocate();
            on_redraw_request(k3d::iviewport::ASYNCHRONOUS);

            return true;
      }

      void on_viewport_deleted()
      {
            m_viewport_deleted_connection.disconnect();
            m_viewport_host_changed_connection.disconnect();
            m_viewport_redraw_request_connection.disconnect();
            m_viewport_aspect_ratio_changed_connection.disconnect();
            
            m_viewport = 0;

            m_viewport_changed_signal.emit(m_viewport);
            
            on_size_allocate();           
            on_redraw_request(k3d::iviewport::SYNCHRONOUS);
      }

      void on_viewport_host_changed()
      {
            m_viewport_changed_signal.emit(m_viewport);
      }
      
      void on_viewport_aspect_ratio_changed()
      {
            // Update our aspect ratio to match ...
            on_size_allocate();
      }

      k3d::viewport::control::viewport_changed_signal_t& viewport_changed_signal()
      {
            return m_viewport_changed_signal;
      }
      
      /// Signal for notifying observers that the viewport has changed (note: the new viewport could be NULL)
      k3d::viewport::control::viewport_changed_signal_t m_viewport_changed_signal;

      bool render_preview()
      {
            /** \todo Figure out something better to do than just failing, here */
            return false;
      }
      
      bool render_frame(const boost::filesystem::path& OutputImage, const bool ViewCompletedImage)
      {
            return save_frame(OutputImage, ViewCompletedImage);
      }
      
      bool render_animation(const boost::filesystem::path& OutputImages, const bool ViewCompletedImages)
      {
            // Sanity checks ...
            return_val_if_fail(!OutputImages.empty(), false);

            // Ensure that the document has animation capabilities, first ...
            k3d::iproperty* const start_time_property = k3d::get_start_time(m_document);
            k3d::iproperty* const end_time_property = k3d::get_end_time(m_document);
            k3d::iproperty* const frame_rate_property = k3d::get_frame_rate(m_document);
            k3d::iwritable_property* const time_property = dynamic_cast<k3d::iwritable_property*>(k3d::get_time(m_document));
            return_val_if_fail(start_time_property && end_time_property && frame_rate_property && time_property, false);

            // Test the output images filepath to make sure it can hold all the frames we're going to generate ...
            const double start_time = boost::any_cast<double>(k3d::get_property_value(m_document.dag(), *start_time_property));
            const double end_time = boost::any_cast<double>(k3d::get_property_value(m_document.dag(), *end_time_property));
            const double frame_rate = boost::any_cast<double>(k3d::get_property_value(m_document.dag(), *frame_rate_property));
            
            const long start_frame = static_cast<long>(k3d::round(frame_rate * start_time));
            const long end_frame = static_cast<long>(k3d::round(frame_rate * end_time));
            
            k3d::frames frames(OutputImages, start_frame, end_frame);
            return_val_if_fail(frames.max_frame() >= end_frame, false);

            // For each frame to be rendered ...
            for(long view_frame = start_frame; view_frame < end_frame; ++view_frame)
                  {
                        // Set the frame time ...
                        time_property->set_value(view_frame / frame_rate);

                        // Save that baby ...
                        boost::filesystem::path destination;
                        frames.frame(view_frame, destination);
                        
                        return_val_if_fail(save_frame(destination, ViewCompletedImages), false);
                  }

            return true;
      }
      
private:
      void OnEvent(sdpGtkEvent* Event)
      {
            // Sanity checks ...
            assert_warning(Event);

            if(Event->Name() == "expose_drawing_area")
                  on_redraw();
            else if(Event->Name() == "size_allocate")
                  on_size_allocate();
            else if(Event->Name() == "mousemove")
                  RawMouseMove(Event);
            else if(Event->Name() == "buttondown")
                  RawButtonDown(Event);
            else if(Event->Name() == "buttonup")
                  RawButtonUp(Event);
            else
                  base::OnEvent(Event);
      }

      void on_size_allocate()
      {
            // If we're minimized, we're done ...
            const double width = RootWidget().Width();
            const double height = RootWidget().Height();
            if(!width || !height)
                  return;
                        
            // If we're attached to a viewport, give it a chance to override the new aspect ratio ...
            double aspect_ratio = width / height;
            if(m_viewport)
                  m_viewport->constrain_screen_aspect_ratio(aspect_ratio);

            // Update our frame with the new ratio ...
            AspectFrame(control_frame).Set(0.5, 0.5, aspect_ratio, false);
      }

      bool save_frame(const boost::filesystem::path& OutputImage, const bool ViewCompletedImage)
      {
            return_val_if_fail(m_drawing_area.Initialized(), false);
            
            // Draw the image as we normally would ...
            const unsigned long width = m_drawing_area.Width();
            const unsigned long height = m_drawing_area.Height();
            return_val_if_fail(width && height, false);
            
            m_drawing_area.Begin();

            if(m_viewport)
                  {
                        m_viewport->redraw(width, height, m_drawing_area.FontBase());
                  }
            else
                  {
                        // Nothing attached, so just fill the screen
                        glClearColor(0.6f, 0.6f, 0.6f, 0.0f);
                        glClear(GL_COLOR_BUFFER_BIT);
                  }

            glFlush();  
            
            // Get the rendered image ...
            std::vector<unsigned char> image_buffer(width * height * 3, 0);
            glReadBuffer(GL_BACK);
            glPixelStorei(GL_PACK_SWAP_BYTES, GL_FALSE);
            glPixelStorei(GL_PACK_LSB_FIRST, GL_FALSE);
            glPixelStorei(GL_PACK_ROW_LENGTH, 0);
            glPixelStorei(GL_PACK_SKIP_ROWS, 0);
            glPixelStorei(GL_PACK_SKIP_PIXELS, 0);
            glPixelStorei(GL_PACK_ALIGNMENT, 1);
            glPixelStorei(GL_PACK_IMAGE_HEIGHT, 0);
            glPixelZoom(1.0, -1.0);
            glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, &image_buffer[0]);
            
            m_drawing_area.SwapBuffers();
            m_drawing_area.End();

            // Save that bad-boy ...
            boost::filesystem::ofstream stream(OutputImage);
          
            stream << "P6" << std::endl;
            stream << width << " " << height << std::endl;
            stream << "255" << std::endl;
                                                                                                                                                                                                                                                                   
            // Write data ...
            for(unsigned long y = 0; y != height; ++y)
                  std::copy(&image_buffer[(height - 1 - y) * width * 3], &image_buffer[(height - y) * width * 3], std::ostreambuf_iterator<char>(stream));
            
            return true;
      }

      void on_redraw()
      {
            return_if_fail(m_drawing_area.Initialized());
            
            m_drawing_area.Begin();

            if(m_viewport)
                  {
                        k3d::timer timer;

                        m_viewport->redraw(m_drawing_area.Width(), m_drawing_area.Height(), m_drawing_area.FontBase());
                        
                        const double elapsed = timer.elapsed();
                        if(elapsed)
                              {
                                    glListBase(m_drawing_area.FontBase());
            
                                    std::stringstream buffer;
                                    buffer << std::fixed << std::setprecision(1) << 1.0 / elapsed << "fps" << std::endl;

                                    glMatrixMode(GL_PROJECTION);
                                    glLoadIdentity();
                                    glOrtho(-1, 1, -1, 1, -1, 1);
                  
                                    glMatrixMode(GL_MODELVIEW);
                                    glLoadIdentity();
                  
                                    glDisable(GL_LIGHTING);
                                    glDisable(GL_TEXTURE_1D);
                                    glDisable(GL_TEXTURE_2D);
                                    glDisable(GL_BLEND);

                                    glColor3d(0, 0, 0);
                  
                                    glRasterPos3d(-0.95, -0.95, 0);
                                    glCallLists(buffer.str().size(), GL_UNSIGNED_BYTE, buffer.str().data());
                              }
                  }
            else
                  {
                        // Nothing attached, so just fill the screen
                        glClearColor(0.6f, 0.6f, 0.6f, 0.0f);
                        glClear(GL_COLOR_BUFFER_BIT);
                  }

            glFlush();  
            m_drawing_area.SwapBuffers();
            m_drawing_area.End();
      }

      const GLint select(const k3d::rectangle& SelectionRegion)
      {
            // If width or height are zero, we're done ...
            if(!m_drawing_area.Width() || !m_drawing_area.Height())
                  return 0;

            // Viewport doesn't support selection, so we're done ...
            k3d::viewport::iselection_engine* const selection_engine = dynamic_cast<k3d::viewport::iselection_engine*>(m_viewport);
            if(!selection_engine)
                  return 0;

            // Set our selection buffer to a sensible minimum size ...
            if(m_selection_buffer.size() < 128)
                  m_selection_buffer.resize(128);

            // Set an (arbitrary) upper-limit on how large we let the buffer grow ...
            while(m_selection_buffer.size() < 10000000)
                  {
                        // Draw the scene, recording hits ...
                        m_drawing_area.Begin();
                        glSelectBuffer(m_selection_buffer.size(), &m_selection_buffer[0]);
                        glRenderMode(GL_SELECT);
                        glInitNames();
                        selection_engine->select(m_drawing_area.Width(), m_drawing_area.Height(), m_drawing_area.FontBase(), SelectionRegion);
                        const GLint hits = glRenderMode(GL_RENDER);
                        glFlush();
                        m_drawing_area.End();

                        // If we got a positive number of hits, we're done ...
                        if(hits >= 0)
                              return hits;

                        // A negative number means there was buffer overflow, so try again ...
                        m_selection_buffer.resize(m_selection_buffer.size() * 2);
                  }

            // Ran out of buffer space!
            std::cerr << error << "Ran out of selection-buffer space" << std::endl;
            return 0;
      }

      void on_redraw_request(k3d::iviewport::redraw_type_t RedrawType)
      {
            // If this is a synchronous request, do a redraw right away ...
            if(k3d::iviewport::SYNCHRONOUS == RedrawType)
                  {
                        on_redraw();
                        return;
                  }
                  
            // Otherwise, request a redraw the next time the UI catches up ...
            m_drawing_area.QueueDraw();
      }

      template<typename functor_t>
      void dispatch_mouse_event(functor_t Functor, const std::string& Command, const GdkModifierType Modifiers, const k3d::vector2 CurrentMouse)
      {
            // If there's no viewport attached, we're done ...
            if(!m_viewport)
                  return;

            // If we're minimized, we're done ...
            const double width = m_drawing_area.Width();
            const double height = m_drawing_area.Height();
            if(0 == width || 0 == height)
                  return;

            // Convert mouse coordinates to Normalized Device Coordinates in the range [0, 1] ...
            const k3d::vector2 current_mouse(CurrentMouse[0] / width, CurrentMouse[1] / height);
                                                      
            k3d::imouse_event_observer::event_state state(*m_viewport, k3d::convert(Modifiers), m_active_axis.property_value());

            // Dispatch it ... see if there's an active observer, first ...
            bool result = false;
            k3d::imouse_event_observer* const focus = m_document.mouse_focus();
            if(focus)
                  result = Functor(*focus, state, current_mouse);

            // If the active observer didn't handle it, give ourselves a chance ...
            if(!result)
                  result = Functor(*this, state, current_mouse);

            // If the event was consumed, record it ...
            if(result)
                  {
                        k3d::record_command(*this, k3d::icommand_node::command_t::USER_INTERFACE, Command, k3d::to_string(k3d::convert(Modifiers)) + " " + k3d::to_string(current_mouse));
                  }
      }

      template<typename functor_t>
      void dispatch_mouse_event(functor_t Functor, const std::string& Command, const GdkModifierType Modifiers, const k3d::vector2 CurrentMouse, const k3d::vector2 LastMouse, const k3d::vector2 StartMouse, const drag_type_t DragType)
      {
            // If there's no viewport attached, we're done ...
            if(!m_viewport)
                  return;
                  
            // If we're minimized, we're done ...
            const double width = m_drawing_area.Width();
            const double height = m_drawing_area.Height();
            if(0 == width || 0 == height)
                  return;
                                                      
            // Convert mouse coordinates to Normalized Device Coordinates in the range [0, 1] ...
            const k3d::vector2 current_mouse(CurrentMouse[0] / width, CurrentMouse[1] / height);
            const k3d::vector2 last_mouse(LastMouse[0] / width, LastMouse[1] / height);
            const k3d::vector2 start_mouse(StartMouse[0] / width, StartMouse[1] / height);
                                                      
            k3d::imouse_event_observer::event_state state(*m_viewport, k3d::convert(Modifiers), m_active_axis.property_value());                                        
                                                      
            // Dispatch it ... see if there's an active observer, first ...
            bool result = false;
            k3d::imouse_event_observer* const focus = m_document.mouse_focus();
            if(focus)
                  result = Functor(*focus, state, current_mouse, last_mouse, start_mouse, DragType);

            // If the active observer didn't handle it, give ourselves a chance ...
            if(!result)
                  result = Functor(*this, state, current_mouse, last_mouse, start_mouse, DragType);

            // If the event was consumed, record it ...
            if(result)
                  {
                        k3d::record_command(*this, k3d::icommand_node::command_t::USER_INTERFACE, Command, k3d::to_string(k3d::convert(Modifiers)) + " " + k3d::to_string(current_mouse) + " " + k3d::to_string(last_mouse) + " " + k3d::to_string(start_mouse));
                  }
      }

      void OnMouseMove(GdkModifierType Modifiers, const k3d::vector2 CurrentMouse)
      {
            dispatch_mouse_event(boost::mem_fn(&k3d::imouse_event_observer::OnMouseMove), control_mousemove, Modifiers, CurrentMouse);
      }

      void OnLButtonDown(GdkModifierType Modifiers, const k3d::vector2 CurrentMouse)
      {
            dispatch_mouse_event(boost::mem_fn(&k3d::imouse_event_observer::OnLButtonDown), control_lbuttondown, Modifiers, CurrentMouse);
      }

      void OnLButtonClick(GdkModifierType Modifiers, const k3d::vector2 CurrentMouse)
      {
            dispatch_mouse_event(boost::mem_fn(&k3d::imouse_event_observer::OnLButtonClick), control_lbuttonclick, Modifiers, CurrentMouse);
      }

      void OnLButtonUp(GdkModifierType Modifiers, const k3d::vector2 CurrentMouse)
      {
            dispatch_mouse_event(boost::mem_fn(&k3d::imouse_event_observer::OnLButtonUp), control_lbuttonup, Modifiers, CurrentMouse);
      }

      void OnLButtonDoubleClick(GdkModifierType Modifiers, const k3d::vector2 CurrentMouse)
      {
            dispatch_mouse_event(boost::mem_fn(&k3d::imouse_event_observer::OnLButtonDoubleClick), control_lbuttondoubleclick, Modifiers, CurrentMouse);
      }

      void OnLButtonStartDrag(GdkModifierType Modifiers, const k3d::vector2 CurrentMouse)
      {
            dispatch_mouse_event(boost::mem_fn(&k3d::imouse_event_observer::OnLButtonDrag), control_lbuttonstartdrag, Modifiers, CurrentMouse, CurrentMouse, CurrentMouse, k3d::imouse_event_observer::START_DRAG);
      }

      void OnLButtonDrag(GdkModifierType Modifiers, const k3d::vector2 CurrentMouse, const k3d::vector2 LastMouse, const k3d::vector2 StartMouse)
      {
            dispatch_mouse_event(boost::mem_fn(&k3d::imouse_event_observer::OnLButtonDrag), control_lbuttondrag, Modifiers, CurrentMouse, LastMouse, StartMouse, k3d::imouse_event_observer::CONTINUE_DRAG);
      }

      void OnLButtonEndDrag(GdkModifierType Modifiers, const k3d::vector2 CurrentMouse, const k3d::vector2 LastMouse, const k3d::vector2 StartMouse)
      {
            dispatch_mouse_event(boost::mem_fn(&k3d::imouse_event_observer::OnLButtonDrag), control_lbuttonenddrag, Modifiers, CurrentMouse, LastMouse, StartMouse, k3d::imouse_event_observer::END_DRAG);
      }

      void OnMButtonDown(GdkModifierType Modifiers, const k3d::vector2 CurrentMouse)
      {
            dispatch_mouse_event(boost::mem_fn(&k3d::imouse_event_observer::OnMButtonDown), control_mbuttondown, Modifiers, CurrentMouse);
      }

      void OnMButtonClick(GdkModifierType Modifiers, const k3d::vector2 CurrentMouse)
      {
            dispatch_mouse_event(boost::mem_fn(&k3d::imouse_event_observer::OnMButtonClick), control_mbuttonclick, Modifiers, CurrentMouse);
      }

      void OnMButtonUp(GdkModifierType Modifiers, const k3d::vector2 CurrentMouse)
      {
            dispatch_mouse_event(boost::mem_fn(&k3d::imouse_event_observer::OnMButtonUp), control_mbuttonup, Modifiers, CurrentMouse);
      }

      void OnMButtonDoubleClick(GdkModifierType Modifiers, const k3d::vector2 CurrentMouse)
      {
            dispatch_mouse_event(boost::mem_fn(&k3d::imouse_event_observer::OnMButtonDoubleClick), control_mbuttondoubleclick, Modifiers, CurrentMouse);
      }

      void OnMButtonStartDrag(GdkModifierType Modifiers, const k3d::vector2 CurrentMouse)
      {
            dispatch_mouse_event(boost::mem_fn(&k3d::imouse_event_observer::OnMButtonDrag), control_mbuttonstartdrag, Modifiers, CurrentMouse, CurrentMouse, CurrentMouse, k3d::imouse_event_observer::START_DRAG);
      }

      void OnMButtonDrag(GdkModifierType Modifiers, const k3d::vector2 CurrentMouse, const k3d::vector2 LastMouse, const k3d::vector2 StartMouse)
      {
            dispatch_mouse_event(boost::mem_fn(&k3d::imouse_event_observer::OnMButtonDrag), control_mbuttondrag, Modifiers, CurrentMouse, LastMouse, StartMouse, k3d::imouse_event_observer::CONTINUE_DRAG);
      }

      void OnMButtonEndDrag(GdkModifierType Modifiers, const k3d::vector2 CurrentMouse, const k3d::vector2 LastMouse, const k3d::vector2 StartMouse)
      {
            dispatch_mouse_event(boost::mem_fn(&k3d::imouse_event_observer::OnMButtonDrag), control_mbuttonenddrag, Modifiers, CurrentMouse, LastMouse, StartMouse, k3d::imouse_event_observer::END_DRAG);
      }

      void OnRButtonDown(GdkModifierType Modifiers, const k3d::vector2 CurrentMouse)
      {
            dispatch_mouse_event(boost::mem_fn(&k3d::imouse_event_observer::OnRButtonDown), control_rbuttondown, Modifiers, CurrentMouse);
      }

      void OnRButtonClick(GdkModifierType Modifiers, const k3d::vector2 CurrentMouse)
      {
            dispatch_mouse_event(boost::mem_fn(&k3d::imouse_event_observer::OnRButtonClick), control_rbuttonclick, Modifiers, CurrentMouse);
      }

      void OnRButtonUp(GdkModifierType Modifiers, const k3d::vector2 CurrentMouse)
      {
            dispatch_mouse_event(boost::mem_fn(&k3d::imouse_event_observer::OnRButtonUp), control_rbuttonup, Modifiers, CurrentMouse);
      }

      void OnRButtonDoubleClick(GdkModifierType Modifiers, const k3d::vector2 CurrentMouse)
      {
            dispatch_mouse_event(boost::mem_fn(&k3d::imouse_event_observer::OnRButtonDoubleClick), control_rbuttondoubleclick, Modifiers, CurrentMouse);
      }

      void OnRButtonStartDrag(GdkModifierType Modifiers, const k3d::vector2 CurrentMouse)
      {
            dispatch_mouse_event(boost::mem_fn(&k3d::imouse_event_observer::OnRButtonDrag), control_rbuttonstartdrag, Modifiers, CurrentMouse, CurrentMouse, CurrentMouse, k3d::imouse_event_observer::START_DRAG);
      }

      void OnRButtonDrag(GdkModifierType Modifiers, const k3d::vector2 CurrentMouse, const k3d::vector2 LastMouse, const k3d::vector2 StartMouse)
      {
            dispatch_mouse_event(boost::mem_fn(&k3d::imouse_event_observer::OnRButtonDrag), control_rbuttondrag, Modifiers, CurrentMouse, LastMouse, StartMouse, k3d::imouse_event_observer::CONTINUE_DRAG);
      }

      void OnRButtonEndDrag(GdkModifierType Modifiers, const k3d::vector2 CurrentMouse, const k3d::vector2 LastMouse, const k3d::vector2 StartMouse)
      {
            dispatch_mouse_event(boost::mem_fn(&k3d::imouse_event_observer::OnRButtonDrag), control_rbuttonenddrag, Modifiers, CurrentMouse, LastMouse, StartMouse, k3d::imouse_event_observer::END_DRAG);
      }

      bool OnLButtonDown(const k3d::imouse_event_observer::event_state& State, const k3d::vector2& Current)
      {
            k3d::viewport::iselection_engine* const selection_engine = dynamic_cast<k3d::viewport::iselection_engine*>(m_viewport);
            return_val_if_fail(selection_engine, true);
                  
            m_last_pick = 0;
      
            const double sensitivity = 1;
            const k3d::rectangle selection_region(
                  Current[0] * m_drawing_area.Width() - sensitivity,
                  Current[0] * m_drawing_area.Width() + sensitivity,
                  Current[1] * m_drawing_area.Height() - sensitivity,
                  Current[1] * m_drawing_area.Height() + sensitivity);
            const unsigned int hit_count = select(selection_region);

            // We only care about the state of the shift and control keys ...
            const k3d::key_modifiers modifiers = State.modifiers & k3d::key_modifiers().set_shift().set_control();

            // Nothing selected, so deselect all
            if(0 == hit_count && !modifiers.control() && !modifiers.shift())
                  {
                        k3d::deselect_all(m_document);
                        k3d::viewport::redraw_all(m_document, k3d::iviewport::ASYNCHRONOUS);
                        return true;
                  }

            const hit_iterator nearest_hit = std::min_element(hit_iterator(m_selection_buffer, hit_count), hit_iterator());
            if(nearest_hit == hit_iterator())
                  return true;

/*
std::cerr << __PRETTY_FUNCTION__ << std::endl;
for(hit_iterator hit(m_selection_buffer, hit_count); hit != hit_iterator(); ++hit)
      std::cerr << "   " << *hit << std::endl;

std::cerr << "nearest hit: " << *nearest_hit << std::endl;

std::cerr << std::endl;
*/

            const bool select_objects = m_select_objects.property_value();
            const bool select_meshes = m_select_meshes.property_value();
            const bool select_edges = m_select_edges.property_value();
            const bool select_faces = m_select_faces.property_value();
            const bool select_linear_curves = m_select_curves.property_value();
            const bool select_cubic_curves = m_select_curves.property_value();
            const bool select_nucurves = m_select_curves.property_value();
            const bool select_bilinear_patches = m_select_patches.property_value();
            const bool select_bicubic_patches = m_select_patches.property_value();
            const bool select_nupatches = m_select_patches.property_value();
            const bool select_point_groups = m_select_point_groups.property_value();
            const bool select_points = m_select_points.property_value();

            k3d::iselectable* selection = 0;
            for(hit_record::const_name_iterator name = (*nearest_hit).name_begin(); name != (*nearest_hit).name_end(); )
                  {
                        k3d::iunknown* const unknown = k3d::glGetName(name);

                        // If we hit a "handle", we short-circuit the rest of the process and "grab" it ...
                        k3d::ihandle* const handle = dynamic_cast<k3d::ihandle*>(unknown);
                        if(handle)
                              {
                                    handle->grab();

                                    k3d::viewport::redraw_all(m_document, k3d::iviewport::ASYNCHRONOUS);
                                    return true;
                              }

                        // Nope, so see if we hit something selectable ...
                        k3d::iselectable* const selectable = dynamic_cast<k3d::iselectable*>(unknown);
                        if(!selectable)
                              continue;

                        // If it's an object, keep track of it for later ...
                        if(dynamic_cast<k3d::iobject*>(unknown))
                              m_last_pick = selectable;

                        // Handle different selection modes ...
                        if(select_objects && dynamic_cast<k3d::iobject*>(unknown))
                              selection = selectable;
                        else if(select_meshes && dynamic_cast<k3d::mesh*>(unknown))
                              selection = selectable;
                        else if(select_point_groups && dynamic_cast<k3d::point_group*>(unknown))
                              selection = selectable;
                        else if(select_edges && dynamic_cast<k3d::split_edge*>(unknown))
                              selection = selectable;
                        else if(select_faces && dynamic_cast<k3d::face*>(unknown))
                              selection = selectable;
                        else if(select_linear_curves && dynamic_cast<k3d::linear_curve*>(unknown))
                              selection = selectable;
                        else if(select_cubic_curves && dynamic_cast<k3d::cubic_curve*>(unknown))
                              selection = selectable;
                        else if(select_bilinear_patches && dynamic_cast<k3d::bilinear_patch*>(unknown))
                              selection = selectable;
                        else if(select_bicubic_patches && dynamic_cast<k3d::bicubic_patch*>(unknown))
                              selection = selectable;
                        else if(select_nucurves && dynamic_cast<k3d::nucurve*>(unknown))
                              selection = selectable;
                        else if(select_nupatches && dynamic_cast<k3d::nupatch*>(unknown))
                              selection = selectable;
                        else if(select_points && dynamic_cast<k3d::point*>(unknown))
                              selection = selectable;
                  }

            // If the control key is down, we subtract from the current selection
            if(modifiers.control())
                  {
                        m_last_pick = 0;
                        if(selection)
                              k3d::deselect(m_document, k3d::deep_selection(m_document.dag(), k3d::make_selection(*selection)));
                  }
            // If the shift key is down, we add to the current selection
            else if(modifiers.shift())
                  {
                        if(selection)
                              k3d::select(m_document, k3d::deep_selection(m_document.dag(), k3d::make_selection(*selection)));
                  }
            // Otherwise, we replace the current selection
            else
                  {
                        k3d::deselect_all(m_document);
                        if(selection)
                              k3d::select(m_document, k3d::deep_selection(m_document.dag(), k3d::make_selection(*selection)));
                  }

            k3d::viewport::redraw_all(m_document, k3d::iviewport::ASYNCHRONOUS);
            return true;
      }

      bool OnLButtonDoubleClick(const k3d::imouse_event_observer::event_state& State, const k3d::vector2& Current)
      {
            if(!m_viewport)
                  return true;

            if(!k3d::application().user_interface())
                  return true;

            k3d::iselection::selection_t selection;
            if(m_last_pick)
                  selection.insert(m_last_pick);

            if(selection.empty())
                  selection.insert(dynamic_cast<k3d::iselectable*>(m_viewport));

            for(k3d::iselection::selection_t::const_iterator selected = selection.begin(); selected != selection.end(); ++selected)
                  k3d::application().user_interface()->show(**selected);

            k3d::viewport::redraw_all(m_document, k3d::iviewport::ASYNCHRONOUS);

            return true;
      }

      bool OnLButtonDrag(const k3d::imouse_event_observer::event_state& State, const k3d::vector2& Current, const k3d::vector2& Last, const k3d::vector2& Start, const drag_type_t DragType)
      {
            if(!m_viewport)
                  return false;
                  
            if(START_DRAG == DragType)
                  {
                        // We always reset the lasso ...
                        m_lasso.clear();

/*
                        // Caps lock on?  If so, start drawing a lasso ...
                        if(State.modifiers.lock())
                              {
                                    m_lasso.push_back(k3d::vector2(Current[0] * m_drawing_area.Width(), Current[1] * m_drawing_area.Height()));
                              }
                        else
*/
                              {
                                    // Start drawing a rubber-band box ...
                                    m_rubber_band = k3d::rectangle(Current[0] * m_drawing_area.Width(), Current[0] * m_drawing_area.Width(), Current[1] * m_drawing_area.Height(), Current[1] * m_drawing_area.Height());
                                    draw_rubber_band(m_drawing_area, m_rubber_band);
                              }
                  }
            else if(CONTINUE_DRAG == DragType)
                  {
                        // If we're in lasso mode ...
                        if(!m_lasso.empty())
                              {
                                    if((Current - m_lasso[m_lasso.size()-1]).Length2() > 10)
                                          {
                                                m_lasso.push_back(k3d::vector2(Current[0] * m_drawing_area.Width(), Current[1] * m_drawing_area.Height()));

                                                GdkGC* const gc = selection_gc(m_drawing_area);

                                                gdk_draw_line(static_cast<GtkWidget*>(m_drawing_area)->window,
                                                      gc,
                                                      int(m_lasso[m_lasso.size() - 2][0]),
                                                      int(m_lasso[m_lasso.size() - 2][1]),
                                                      int(m_lasso[m_lasso.size() - 1][0]),
                                                      int(m_lasso[m_lasso.size() - 1][1]));

                                                gdk_gc_destroy(gc);
                                          }

                                    return true;
                              }

                        // Otherwise, we're in rubber-band box mode ...
                        draw_rubber_band(m_drawing_area, m_rubber_band);
                        m_rubber_band = k3d::rectangle(Start[0] * m_drawing_area.Width(), Last[0] * m_drawing_area.Width(), Start[1] * m_drawing_area.Height(), Last[1] * m_drawing_area.Height());
                        draw_rubber_band(m_drawing_area, m_rubber_band);
                        return true;
                  }
            else if(END_DRAG == DragType)
                  {
                        k3d::viewport::iselection_engine* const selection_engine = dynamic_cast<k3d::viewport::iselection_engine*>(m_viewport);
                        return_val_if_fail(selection_engine, true);
                  
                        const bool select_objects = m_select_objects.property_value();
                        const bool select_meshes = m_select_meshes.property_value();
                        const bool select_edges = m_select_edges.property_value();
                        const bool select_faces = m_select_faces.property_value();
                        const bool select_linear_curves = m_select_curves.property_value();
                        const bool select_cubic_curves = m_select_curves.property_value();
                        const bool select_nucurves = m_select_curves.property_value();
                        const bool select_bilinear_patches = m_select_patches.property_value();
                        const bool select_bicubic_patches = m_select_patches.property_value();
                        const bool select_nupatches = m_select_patches.property_value();
                        const bool select_point_groups = m_select_point_groups.property_value();
                        const bool select_points = m_select_points.property_value();

                        // Keep track of what we select ...
                        k3d::iselection::selection_t selection;

                        // If we're in lasso-selection mode
                        if(!m_lasso.empty())
                              {
                              }
                        else
                        // Rubber-band box selection mode ...
                              {
                                    // Clean-up the rubber-band ...
                                    draw_rubber_band(m_drawing_area, m_rubber_band);

                                    const k3d::rectangle selection_region(
                                          std::min(Start[0], Current[0]) * m_drawing_area.Width(),
                                          std::max(Start[0], Current[0]) * m_drawing_area.Width(),
                                          std::min(Start[1], Current[1]) * m_drawing_area.Height(),
                                          std::max(Start[1], Current[1]) * m_drawing_area.Height());
                                    const unsigned int hit_count = select(selection_region);

                                    for(hit_iterator hit(m_selection_buffer, hit_count); hit != hit_iterator(); ++hit)
                                          {
                                                for(hit_record::const_name_iterator name = (*hit).name_begin(); name != (*hit).name_end(); )
                                                      {
                                                            k3d::iunknown* const unknown = k3d::glGetName(name);

                                                            k3d::iselectable* const selectable = dynamic_cast<k3d::iselectable*>(unknown);
                                                            if(!selectable)
                                                                  continue;

                                                            // Handle different selection modes ...
                                                            if(select_objects && dynamic_cast<k3d::iobject*>(unknown))
                                                                  selection.insert(selectable);
                                                            else if(select_meshes && dynamic_cast<k3d::mesh*>(unknown))
                                                                  selection.insert(selectable);
                                                            else if(select_point_groups && dynamic_cast<k3d::point_group*>(unknown))
                                                                  selection.insert(selectable);
                                                            else if(select_edges && dynamic_cast<k3d::split_edge*>(unknown))
                                                                  selection.insert(selectable);
                                                            else if(select_faces && dynamic_cast<k3d::face*>(unknown))
                                                                  selection.insert(selectable);
                                                            else if(select_linear_curves && dynamic_cast<k3d::linear_curve*>(unknown))
                                                                  selection.insert(selectable);
                                                            else if(select_cubic_curves && dynamic_cast<k3d::cubic_curve*>(unknown))
                                                                  selection.insert(selectable);
                                                            else if(select_bilinear_patches && dynamic_cast<k3d::bilinear_patch*>(unknown))
                                                                  selection.insert(selectable);
                                                            else if(select_bicubic_patches && dynamic_cast<k3d::bicubic_patch*>(unknown))
                                                                  selection.insert(selectable);
                                                            else if(select_nucurves && dynamic_cast<k3d::nucurve*>(unknown))
                                                                  selection.insert(selectable);
                                                            else if(select_nupatches && dynamic_cast<k3d::nupatch*>(unknown))
                                                                  selection.insert(selectable);
                                                            else if(select_points && dynamic_cast<k3d::point*>(unknown))
                                                                  selection.insert(selectable);
                                                      }
                                          }
                              }

                        // Nothing selected?  We're done ...
                        if(selection.empty())
                              return true;

                        // We only care about the state of the shift and control keys ...
                        const k3d::key_modifiers modifiers = State.modifiers & k3d::key_modifiers().set_shift().set_control();

                        // If the control key is down, we subtract from the current selection
                        if(modifiers.control())
                              {
                                    k3d::deselect(m_document, k3d::deep_selection(m_document.dag(), selection));
                              }
                        // If the shift key is down, we add to the current selection
                        else if(modifiers.shift())
                              {
                                    k3d::select(m_document, k3d::deep_selection(m_document.dag(), selection));
                              }
                        // Otherwise, we replace the current selection
                        else
                              {
                                    k3d::deselect_all(m_document);
                                    k3d::select(m_document, k3d::deep_selection(m_document.dag(), selection));
                              }

                        k3d::viewport::redraw_all(m_document, k3d::iviewport::ASYNCHRONOUS);
                        return true;
                  }

            return true;
      }

      // Change focal length
      bool OnMButtonDrag(const k3d::imouse_event_observer::event_state& State, const k3d::vector2& Current, const k3d::vector2& Last, const k3d::vector2& Start, const drag_type_t DragType)
      {
            if(!m_viewport)
                  return false;

            const double sensitivity = 4.0;
            const double zoom_factor = (Current[1] < Last[1]) ? std::pow(sensitivity, Current[1] - Last[1]) : std::pow(1 / sensitivity, Last[1] - Current[1]);

            k3d::iprojection& projection = m_viewport->projection();

            k3d::iperspective* const perspective = dynamic_cast<k3d::iperspective*>(&projection);
            if(perspective)
                  {
                        k3d::set_property_value(perspective->left(), boost::any_cast<double>(perspective->left().value()) * zoom_factor);
                        k3d::set_property_value(perspective->right(), boost::any_cast<double>(perspective->right().value()) * zoom_factor);
                        k3d::set_property_value(perspective->top(), boost::any_cast<double>(perspective->top().value()) * zoom_factor);
                        k3d::set_property_value(perspective->bottom(), boost::any_cast<double>(perspective->bottom().value()) * zoom_factor);
                        
                        k3d::viewport::redraw_all(m_document, k3d::iviewport::ASYNCHRONOUS);
                        return true;
                  }
                  
            k3d::iorthographic* const orthographic = dynamic_cast<k3d::iorthographic*>(&projection);
            if(orthographic)
                  {
                        k3d::set_property_value(orthographic->left(), boost::any_cast<double>(orthographic->left().value()) * zoom_factor);
                        k3d::set_property_value(orthographic->right(), boost::any_cast<double>(orthographic->right().value()) * zoom_factor);
                        k3d::set_property_value(orthographic->top(), boost::any_cast<double>(orthographic->top().value()) * zoom_factor);
                        k3d::set_property_value(orthographic->bottom(), boost::any_cast<double>(orthographic->bottom().value()) * zoom_factor);
                        
                        k3d::viewport::redraw_all(m_document, k3d::iviewport::ASYNCHRONOUS);
                        return true;
                  }

            std::cerr << error << "Unknown projection type" << std::endl;
            return true;
      }

      bool OnRButtonClick(const k3d::imouse_event_observer::event_state& EventState, const k3d::vector2& Current)
      {
            const double sensitivity = 1;
            const k3d::rectangle selection_region(
                  Current[0] * m_drawing_area.Width() - sensitivity,
                  Current[0] * m_drawing_area.Width() + sensitivity,
                  Current[1] * m_drawing_area.Height() - sensitivity,
                  Current[1] * m_drawing_area.Height() + sensitivity);
            
            k3d::viewport::iselection_engine* const selection_engine = dynamic_cast<k3d::viewport::iselection_engine*>(m_viewport);
            return_val_if_fail(selection_engine, true);
                  
            const unsigned int hit_count = select(selection_region);

            // See if the user clicked on anything (otherwise, default to the camera) ...
            k3d::iobject* object = dynamic_cast<k3d::iobject*>(m_viewport);
            const hit_iterator nearest_hit = std::min_element(hit_iterator(m_selection_buffer, hit_count), hit_iterator());
            if(nearest_hit != hit_iterator())
                  {
                        for(hit_record::const_name_iterator name = (*nearest_hit).name_begin(); name != (*nearest_hit).name_end(); )
                              {
                                    k3d::iunknown* const unknown = k3d::glGetName(name);

                                    // If we hit a "handle", we short-circuit the rest of the process and "grab" it ...
                                    k3d::ihandle* const handle = dynamic_cast<k3d::ihandle*>(unknown);
                                    if(handle)
                                          break;

                                    // Nope, so see if we hit something selectable ...
                                    k3d::iselectable* const selectable = dynamic_cast<k3d::iselectable*>(unknown);
                                    if(!selectable)
                                          break;

                                    // See if we got an object ...
                                    if(dynamic_cast<k3d::iobject*>(unknown))
                                          {
                                                object = dynamic_cast<k3d::iobject*>(unknown);
                                                break;
                                          }
                              }
                  }

            if(!object)
                  return true;

            m_context_menu.show(this, *object);

            return true;
      }

      // "Tripod" transformations, e.g. dolly, pan, tilt, dutch-tilt ...
      void tripod_mode_drag(const k3d::imouse_event_observer::event_state& State, const k3d::vector2& Current, const k3d::vector2& Last, const k3d::vector2& Start)
      {
            // Dolly forward - back
            if(State.modifiers.shift() && State.modifiers.control())
                  {
                        k3d::iproperty* const position = k3d::get_typed_property<k3d::vector3>(*interactive_target(), "position");
                        k3d::iwritable_property* const writable_position = dynamic_cast<k3d::iwritable_property*>(position);
                        if(position && writable_position)
                              {
                                    k3d::iprojection& projection = m_viewport->projection();

                                    double focal_length = 1.0;
                                    k3d::iperspective* const perspective = dynamic_cast<k3d::iperspective*>(&projection);
                                    if(perspective)
                                          {
                                                focal_length = std::abs(boost::any_cast<double>(perspective->right().value()) - boost::any_cast<double>(perspective->left().value()));
                                          }
      
                                    k3d::iorthographic* const orthographic = dynamic_cast<k3d::iorthographic*>(&projection);
                                    if(orthographic)
                                          {
                                                focal_length = std::abs(boost::any_cast<double>(orthographic->right().value()) - boost::any_cast<double>(orthographic->left().value()));
                                          }
                                    const double sensitivity = focal_length ? 20.0 / focal_length : 0.0;
      
                                    const double deltay = Last[1] - Current[1];
            
                                    const k3d::matrix4 matrix = k3d::object_to_world_matrix(*interactive_target());
                                    const k3d::vector3 forward = (matrix * k3d::vector3(0, 0, 1)) - (matrix * k3d::vector3(0, 0, 0));
            
                                    writable_position->set_value(boost::any_cast<k3d::vector3>(position->value()) + (deltay * sensitivity * forward));
                                    
                                    k3d::viewport::redraw_all(m_document, k3d::iviewport::ASYNCHRONOUS);
                              }
                        
                        return;
                  }
                  
            // Dolly left - right - up - down
            if(State.modifiers.shift())
                  {
                        k3d::iproperty* const position = k3d::get_typed_property<k3d::vector3>(*interactive_target(), "position");
                        k3d::iwritable_property* const writable_position = dynamic_cast<k3d::iwritable_property*>(position);
                        if(position && writable_position)
                              {
                                    k3d::iprojection& projection = m_viewport->projection();

                                    double focal_length = 1.0;
                                    k3d::iperspective* const perspective = dynamic_cast<k3d::iperspective*>(&projection);
                                    if(perspective)
                                          {
                                                focal_length = std::abs(boost::any_cast<double>(perspective->right().value()) - boost::any_cast<double>(perspective->left().value()));
                                          }
      
                                    k3d::iorthographic* const orthographic = dynamic_cast<k3d::iorthographic*>(&projection);
                                    if(orthographic)
                                          {
                                                focal_length = std::abs(boost::any_cast<double>(orthographic->right().value()) - boost::any_cast<double>(orthographic->left().value()));
                                          }
                                    const double sensitivity = focal_length ? 20.0 / focal_length : 0.0;
      
                                    const double deltax = Current[0] - Last[0];
                                    const double deltay = Last[1] - Current[1];
            
                                    const k3d::matrix4 matrix = k3d::object_to_world_matrix(*interactive_target());
                                    const k3d::vector3 right = (matrix * k3d::vector3(1, 0, 0)) - (matrix * k3d::vector3(0, 0, 0));
                                    const k3d::vector3 up = (matrix * k3d::vector3(0, 1, 0)) - (matrix * k3d::vector3(0, 0, 0));
            
                                    writable_position->set_value(boost::any_cast<k3d::vector3>(position->value()) + (sensitivity * deltax * right) + (sensitivity * deltay * up));
                                    k3d::viewport::redraw_all(m_document, k3d::iviewport::ASYNCHRONOUS);
                              }
                        
                        return;
                  }
            
            // Dutch tilt ...
            if(State.modifiers.control())
                  {
                        k3d::iproperty* const orientation = k3d::get_typed_property<k3d::angle_axis>(*interactive_target(), "orientation");
                        k3d::iwritable_property* const writable_orientation = dynamic_cast<k3d::iwritable_property*>(orientation);
                        if(orientation && writable_orientation)
                              {
                                    // We want mouse coordinates relative to the center of the window ...
                                    k3d::vector2 currentmouse(Current[0] - 0.5, 0.5 - Current[1]);
                                    k3d::vector2 lastmouse(Last[0] - 0.5, 0.5 - Last[1]);
                                    const double deltatheta = currentmouse.Angle() - lastmouse.Angle();

                                    k3d::quaternion quaternion(boost::any_cast<k3d::angle_axis>(orientation->value()));
                                    k3d::euler_angles angles(quaternion, k3d::euler_angles::ZXYstatic);
                                    angles[0] -= deltatheta;
                                    
                                    writable_orientation->set_value(k3d::angle_axis(angles));
                                    k3d::viewport::redraw_all(m_document, k3d::iviewport::ASYNCHRONOUS);
                              }
                              
                        return;
                  }

            // Pan & tilt ...
            k3d::iproperty* const orientation = k3d::get_typed_property<k3d::angle_axis>(*interactive_target(), "orientation");
            k3d::iwritable_property* const writable_orientation = dynamic_cast<k3d::iwritable_property*>(orientation);
            if(orientation && writable_orientation)
                  {
                        k3d::quaternion quaternion(boost::any_cast<k3d::angle_axis>(orientation->value()));
                        k3d::euler_angles angles(quaternion, k3d::euler_angles::ZXYstatic);
                        
                        angles[1] += Current[1] - Last[1];
                        angles[2] += Current[0] - Last[0];
                        
                        writable_orientation->set_value(k3d::angle_axis(angles));
                        k3d::viewport::redraw_all(m_document, k3d::iviewport::ASYNCHRONOUS);
                  }
            
            k3d::viewport::redraw_all(m_document, k3d::iviewport::ASYNCHRONOUS);
      }
      
      // "Modeling" transformations, i.e. orbit around the origin ...
      void modeling_mode_drag(const k3d::imouse_event_observer::event_state& State, const k3d::vector2& Current, const k3d::vector2& Last, const k3d::vector2& Start)
      {
            // Dolly forward - back
            if(State.modifiers.shift() && State.modifiers.control())
                  {
                        k3d::iproperty* const position = k3d::get_typed_property<k3d::vector3>(*interactive_target(), "position");
                        k3d::iwritable_property* const writable_position = dynamic_cast<k3d::iwritable_property*>(position);
                        if(position && writable_position)
                              {
                                    k3d::iprojection& projection = m_viewport->projection();

                                    const double sensitivity = 30.0;
                                    double focal_length = 1.0;
                                    k3d::iperspective* const perspective = dynamic_cast<k3d::iperspective*>(&projection);
                                    if(perspective)
                                          {
                                                focal_length = std::abs(boost::any_cast<double>(perspective->right().value()) - boost::any_cast<double>(perspective->left().value()));
                                          }
      
                                    k3d::iorthographic* const orthographic = dynamic_cast<k3d::iorthographic*>(&projection);
                                    if(orthographic)
                                          {
                                                focal_length = std::abs(boost::any_cast<double>(orthographic->right().value()) - boost::any_cast<double>(orthographic->left().value()));
                                          }
      
                                    const double deltay = Last[1] - Current[1];
            
                                    const k3d::matrix4 matrix = k3d::object_to_world_matrix(*interactive_target());
                                    const k3d::vector3 forward = (matrix * k3d::vector3(0, 0, 1)) - (matrix * k3d::vector3(0, 0, 0));
            
                                    writable_position->set_value(boost::any_cast<k3d::vector3>(position->value()) + deltay * (sensitivity / focal_length) * forward);
                                    
                                    k3d::viewport::redraw_all(m_document, k3d::iviewport::ASYNCHRONOUS);
                              }
                        
                        return;
                  }
                  
            // "Orbit" the origin ...
            k3d::iproperty* const position = k3d::get_typed_property<k3d::vector3>(*interactive_target(), "position");
            k3d::iwritable_property* const writable_position = dynamic_cast<k3d::iwritable_property*>(position);
            k3d::iproperty* const orientation = k3d::get_typed_property<k3d::angle_axis>(*interactive_target(), "orientation");
            k3d::iwritable_property* const writable_orientation = dynamic_cast<k3d::iwritable_property*>(orientation);

            if(position && writable_position && orientation && writable_orientation)
                  {           
                        // Calculate our field-of-view (based on a perspective view) ...
                        const double deltax = k3d::pi();
                        const double deltay = k3d::pi();

                        k3d::matrix4 matCameraToWorld = k3d::object_to_world_matrix(*interactive_target());
                        k3d::matrix4 matWorldToParent = k3d::identity3D();
                        k3d::matrix4 matParentToWorld = k3d::identity3D();

                        k3d::quaternion q(boost::any_cast<k3d::angle_axis>(orientation->value()));

                        // Get the rotation centre in world coordinates, just default to the world origin for now.
                        k3d::vector3 centre = k3d::vector3(0,0,0);
                        // Get the camera offset position.
                        k3d::vector3 pos = matCameraToWorld * k3d::vector3(0, 0, 0);
                        pos = matParentToWorld * pos;
                        // Move so that the requested rotation centre is at the origin of camera space
                        pos -= centre;
                        // Rotate about the local x axis using the y mouse movement.
                        k3d::vector3 xaxis = matCameraToWorld * k3d::vector3(1,0,0);
                        xaxis -= (matCameraToWorld * k3d::vector3(0,0,0));
                        k3d::angle_axis axrot(-(double(Last[1] - Current[1]) * deltay), xaxis);
                        pos = k3d::matrix4(k3d::rotation3D(axrot)) * pos;
                        q = k3d::quaternion(axrot)*q;
                        // Now rotate about the world y axis using the mouse x movement.
                        k3d::vector3 yaxis = matWorldToParent * k3d::vector3(0,1,0);
                        yaxis -= (matWorldToParent * k3d::vector3(0,0,0));
                        k3d::angle_axis ayrot = k3d::angle_axis(-(double(Last[0] - Current[0]) * deltax), yaxis);
                        pos = k3d::matrix4(k3d::rotation3D(ayrot)) * pos;
                        q = k3d::quaternion(ayrot)*q;
                        // Move back by the centre offset.
                        pos += centre;
                        // and convert back into parent coordinates.
                        pos = matWorldToParent * pos;
                        // Put the new position and orientation of the camera back.
                        k3d::set_position(*interactive_target(), matWorldToParent * pos);
                        k3d::set_orientation(*interactive_target(), q);
            
                        k3d::viewport::redraw_all(m_document, k3d::iviewport::ASYNCHRONOUS);
                  }
      }

      k3d::iunknown* interactive_target()
      {
            iunknown* result = m_viewport;
            if(m_viewport && m_viewport->host())
                  result = m_viewport->host();

            return result;
      }

      bool OnRButtonDrag(const k3d::imouse_event_observer::event_state& State, const k3d::vector2& Current, const k3d::vector2& Last, const k3d::vector2& Start, const drag_type_t DragType)
      {
            if(!m_viewport)
                  return false;
                  
            switch(m_navigation_mode.property_value())
                  {
                        case TRIPOD:
                              tripod_mode_drag(State, Current, Last, Start);
                              break;
                        case MODELING:
                              modeling_mode_drag(State, Current, Last, Start);
                              break;
                  }
            
            return true;
      }

      void mouse_command(GtkWidget* const Widget, const k3d::iuser_interface::mouse_action_t Action, const k3d::key_modifiers& Modifiers, const k3d::vector2& CurrentMouse)
      {
            return_if_fail(Widget);

            if(k3d::application().user_interface())
                  {
                        k3d::application().user_interface()->tutorial_mouse_message("", Action, Modifiers);
                        sdpGtkWarpPointer(Widget, int(CurrentMouse[0]), int(CurrentMouse[1]));
                        sdpGtkHandlePendingEvents();
                        sdpGtkSleep(20);
                  }
      }

      bool execute_command(const std::string& Command, const std::string& Arguments)
      {
            // Extract state information from the arguments ...
            std::istringstream arguments(Arguments);
            k3d::key_modifiers modifiers;
            k3d::vector2 currentmouse;
            k3d::vector2 lastmouse;
            k3d::vector2 startmouse;
            arguments >> modifiers >> currentmouse >> lastmouse >> startmouse;

            // Convert the mouse coordinates from percentages to pixels ...
            const k3d::vector2 viewport_size(static_cast<double>(m_drawing_area.Width()), static_cast<double>(m_drawing_area.Height()));
            currentmouse = Product(currentmouse, viewport_size);
            lastmouse = Product(lastmouse, viewport_size);
            startmouse = Product(startmouse, viewport_size);

            const double tutorialspeed = k3d::application().options().tutorial_speed();
            const unsigned long delay = static_cast<unsigned long>(1200 / tutorialspeed);

            GtkWidget* widget = GTK_WIDGET(m_drawing_area.Object());

            if(Command == control_mousemove)
                  {
                        OnMouseMove(k3d::convert(modifiers), currentmouse);
                        mouse_command(widget, k3d::iuser_interface::MB_NONE, modifiers, currentmouse);
                  }
            else if(Command == control_lbuttondown)
                  {
                        m_drawing_area.InteractiveWarpPointer(int(currentmouse[0]), int(currentmouse[1]), tutorialspeed, true, false);
                        OnLButtonDown(k3d::convert(modifiers), currentmouse);
                        mouse_command(widget, k3d::iuser_interface::LMB_CLICK, modifiers, currentmouse);
                        sdpGtkSleep(delay);
                  }
            else if(Command == control_lbuttonclick)
                  {
                        m_drawing_area.InteractiveWarpPointer(int(currentmouse[0]), int(currentmouse[1]), tutorialspeed, true, false);
                        OnLButtonClick(k3d::convert(modifiers), currentmouse);
                        mouse_command(widget, k3d::iuser_interface::LMB_CLICK, modifiers, currentmouse);
                        sdpGtkSleep(delay);
                  }
            else if(Command == control_lbuttondoubleclick)
                  {
                        m_drawing_area.InteractiveWarpPointer(int(currentmouse[0]), int(currentmouse[1]), tutorialspeed, true, false);
                        OnLButtonDoubleClick(k3d::convert(modifiers), currentmouse);
                        mouse_command(widget, k3d::iuser_interface::LMB_DOUBLE_CLICK, modifiers, currentmouse);
                        sdpGtkSleep(delay);
                  }
            else if(Command == control_lbuttonstartdrag)
                  {
                        m_drawing_area.InteractiveWarpPointer(int(currentmouse[0]), int(currentmouse[1]), tutorialspeed, true, false);
                        OnLButtonStartDrag(k3d::convert(modifiers), currentmouse);
                        mouse_command(widget, k3d::iuser_interface::LMB_DRAG, modifiers, currentmouse);
                  }
            else if(Command == control_lbuttondrag)
                  {
                        OnLButtonDrag(k3d::convert(modifiers), currentmouse, lastmouse, startmouse);
                        mouse_command(widget, k3d::iuser_interface::LMB_DRAG, modifiers, currentmouse);
                  }
            else if(Command == control_lbuttonenddrag)
                  {
                        OnLButtonEndDrag(k3d::convert(modifiers), currentmouse, lastmouse, startmouse);
                        mouse_command(widget, k3d::iuser_interface::LMB_DRAG, modifiers, currentmouse);
                        sdpGtkSleep(delay);
                  }
            else if(Command == control_mbuttonclick)
                  {
                        m_drawing_area.InteractiveWarpPointer(int(currentmouse[0]), int(currentmouse[1]), tutorialspeed, true, false);
                        OnMButtonClick(k3d::convert(modifiers), currentmouse);
                        mouse_command(widget, k3d::iuser_interface::MMB_CLICK, modifiers, currentmouse);
                        sdpGtkSleep(delay);
                  }
            else if(Command == control_mbuttondoubleclick)
                  {
                        m_drawing_area.InteractiveWarpPointer(int(currentmouse[0]), int(currentmouse[1]), tutorialspeed, true, false);
                        OnMButtonDoubleClick(k3d::convert(modifiers), currentmouse);
                        mouse_command(widget, k3d::iuser_interface::MMB_DOUBLE_CLICK, modifiers, currentmouse);
                        sdpGtkSleep(delay);
                  }
            else if(Command == control_mbuttonstartdrag)
                  {
                        m_drawing_area.InteractiveWarpPointer(int(currentmouse[0]), int(currentmouse[1]), tutorialspeed, true, false);
                        OnMButtonStartDrag(k3d::convert(modifiers), currentmouse);
                        mouse_command(widget, k3d::iuser_interface::MMB_DRAG, modifiers, currentmouse);
                  }
            else if(Command == control_mbuttondrag)
                  {
                        OnMButtonDrag(k3d::convert(modifiers), currentmouse, lastmouse, startmouse);
                        mouse_command(widget, k3d::iuser_interface::MMB_DRAG, modifiers, currentmouse);
                  }
            else if(Command == control_mbuttonenddrag)
                  {
                        OnMButtonEndDrag(k3d::convert(modifiers), currentmouse, lastmouse, startmouse);
                        mouse_command(widget, k3d::iuser_interface::MMB_DRAG, modifiers, currentmouse);
                        sdpGtkSleep(delay);
                  }
            else if(Command == control_rbuttonclick)
                  {
                        m_drawing_area.InteractiveWarpPointer(int(currentmouse[0]), int(currentmouse[1]), tutorialspeed, true, false);
                        OnRButtonClick(k3d::convert(modifiers), currentmouse);
                        mouse_command(widget, k3d::iuser_interface::RMB_CLICK, modifiers, currentmouse);
                        sdpGtkSleep(delay);
                  }
            else if(Command == control_rbuttondoubleclick)
                  {
                        m_drawing_area.InteractiveWarpPointer(int(currentmouse[0]), int(currentmouse[1]), tutorialspeed, true, false);
                        OnRButtonDoubleClick(k3d::convert(modifiers), currentmouse);
                        mouse_command(widget, k3d::iuser_interface::RMB_DOUBLE_CLICK, modifiers, currentmouse);
                        sdpGtkSleep(delay);
                  }
            else if(Command == control_rbuttonstartdrag)
                  {
                        m_drawing_area.InteractiveWarpPointer(int(currentmouse[0]), int(currentmouse[1]), tutorialspeed, true, false);
                        OnRButtonStartDrag(k3d::convert(modifiers), currentmouse);
                        mouse_command(widget, k3d::iuser_interface::RMB_DRAG, modifiers, currentmouse);
                  }
            else if(Command == control_rbuttondrag)
                  {
                        OnRButtonDrag(k3d::convert(modifiers), currentmouse, lastmouse, startmouse);
                        mouse_command(widget, k3d::iuser_interface::RMB_DRAG, modifiers, currentmouse);
                  }
            else if(Command == control_rbuttonenddrag)
                  {
                        OnRButtonEndDrag(k3d::convert(modifiers), currentmouse, lastmouse, startmouse);
                        mouse_command(widget, k3d::iuser_interface::RMB_DRAG, modifiers, currentmouse);
                        sdpGtkSleep(delay);
                  }
            else if(Command == control_lrbuttonstartdrag)
                  {
                        m_drawing_area.InteractiveWarpPointer(int(currentmouse[0]), int(currentmouse[1]), tutorialspeed, true, false);
                        OnLRButtonStartDrag(k3d::convert(modifiers), currentmouse);
                        mouse_command(widget, k3d::iuser_interface::LMBRMB_DRAG, modifiers, currentmouse);
                  }
            else if(Command == control_lrbuttondrag)
                  {
                        OnLRButtonDrag(k3d::convert(modifiers), currentmouse, lastmouse, startmouse);
                        mouse_command(widget, k3d::iuser_interface::LMBRMB_DRAG, modifiers, currentmouse);
                  }
            else if(Command == control_lrbuttonenddrag)
                  {
                        OnLRButtonEndDrag(k3d::convert(modifiers), currentmouse, lastmouse, startmouse);
                        mouse_command(widget, k3d::iuser_interface::LMBRMB_DRAG, modifiers, currentmouse);
                        sdpGtkSleep(delay);
                  }
            else
                  {
                        return base::execute_command(Command, Arguments);
                  }

            return true;
      }

      /// Owning document
      k3d::idocument& m_document;
      /// OpenGL drawing area widget
      sdpGtkOpenGLDrawingArea m_drawing_area;
      /// Stores a reference to the (optional) attached viewport
      k3d::iviewport* m_viewport;
      /// Stores a connection to the attached viewport object's delete signal
      SigC::Connection m_viewport_deleted_connection;
      /// Stores a connection to the attached viewport object's host changed signal
      SigC::Connection m_viewport_host_changed_connection;
      /// Stores a connection to the attached viewport's redraw request signal
      SigC::Connection m_viewport_redraw_request_connection;
      /// Stores a connection to the attached viewport's aspect ratio changed signal
      SigC::Connection m_viewport_aspect_ratio_changed_connection;

      /// Stores the current rubber band (rectangular) selection  
      k3d::rectangle m_rubber_band;
      /// Stores the current lasso (arbitrary shape) selection
      std::vector<k3d::vector2> m_lasso;
      /// Buffers OpenGL selection data
      gl_selection_buffer_t m_selection_buffer;
      /// Caches a reference to the last item pick-selected
      k3d::iselectable* m_last_pick;

      /// Enumerates available "navigation modes" for interactively modifying the viewport
      typedef enum
      {
            TRIPOD,
            MODELING
      } navigation_mode_t;
      
      /// Returns descriptions of the allowed axis values, for use with enumeration properties
      const ienumeration_property::values_t& navigation_mode_values()
      {
            static ienumeration_property::values_t values;
            if(values.empty())
                  {
                        values.push_back(ienumeration_property::value_t("Tripod", "tripod", "Tripod"));
                        values.push_back(ienumeration_property::value_t("Modeling", "modeling", "Modeling"));
                  }
      
            return values;
      }

      /// Serialization
      friend std::ostream& operator<<(std::ostream& Stream, const navigation_mode_t& RHS)
      {
            switch(RHS)
                  {
                        case TRIPOD:
                              Stream << "tripod";
                              break;
                        case MODELING:
                              Stream << "modeling";
                              break;
                  }
                  
            return Stream;
      }
      
      /// Serialization
      friend std::istream& operator>>(std::istream& Stream, navigation_mode_t& RHS)
      {
            std::string s;
            Stream >> s;
            
            if(s == "tripod")
                  RHS = TRIPOD;
            else if(s == "modeling")
                  RHS = MODELING;
            else
                  std::cerr << error << __PRETTY_FUNCTION__ << " could not extract value [" << s << "]" << std::endl;
            
            return Stream;
      }

      /// Stores the active axis
      k3d_enumeration_property(k3d::axis, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_active_axis;
      /// Stores the navigation mode
      k3d_enumeration_property(navigation_mode_t, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_navigation_mode;

      // Selection behavior
      k3d_data_property(bool, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_select_objects;
      k3d_data_property(bool, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_select_meshes;
      k3d_data_property(bool, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_select_edges;
      k3d_data_property(bool, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_select_faces;
      k3d_data_property(bool, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_select_curves;
      k3d_data_property(bool, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_select_patches;
      k3d_data_property(bool, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_select_point_groups;
      k3d_data_property(bool, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_select_points;

      /// Context menu when the user right-clicks on something 
      k3d::context_menu::object m_context_menu;
};

////////////////////////////////////////////////////////////////////////////
// control

control::control(idocument& Document, iunknown& ParentCommandNode, const sdpGtkWidget& Owner) :
      m_implementation(new implementation(Document, ParentCommandNode, Owner))
{
}

control::~control()
{
      delete m_implementation;
}

bool control::attach(k3d::iviewport& Viewport)
{
      return m_implementation->attach(Viewport);
}

control::viewport_changed_signal_t& control::viewport_changed_signal()
{
      return m_implementation->viewport_changed_signal();
}

bool control::render_preview()
{
      return m_implementation->render_preview();
}

bool control::render_frame(const boost::filesystem::path& OutputImage, const bool ViewCompletedImage)
{
      return m_implementation->render_frame(OutputImage, ViewCompletedImage);
}

bool control::render_animation(const boost::filesystem::path& OutputImages, const bool ViewCompletedImages)
{
      return m_implementation->render_animation(OutputImages, ViewCompletedImages);
}

} // namespace viewport

} // namespace k3d

Generated by  Doxygen 1.6.0   Back to index