Remove ancient trunk folder from svn repository
[synfig.git] / synfig-studio / src / gtkmm / cellrenderer_timetrack.cpp
diff --git a/synfig-studio/src/gtkmm/cellrenderer_timetrack.cpp b/synfig-studio/src/gtkmm/cellrenderer_timetrack.cpp
new file mode 100644 (file)
index 0000000..8382465
--- /dev/null
@@ -0,0 +1,906 @@
+/* === S Y N F I G ========================================================= */
+/*!    \file cellrenderer_timetrack.cpp
+**     \brief Template Header
+**
+**     $Id$
+**
+**     \legal
+**     Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
+**     Copyright (c) 2007, 2008 Chris Moore
+**
+**     This package 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 package 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.
+**     \endlegal
+*/
+/* ========================================================================= */
+
+/* === H E A D E R S ======================================================= */
+
+#ifdef USING_PCH
+#      include "pch.h"
+#else
+#ifdef HAVE_CONFIG_H
+#      include <config.h>
+#endif
+
+#include <gtkmm/label.h>
+#include "cellrenderer_timetrack.h"
+#include <gtk/gtk.h>
+#include <gtkmm/spinbutton.h>
+#include <gtkmm/combo.h>
+#include <ETL/stringf>
+#include "widget_value.h"
+#include "app.h"
+#include <gtkmm/menu.h>
+#include <gtkmm/optionmenu.h>
+#include "widget_time.h"
+#include "widget_timeslider.h"
+
+#include <synfigapp/canvasinterface.h>
+#include "instance.h"
+
+#include "general.h"
+
+#endif
+
+using namespace synfig;
+using namespace std;
+using namespace etl;
+using namespace studio;
+
+/* === M A C R O S ========================================================= */
+
+/* === G L O B A L S ======================================================= */
+
+static char stipple_xpm[] = { 2, 0 };
+
+//mode for modifier keys
+enum MODMODE
+{
+       NONE = 0,
+       SELECT_MASK = Gdk::CONTROL_MASK,
+       COPY_MASK = Gdk::SHIFT_MASK,
+       DELETE_MASK = Gdk::MOD1_MASK
+};
+
+/* === P R O C E D U R E S ================================================= */
+
+/* === M E T H O D S ======================================================= */
+
+CellRenderer_TimeTrack::CellRenderer_TimeTrack():
+       Glib::ObjectBase        (typeid(CellRenderer_TimeTrack)),
+       Gtk::CellRenderer       (),
+       adjustment_                     (10,10,20,0,0,0),
+
+       property_valuedesc_     (*this,"value_desc",synfigapp::ValueDesc()),
+       property_canvas_        (*this,"canvas",synfig::Canvas::Handle()),
+       property_adjustment_(*this,"adjustment",&adjustment_),
+       property_enable_timing_info_(*this,"enable-timing-info", false)
+{
+       dragging=false;
+       selection=false;
+}
+
+CellRenderer_TimeTrack::~CellRenderer_TimeTrack()
+{
+       if (getenv("SYNFIG_DEBUG_DESTRUCTORS"))
+               synfig::info("CellRenderer_TimeTrack::~CellRenderer_TimeTrack(): Deleted");
+}
+
+void
+CellRenderer_TimeTrack::set_adjustment(Gtk::Adjustment &x)
+{
+       property_adjustment_=&x;
+//     x.signal_value_changed().connect(sigc::mem_fun(*this,&Gtk::Widget::queue_draw));
+}
+
+synfig::Canvas::Handle
+CellRenderer_TimeTrack::get_canvas()const
+{
+       return const_cast<CellRenderer_TimeTrack*>(this)->property_canvas().get_value();
+}
+
+Gtk::Adjustment *
+CellRenderer_TimeTrack::get_adjustment()
+{
+       return (Gtk::Adjustment*)property_adjustment_;
+}
+
+const Gtk::Adjustment *
+CellRenderer_TimeTrack::get_adjustment()const
+{
+       return (const Gtk::Adjustment*)property_adjustment_;
+}
+
+bool
+CellRenderer_TimeTrack::is_selected(const Waypoint& waypoint)const
+{
+       return selected==waypoint;
+}
+
+const synfig::Time get_time_offset_from_vdesc(const synfigapp::ValueDesc &v)
+{
+#ifdef ADJUST_WAYPOINTS_FOR_TIME_OFFSET
+       if(getenv("SYNFIG_SHOW_CANVAS_PARAM_WAYPOINTS") ||
+          v.get_value_type() != synfig::ValueBase::TYPE_CANVAS)
+               return synfig::Time::zero();
+
+       synfig::Canvas::Handle canvasparam = v.get_value().get(Canvas::Handle());
+       if(!canvasparam)
+               return synfig::Time::zero();
+
+       if (!v.parent_is_layer_param())
+               return synfig::Time::zero();
+
+       synfig::Layer::Handle layer = v.get_layer();
+
+       if (layer->get_name()!="PasteCanvas")
+               return synfig::Time::zero();
+
+       return layer->get_param("time_offset").get(Time());
+#else // ADJUST_WAYPOINTS_FOR_TIME_OFFSET
+       return synfig::Time::zero();
+#endif
+}
+
+//kind of a hack... pointer is ugly
+const synfig::Node::time_set *get_times_from_vdesc(const synfigapp::ValueDesc &v)
+{
+       if(!getenv("SYNFIG_SHOW_CANVAS_PARAM_WAYPOINTS") &&
+          v.get_value_type() == synfig::ValueBase::TYPE_CANVAS)
+       {
+               synfig::Canvas::Handle canvasparam = v.get_value().get(Canvas::Handle());
+
+               if(canvasparam)
+                       return &canvasparam->get_times();
+       }
+
+       ValueNode *base_value = v.get_value_node().get();
+
+       ValueNode_DynamicList *parent_value_node =
+                       v.parent_is_value_node() ?
+                               dynamic_cast<ValueNode_DynamicList *>(v.get_parent_value_node().get()) :
+                               0;
+
+       //we want a dynamic list entry to override the normal...
+       if(parent_value_node)
+       {
+               return &parent_value_node->list[v.get_index()].get_times();
+       }else if(base_value) //don't render stuff if it's just animated...
+       {
+               return &base_value->get_times();
+       }
+       return 0;
+}
+
+bool get_closest_time(const synfig::Node::time_set &tset, const Time &t, const Time &range, Time &out)
+{
+       Node::time_set::const_iterator  i,j,end = tset.end();
+
+       // stop the crash mentioned in bug #1689282
+       // doesn't solve the underlying problem though, I don't think
+       if (tset.size() == 0)
+       {
+               synfig::error(__FILE__":%d: tset.size() == 0",__LINE__);
+               return false;
+       }
+
+       //TODO add in RangeGet so it's not so damn hard to click on points
+
+       i = tset.upper_bound(t); //where t is the lower bound, t < [first,i)
+       j = i; --j;
+
+       double dist = Time::end();
+       double closest = 0;
+
+       if(i != end)
+       {
+               closest = i->get_time();
+               dist = abs(i->get_time() - t);
+       }
+
+       if(j != end && (abs(j->get_time() - t) < dist) )
+       {
+               closest = j->get_time();
+               dist = abs(j->get_time() - t);
+       }
+
+       if( dist <= range/2 )
+       {
+               out = closest;
+               return true;
+       }
+
+       return false;
+}
+
+void
+CellRenderer_TimeTrack::render_vfunc(
+               const Glib::RefPtr<Gdk::Drawable>& window,
+               Gtk::Widget& widget,
+               const Gdk::Rectangle& /*background_area*/,
+               const Gdk::Rectangle& area_,
+               const Gdk::Rectangle& /*expose_area*/,
+               Gtk::CellRendererState /*flags*/)
+{
+       if(!window)
+               return;
+
+       Glib::RefPtr<Gdk::GC> gc(Gdk::GC::create(window));
+       Glib::RefPtr<Gdk::GC> inactive_gc(Gdk::GC::create(window));
+       Gtk::Adjustment *adjustment=get_adjustment();
+       // Gtk::StateType state = Gtk::STATE_ACTIVE;
+       // Gtk::ShadowType shadow;
+
+       Gdk::Color
+               curr_time_color("#0000ff"),
+               inactive_color("#000000"),
+               keyframe_color("#a07f7f");
+       Gdk::Color activepoint_color[2];
+
+       activepoint_color[0]=Gdk::Color("#ff0000");
+       activepoint_color[1]=Gdk::Color("#00ff00");
+
+       inactive_gc->set_rgb_fg_color(inactive_color);
+       inactive_gc->set_stipple(Gdk::Bitmap::create(stipple_xpm,2,2));
+       inactive_gc->set_fill(Gdk::STIPPLED);
+
+       synfig::Canvas::Handle canvas(property_canvas().get_value());
+
+       synfigapp::ValueDesc value_desc = property_value_desc().get_value();
+       synfig::ValueNode *base_value = value_desc.get_value_node().get();
+       // synfig::ValueNode_Animated *value_node=dynamic_cast<synfig::ValueNode_Animated*>(base_value);
+
+       synfig::ValueNode_DynamicList *parent_value_node(0);
+       if(property_value_desc().get_value().parent_is_value_node())
+               parent_value_node=dynamic_cast<synfig::ValueNode_DynamicList*>(property_value_desc().get_value().get_parent_value_node().get());
+
+       // If the canvas is defined, then load up the keyframes
+       if(canvas)
+       {
+               const synfig::KeyframeList& keyframe_list(canvas->keyframe_list());
+               synfig::KeyframeList::const_iterator iter;
+
+               for(iter=keyframe_list.begin();iter!=keyframe_list.end();++iter)
+               {
+                       if(!iter->get_time().is_valid())
+                               continue;
+
+                       const int x((int)((float)area_.get_width()/(adjustment->get_upper()-adjustment->get_lower())*(iter->get_time()-adjustment->get_lower())));
+                       if(iter->get_time()>=adjustment->get_lower() && iter->get_time()<adjustment->get_upper())
+                       {
+                               gc->set_rgb_fg_color(keyframe_color);
+                               window->draw_rectangle(gc, true, area_.get_x()+x, area_.get_y(), 1, area_.get_height()+1);
+                       }
+               }
+       }
+
+       //render all the time points that exist
+       {
+               const synfig::Node::time_set *tset = get_times_from_vdesc(value_desc);
+
+               if(tset)
+               {
+                       const synfig::Time time_offset = get_time_offset_from_vdesc(value_desc);
+                       synfig::Node::time_set::const_iterator  i = tset->begin(), end = tset->end();
+
+                       float   lower = adjustment->get_lower(),
+                                       upper = adjustment->get_upper();
+
+                       Glib::RefPtr<Gdk::GC>   gc = Gdk::GC::create(widget.get_window());
+
+                       Gdk::Rectangle area(area_);
+                       gc->set_clip_rectangle(area);
+                       gc->set_line_attributes(1,Gdk::LINE_SOLID,Gdk::CAP_BUTT,Gdk::JOIN_MITER);
+
+                       bool valselected = sel_value.get_value_node() == base_value && !sel_times.empty();
+
+                       float cfps = get_canvas()->rend_desc().get_frame_rate();
+
+                       vector<Time>    drawredafter;
+
+                       Time diff = actual_time - actual_dragtime;//selected_time-drag_time;
+                       for(; i != end; ++i)
+                       {
+                               //find the coordinate in the drawable space...
+                               Time t_orig = i->get_time();
+                               if(!t_orig.is_valid()) continue;
+                               Time t = t_orig - time_offset;
+                               if(t<adjustment->get_lower() || t>adjustment->get_upper()) continue;
+
+                               //if it found it... (might want to change comparison, and optimize
+                               //                                       sel_times.find to not produce an overall nlogn solution)
+
+                               bool selected=false;
+                               //not dragging... just draw as per normal
+                               //if move dragging draw offset
+                               //if copy dragging draw both...
+
+                               if(valselected && sel_times.find(t_orig) != sel_times.end())
+                               {
+                                       if(dragging) //skip if we're dragging because we'll render it later
+                                       {
+                                               if(mode & COPY_MASK) // draw both blue and red moved
+                                               {
+                                                       drawredafter.push_back(t + diff.round(cfps));
+                                                       gc->set_rgb_fg_color(Gdk::Color("#00EEEE"));
+                                               }else if(mode & DELETE_MASK) //it's just red...
+                                               {
+                                                       gc->set_rgb_fg_color(Gdk::Color("#EE0000"));
+                                                       selected=true;
+                                               }else //move - draw the red on top of the others...
+                                               {
+                                                       drawredafter.push_back(t + diff.round(cfps));
+                                                       continue;
+                                               }
+                                       }else
+                                       {
+                                               gc->set_rgb_fg_color(Gdk::Color("#EE0000"));
+                                               selected=true;
+                                       }
+                               }else
+                               {
+                                       gc->set_rgb_fg_color(Gdk::Color("#00EEEE"));
+                               }
+
+                               //synfig::info("Displaying time: %.3f s",(float)t);
+                               const int x = (int)((t-lower)*area.get_width()/(upper-lower));
+
+                               //should draw me a grey filled circle...
+                               Gdk::Rectangle area2(
+                                       area.get_x() - area.get_height()/2 + x + 1,
+                                       area.get_y() + 1,
+                                       area.get_height()-2,
+                                       area.get_height()-2
+                               );
+                               render_time_point_to_window(window,area2,*i - time_offset,selected);
+
+                               /*window->draw_arc(gc,true,
+                               area.get_x() + x - area.get_height()/4, area.get_y() + area.get_height()/8,
+                               area.get_height()/2, area.get_height()*3/4,
+                               0, 64*360);
+
+                               gc->set_rgb_fg_color(Gdk::Color("#000000"));
+                               window->draw_arc(gc,false,
+                               area.get_x() + x - area.get_height()/4, area.get_y() + area.get_height()/8,
+                               area.get_height()/2, area.get_height()*3/4,
+                               0, 64*360);
+                               */
+                       }
+
+                       {
+                               vector<Time>::iterator i = drawredafter.begin(), end = drawredafter.end();
+                               for(; i != end; ++i)
+                               {
+                                       //find the coordinate in the drawable space...
+                                       Time t = *i;
+
+                                       if(!t.is_valid())
+                                               continue;
+
+                                       //synfig::info("Displaying time: %.3f s",(float)t);
+                                       const int x = (int)((t-lower)*area.get_width()/(upper-lower));
+
+                                       //should draw me a grey filled circle...
+
+                                       Gdk::Rectangle area2(
+                                               area.get_x() - area.get_height()/2 + x + 1,
+                                               area.get_y() + 1,
+                                               area.get_height()-2,
+                                               area.get_height()-2
+                                       );
+                                       render_time_point_to_window(window,area2,*i,true);
+/*                                     gc->set_rgb_fg_color(Gdk::Color("#EE0000"));
+                                       window->draw_arc(gc,true,
+                                       area.get_x() + x - area.get_height()/4, area.get_y() + area.get_height()/8,
+                                       area.get_height()/2, area.get_height()*3/4,
+                                       0, 64*360);
+
+                                       gc->set_rgb_fg_color(Gdk::Color("#000000"));
+                                       window->draw_arc(gc,false,
+                                       area.get_x() + x - area.get_height()/4, area.get_y() + area.get_height()/8,
+                                       area.get_height()/2, area.get_height()*3/4,
+                                       0, 64*360);
+*/
+                               }
+                       }
+               }
+       }
+
+       /* THIS IS NOW HANDLED ENTIRELY BY THE TIMEPOINT SYSTEM
+       // This this is an animated value node, then render the waypoints
+       if(value_node)
+       {
+               //now render the actual waypoints
+               synfig::ValueNode_Animated::WaypointList::iterator iter;
+               for(
+                       iter=value_node->waypoint_list().begin();
+                       iter!=value_node->waypoint_list().end();
+                       iter++
+               )
+               {
+                       if(!iter->get_time().is_valid())
+                               continue;
+                       int x;
+                       bool selected=false;
+                       if(is_selected(*iter))
+                       {
+                               Time t(iter->get_time());
+
+
+                               if(dragging)
+                                       t=(t+selected_time-drag_time).round(get_canvas()->rend_desc().get_frame_rate());
+
+                               x=(int)((float)area.get_width()/(adjustment->get_upper()-adjustment->get_lower())*(t-adjustment->get_lower()));
+                               shadow=Gtk::SHADOW_IN;
+                               selected=true;
+                       }
+                       else
+                       {
+                               x=(int)((float)area.get_width()/(adjustment->get_upper()-adjustment->get_lower())*(iter->get_time()-adjustment->get_lower()));
+                               shadow=Gtk::SHADOW_OUT;
+                               selected=false;
+                       }
+
+
+                       widget.get_style()->paint_diamond(
+                               Glib::RefPtr<Gdk::Window>::cast_static(window),
+                               state,
+                               shadow,
+                               area,
+                               widget,
+                               "solid",
+                               area.get_x()+x-area.get_height()/4,
+                               area.get_y()+area.get_height()/4,
+                               area.get_height()/2,
+                               area.get_height()/2
+                       );
+               }
+       }
+       */
+               Gdk::Rectangle area(area_);
+       // If the parent of this value node is a dynamic list, then
+       // render the on and off times
+       if(parent_value_node)
+       {
+               const int index(property_value_desc().get_value().get_index());
+               const synfig::ValueNode_DynamicList::ListEntry& list_entry(parent_value_node->list[index]);
+               const synfig::ValueNode_DynamicList::ListEntry::ActivepointList& activepoint_list(list_entry.timing_info);
+               synfig::ValueNode_DynamicList::ListEntry::ActivepointList::const_iterator iter,next;
+
+               bool is_off(false);
+               if(!activepoint_list.empty())
+                       is_off=!activepoint_list.front().state;
+
+               int xstart(0);
+
+               int x=0,prevx=0;
+               for(next=activepoint_list.begin(),iter=next++;iter!=activepoint_list.end();iter=next++)
+               {
+                       x=((int)((float)area.get_width()/(adjustment->get_upper()-adjustment->get_lower())*(iter->time-adjustment->get_lower())));
+                       if(x<0)x=0;
+                       if(x>area.get_width())x=area.get_width();
+
+                       bool status_at_time=0;
+                       if(next!=activepoint_list.end())
+                       {
+                               status_at_time=!list_entry.status_at_time((iter->time+next->time)/2.0);
+                       }
+                       else
+                               status_at_time=!list_entry.status_at_time(Time::end());
+
+                       if(!is_off && status_at_time)
+                       {
+                               xstart=x;
+                               is_off=true;
+                       }
+                       else
+                       if(is_off && !status_at_time)
+                       {
+                               window->draw_rectangle(inactive_gc, true, area.get_x()+xstart, area.get_y(), x-xstart, area.get_height());
+                               is_off=false;
+                       }
+
+                       /*
+                       if(!is_off && iter!=activepoint_list.end() && next->state==false && iter->state==false)
+                       {
+                               xstart=x;
+                               is_off=true;
+                       }
+                       else if(is_off && next!=activepoint_list.end() && iter->state==false && next->state==true)
+                       {
+                               window->draw_rectangle(inactive_gc, true, area.get_x()+xstart, area.get_y(), x-xstart, area.get_height());
+                               is_off=false;
+                       }
+                       else if(is_off && iter!=activepoint_list.end() && iter->state==true)
+                       {
+                               window->draw_rectangle(inactive_gc, true, area.get_x()+xstart, area.get_y(), prevx-xstart, area.get_height());
+                               is_off=false;
+                       }
+                       */
+
+
+
+                       if(iter->time>=adjustment->get_lower() && iter->time<adjustment->get_upper())
+                       {
+                               int w(1);
+                               if(selected==*iter)
+                                       w=3;
+                               gc->set_rgb_fg_color(activepoint_color[iter->state]);
+                               window->draw_rectangle(gc, true, area.get_x()+x-w/2, area.get_y(), w, area.get_height());
+                       }
+                       prevx=x;
+               }
+               if(is_off)
+               {
+                       window->draw_rectangle(inactive_gc, true, area.get_x()+xstart, area.get_y(), area.get_width()-xstart, area.get_height());
+               }
+       }
+
+       // Render a line that defines the current tick in time
+       {
+               gc->set_rgb_fg_color(curr_time_color);
+
+               const int x((int)((float)area.get_width()/(adjustment->get_upper()-adjustment->get_lower())*(adjustment->get_value()-adjustment->get_lower())));
+
+               if(adjustment->get_value()>=adjustment->get_lower() && adjustment->get_value()<adjustment->get_upper())
+                       window->draw_rectangle(gc, true, area.get_x()+x, area.get_y(), 1, area.get_height());
+       }
+}
+
+synfig::ValueNode_Animated::WaypointList::iterator
+CellRenderer_TimeTrack::find_waypoint(const synfig::Time& /*t*/,const synfig::Time& scope)
+{
+       synfig::ValueNode_Animated *value_node=dynamic_cast<synfig::ValueNode_Animated*>(property_value_desc().get_value().get_value_node().get());
+
+    Time nearest(Time::end());
+
+       synfig::ValueNode_Animated::WaypointList::iterator iter,ret;
+
+       if(value_node)
+       {
+               for(
+                       iter=value_node->waypoint_list().begin();
+                       iter!=value_node->waypoint_list().end();
+                       iter++
+                       )
+               {
+                       Time val=abs(iter->get_time()-selected_time);
+                       if(val<nearest)
+                       {
+                               nearest=val;
+                               ret=iter;
+                       }
+               }
+
+               if(nearest!=Time::end() && nearest<scope)
+               {
+                       return ret;
+               }
+       }
+       throw int();
+}
+
+bool
+CellRenderer_TimeTrack::activate_vfunc(
+       GdkEvent* event,
+       Gtk::Widget& /*widget*/,
+       const Glib::ustring& treepath,
+       const Gdk::Rectangle& /*background_area*/,
+       const Gdk::Rectangle& cell_area,
+       Gtk::CellRendererState /*flags*/)
+{
+       path=treepath;
+       synfig::ValueNode_Animated::WaypointList::iterator iter;
+    Time nearest=1000000000;
+       Gtk::Adjustment *adjustment=get_adjustment();
+
+       // synfig::ValueNode_Animated *value_node=dynamic_cast<synfig::ValueNode_Animated*>(property_value_desc().get_value().get_value_node().get());
+
+       synfig::Canvas::Handle canvas(get_canvas());
+
+       synfig::ValueNode_DynamicList *parent_value_node(0);
+       if(property_value_desc().get_value().parent_is_value_node())
+               parent_value_node=dynamic_cast<synfig::ValueNode_DynamicList*>(property_value_desc().get_value().get_parent_value_node().get());
+
+       Time deltatime = 0;
+       Time curr_time;
+       switch(event->type)
+       {
+       case GDK_MOTION_NOTIFY:
+               curr_time=((float)event->motion.x-(float)cell_area.get_x())/(float)cell_area.get_width()*(adjustment->get_upper()-adjustment->get_lower())+adjustment->get_lower();
+
+               mode = NONE;
+               {
+                       Gdk::ModifierType mod;
+                       Gdk::Event(event).get_state(mod);
+                       mode = mod;
+               }
+               break;
+       case GDK_BUTTON_PRESS:
+       case GDK_BUTTON_RELEASE:
+       default:
+               curr_time=((float)event->button.x-(float)cell_area.get_x())/(float)cell_area.get_width()*(adjustment->get_upper()-adjustment->get_lower())+adjustment->get_lower();
+               {
+                       Gdk::ModifierType mod;
+                       Gdk::Event(event).get_state(mod);
+                       mode = mod;
+               }
+               break;
+       }
+       actual_time = curr_time;
+       if(canvas)
+               curr_time=curr_time.round(canvas->rend_desc().get_frame_rate());
+       selected_time=curr_time;
+
+    Time pixel_width((adjustment->get_upper()-adjustment->get_lower())/cell_area.get_width());
+
+    switch(event->type)
+    {
+       case GDK_BUTTON_PRESS:
+               //selected_time=((float)event->button.x-(float)cell_area.get_x())/(float)cell_area.get_width()*(adjustment->get_upper()-adjustment->get_lower())+adjustment->get_lower();
+
+               //Deal with time point selection, but only if they aren't involved in the insanity...
+               if(/*!value_node && */event->button.button == 1)
+               {
+                       Time stime;
+
+                       /*!     UI specification:
+
+                               When nothing is selected, clicking on a point in either normal mode or
+                                       additive mode will select the time point closest to the click.
+                                       Subtractive click will do nothing
+
+                               When things are already selected, clicking on a selected point does
+                                       nothing (in both normal and add mode).  Add mode clicking on an unselected
+                                       point adds it to the set.  Normal clicking on an unselected point will
+                                       select only that one time point.  Subtractive clicking on any point
+                                       will remove it from the the set if it is included.
+                       */
+
+                       synfigapp::ValueDesc valdesc = property_value_desc().get_value();
+                       const Node::time_set *tset = get_times_from_vdesc(valdesc);
+                       const synfig::Time time_offset = get_time_offset_from_vdesc(valdesc);
+
+                       bool clickfound = tset && get_closest_time(*tset,actual_time+time_offset,pixel_width*cell_area.get_height(),stime);
+                       bool selectmode = mode & SELECT_MASK;
+
+                       //NOTE LATER ON WE SHOULD MAKE IT SO MULTIPLE VALUENODES CAN BE SELECTED AT ONCE
+                       //we want to jump to the value desc if we're not currently on it
+                       //      but only if we want to add the point
+                       if(clickfound && !(sel_value == valdesc))
+                       {
+                               sel_value = valdesc;
+                               sel_times.clear();
+                       }
+
+                       //now that we've made sure we're selecting the correct value, deal with the already selected points
+                       set<Time>::iterator foundi = clickfound ? sel_times.find(stime) : sel_times.end();
+                       bool found = foundi != sel_times.end();
+
+                       //remove all other points from our list... (only select the one we need)
+                       if(!selectmode && !found)
+                       {
+                               sel_times.clear();
+                       }
+
+                       if(found && selectmode) //remove a single already selected point
+                       {
+                               sel_times.erase(foundi);
+                       }else if(clickfound) //otherwise look at adding it
+                       {
+                               //for replace the list was cleared earlier, and for add it wasn't so it works
+                               sel_times.insert(stime);
+                       }
+               }
+
+               selection=false;
+               try
+               {
+                       iter=find_waypoint(selected_time,pixel_width*cell_area.get_height()/2);
+                       selected_waypoint=iter;
+                       selected=*iter;
+
+                       selection=true;
+               }
+               catch(int)
+               {
+                       selection=false;
+                       selected=synfig::UniqueID::nil();
+               }
+
+               if((!sel_times.empty() || selection) && event->button.button==1)
+               {
+                       dragging=true;
+                       drag_time=selected_time;
+                       actual_dragtime=actual_time;
+               }
+               //selected_time=iter->time;
+
+               /*
+               // Activepoint Selection
+               if(parent_value_node)
+               {
+                       const int index(property_value_desc().get_value().get_index());
+                       const synfig::ValueNode_DynamicList::ListEntry::ActivepointList& activepoint_list(parent_value_node->list[index].timing_info);
+                       synfig::ValueNode_DynamicList::ListEntry::ActivepointList::const_iterator iter;
+
+                       for(iter=activepoint_list.begin();iter!=activepoint_list.end();++iter)
+                       {
+                               Time val=abs(iter->time-selected_time);
+                               if(val<nearest)
+                               {
+                                       nearest=val;
+                                       selected=*iter;
+                                       selection=true;
+                               }
+                       }
+                       // Perhaps I should signal if we selected this activepoint?
+               }*/
+
+                       if(event->button.button==3)
+                       {
+                               Time stime;
+                               synfigapp::ValueDesc valdesc = property_value_desc().get_value();
+                               const Node::time_set *tset = get_times_from_vdesc(valdesc);
+                               synfig::Time time_offset = get_time_offset_from_vdesc(valdesc);
+
+                               bool clickfound = tset && get_closest_time(*tset,actual_time+time_offset,pixel_width*cell_area.get_height(),stime);
+
+                               etl::handle<synfig::Node> node;
+                               if(!getenv("SYNFIG_SHOW_CANVAS_PARAM_WAYPOINTS") &&
+                                  valdesc.get_value(stime).get_type()==ValueBase::TYPE_CANVAS)
+                               {
+                                       node=Canvas::Handle(valdesc.get_value(stime).get(Canvas::Handle()));
+                               }
+                               else //if(valdesc.is_value_node())
+                               {
+                                       node=valdesc.get_value_node();
+                               }
+
+                               if(clickfound && node)
+                                       signal_waypoint_clicked_cellrenderer()(node, stime, time_offset, 2);
+                       }
+
+               break;
+       case GDK_MOTION_NOTIFY:
+               //if(selection && dragging)
+               //      selected_time=((float)event->motion.x-(float)cell_area.get_x())/(float)cell_area.get_width()*(adjustment->get_upper()-adjustment->get_lower())+adjustment->get_lower();
+               return true;
+
+               break;
+       case GDK_BUTTON_RELEASE:
+               {
+                       //selected_time=((float)event->button.x-(float)cell_area.get_x())/(float)cell_area.get_width()*(adjustment->get_upper()-adjustment->get_lower())+adjustment->get_lower();
+                       dragging=false;
+
+                       /*if(event->button.button==3 && selection)
+                       {
+                               signal_waypoint_clicked_cellrenderer()(path,*selected_waypoint,event->button.button-1);
+                               return true;
+                       }
+                       */
+
+                       //Time point stuff...
+                       if(event->button.button == 1)
+                       {
+                               bool delmode = (mode & DELETE_MASK) && !(mode & COPY_MASK);
+                               deltatime = actual_time - actual_dragtime;
+                               if(sel_times.size() != 0 && (delmode || !deltatime.is_equal(Time(0))))
+                               {
+                                       synfigapp::Action::ParamList param_list;
+                                       param_list.add("canvas",canvas_interface()->get_canvas());
+                                       param_list.add("canvas_interface",canvas_interface());
+
+                                       if(!getenv("SYNFIG_SHOW_CANVAS_PARAM_WAYPOINTS") &&
+                                          sel_value.get_value_type() == synfig::ValueBase::TYPE_CANVAS)
+                                       {
+                                               param_list.add("addcanvas",sel_value.get_value().get(Canvas::Handle()));
+                                       }else
+                                       {
+                                               param_list.add("addvaluedesc",sel_value);
+                                       }
+
+                                       set<Time>       newset;
+                                       std::set<synfig::Time>::iterator i = sel_times.begin(), end = sel_times.end();
+                                       for(; i != end; ++i)
+                                       {
+                                               param_list.add("addtime",*i);
+
+                                               newset.insert((*i + deltatime).round(get_canvas()->rend_desc().get_frame_rate()));
+                                       }
+
+                                       if(!delmode)
+                                               param_list.add("deltatime",deltatime);
+                               //      param_list.add("time",canvas_interface()->get_time());
+
+                                       if(mode & COPY_MASK) //copy
+                                       {
+                                               etl::handle<studio::Instance>::cast_static(canvas_interface()->get_instance())
+                                                       ->process_action("TimepointsCopy", param_list);
+                                       }else if(delmode) //DELETE
+                                       {
+                                               etl::handle<studio::Instance>::cast_static(canvas_interface()->get_instance())
+                                                       ->process_action("TimepointsDelete", param_list);
+                                       }else //MOVE
+                                       {
+                                               etl::handle<studio::Instance>::cast_static(canvas_interface()->get_instance())
+                                                       ->process_action("TimepointsMove", param_list);
+                                       }
+
+                                       //now replace all the selected with the new selected
+                                       sel_times = newset;
+                               }
+                       }
+
+
+
+                       /*if(value_node && selection)
+                       {
+                               if(selected_time==drag_time && event->button.button!=3)
+                                       signal_waypoint_clicked_cellrenderer()(path,*selected_waypoint,event->button.button-1);
+                               else
+                               if(event->button.button==1)
+                               {
+                                       synfig::Waypoint waypoint(*selected_waypoint);
+                                       Time newtime((waypoint.get_time()+(selected_time-drag_time)).round(canvas->rend_desc().get_frame_rate()));
+                                       if(waypoint.get_time()!=newtime)
+                                       {
+                                               waypoint.set_time(newtime);
+                                               signal_waypoint_changed_(waypoint,value_node);
+                                       }
+                               }
+                       }*/
+
+                       //if(selection)
+                       //      selected_time=iter->time;
+                       //selected_time=iter->get_time();
+                       return true;
+               }
+       default:
+               //std::cerr<<"unknown event type "<<event->type<<std::endl;
+               return false;
+               break;
+       }
+
+
+
+       return false;
+}
+
+
+
+// The following three functions don't get documented correctly by
+// doxygen 1.5.[23] because of a bug with any function whose name
+// begins with 'property'.  Fixed in doxygen 1.5.4 apparently.  See
+// http://bugzilla.gnome.org/show_bug.cgi?id=471185 .
+Glib::PropertyProxy<synfigapp::ValueDesc>
+CellRenderer_TimeTrack::property_value_desc()
+{
+       return Glib::PropertyProxy<synfigapp::ValueDesc>(this,"value_desc");
+}
+
+Glib::PropertyProxy<synfig::Canvas::Handle>
+CellRenderer_TimeTrack::property_canvas()
+{
+       return Glib::PropertyProxy<synfig::Canvas::Handle>(this,"canvas");
+}
+
+Glib::PropertyProxy<Gtk::Adjustment* >
+CellRenderer_TimeTrack::property_adjustment()
+{
+       return Glib::PropertyProxy<Gtk::Adjustment* >(this,"adjustment");
+}
+
+void
+CellRenderer_TimeTrack::set_canvas_interface(etl::loose_handle<synfigapp::CanvasInterface>     h)
+{
+       canvas_interface_ = h;
+}