Logo Search packages:      
Sourcecode: k3d version File versions

color_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/color.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/persistence.h>
#include <k3dsdk/mouse_event_observer.h>
#include <k3dsdk/object.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
{

/////////////////////////////////////////////////////////////////////////////
// color_value_properties

namespace
{

const std::string control_close("close");
const std::string control_colorchanged("colorchanged");
const std::string control_colorselection("colorselection");

} // namespace

/// Provides a UI for modifying a color value
00079 class color_value_properties :
      public k3dDialog
{
      typedef k3dDialog base;
      typedef k3d::ibezier_channel<k3d::color>::control_points_t control_points_t;
      typedef k3d::ibezier_channel<k3d::color>::values_t values_t;

public:
      color_value_properties(k3d::iunknown* const ParentCommandNode, k3d::ibezier_channel<k3d::color>& BezierChannel, const unsigned long ControlPoint) :
            base(ParentCommandNode, "setnodecolor", 0),
            m_object(BezierChannel),
            m_control_point(ControlPoint)
      {
            // We want to be notified if the owning channel goes away ...
            k3d::iobject* const object = dynamic_cast<k3d::iobject*>(&m_object);
            return_if_fail(object);
            object->deleted_signal().connect(SigC::slot(*this, &color_value_properties::on_channel_deleted));

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

            // Cache the current channel state ...
            m_object.get_curve(m_control_points, m_values);

            // Sanity check ...
            assert(ControlPoint < m_values.size());

            // Create the UI ...
            return_if_fail(Load(color_value_properties_gtkml()));

            // Initialize the color selection widget ...
            m_current_color = m_values[m_control_point];
            ColorSelection(control_colorselection.c_str()).SetColor(m_current_color.red, m_current_color.green, m_current_color.blue);
            ColorSelection(control_colorselection.c_str()).SetUpdatePolicy(GTK_UPDATE_DISCONTINUOUS);

            // Set our title ...
            RootWindow().SetTitle("Set Node Color");

            // Make ourselves visible ...
            Show();
      }

00123       bool execute_command(const std::string& Command, const std::string& Arguments)
      {
            if(Command == control_colorchanged)
                  {
                        const k3d::color color(k3d::from_string<k3d::color>(Arguments, k3d::color(0, 0, 0)));

                        ColorSelection(control_colorselection.c_str()).InteractiveSetColor(color.red, color.green, color.blue, k3d::application().options().tutorial_speed(), true);
                        set_color(color);

                        return true;
                  }

            return base::execute_command(Command, Arguments);
      }


private:
      static sdpxml::Document& color_value_properties_gtkml()
      {
            static sdpxml::Document gtkml("empty");
            if(gtkml.Name() == "empty")
                  {
                        std::istringstream uitemplate(
                              "<gtkml>"
                                    "<window type=\"toplevel\" show=\"false\">"
                                          "<vbox homogeneous=\"false\">"
                                                "<colorselection name=\"colorselection\">"
                                                      "<event signal=\"color-changed\" name=\"colorchanged\"/>"
                                                "</colorselection>"
                                                "<hbuttonbox layout=\"end\">"
                                                      "<button name=\"close\">Close"
                                                            "<event signal=\"clicked\" name=\"close\"/>"
                                                      "</button>"
                                                "</hbuttonbox>"
                                          "</vbox>"
                                    "</window>"
                              "</gtkml>");

                        assert(gtkml.Load(uitemplate, "color_value_properties builtin template"));
                  }

            return gtkml;
      }

      void on_channel_deleted()
      {
            delete this;
      }

      void on_channel_modified()
      {
            // Cache the current channel state ...
            m_object.get_curve(m_control_points, m_values);

            // There's a possibility that the value we front for no longer exists ... if so, close ourselves ...
            if(m_control_point >= m_values.size())
                  {
                        delete this;
                        return;
                  }

            // There's a possibility that the value we front for was modified, so update the display ...
            if(m_current_color != m_values[m_control_point])
                  {
                        const k3d::color temp(m_values[m_control_point]);
                        ColorSelection(control_colorselection.c_str()).SetColor(temp.red, temp.green, temp.blue);
                  }
      }

00192       void OnEvent(sdpGtkEvent* Event)
      {
            if(Event->Name() == control_close)
                  OnClose();
            else if(Event->Name() == control_colorchanged)
                  on_color_changed();
            else
                  base::OnEvent(Event);
      }

      void OnClose()
      {
            // Record this as a user action ...
            k3d::record_command(*this, k3d::icommand_node::command_t::USER_INTERFACE, control_close);
            base::OnClose();
      }

      void on_color_changed()
      {
            k3d::color temp;
            ColorSelection(control_colorselection.c_str()).GetColor(temp.red, temp.green, temp.blue);
            set_color(temp);
      }

      void set_color(const k3d::color NewColor)
      {
            if(m_current_color == NewColor)
                  return;

            k3d::iobject* const object = dynamic_cast<k3d::iobject*>(&m_object);
            return_if_fail(object);

            k3d::record_command(*this, k3d::icommand_node::command_t::USER_INTERFACE, control_colorchanged, k3d::to_string(NewColor));
            k3d::record_state_change_set undo(object->document(), "Set Color Value");

            m_current_color = NewColor;

            m_values[m_control_point] = NewColor;
            m_object.set_curve(m_control_points, m_values);
      }

      /// Stores a reference to the owning channel
00234       k3d::ibezier_channel<k3d::color>& m_object;
      /// Zero-based index to the value we're editing
00236       const unsigned long m_control_point;
      /// Caches the channel control points
00238       control_points_t m_control_points;
      /// Caches the channel values
00240       values_t m_values;
      k3d::color m_current_color;
};

/////////////////////////////////////////////////////////////////////////////
// color_bezier_channel_properties

namespace
{

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

k3d::color background_color(0.8, 0.8, 0.8);
k3d::color current_time_color(0.0, 1.0, 0.0);
k3d::color cursor_color(1.0, 0.0, 0.0);
k3d::color normal_color(0.0, 0.0, 0.0);
k3d::color value_node_color(1.0, 1.0, 0.0);
k3d::color selected_color(1.0, 1.0, 1.0);
k3d::color tangent_color(0.0, 0.0, 1.0);
k3d::color 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
00294 class color_bezier_channel_properties :
      public k3dObjectDialog,
      public sdpGtkMouseInput
{
      // Convenience typedefs
      typedef k3dObjectDialog base;
      typedef k3d::ibezier_channel<k3d::color>::control_points_t control_points_t;
      typedef k3d::ibezier_channel<k3d::color>::values_t values_t;
      /// Stores a set of control points by zero-based index
00303       typedef std::set<unsigned long> control_point_set;

public:
      color_bezier_channel_properties(iobject& Object) :
            base(Object, false, false, true, "properties", new k3d::options_window_geometry_store()),
            m_object(Object),
            m_channel(dynamic_cast<ibezier_channel<color>*>(&Object))
      {
            // Load the dialog template ...
            return_if_fail(LoadGTKMLTemplate("color_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 << "bezier_channel_properties(): Could not find useable OpenGL visual" << std::endl;

            sdpGtkContainer colorcontainer(Container("color"));
            if(!m_color_widget.Create(colorcontainer, true, true, 8, 8, 8, 0))
                  if(!m_color_widget.Create(colorcontainer, true, true, 5, 5, 5, 0))
                        if(!m_color_widget.Create(colorcontainer, false, false, 4, 4, 4, 0))
                              std::cerr << "bezier_channel_properties(): Could not find useable OpenGL visual" << std::endl;

            if(m_color_widget.Initialized())
                  {
                        MapEvent("configure-event", "configurecolor", false, m_color_widget, true);
                        MapEvent("expose-event", "exposecolor", false, m_color_widget, true);
                  }

            // 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<color>* const channel = dynamic_cast<ichannel<color>*>(&m_object);
            return_if_fail(channel);
            channel->changed_signal().connect(SigC::slot(*this, &color_bezier_channel_properties::on_channel_modified));

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

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

            // Cache the new curve ...
            m_channel->get_curve(m_control_points, m_values);
            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
      }

      // k3d::icommand_node implementation ...
00373       bool execute_command(const std::string& Command, const std::string& Arguments)
      {
            // Extract state information from the arguments ...
            std::istringstream arguments(Arguments);
            int temp = 0;
            k3d::vector2 currentmouse;
            k3d::vector2 lastmouse;
            k3d::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 = k3d::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, k3d::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, k3d::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, k3d::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, k3d::iuser_interface::LMB_DRAG, modifiers, currentmouse);
                  }
            else if(Command == control_lbuttondrag)
                  {
                        OnLButtonDrag(modifiers, currentmouse, lastmouse, startmouse);
                        mouse_command(widget, k3d::iuser_interface::LMB_DRAG, modifiers, currentmouse);
                  }
            else if(Command == control_lbuttonenddrag)
                  {
                        OnLButtonEndDrag(modifiers, currentmouse, lastmouse, startmouse);
                        mouse_command(widget, k3d::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, k3d::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, k3d::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, k3d::iuser_interface::MMB_DRAG, modifiers, currentmouse);
                  }
            else if(Command == control_mbuttondrag)
                  {
                        OnMButtonDrag(modifiers, currentmouse, lastmouse, startmouse);
                        mouse_command(widget, k3d::iuser_interface::MMB_DRAG, modifiers, currentmouse);
                  }
            else if(Command == control_mbuttonenddrag)
                  {
                        OnMButtonEndDrag(modifiers, currentmouse, lastmouse, startmouse);
                        mouse_command(widget, k3d::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, k3d::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, k3d::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, k3d::iuser_interface::RMB_DRAG, modifiers, currentmouse);
                  }
            else if(Command == control_rbuttondrag)
                  {
                        OnRButtonDrag(modifiers, currentmouse, lastmouse, startmouse);
                        mouse_command(widget, k3d::iuser_interface::RMB_DRAG, modifiers, currentmouse);
                  }
            else if(Command == control_rbuttonenddrag)
                  {
                        OnRButtonEndDrag(modifiers, currentmouse, lastmouse, startmouse);
                        mouse_command(widget, k3d::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, k3d::iuser_interface::LMBRMB_DRAG, modifiers, currentmouse);
                  }
            else if(Command == control_lrbuttondrag)
                  {
                        OnLRButtonDrag(modifiers, currentmouse, lastmouse, startmouse);
                        mouse_command(widget, k3d::iuser_interface::LMBRMB_DRAG, modifiers, currentmouse);
                  }
            else if(Command == control_lrbuttonenddrag)
                  {
                        OnLRButtonEndDrag(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;
      }

private:
      void on_channel_modified()
      {
            // Cache the new curve ...
            m_channel->get_curve(m_control_points, m_values);
            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
00533       void OnEvent(sdpGtkEvent* Event)
      {
            if(Event->Name() == "configurecurve" || Event->Name() == "exposecurve")
                  draw_curve();
            else if(Event->Name() == "configurecolor" || Event->Name() == "exposecolor")
                  draw_color();
            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
00566       void on_straighten_channel()
      {
            k3d::record_command(*this, k3d::icommand_node::command_t::USER_INTERFACE, control_straightenchannel);
            k3d::record_state_change_set undo(m_object.document(), "Straighten Channel");

            // Get the maximum extents of the curve ...
            k3d::rectangle extents = value_extents(m_control_points, m_values);

            // 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 k3d::vector2 left = k3d::vector2(extents.Left(), extents.Bottom());
            const k3d::vector2 right = k3d::vector2(extents.Right(), extents.Top());

            for(unsigned long i = 0; i < m_control_points.size(); ++i)
                  {
                        const double position = i % 3;
                        const double odd = (i / 3) % 2;

                        k3d::vector2 coords(k3d::mix(left, right, static_cast<double>(i) / static_cast<double>(m_control_points.size()-1)));
                        coords[1] = k3d::mix(odd, 1.0 - odd, position / 3.0);

                        m_control_points[i] = coords;
                  }

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

      /// Called to mirror the entire channel
00597       void on_mirror_channel_x()
      {
            k3d::record_command(*this, k3d::icommand_node::command_t::USER_INTERFACE, control_mirrorxchannel);
            k3d::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, m_values).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 = k3d::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());
            std::reverse(m_values.begin(), m_values.end());

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

      /// Called to mirror the entire channel
00618       void on_mirror_channel_y()
      {
            k3d::record_command(*this, k3d::icommand_node::command_t::USER_INTERFACE, control_mirrorychannel);
            k3d::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, m_values).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 = k3d::vector2((*control_point)[0], reflection_axis - ((*control_point)[1] - reflection_axis));

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

      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.red, background_color.green, background_color.blue, 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);

            glColor3d(background_color.red * 0.8, background_color.green * 0.8, background_color.blue * 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(k3d::vector2(0.0, 0.0))[0];
            const double top = world(k3d::vector2(0.0, 0.0))[1];
            const double right = world(k3d::vector2(0.0, static_cast<double>(m_curve_widget.Width())))[0];
            const double bottom = world(k3d::vector2(0.0, static_cast<double>(m_curve_widget.Height())))[1];

            // Draw the origin / axes ...
            glColor3d(background_color.red * 0.6, background_color.green * 0.6, background_color.blue * 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(k3d::vector2(0.0, 0.0))[1];
            double bottom = world(k3d::vector2(0.0, static_cast<double>(m_curve_widget.Height())))[1];

            glColor3dv(cursor_color.data());
            glBegin(GL_LINES);

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

            glEnd();
      }

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

            const double time = boost::any_cast<double>(k3d::get_property_value(m_object.document().dag(), *time_property));
            const double top = world(k3d::vector2(0.0, 0.0))[1];
            const double bottom = world(k3d::vector2(0.0, static_cast<double>(m_curve_widget.Height())))[1];

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

      void draw_control_curve()
      {
            // Draw initial straight line node before first node
            glColor3dv(normal_color.data());
            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.data());
            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.data() : value_node_color.data());
                              }
                        else
                              {
                                    glPointSize(5.0);
                                    glColor3dv(selected(i) ? selected_color.data() : normal_color.data());
                              }

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

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

            m_color_widget.Begin();

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

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

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

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

            // Make it happen ...
            glBegin(GL_QUADS);

            const double error = 0.01;
            const unsigned long granularity = 2;

            k3d::ichannel<k3d::color>* const channel = dynamic_cast<k3d::ichannel<k3d::color>*>(&m_object);
            if(channel)
                  {
                        double x2;
                        double x1 = world(k3d::vector2(0.0, 0.0))[0];
                        for(unsigned long i = 0; i < width + granularity; i += granularity)
                              {
                                    x2 = world(k3d::vector2(double(i+1), 0.0))[0];

                                    k3d::color color = channel->value(x2, error);

                                    glColor3dv(color.data());
                                    glVertex2d(x1, -1);
                                    glVertex2d(x1, 1);
                                    glVertex2d(x2, 1);
                                    glVertex2d(x2, -1);

                                    x1 = x2;
                              }
                  }

            glEnd();

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

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

      k3d::vector2 world(const k3d::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();

            k3d::vector2 result;

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

            if(height)
                  result[1] = k3d::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;
      }

      k3d::vector2 screen(const k3d::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 ...
            k3d::vector2 world_position(WorldPosition);
            world_position[1] *= (1.0 / m_vertical_scale);
            k3d::vector2 result = Product((world_position - m_origin), k3d::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()
      {
            k3d::record_command(*this, k3d::icommand_node::command_t::USER_INTERFACE, control_resetview);
            reset_view();
            redraw_all();
      }

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

            // 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 = k3d::vector2(extents.Width(), extents.Height()) * 0.55;
            m_origin = extents.Center();
      }

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

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

            set_cursor_x(xvalue);

            if(m_active_control_point != m_control_points.end())
                  {
                        k3d::record_state_change_set undo(m_object.document(), "Channel Node X Value change");
                        *m_active_control_point = k3d::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("******");
//          Entry("cursorvalue").SetText(sdpToString(channel->value(m_cursor[0], 0.001)));
      }

      void set_cursor_xy(const k3d::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]));
//          Entry("cursorvalue").SetText(sdpToString(channel->value(m_cursor[0], 0.001)));
      }

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

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

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

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

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

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

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

            control_point_set 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 k3d::vector2 CurrentMouse)
      {
            record_event(control_lbuttondoubleclick, Modifiers, CurrentMouse);

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

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

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

            // Create control points ...
            control_point_set new_control_points = insert_value(worldcoords, k3d::color(1, 1, 1));

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

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

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

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

      control_point_set insert_value(const k3d::vector2 WorldCoords, const k3d::color Value)
      {
            control_point_set results;

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

                        control_points_t new_control_points;
                        new_control_points.push_back(left);
                        new_control_points.push_back(k3d::vector2(k3d::mix<double>(left[0], right[0], 0.3), left[1]));
                        new_control_points.push_back(k3d::vector2(k3d::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());
                        m_values.insert(m_values.begin(), Value);

                        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 k3d::vector2 left(m_control_points.back());
                        const k3d::vector2 right(WorldCoords);

                        control_points_t new_control_points;
                        new_control_points.push_back(k3d::vector2(k3d::mix<double>(left[0], right[0], 0.3), left[1]));
                        new_control_points.push_back(k3d::vector2(k3d::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());
                        m_values.insert(m_values.end(), Value);

                        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 k3d::vector2 left(m_control_points[i]);
                        const k3d::vector2 right(m_control_points[i+3]);

                        control_points_t new_control_points;
                        new_control_points.push_back(k3d::vector2(k3d::mix<double>(left[0], WorldCoords[0], 0.7), WorldCoords[1]));
                        new_control_points.push_back(WorldCoords);
                        new_control_points.push_back(k3d::vector2(k3d::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());
                        m_values.insert(m_values.begin() + (i / 3) + 1, Value);

                        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 k3d::vector2 CurrentMouse)
      {
            record_event(control_lbuttonstartdrag, Modifiers, CurrentMouse);

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

      void OnLButtonDrag(GdkModifierType Modifiers, const k3d::vector2 CurrentMouse, const k3d::vector2 LastMouse, const k3d::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 k3d::vector2 delta(world(CurrentMouse) - world(LastMouse));

            // For each selected node ...
            for(control_point_set::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, m_values);

            redraw_all();
      }

      /// Moves a control point, and possibly moves its neighbors, based on user input ...
01239       void move_control_point(const unsigned long ControlPoint, const k3d::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(), k3d::vector2(m_control_points[last_control_point()][0], Position[1]));
                                    else if(is_last_value_control_point(ControlPoint))
                                          move_control_point(first_control_point(), k3d::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 k3d::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
01273       void move_control_point(const unsigned long ControlPoint, const k3d::vector2 Position)
      {
/*
            // Get the range of allowable values ...
            double minimum;
            double maximum;
            m_object.range(minimum, maximum);
            k3d::vector2 position(Position[0], std::max(minimum, std::min(maximum, Position[1])));
*/
            k3d::vector2 position(Position[0], std::max(0.0, std::min(1.0, Position[1])));

            // 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, k3d::vector2(Position[0], m_control_points[a][1]));

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

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

                        if(d != ControlPoint && Position[0] > m_control_points[d][0])
                              move_control_point(d, k3d::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, k3d::vector2(Position[0], m_control_points[b][1]));

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

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

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

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

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

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

            for(control_point_set::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 k3d::vector2 first(m_control_points[a]);
                        const k3d::vector2 last(m_control_points[d]);

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

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

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

            // Figure-out exactly which control points need to be deleted
            control_point_set lambs;
            for(control_point_set::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())
                  {
                        k3d::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())
                  {
                        k3d::error_message("You can't delete the last control point!", "Delete Selected:");
                        return;
                  }

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

            // Delete values ...
            for(control_point_set::reverse_iterator control_point = lambs.rbegin(); control_point != lambs.rend(); ++control_point)
                  if(is_value_control_point(*control_point))
                        m_values.erase(m_values.begin() + (*control_point));

            // Delete control points ...
            for(control_point_set::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 ...
            k3d::record_state_change_set undo(m_object.document(), "Delete selected control point(s)");
            m_channel->set_curve(m_control_points, m_values);
      }

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

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

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

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

            redraw_all();
      }

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

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

      void OnMButtonDrag(GdkModifierType Modifiers, const k3d::vector2 CurrentMouse, const k3d::vector2 LastMouse, const k3d::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 k3d::vector2 CurrentMouse, const k3d::vector2 LastMouse, const k3d::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 ControlPoints)
      {
            for(control_point_set::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;

                        new color_value_properties(&m_object, *m_channel, (*control_point) / 3);
                  }
      }

      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 hit_test(const k3d::vector2 Mouse)
      {
            control_point_set 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;
      }

      /// Stores a reference to the owning object
01608       iobject& m_object;
      ibezier_channel<color>* const m_channel;

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

      /// Stores the coordinates of the user interface cursor
01617       k3d::vector2 m_cursor;

      /// Stores the OpenGL context for the control curve graph
01620       sdpGtkOpenGLDrawingArea m_curve_widget;
      /// Stores the OpenGL context for the color preview bar
01622       sdpGtkOpenGLDrawingArea m_color_widget;

      /// Caches the node control points
01625       control_points_t m_control_points;
      /// Caches the node values
01627       values_t m_values;

      /// Keeps track of currently-selected nodes (by index)
01630       control_point_set m_selection;
      /// Keeps track of currently-active node
01632       control_points_t::iterator m_active_control_point;

      /// Returns the rectangle that encloses all control points
01635       const k3d::rectangle control_point_extents(const control_points_t ControlPoints, const values_t Values)
      {
            k3d::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)
01652       const k3d::rectangle value_extents(const control_points_t ControlPoints, const values_t Values)
      {
            k3d::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;
      }

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

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

};

void create_color_bezier_channel_properties(iobject& Object)
{
      new color_bezier_channel_properties(Object);
}

} // namespace k3d



Generated by  Doxygen 1.6.0   Back to index