Logo Search packages:      
Sourcecode: k3d version File versions

scalar_bezier_channel_properties.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
            \author Tim Shead (tshead@k-3d.com)
*/

#include "k3dobjectdialog.h"
#include "keyboard.h"
#include "spin_button.h"

#include <k3dsdk/application.h>
#include <k3dsdk/basic_math.h>
#include <k3dsdk/bezier.h>
#include <k3dsdk/classes.h>
#include <k3dsdk/computed_property.h>
#include <k3dsdk/geometry.h>
#include <k3dsdk/ibezier_channel.h>
#include <k3dsdk/ichannel.h>
#include <k3dsdk/itime_sink.h>
#include <k3dsdk/iuser_interface.h>
#include <k3dsdk/object.h>
#include <k3dsdk/persistence.h>
#include <k3dsdk/mouse_event_observer.h>
#include <k3dsdk/ioptions.h>
#include <k3dsdk/property.h>
#include <k3dsdk/state_change_set.h>
#include <k3dsdk/string_modifiers.h>
#include <k3dsdk/time_source.h>
#include <k3dsdk/user_interface.h>
#include <k3dsdk/vectors.h>

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

#include <iterator>
#include <set>

// We have an unfortunate clash with X
#ifdef RootWindow
#undef RootWindow
#endif // RootWindow

namespace k3d
{

/////////////////////////////////////////////////////////////////////////////
// scalar_bezier_channel_properties

namespace
{

const rectangle frustum(-1, 1, 1, -1);

vector3 background_color(0.8, 0.8, 0.8);
vector3 current_time_color(0.0, 1.0, 0.0);
vector3 cursor_color(1.0, 0.0, 0.0);
vector3 normal_color(0.0, 0.0, 0.0);
vector3 value_node_color(1.0, 1.0, 0.0);
vector3 selected_color(1.0, 1.0, 1.0);
vector3 tangent_color(0.0, 0.0, 1.0);
vector3 selector_color(0.215, 0.215, 0.8);

const std::string control_straightenchannel = "straightenchannel";
const std::string control_mirrorxchannel = "mirrorxchannel";
const std::string control_mirrorychannel = "mirrorychannel";
const std::string control_cursorx = "cursorx";
const std::string control_cycles = "cycles";
const std::string control_phase = "phase";
const std::string control_straightenselected = "straightenselected";
const std::string control_deleteselected = "deleteselected";
const std::string control_resetview = "resetview";
const std::string control_mousemove = "mousemove";
const std::string control_lbuttondown = "lbuttondown";
const std::string control_lbuttonclick = "lbuttonclick";
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_mbuttonclick = "mbuttonclick";
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_rbuttonclick = "rbuttonclick";
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";

} // namespace

/// The main user interface for a bezier control channel object
00115 class scalar_bezier_channel_properties :
      public k3dObjectDialog,
      public sdpGtkMouseInput
{
      typedef k3dObjectDialog base;

      /// Convenience typedef for our channel type
00122       typedef ibezier_channel<void> channel_t;
      /// Convenience typedef for control-point storage for our channel type
00124       typedef channel_t::control_points_t control_points_t;
      /// Stores a set of control points by zero-based index
00126       typedef std::set<unsigned long> control_point_set_t;

public:

      scalar_bezier_channel_properties(iobject& Object) :
            base(Object, false, false, true, "properties", new options_window_geometry_store()),
            m_object(Object),
            m_channel(dynamic_cast<channel_t*>(&Object))
      {
            // Load the dialog template ...
            return_if_fail(LoadGTKMLTemplate("scalar_bezier_channel.gtkml"));

            // Create the OpenGL widgets ...
            sdpGtkContainer curvecontainer(Container("curve"));
            if(!m_curve_widget.Create(curvecontainer, true, true, 8, 8, 8, 0))
                  if(!m_curve_widget.Create(curvecontainer, true, true, 5, 5, 5, 0))
                        if(!m_curve_widget.Create(curvecontainer, false, false, 4, 4, 4, 0))
                              std::cerr << "scalar_bezier_channel_properties(): Could not find useable OpenGL visual" << std::endl;

            // Setup some events ...
            if(m_curve_widget.Initialized())
                  {
                        MapEvent("configure-event", "configurecurve", false, m_curve_widget, true);
                        MapEvent("expose-event", "exposecurve", false, m_curve_widget, true);
                        MapEvent("motion-notify-event", "mousemovecurve", false, m_curve_widget, true);
                        MapEvent("button-press-event", "buttondowncurve", false, m_curve_widget, true);
                        MapEvent("button-release-event", "buttonupcurve", false, m_curve_widget, true);
                  }

            // We want to be notified if the channel is modified ...
            ichannel<double>* const channel = dynamic_cast<ichannel<double>*>(&m_object);
            return_if_fail(channel);
            channel->changed_signal().connect(SigC::slot(*this, &scalar_bezier_channel_properties::on_channel_modified));

            // We want to be notified when the document time changes ...
            iproperty* const time = get_time(m_object.document());
            if(time)
                  time->changed_signal().connect(SigC::slot(*this, &scalar_bezier_channel_properties::on_frame_time_changed));

            // Initialize the cursor ...
            set_cursor_x(0.0);

            // Cache the new curve ...
            m_channel->get_curve(m_control_points);
            m_active_control_point = m_control_points.end();

            // Reset our view so it's properly scaled & centered ...
            reset_view();

            Show();

            // The Win32 port of GTK+ has some kind of refresh problem, so force one to get us going ...
#ifdef SDPWIN32
            RootWidget().QueueClear();
            sdpGtkHandlePendingEvents();
#endif
      }

      // icommand_node implementation ...
00185       bool execute_command(const std::string& Command, const std::string& Arguments)
      {
            // Extract state information from the arguments ...
            std::istringstream arguments(Arguments);
            int temp = 0;
            vector2 currentmouse;
            vector2 lastmouse;
            vector2 startmouse;
            arguments >> temp >> currentmouse >> lastmouse >> startmouse;
            GdkModifierType modifiers = GdkModifierType(temp);

            // Convert the mouse from world coordinates to screen ...
            currentmouse = screen(currentmouse);
            lastmouse = screen(lastmouse);
            startmouse = screen(startmouse);

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

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

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

            return true;
      }

private:
      void on_channel_modified()
      {
            // Cache the new curve ...
            m_channel->get_curve(m_control_points);
            m_active_control_point = m_control_points.end();

            // Update the current selection (in case it contains references to control points that no longer exist) ...
            m_selection.erase(m_selection.upper_bound(m_control_points.size()-1), m_selection.end());

            redraw_all();
      }

      /// Dispatches custom events
00345       void OnEvent(sdpGtkEvent* Event)
      {
            if(Event->Name() == "configurecurve" || Event->Name() == "exposecurve")
                  draw_curve();
            else if(Event->Name() == "mousemovecurve")
                  RawMouseMove(Event);
            else if(Event->Name() == "buttondowncurve")
                  RawButtonDown(Event);
            else if(Event->Name() == "buttonupcurve")
                  RawButtonUp(Event);
            else if(Event->Name() == "vscale")
                  on_vertical_scale();
            else if(Event->Name() == control_cursorx)
                  on_cursor_x();
            else if(Event->Name() == control_straightenchannel)
                  on_straighten_channel();
            else if(Event->Name() == control_mirrorxchannel)
                  on_mirror_channel_x();
            else if(Event->Name() == control_mirrorychannel)
                  on_mirror_channel_y();
            else if(Event->Name() == control_straightenselected)
                  on_straighten_selected();
            else if(Event->Name() == control_deleteselected)
                  on_delete_selected();
            else if(Event->Name() == control_resetview)
                  on_reset_view();
            else
                  base::OnEvent(Event);
      }

      /// Called to straighten the entire channel
00376       void on_straighten_channel()
      {
            record_command(*this, icommand_node::command_t::USER_INTERFACE, control_straightenchannel);
            record_state_change_set undo(m_object.document(), "Straighten Channel");

            // Get the maximum extents of the curve ...
            rectangle extents = value_extents(m_control_points);

            // Take a guess at whether the slope of the result should be positive or negative ...
            if(m_control_points.front()[1] > m_control_points.back()[1])
                  std::swap(extents.top, extents.bottom);

            const vector2 left = vector2(extents.Left(), extents.Bottom());
            const vector2 right = vector2(extents.Right(), extents.Top());

            // Space the nodes out evenly to create a linear curve ...
            for(unsigned long i = 0; i < m_control_points.size(); ++i)
                  m_control_points[i] = mix(left, right, static_cast<double>(i) / static_cast<double>(m_control_points.size() - 1));

            // Set the new curve ...
            m_channel->set_curve(m_control_points);
      }

      /// Called to mirror the entire channel
00400       void on_mirror_channel_x()
      {
            record_command(*this, icommand_node::command_t::USER_INTERFACE, control_mirrorxchannel);
            record_state_change_set undo(m_object.document(), "Channel Mirror X");

            // Pick a reflection axis through the "middle" of the curve ...
            const double reflection_axis = value_extents(m_control_points).Center()[0];

            // Mirror each control point around the axis ...
            for(control_points_t::iterator control_point = m_control_points.begin(); control_point != m_control_points.end(); ++control_point)
                  *control_point = vector2(reflection_axis - ((*control_point)[0] - reflection_axis), (*control_point)[1]);

            // Reverse the order of the control points and valuesnodes ...
            std::reverse(m_control_points.begin(), m_control_points.end());

            // Set the new curve ...
            m_channel->set_curve(m_control_points);
      }

      /// Called to mirror the entire channel
00420       void on_mirror_channel_y()
      {
            record_command(*this, icommand_node::command_t::USER_INTERFACE, control_mirrorychannel);
            record_state_change_set undo(m_object.document(), "Channel Mirror Y");

            // Pick a reflection axis througharound the "middle" of the curveGet the curve extents ...
            const double reflection_axis = value_extents(m_control_points).Center()[1];

            // Mirror each control point around the axis ...
            for(control_points_t::iterator control_point = m_control_points.begin(); control_point != m_control_points.end(); ++control_point)
                  *control_point = vector2((*control_point)[0], reflection_axis - ((*control_point)[1] - reflection_axis));

            // Set the new curve ...
            m_channel->set_curve(m_control_points);
      }

      void draw_curve()
      {
            // If we're not completely initialized, we're done ...
            if(!m_curve_widget.Initialized())
                  return;

            m_curve_widget.Begin();

            // Get the widget sizes ...
            unsigned long width = m_curve_widget.Width();
            unsigned long height = m_curve_widget.Height();
            glViewport(0, 0, width, height);

            // Clear the background ...
            glClearColor(background_color[0], background_color[1], background_color[2], 1.0);
            glClear(GL_COLOR_BUFFER_BIT);

            // Setup the viewing projection ...
            glMatrixMode(GL_PROJECTION);
            glLoadIdentity();
            glOrtho(frustum.Left(), frustum.Right(), frustum.Bottom(), frustum.Top(), -1, 1);

            glMatrixMode(GL_MODELVIEW);
            glLoadIdentity();
            glScaled(1.0 / m_scale[0], 1.0 / m_scale[1], 1.0);
            glTranslated(-m_origin[0], -m_origin[1], 0.0);
            glScaled(1, 1.0 / m_vertical_scale, 1.0);

            // Setup drawing options ...
            glDisable(GL_DEPTH_TEST);
            glDisable(GL_LIGHTING);
            glShadeModel(GL_FLAT);

            // Make it happen ...
            draw_grid();
            draw_cursor();
            draw_current_time();
            draw_control_curve();
            draw_tangents();
            draw_nodes();

            // Cleanup ...
            m_curve_widget.SwapBuffers();
            m_curve_widget.End();
      }

      void draw_grid()
      {
            glPushAttrib(GL_ALL_ATTRIB_BITS);

            glColor3dv(background_color * 0.8);

            // Draw the grid ...
            const static int xdivisions = 10;
            const static int ydivisions = 10;

            glBegin(GL_LINES);

            for(int i = 0; i <= xdivisions; i++)
                  {
                        glVertex2d(double(i) / double(xdivisions), 0.0);
                        glVertex2d(double(i) / double(xdivisions), 1.0);
                  }

            for(int j = 0; j <= ydivisions; j++)
                  {
                        glVertex2d(0.0, double(j) / double(ydivisions));
                        glVertex2d(1.0, double(j) / double(ydivisions));
                  }

            glEnd();

            const double left = world(vector2(0.0, 0.0))[0];
            const double top = world(vector2(0, 0.0))[1];
            const double right = world(vector2(0.0, static_cast<double>(m_curve_widget.Width())))[0];
            const double bottom = world(vector2(0.0, static_cast<double>(m_curve_widget.Height())))[1];

            // Draw the origin / axes ...
            glColor3dv(background_color * 0.6);
            glLineWidth(2.0f);

            glBegin(GL_LINES);

            glVertex2d(0.0, top);
            glVertex2d(0.0, bottom);

            glVertex2d(left, 0.0);
            glVertex2d(right, 0.0);

            glEnd();

            glPopAttrib();
      }

      void draw_cursor()
      {
            double top = world(vector2(0.0, 0.0))[1];
            double bottom = world(vector2(0.0, static_cast<double>(m_curve_widget.Height())))[1];

            glColor3dv(cursor_color);
            glBegin(GL_LINES);

            // Vertical cursor ...
            glVertex2d(m_cursor[0], top);
            glVertex2d(m_cursor[0], bottom);

            glEnd();
      }

      void draw_current_time()
      {
            iproperty* const time_property = get_time(m_object.document());
            if(!time_property)
                  return;

            const double time = boost::any_cast<double>(get_property_value(m_object.document().dag(), *time_property));

            const double top = world(vector2(0.0, 0.0))[1];
            const double bottom = world(vector2(0.0, static_cast<double>(m_curve_widget.Height())))[1];

            glColor3dv(current_time_color);
            glBegin(GL_LINES);
            glVertex2d(time, top);
            glVertex2d(time, bottom);
            glEnd();
      }

      void draw_control_curve()
      {
            // Draw initial straight line node before first node
            glColor3dv(normal_color);
            glBegin(GL_LINES);
            glVertex2d(-10000.0, m_control_points.front()[1]);
            glVertex2dv(m_control_points.front());
            glEnd();

            // Draw straight line node after last node
            glBegin(GL_LINES);
            glVertex2dv(m_control_points.back());
            glVertex2d(10000.0, m_control_points.back()[1]);
            glEnd();

            // We may be done at this point
            if(1 == m_control_points.size())
                  return;

            // Setup Bezier curve drawing
            glEnable(GL_MAP1_VERTEX_3);

            GLdouble controlpoints[4][3];
            memset(controlpoints, 0, sizeof(controlpoints));

            for(unsigned long i = 0; i < m_control_points.size()-1; i += 3)
                  {
                        controlpoints[0][0] = m_control_points[i][0];
                        controlpoints[0][1] = m_control_points[i][1];

                        controlpoints[1][0] = m_control_points[i+1][0];
                        controlpoints[1][1] = m_control_points[i+1][1];

                        controlpoints[2][0] = m_control_points[i+2][0];
                        controlpoints[2][1] = m_control_points[i+2][1];

                        controlpoints[3][0] = m_control_points[i+3][0];
                        controlpoints[3][1] = m_control_points[i+3][1];

                        glMap1d(GL_MAP1_VERTEX_3, 0.0, 1.0, 3, 4, &controlpoints[0][0]);

                        glBegin(GL_LINE_STRIP);
                        const static int stepsize = 50;
                        for(int j = 0; j <= stepsize; ++j)
                              glEvalCoord1d(static_cast<GLdouble>(j) / static_cast<GLdouble>(stepsize));
                        glEnd();
                  }
      }

      void draw_tangents()
      {
            glColor3dv(tangent_color);
            glBegin(GL_LINES);

            for(unsigned long i = 0; i < m_control_points.size(); ++i)
                  {
                        if(1 == i % 3)
                              {
                                    glVertex2dv(m_control_points[i-1]);
                                    glVertex2dv(m_control_points[i]);
                              }
                        else if(2 == i % 3)
                              {
                                    glVertex2dv(m_control_points[i]);
                                    glVertex2dv(m_control_points[i+1]);
                              }
                  }

            glEnd();
      }

      void draw_nodes()
      {
            // Set ourselves up to draw some big round points ...
            glEnable(GL_POINT_SMOOTH);
            glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);

            for(unsigned long i = 0; i < m_control_points.size(); ++i)
                  {
                        if(0 == i % 3)
                              {
                                    glPointSize(6.0);
                                    glColor3dv(selected(i) ? selected_color : value_node_color);
                              }
                        else
                              {
                                    glPointSize(5.0);
                                    glColor3dv(selected(i) ? selected_color : normal_color);
                              }

                        glBegin(GL_POINTS);
                        glVertex2dv(m_control_points[i]);
                        glEnd();
                  }
      }

      void on_frame_time_changed()
      {
            redraw_all();
      }

      void redraw_all()
      {
            Widget("curve").QueueDraw();
      }

      vector2 world(const vector2 ScreenPosition)
      {
            // Get the dimensions of the curve window ...
            const unsigned long width = m_curve_widget.Width();
            const unsigned long height = m_curve_widget.Height();

            vector2 result;

            // Convert pixel coordinates to "screen" coordinates ...
            // (Make sure we handle (possible) minimized / hidden widget)
            if(width)
                  result[0] = mix(frustum.Left(), frustum.Right(), ScreenPosition[0] / width);

            if(height)
                  result[1] = mix(frustum.Top(), frustum.Bottom(), ScreenPosition[1] / height);

            // Scale the results to account for pan/zoom ...
            result = Product(result, m_scale) + m_origin;
            result[1] *= m_vertical_scale;

            return result;
      }

      vector2 screen(const vector2 WorldPosition)
      {
            // Get the dimensions of the curve window ...
            const unsigned long width = m_curve_widget.Width();
            const unsigned long height = m_curve_widget.Height();

            // Convert world coordinates to "screen" coordinates ...
            vector2 world_position(WorldPosition);
            world_position[1] *= (1.0 / m_vertical_scale);
            vector2 result = Product((world_position - m_origin), vector2(1.0 / m_scale[0], 1.0 / m_scale[1]));

            // Convert to pixel coordinates ...
            result[0] = width * (result[0] - frustum.Left()) / frustum.Width();
            result[1] = height * (1 - ((result[1] - frustum.Bottom()) / frustum.Height()));

            return result;
      }

      void on_reset_view()
      {
            record_command(*this, icommand_node::command_t::USER_INTERFACE, control_resetview);
            reset_view();
            redraw_all();
      }

      void reset_view()
      {
            // Get the maximum extents of the curve ...
            rectangle extents = control_point_extents(m_control_points);

            // Make sure we can always see the X axis ...
            if(extents.bottom > 0)
                  extents.bottom = 0;

            if(extents.top < 0)
                  extents.top = 0;

            // Make sure we can handle curves that are straight across the origin ...
            if(extents.top == extents.bottom)
                  extents.top += 1;

            // Make sure we can always see the Y axis ...
            if(extents.left > 0)
                  extents.left = 0;

            if(extents.right < 0)
                  extents.right = 0;

            // Make sure we handle single-point curves ...
            if(extents.left == extents.right)
                  extents.right += 1;

            // Initialize the scale & origin ...
            m_vertical_scale = 1.0;
            m_scale = vector2(extents.Width(), extents.Height()) * 0.55;
            m_origin = extents.Center();
      }

      void on_cursor_x()
      {
            record_command(*this, icommand_node::command_t::USER_INTERFACE, control_cursorx);

            const double xvalue = from_string<double>(Entry(control_cursorx.c_str()).GetText(), -1);

            set_cursor_x(xvalue);

            if(m_active_control_point != m_control_points.end())
                  {
                        record_state_change_set undo(m_object.document(), "Channel Node X Value change");
                        *m_active_control_point = vector2(xvalue, (*m_active_control_point)[1]);
                  }

            redraw_all();
      }

      void set_cursor_x(const double X)
      {
            m_cursor[0] = X;

            Entry("cursorx").SetText(sdpToString(m_cursor[0]));
            Entry("cursory").SetText("******");

            ichannel<double>* const channel = dynamic_cast<ichannel<double>*>(&m_object);
            return_if_fail(channel);
            Entry("cursorvalue").SetText(sdpToString(channel->value(m_cursor[0], 0.001)));
      }

      void set_cursor_xy(const vector2 XY)
      {
            m_cursor[0] = XY[0];
            m_cursor[1] = XY[1];

            Entry("cursorx").SetText(sdpToString(m_cursor[0]));
            Entry("cursory").SetText(sdpToString(m_cursor[1]));

            ichannel<double>* const channel = dynamic_cast<ichannel<double>*>(&m_object);
            return_if_fail(channel);
            Entry("cursorvalue").SetText(sdpToString(channel->value(m_cursor[0], 0.001)));
      }

      void record_event(const std::string Command, GdkModifierType Modifiers, const vector2 CurrentMouse)
      {
            // Sanity checks ...
            assert(Command.size());

            // Setup mouse coordinates ...
            vector2 currentmouse(world(CurrentMouse));

            // Record the event ...
            record_command(*this, icommand_node::command_t::USER_INTERFACE, Command, to_string(int(Modifiers)) + " " + to_string(currentmouse));
      }

      void record_drag_event(const std::string Command, GdkModifierType Modifiers, const vector2 CurrentMouse, const vector2 LastMouse, const vector2 StartMouse)
      {
            // Sanity checks ...
            assert(Command.size());

            // Setup mouse coordinates ...
            vector2 currentmouse(world(CurrentMouse));
            vector2 lastmouse(world(LastMouse));
            vector2 startmouse(world(StartMouse));

            // Record the event ...
            record_command(*this, icommand_node::command_t::USER_INTERFACE, Command, to_string(int(Modifiers)) + " " + to_string(currentmouse) + " " + to_string(lastmouse) + " " + to_string(startmouse));
      }

      void OnLButtonDown(GdkModifierType Modifiers, const vector2 CurrentMouse)
      {
            record_event(control_lbuttondown, Modifiers, CurrentMouse);

            control_point_set_t hits = hit_test(CurrentMouse);
            if(hits.empty())
                  {
                        set_cursor_x(world(CurrentMouse)[0]);
                        redraw_all();
                        return;
                  }

            // Keep track of the "active" control point ...
            m_active_control_point = m_control_points.begin() + (*hits.begin());

            // Update our selection ...
            m_selection.insert(hits.begin(), hits.end());

            // Set the cursor ...
            set_cursor_xy(m_control_points[*hits.begin()]);

            // Handle optional additional selections
            if(Modifiers & GDK_SHIFT_MASK)
                  {
                        if(is_value_control_point(*hits.begin()))
                              {
                                    m_selection.insert(previous_control_point(*hits.begin()));
                                    m_selection.insert(next_control_point(*hits.begin()));
                              }
                  }

            redraw_all();
      }

      void OnLButtonDoubleClick(GdkModifierType Modifiers, const vector2 CurrentMouse)
      {
            record_event(control_lbuttondoubleclick, Modifiers, CurrentMouse);

            // Convert mouse coordinates to world coordinates ...
            const vector2 worldcoords = world(CurrentMouse);

            // See if the user clicked on an existing node, first ...
            const control_point_set_t hits = hit_test(CurrentMouse);
            if(!hits.empty())
                  {
                        set_cursor_xy(vector2(worldcoords[0], worldcoords[1]));
                        edit_values(hits);
                        return;
                  }

            // Otherwise, create a new value ...
            record_state_change_set undo(m_object.document(), "Create Node(s)");

            // Create control points ...
            control_point_set_t new_control_points = insert_value(worldcoords);

            // Select 'em ...
            m_selection = new_control_points;

            // Position the cursor ...
            set_cursor_xy(worldcoords);

            // Update the curve
            m_channel->set_curve(m_control_points);

            // Edit that baby!
            edit_values(m_selection);
      }

      control_point_set_t insert_value(const vector2 WorldCoords)
      {
            control_point_set_t results;

            // Handle the easiest special-case: new value precedes any existing values
            if(WorldCoords[0] <= m_control_points.front()[0])
                  {
                        const vector2 left(WorldCoords);
                        const vector2 right(m_control_points.front());

                        control_points_t new_control_points;
                        new_control_points.push_back(left);
                        new_control_points.push_back(vector2(mix<double>(left[0], right[0], 0.3), left[1]));
                        new_control_points.push_back(vector2(mix<double>(left[0], right[0], 0.7), right[1]));

                        m_control_points.insert(m_control_points.begin(), new_control_points.begin(), new_control_points.end());

                        results.insert(0);
                        results.insert(1);
                        results.insert(2);

                        return results;
                  }

            // Next-easiest special-case: new value comes after all existing values
            if(WorldCoords[0] > m_control_points.back()[0])
                  {
                        const vector2 left(m_control_points.back());
                        const vector2 right(WorldCoords);

                        control_points_t new_control_points;
                        new_control_points.push_back(vector2(mix<double>(left[0], right[0], 0.3), left[1]));
                        new_control_points.push_back(vector2(mix<double>(left[0], right[0], 0.7), right[1]));
                        new_control_points.push_back(right);

                        m_control_points.insert(m_control_points.end(), new_control_points.begin(), new_control_points.end());

                        results.insert(m_control_points.size() - 3);
                        results.insert(m_control_points.size() - 2);
                        results.insert(m_control_points.size() - 1);

                        return results;
                  }

            // OK, new value is somewhere in the middle ...
            for(unsigned int i = 0; i < m_control_points.size() - 1; i += 3)
                  {
                        if(WorldCoords[0] > m_control_points[i+3][0])
                              continue;

                        const vector2 left(m_control_points[i]);
                        const vector2 right(m_control_points[i+3]);

                        control_points_t new_control_points;
                        new_control_points.push_back(vector2(mix<double>(left[0], WorldCoords[0], 0.7), WorldCoords[1]));
                        new_control_points.push_back(WorldCoords);
                        new_control_points.push_back(vector2(mix<double>(WorldCoords[0], right[0], 0.3), WorldCoords[1]));

                        m_control_points.insert(m_control_points.begin() + i + 2, new_control_points.begin(), new_control_points.end());

                        results.insert(i + 2);
                        results.insert(i + 3);
                        results.insert(i + 4);

                        return results;
                  }

            // Should never be reached!
            assert_warning(0);
            return results;
      }

      void OnLButtonStartDrag(GdkModifierType Modifiers, const vector2 CurrentMouse)
      {
            record_event(control_lbuttonstartdrag, Modifiers, CurrentMouse);

            if(!m_selection.empty())
                  start_state_change_set(m_object.document());
      }

      void OnLButtonDrag(GdkModifierType Modifiers, const vector2 CurrentMouse, const vector2 LastMouse, const vector2 StartMouse)
      {
            record_drag_event(control_lbuttondrag, Modifiers, CurrentMouse, LastMouse, StartMouse);

            // Update the cursor position ...
            set_cursor_x(world(CurrentMouse)[0]);

            // Calculate the amount of mouse movement in world coordinates ...
            const vector2 delta(world(CurrentMouse) - world(LastMouse));

            // For each selected node ...
            for(control_point_set_t::iterator control_point = m_selection.begin(); control_point != m_selection.end(); ++control_point)
                  move_control_point(*control_point, m_control_points[*control_point] + delta, Modifiers);

            // Set the new curve ...
            m_channel->set_curve(m_control_points);

            redraw_all();
      }

      /// Moves a control point, and possibly moves its neighbors, based on user input ...
00988       void move_control_point(const unsigned long ControlPoint, const vector2 Position, const GdkModifierType Modifiers)
      {
            // Move the control point ...
            move_control_point(ControlPoint, Position);

            // If there aren't any other points, we're done ...
            if(1 == m_control_points.size())
                  return;

            // If it's a value control point ...
            if(is_value_control_point(ControlPoint))
                  {
                        // If the user holds down the shift key, constrain the first and last control points to the same position (good for looping animations)
                        if(Modifiers & GDK_SHIFT_MASK)
                              {
                                    if(is_first_value_control_point(ControlPoint))
                                          move_control_point(last_control_point(), vector2(m_control_points[last_control_point()][0], Position[1]));
                                    else if(is_last_value_control_point(ControlPoint))
                                          move_control_point(first_control_point(), vector2(m_control_points[first_control_point()][0], Position[1]));
                              }
                  }
            // Not a value control point ...
            else
                  {
                        // If the user holds down the shift key, constrain the slope of the opposite control point to be identical
                        if(Modifiers & GDK_SHIFT_MASK)
                              {
                                    const vector2 slope = m_control_points[nearest_value_control_point(ControlPoint)] - m_control_points[ControlPoint];
                                    move_control_point(opposite_control_point(ControlPoint), m_control_points[nearest_value_control_point(opposite_control_point(ControlPoint))] + slope);
                              }
                  }
      }

      /// Moves a control point, constraining to the valid range of values, and moving any of its neighbors as needed
01022       void move_control_point(const unsigned long ControlPoint, const vector2 Position)
      {
/*
            // Get the range of allowable values ...
            double minimum;
            double maximum;
            m_object.range(minimum, maximum);
            vector2 position(Position[0], std::max(minimum, std::min(maximum, Position[1])));
*/
            vector2 position(Position);

            // Move the control point ...
            m_control_points[ControlPoint] = position;

            // Recursively move its neighbors ...
            const unsigned long a = previous_control_point(previous_control_point(ControlPoint));
            const unsigned long b = previous_control_point(ControlPoint);
            const unsigned long c = next_control_point(ControlPoint);
            const unsigned long d = next_control_point(next_control_point(ControlPoint));

            if(is_value_control_point(ControlPoint))
                  {
                        if(a != ControlPoint && Position[0] < m_control_points[a][0])
                              move_control_point(a, vector2(Position[0], m_control_points[a][1]));

                        if(b != ControlPoint && Position[0] < m_control_points[b][0])
                              move_control_point(b, vector2(Position[0], m_control_points[b][1]));

                        if(c != ControlPoint && Position[0] > m_control_points[c][0])
                              move_control_point(c, vector2(Position[0], m_control_points[c][1]));

                        if(d != ControlPoint && Position[0] > m_control_points[d][0])
                              move_control_point(d, vector2(Position[0], m_control_points[d][1]));
                  }
            else
                  {
                        if(is_value_control_point(b))
                              {
                                    if(Position[0] < m_control_points[b][0])
                                          move_control_point(b, vector2(Position[0], m_control_points[b][1]));

                                    if(Position[0] > m_control_points[d][0])
                                          move_control_point(d, vector2(Position[0], m_control_points[d][1]));
                              }
                        else
                              {
                                    if(Position[0] < m_control_points[a][0])
                                          move_control_point(a, vector2(Position[0], m_control_points[a][1]));

                                    if(Position[0] > m_control_points[c][0])
                                          move_control_point(c, vector2(Position[0], m_control_points[c][1]));
                              }
                  }
      }

      void OnLButtonEndDrag(GdkModifierType Modifiers, const vector2 CurrentMouse, const vector2 LastMouse, const vector2 StartMouse)
      {
            record_drag_event(control_lbuttonenddrag, Modifiers, CurrentMouse, LastMouse, StartMouse);

            if(!m_selection.empty())
                  finish_state_change_set(m_object.document(), "Move node(s)");
      }

      void on_straighten_selected()
      {
            record_command(*this, icommand_node::command_t::USER_INTERFACE, control_straightenselected);

            // If we don't have anything, we're done ...
            if(m_selection.empty())
                  {
                        error_message("You must select some control points to straighten!", "Straighten Selected:");
                        return;
                  }

            for(control_point_set_t::iterator control_point = m_selection.begin(); control_point != m_selection.end(); ++control_point)
                  {
                        if(is_value_control_point(*control_point))
                              continue;

                        const unsigned long a = nearest_value_control_point(*control_point);
                        const unsigned long b = *control_point;
                        const unsigned long c = neighbor_control_point(*control_point);
                        const unsigned long d = nearest_value_control_point(neighbor_control_point(*control_point));

                        const vector2 first(m_control_points[a]);
                        const vector2 last(m_control_points[d]);

                        m_control_points[b] = mix(first, last, 0.3);
                        m_control_points[c] = mix(first, last, 0.7);
                  }

            // Store the new curve ...
            record_state_change_set undo(m_object.document(), "Straighten selected segment(s)");
            m_channel->set_curve(m_control_points);
      }

      void on_delete_selected()
      {
            record_command(*this, icommand_node::command_t::USER_INTERFACE, control_deleteselected);

            // Figure-out exactly which control points need to be deleted
            control_point_set_t lambs;
            for(control_point_set_t::iterator control_point = m_selection.begin(); control_point != m_selection.end(); ++control_point)
                  {
                        lambs.insert(nearest_value_control_point(*control_point));
                        if(is_first_value_control_point(nearest_value_control_point(*control_point)))
                              {
                                    lambs.insert(next_control_point(nearest_value_control_point(*control_point)));
                                    lambs.insert(previous_control_point(next_value_control_point(nearest_value_control_point(*control_point))));
                              }
                        else if(is_last_value_control_point(nearest_value_control_point(*control_point)))
                              {
                                    lambs.insert(previous_control_point(nearest_value_control_point(*control_point)));
                                    lambs.insert(next_control_point(previous_value_control_point(nearest_value_control_point(*control_point))));
                              }
                        else
                              {
                                    lambs.insert(previous_control_point(nearest_value_control_point(*control_point)));
                                    lambs.insert(next_control_point(nearest_value_control_point(*control_point)));
                              }
                  }

            // If we don't have anything, we're done ...
            if(lambs.empty())
                  {
                        error_message("You must select some control points to delete!", "Delete Selected:");
                        return;
                  }

            // If we're trying to delete everything, no-can-do ...
            if(lambs.size() == m_control_points.size())
                  {
                        error_message("You can't delete the last control point!", "Delete Selected:");
                        return;
                  }

            // Clear the selection ...
            m_selection.clear();

            // Delete control points ...
            for(control_point_set_t::reverse_iterator control_point = lambs.rbegin(); control_point != lambs.rend(); ++control_point)
                  m_control_points.erase(m_control_points.begin() + (*control_point));

            // Update the new curve ...
            record_state_change_set undo(m_object.document(), "Delete selected control point(s)");
            m_channel->set_curve(m_control_points);
      }

      void OnRButtonClick(GdkModifierType Modifiers, const vector2 CurrentMouse)
      {
            record_event(control_rbuttonclick, Modifiers, CurrentMouse);
            deselect_all();
            redraw_all();
      }

      void OnRButtonStartDrag(GdkModifierType Modifiers, const vector2 CurrentMouse)
      {
            record_event(control_rbuttonstartdrag, Modifiers, CurrentMouse);
      }

      void OnRButtonDrag(GdkModifierType Modifiers, const vector2 CurrentMouse, const vector2 LastMouse, const vector2 StartMouse)
      {
            record_drag_event(control_rbuttondrag, Modifiers, CurrentMouse, LastMouse, StartMouse);

            const vector2 delta = world(CurrentMouse) - world(LastMouse);
            m_origin += delta;

            redraw_all();
      }

      void OnRButtonEndDrag(GdkModifierType Modifiers, const vector2 CurrentMouse, const vector2 LastMouse, const vector2 StartMouse)
      {
            record_drag_event(control_rbuttonenddrag, Modifiers, CurrentMouse, LastMouse, StartMouse);
      }

      void OnMButtonStartDrag(GdkModifierType Modifiers, const vector2 CurrentMouse)
      {
            record_event(control_lrbuttonstartdrag, Modifiers, CurrentMouse);
      }

      void OnMButtonDrag(GdkModifierType Modifiers, const vector2 CurrentMouse, const vector2 LastMouse, const vector2 StartMouse)
      {
            record_drag_event(control_lrbuttondrag, Modifiers, CurrentMouse, LastMouse, StartMouse);

            const double zoomfactor = CurrentMouse[1] > LastMouse[1] ? pow(1.005, CurrentMouse[1] - LastMouse[1]) : pow(0.995, LastMouse[1] - CurrentMouse[1]);
            m_scale *= zoomfactor;
            redraw_all();
      }

      void OnMButtonEndDrag(GdkModifierType Modifiers, const vector2 CurrentMouse, const vector2 LastMouse, const vector2 StartMouse)
      {
            record_drag_event(control_lrbuttonenddrag, Modifiers, CurrentMouse, LastMouse, StartMouse);
      }

      void on_vertical_scale()
      {
            m_vertical_scale = pow(10.0, Adjustment("vscale").Value());
            redraw_all();
      }

      void edit_values(const control_point_set_t ControlPoints)
      {
            for(control_point_set_t::const_iterator control_point = ControlPoints.begin(); control_point != ControlPoints.end(); ++control_point)
                  {
                        // If it isn't a value, skip it ...
                        if(!is_value_control_point(*control_point))
                              continue;
                  }
      }

      bool selected(const unsigned long ControlPointIndex)
      {
            return m_selection.find(ControlPointIndex) != m_selection.end();
      }

      bool is_value_control_point(const unsigned long ControlPointIndex)
      {
            return 0 == ControlPointIndex % 3;
      }

      bool is_first_value_control_point(const unsigned long ControlPointIndex)
      {
            return 0 == ControlPointIndex;
      }

      bool is_last_value_control_point(const unsigned long ControlPointIndex)
      {
            return m_control_points.size() - 1 == ControlPointIndex;
      }

      unsigned long first_control_point()
      {
            return 0;
      }

      unsigned long last_control_point()
      {
            return m_control_points.size() - 1;
      }

      unsigned long previous_control_point(const unsigned long ControlPointIndex)
      {
            return ControlPointIndex ? ControlPointIndex - 1 : 0;
      }

      unsigned long next_control_point(const unsigned long ControlPointIndex)
      {
            return ControlPointIndex < m_control_points.size() - 1 ? ControlPointIndex + 1 : ControlPointIndex;
      }

      unsigned long neighbor_control_point(const unsigned long ControlPointIndex)
      {
            assert_warning(!is_value_control_point(ControlPointIndex));
            switch(ControlPointIndex % 3)
                  {
                        case 1:
                              return ControlPointIndex + 1;
                        case 2:
                              return ControlPointIndex - 1;
                  }

            // Should never be reached
            return_val_if_fail(0, 0);
            return ControlPointIndex;
      }

      unsigned long opposite_control_point(const unsigned long ControlPointIndex)
      {
            assert_warning(!is_value_control_point(ControlPointIndex));
            switch(ControlPointIndex % 3)
                  {
                        case 1:
                              return (ControlPointIndex + m_control_points.size() - 3) % (m_control_points.size() - 1);
                        case 2:
                              return (ControlPointIndex + 2) % (m_control_points.size() - 1);
                  }

            // Should never be reached
            return_val_if_fail(0, 0);
            return ControlPointIndex;
      }

      unsigned long nearest_value_control_point(const unsigned long ControlPointIndex)
      {
            switch(ControlPointIndex % 3)
                  {
                        case 0:
                              return ControlPointIndex;
                        case 1:
                              return ControlPointIndex - 1;
                        case 2:
                              return ControlPointIndex + 1;
                  }

            // Should never be reached
            return_val_if_fail(0, 0);
            return ControlPointIndex;
      }

      unsigned long previous_value_control_point(const unsigned long ControlPointIndex)
      {
            assert(is_value_control_point(ControlPointIndex));
            return ControlPointIndex ? ControlPointIndex - 3 : ControlPointIndex;
      }

      unsigned long next_value_control_point(const unsigned long ControlPointIndex)
      {
            assert(is_value_control_point(ControlPointIndex));
            return ControlPointIndex < m_control_points.size() - 1 ? ControlPointIndex + 3 : ControlPointIndex;
      }

      void deselect_all()
      {
            m_selection.clear();
      }

      control_point_set_t hit_test(const vector2 Mouse)
      {
            control_point_set_t results;

            for(unsigned long i = 0; i < m_control_points.size(); ++i)
                  if((Mouse - screen(m_control_points[i])).Length() < 6.0)
                        results.insert(i);

            return results;
      }

      /// Returns the rectangle that encloses all control points
01350       static const rectangle control_point_extents(const control_points_t ControlPoints)
      {
            rectangle result(DBL_MAX, -DBL_MAX, -DBL_MAX, DBL_MAX);

            // For each control point ...
            for(control_points_t::const_iterator control_point = ControlPoints.begin(); control_point != ControlPoints.end(); ++control_point)
                  {
                        result.left = std::min(result.left, (*control_point)[0]);
                        result.top = std::max(result.top, (*control_point)[1]);
                        result.right = std::max(result.right, (*control_point)[0]);
                        result.bottom = std::min(result.bottom, (*control_point)[1]);
                  }

            return result;
      }

      /// Returns the rectangle that encloses all value nodes (i.e. non-value control points may lie outside)
01367       static const rectangle value_extents(const control_points_t ControlPoints)
      {
            rectangle result(DBL_MAX, -DBL_MAX, -DBL_MAX, DBL_MAX);

            // For each value node ...
            for(unsigned int i = 0; i < ControlPoints.size(); i += 3)
                  {
                        result.left = std::min(result.left, ControlPoints[i][0]);
                        result.top = std::max(result.top, ControlPoints[i][1]);
                        result.right = std::max(result.right, ControlPoints[i][0]);
                        result.bottom = std::min(result.bottom, ControlPoints[i][1]);
                  }

            return result;
      }

      static void mouse_command(GtkWidget* Widget, const iuser_interface::mouse_action_t Action, const GdkModifierType Modifiers, const vector2 CurrentMouse)
      {
            // Sanity checks ...
            return_if_fail(Widget);

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


      /// Stores a reference to the owning object
01399       iobject& m_object;
      channel_t* const m_channel;

      // Zoom / pan parameters ...
      vector2 m_origin;
      vector2 m_scale;
      double m_vertical_scale;

      /// Stores the coordinates of the user interface cursor
01408       vector2 m_cursor;

      /// Stores the OpenGL context for the control curve graph
01411       sdpGtkOpenGLDrawingArea m_curve_widget;

      /// Caches the node control points
01414       control_points_t m_control_points;

      /// Keeps track of currently-selected nodes (by index)
01417       control_point_set_t m_selection;
      /// Keeps track of currently-active node
01419       control_points_t::iterator m_active_control_point;
};

void create_scalar_bezier_channel_properties(iobject& Object)
{
      new scalar_bezier_channel_properties(Object);
}

} // namespace k3d



Generated by  Doxygen 1.6.0   Back to index