Implements PXEGeek's http://wiki.synfig.com/Wish_list entry: "Optionally display...
[synfig.git] / synfig-studio / trunk / src / gtkmm / cellrenderer_timetrack.cpp
1 /* === S Y N F I G ========================================================= */
2 /*!     \file cellrenderer_timetrack.cpp
3 **      \brief Template Header
4 **
5 **      $Id$
6 **
7 **      \legal
8 **      Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
9 **
10 **      This package is free software; you can redistribute it and/or
11 **      modify it under the terms of the GNU General Public License as
12 **      published by the Free Software Foundation; either version 2 of
13 **      the License, or (at your option) any later version.
14 **
15 **      This package is distributed in the hope that it will be useful,
16 **      but WITHOUT ANY WARRANTY; without even the implied warranty of
17 **      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 **      General Public License for more details.
19 **      \endlegal
20 */
21 /* ========================================================================= */
22
23 /* === H E A D E R S ======================================================= */
24
25 #ifdef USING_PCH
26 #       include "pch.h"
27 #else
28 #ifdef HAVE_CONFIG_H
29 #       include <config.h>
30 #endif
31
32 #include <gtkmm/label.h>
33 #include "cellrenderer_timetrack.h"
34 #include <gtk/gtk.h>
35 #include <gtkmm/spinbutton.h>
36 #include <gtkmm/combo.h>
37 #include <ETL/stringf>
38 #include "widget_value.h"
39 #include "app.h"
40 #include <gtkmm/menu.h>
41 #include <gtkmm/optionmenu.h>
42 #include "widget_time.h"
43 #include "widget_timeslider.h"
44
45 #include <synfigapp/canvasinterface.h>
46 #include "instance.h"
47
48 #include <synfig/timepointcollect.h>
49
50 #endif
51
52 using namespace synfig;
53 using namespace std;
54 using namespace etl;
55 using namespace studio;
56
57 /* === M A C R O S ========================================================= */
58
59 /* === G L O B A L S ======================================================= */
60
61 static char stipple_xpm[] = { 2, 0 };
62
63 //mode for modifier keys
64 enum MODMODE
65 {
66         NONE = 0,
67         SELECT_MASK = Gdk::CONTROL_MASK,
68         COPY_MASK = Gdk::SHIFT_MASK,
69         DELETE_MASK = Gdk::MOD1_MASK
70 };
71
72 /* === P R O C E D U R E S ================================================= */
73
74 /* === M E T H O D S ======================================================= */
75
76 CellRenderer_TimeTrack::CellRenderer_TimeTrack():
77         Glib::ObjectBase        (typeid(CellRenderer_TimeTrack)),
78         Gtk::CellRenderer       (),
79         adjustment_                     (10,10,20,0,0,0),
80
81         property_valuedesc_     (*this,"value_desc",synfigapp::ValueDesc()),
82         property_canvas_        (*this,"canvas",synfig::Canvas::Handle()),
83         property_adjustment_(*this,"adjustment",&adjustment_),
84         property_enable_timing_info_(*this,"enable-timing-info", false)
85 {
86                 dragging=false;
87         selection=false;
88 }
89
90 CellRenderer_TimeTrack::~CellRenderer_TimeTrack()
91 {
92         synfig::info("CellRenderer_TimeTrack::~CellRenderer_TimeTrack(): deleted");
93 }
94
95 void
96 CellRenderer_TimeTrack::set_adjustment(Gtk::Adjustment &x)
97 {
98         property_adjustment_=&x;
99 //      x.signal_value_changed().connect(sigc::mem_fun(*this,&Gtk::Widget::queue_draw));
100 }
101
102 synfig::Canvas::Handle
103 CellRenderer_TimeTrack::get_canvas()const
104 {
105         return const_cast<CellRenderer_TimeTrack*>(this)->property_canvas().get_value();
106 }
107
108 Gtk::Adjustment *
109 CellRenderer_TimeTrack::get_adjustment()
110 {
111         return (Gtk::Adjustment*)property_adjustment_;
112 }
113
114 const Gtk::Adjustment *
115 CellRenderer_TimeTrack::get_adjustment()const
116 {
117         return (const Gtk::Adjustment*)property_adjustment_;
118 }
119
120 bool
121 CellRenderer_TimeTrack::is_selected(const Waypoint& waypoint)const
122 {
123         return selected==waypoint;
124 }
125
126 //kind of a hack... pointer is ugly
127 const synfig::Node::time_set *get_times_from_vdesc(const synfigapp::ValueDesc &v)
128 {
129         if(v.get_value_type() == synfig::ValueBase::TYPE_CANVAS)
130         {
131                 synfig::Canvas::Handle canvasparam = v.get_value().get(Canvas::Handle());
132
133                 if(canvasparam)
134                 {
135                         return &canvasparam->get_times();
136                 }
137         }
138
139         ValueNode *base_value = v.get_value_node().get();
140
141         ValueNode_DynamicList *parent_value_node =
142                         v.parent_is_value_node() ?
143                                 dynamic_cast<ValueNode_DynamicList *>(v.get_parent_value_node().get()) :
144                                 0;
145
146         //we want a dynamic list entry to override the normal...
147         if(parent_value_node)
148         {
149                 return &parent_value_node->list[v.get_index()].get_times();
150         }else if(base_value) //don't render stuff if it's just animated...
151         {
152                 return &base_value->get_times();
153         }
154         return 0;
155 }
156
157 bool get_closest_time(const synfig::Node::time_set &tset, const Time &t, const Time &range, Time &out)
158 {
159         Node::time_set::const_iterator  i,j,end = tset.end();
160
161         //TODO add in RangeGet so it's not so damn hard to click on points
162
163         i = tset.upper_bound(t); //where t is the lower bound, t < [first,i)
164         j = i; --j;
165
166         double dist = Time::end();
167         double closest = 0;
168
169         if(i != end)
170         {
171                 closest = i->get_time();
172                 dist = abs(i->get_time() - t);
173         }
174
175         if(j != end && (abs(j->get_time() - t) < dist) )
176         {
177                 closest = j->get_time();
178                 dist = abs(j->get_time() - t);
179         }
180
181         if( dist <= range/2 )
182         {
183                 out = closest;
184                 return true;
185         }
186
187         return false;
188 }
189
190 void
191 CellRenderer_TimeTrack::render_vfunc(
192                 const Glib::RefPtr<Gdk::Drawable>& window,
193                 Gtk::Widget& widget,
194                 const Gdk::Rectangle& background_area,
195                 const Gdk::Rectangle& area_,
196                 const Gdk::Rectangle& expose_area,
197                 Gtk::CellRendererState flags)
198 {
199         if(!window)
200                 return;
201
202         Glib::RefPtr<Gdk::GC> gc(Gdk::GC::create(window));
203         Glib::RefPtr<Gdk::GC> inactive_gc(Gdk::GC::create(window));
204         Gtk::Adjustment *adjustment=get_adjustment();
205         // Gtk::StateType state = Gtk::STATE_ACTIVE;
206         // Gtk::ShadowType shadow;
207
208         Gdk::Color
209                 curr_time_color("#0000ff"),
210                 inactive_color("#000000"),
211                 keyframe_color("#a07f7f");
212         Gdk::Color activepoint_color[2];
213
214         activepoint_color[0]=Gdk::Color("#ff0000");
215         activepoint_color[1]=Gdk::Color("#00ff00");
216
217         inactive_gc->set_rgb_fg_color(inactive_color);
218         inactive_gc->set_stipple(Gdk::Bitmap::create(stipple_xpm,2,2));
219         inactive_gc->set_fill(Gdk::STIPPLED);
220
221         synfig::Canvas::Handle canvas(property_canvas().get_value());
222
223         synfigapp::ValueDesc value_desc = property_value_desc().get_value();
224         synfig::ValueNode *base_value = value_desc.get_value_node().get();
225         // synfig::ValueNode_Animated *value_node=dynamic_cast<synfig::ValueNode_Animated*>(base_value);
226
227         synfig::ValueNode_DynamicList *parent_value_node(0);
228         if(property_value_desc().get_value().parent_is_value_node())
229                 parent_value_node=dynamic_cast<synfig::ValueNode_DynamicList*>(property_value_desc().get_value().get_parent_value_node().get());
230
231         // If the canvas is defined, then load up the keyframes
232         if(canvas)
233         {
234                 const synfig::KeyframeList& keyframe_list(canvas->keyframe_list());
235                 synfig::KeyframeList::const_iterator iter;
236
237                 for(iter=keyframe_list.begin();iter!=keyframe_list.end();++iter)
238                 {
239                         if(!iter->get_time().is_valid())
240                                 continue;
241
242                         const int x((int)((float)area_.get_width()/(adjustment->get_upper()-adjustment->get_lower())*(iter->get_time()-adjustment->get_lower())));
243                         if(iter->get_time()>=adjustment->get_lower() && iter->get_time()<adjustment->get_upper())
244                         {
245                                 gc->set_rgb_fg_color(keyframe_color);
246                                 window->draw_rectangle(gc, true, area_.get_x()+x, area_.get_y(), 1, area_.get_height()+1);
247                         }
248                 }
249         }
250
251         //render all the time points that exist
252         {
253                 const synfig::Node::time_set *tset = get_times_from_vdesc(value_desc);
254
255                 if(tset)
256                 {
257                         synfig::Node::time_set::const_iterator  i = tset->begin(), end = tset->end();
258
259                         float   lower = adjustment->get_lower(),
260                                         upper = adjustment->get_upper();
261
262                         Glib::RefPtr<Gdk::GC>   gc = Gdk::GC::create(widget.get_window());
263
264                         Gdk::Rectangle area(area_);
265                         gc->set_clip_rectangle(area);
266                         gc->set_line_attributes(1,Gdk::LINE_SOLID,Gdk::CAP_BUTT,Gdk::JOIN_MITER);
267
268                         bool valselected = sel_value.get_value_node() == base_value && !sel_times.empty();
269
270                         float cfps = get_canvas()->rend_desc().get_frame_rate();
271
272                         vector<Time>    drawredafter;
273
274                         Time diff = actual_time - actual_dragtime;//selected_time-drag_time;
275                         for(; i != end; ++i)
276                         {
277                                 //find the coordinate in the drawable space...
278                                 Time t = i->get_time();
279
280                                 if(!t.is_valid())
281                                         continue;
282
283                                 //if it found it... (might want to change comparison, and optimize
284                                 //                                       sel_times.find to not produce an overall nlogn solution)
285
286                                 bool selected=false;
287                                 //not dragging... just draw as per normal
288                                 //if move dragging draw offset
289                                 //if copy dragging draw both...
290
291                                 if(valselected && sel_times.find(t) != sel_times.end())
292                                 {
293                                         if(dragging) //skip if we're dragging because we'll render it later
294                                         {
295                                                 if(mode & COPY_MASK) // draw both blue and red moved
296                                                 {
297                                                         drawredafter.push_back((t + diff).round(cfps));
298                                                         gc->set_rgb_fg_color(Gdk::Color("#00EEEE"));
299                                                 }else if(mode & DELETE_MASK) //it's just red...
300                                                 {
301                                                         gc->set_rgb_fg_color(Gdk::Color("#EE0000"));
302                                                         selected=true;
303                                                 }else //move - draw the red on top of the others...
304                                                 {
305                                                         drawredafter.push_back((t + diff).round(cfps));
306                                                         continue;
307                                                 }
308                                         }else
309                                         {
310                                                 gc->set_rgb_fg_color(Gdk::Color("#EE0000"));
311                                                 selected=true;
312                                         }
313                                 }else
314                                 {
315                                         gc->set_rgb_fg_color(Gdk::Color("#00EEEE"));
316                                 }
317
318                                 //synfig::info("Displaying time: %.3f s",(float)t);
319                                 const int x = (int)((t-lower)*area.get_width()/(upper-lower));
320
321                                 //should draw me a grey filled circle...
322                                 Gdk::Rectangle area2(
323                                         area.get_x() - area.get_height()/2 + x + 1,
324                                         area.get_y() + 1,
325                                         area.get_height()-2,
326                                         area.get_height()-2
327                                 );
328                                 render_time_point_to_window(window,area2,*i,selected);
329
330                                 /*window->draw_arc(gc,true,
331                                 area.get_x() + x - area.get_height()/4, area.get_y() + area.get_height()/8,
332                                 area.get_height()/2, area.get_height()*3/4,
333                                 0, 64*360);
334
335                                 gc->set_rgb_fg_color(Gdk::Color("#000000"));
336                                 window->draw_arc(gc,false,
337                                 area.get_x() + x - area.get_height()/4, area.get_y() + area.get_height()/8,
338                                 area.get_height()/2, area.get_height()*3/4,
339                                 0, 64*360);
340                                 */
341                         }
342
343                         {
344                                 vector<Time>::iterator i = drawredafter.begin(), end = drawredafter.end();
345                                 for(; i != end; ++i)
346                                 {
347                                         //find the coordinate in the drawable space...
348                                         Time t = *i;
349
350                                         if(!t.is_valid())
351                                                 continue;
352
353                                         //synfig::info("Displaying time: %.3f s",(float)t);
354                                         const int x = (int)((t-lower)*area.get_width()/(upper-lower));
355
356                                         //should draw me a grey filled circle...
357
358                                         Gdk::Rectangle area2(
359                                                 area.get_x() - area.get_height()/2 + x + 1,
360                                                 area.get_y() + 1,
361                                                 area.get_height()-2,
362                                                 area.get_height()-2
363                                         );
364                                         render_time_point_to_window(window,area2,*i,true);
365 /*                                      gc->set_rgb_fg_color(Gdk::Color("#EE0000"));
366                                         window->draw_arc(gc,true,
367                                         area.get_x() + x - area.get_height()/4, area.get_y() + area.get_height()/8,
368                                         area.get_height()/2, area.get_height()*3/4,
369                                         0, 64*360);
370
371                                         gc->set_rgb_fg_color(Gdk::Color("#000000"));
372                                         window->draw_arc(gc,false,
373                                         area.get_x() + x - area.get_height()/4, area.get_y() + area.get_height()/8,
374                                         area.get_height()/2, area.get_height()*3/4,
375                                         0, 64*360);
376 */
377                                 }
378                         }
379                 }
380         }
381
382         /* THIS IS NOW HANDLED ENTIRELY BY THE TIMEPOINT SYSTEM
383         // This this is an animated value node, then render the waypoints
384         if(value_node)
385         {
386                 //now render the actual waypoints
387                 synfig::ValueNode_Animated::WaypointList::iterator iter;
388                 for(
389                         iter=value_node->waypoint_list().begin();
390                         iter!=value_node->waypoint_list().end();
391                         iter++
392                 )
393                 {
394                         if(!iter->get_time().is_valid())
395                                 continue;
396                         int x;
397                         bool selected=false;
398                         if(is_selected(*iter))
399                         {
400                                 Time t(iter->get_time());
401
402
403                                 if(dragging)
404                                         t=(t+selected_time-drag_time).round(get_canvas()->rend_desc().get_frame_rate());
405
406                                 x=(int)((float)area.get_width()/(adjustment->get_upper()-adjustment->get_lower())*(t-adjustment->get_lower()));
407                                 shadow=Gtk::SHADOW_IN;
408                                 selected=true;
409                         }
410                         else
411                         {
412                                 x=(int)((float)area.get_width()/(adjustment->get_upper()-adjustment->get_lower())*(iter->get_time()-adjustment->get_lower()));
413                                 shadow=Gtk::SHADOW_OUT;
414                                 selected=false;
415                         }
416
417
418                         widget.get_style()->paint_diamond(
419                                 Glib::RefPtr<Gdk::Window>::cast_static(window),
420                                 state,
421                                 shadow,
422                                 area,
423                                 widget,
424                                 "solid",
425                                 area.get_x()+x-area.get_height()/4,
426                                 area.get_y()+area.get_height()/4,
427                                 area.get_height()/2,
428                                 area.get_height()/2
429                         );
430                 }
431         }
432         */
433                 Gdk::Rectangle area(area_);
434         // If the parent of this value node is a dynamic list, then
435         // render the on and off times
436         if(parent_value_node)
437         {
438                 const int index(property_value_desc().get_value().get_index());
439                 const synfig::ValueNode_DynamicList::ListEntry& list_entry(parent_value_node->list[index]);
440                 const synfig::ValueNode_DynamicList::ListEntry::ActivepointList& activepoint_list(list_entry.timing_info);
441                 synfig::ValueNode_DynamicList::ListEntry::ActivepointList::const_iterator iter,next;
442
443                 bool is_off(false);
444                 if(!activepoint_list.empty())
445                         is_off=!activepoint_list.front().state;
446
447                 int xstart(0);
448
449                 int x=0,prevx=0;
450                 for(next=activepoint_list.begin(),iter=next++;iter!=activepoint_list.end();iter=next++)
451                 {
452                         x=((int)((float)area.get_width()/(adjustment->get_upper()-adjustment->get_lower())*(iter->time-adjustment->get_lower())));
453                         if(x<0)x=0;
454                         if(x>area.get_width())x=area.get_width();
455
456                         bool status_at_time=0;
457                         if(next!=activepoint_list.end())
458                         {
459                                 status_at_time=!list_entry.status_at_time((iter->time+next->time)/2.0);
460                         }
461                         else
462                                 status_at_time=!list_entry.status_at_time(Time::end());
463
464                         if(!is_off && status_at_time)
465                         {
466                                 xstart=x;
467                                 is_off=true;
468                         }
469                         else
470                         if(is_off && !status_at_time)
471                         {
472                                 window->draw_rectangle(inactive_gc, true, area.get_x()+xstart, area.get_y(), x-xstart, area.get_height());
473                                 is_off=false;
474                         }
475
476                         /*
477                         if(!is_off && iter!=activepoint_list.end() && next->state==false && iter->state==false)
478                         {
479                                 xstart=x;
480                                 is_off=true;
481                         }
482                         else if(is_off && next!=activepoint_list.end() && iter->state==false && next->state==true)
483                         {
484                                 window->draw_rectangle(inactive_gc, true, area.get_x()+xstart, area.get_y(), x-xstart, area.get_height());
485                                 is_off=false;
486                         }
487                         else if(is_off && iter!=activepoint_list.end() && iter->state==true)
488                         {
489                                 window->draw_rectangle(inactive_gc, true, area.get_x()+xstart, area.get_y(), prevx-xstart, area.get_height());
490                                 is_off=false;
491                         }
492                         */
493
494
495
496                         if(iter->time>=adjustment->get_lower() && iter->time<adjustment->get_upper())
497                         {
498                                 int w(1);
499                                 if(selected==*iter)
500                                         w=3;
501                                 gc->set_rgb_fg_color(activepoint_color[iter->state]);
502                                 window->draw_rectangle(gc, true, area.get_x()+x-w/2, area.get_y(), w, area.get_height());
503                         }
504                         prevx=x;
505                 }
506                 if(is_off)
507                 {
508                         window->draw_rectangle(inactive_gc, true, area.get_x()+xstart, area.get_y(), area.get_width()-xstart, area.get_height());
509                 }
510         }
511
512         // Render a line that defines the current tick in time
513         {
514                 gc->set_rgb_fg_color(curr_time_color);
515
516                 const int x((int)((float)area.get_width()/(adjustment->get_upper()-adjustment->get_lower())*(adjustment->get_value()-adjustment->get_lower())));
517
518                 if(adjustment->get_value()>=adjustment->get_lower() && adjustment->get_value()<adjustment->get_upper())
519                         window->draw_rectangle(gc, true, area.get_x()+x, area.get_y(), 1, area.get_height());
520         }
521 }
522
523 synfig::ValueNode_Animated::WaypointList::iterator
524 CellRenderer_TimeTrack::find_waypoint(const synfig::Time& t,const synfig::Time& scope)
525 {
526         synfig::ValueNode_Animated *value_node=dynamic_cast<synfig::ValueNode_Animated*>(property_value_desc().get_value().get_value_node().get());
527
528     Time nearest(Time::end());
529
530         synfig::ValueNode_Animated::WaypointList::iterator iter,ret;
531
532         if(value_node)
533         {
534                 for(
535                         iter=value_node->waypoint_list().begin();
536                         iter!=value_node->waypoint_list().end();
537                         iter++
538                         )
539                 {
540                         Time val=abs(iter->get_time()-selected_time);
541                         if(val<nearest)
542                         {
543                                 nearest=val;
544                                 ret=iter;
545                         }
546                 }
547
548                 if(nearest!=Time::end() && nearest<scope)
549                 {
550                         return ret;
551                 }
552         }
553         throw int();
554 }
555
556 bool
557 CellRenderer_TimeTrack::activate_vfunc(
558         GdkEvent* event,
559         Gtk::Widget& widget,
560         const Glib::ustring& treepath,
561         const Gdk::Rectangle& background_area,
562         const Gdk::Rectangle& cell_area,
563         Gtk::CellRendererState flags)
564 {
565         path=treepath;
566         synfig::ValueNode_Animated::WaypointList::iterator iter;
567     Time nearest=1000000000;
568         Gtk::Adjustment *adjustment=get_adjustment();
569
570         // synfig::ValueNode_Animated *value_node=dynamic_cast<synfig::ValueNode_Animated*>(property_value_desc().get_value().get_value_node().get());
571
572         synfig::Canvas::Handle canvas(get_canvas());
573
574         synfig::ValueNode_DynamicList *parent_value_node(0);
575         if(property_value_desc().get_value().parent_is_value_node())
576                 parent_value_node=dynamic_cast<synfig::ValueNode_DynamicList*>(property_value_desc().get_value().get_parent_value_node().get());
577
578         Time deltatime = 0;
579         Time curr_time;
580         switch(event->type)
581         {
582         case GDK_MOTION_NOTIFY:
583                 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();
584
585                 mode = NONE;
586                 {
587                         Gdk::ModifierType mod;
588                         Gdk::Event(event).get_state(mod);
589                         mode = mod;
590                 }
591                 break;
592         case GDK_BUTTON_PRESS:
593         case GDK_BUTTON_RELEASE:
594         default:
595                 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();
596                 {
597                         Gdk::ModifierType mod;
598                         Gdk::Event(event).get_state(mod);
599                         mode = mod;
600                 }
601                 break;
602         }
603         actual_time = curr_time;
604         if(canvas)
605                 curr_time=curr_time.round(canvas->rend_desc().get_frame_rate());
606         selected_time=curr_time;
607
608     Time pixel_width((adjustment->get_upper()-adjustment->get_lower())/cell_area.get_width());
609
610     switch(event->type)
611     {
612         case GDK_BUTTON_PRESS:
613                 //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();
614
615                 //Deal with time point selection, but only if they aren't involved in the insanity...
616                 if(/*!value_node && */event->button.button == 1)
617                 {
618                         Time stime;
619
620                         /*!     UI specification:
621
622                                 When nothing is selected, clicking on a point in either normal mode order
623                                         addative mode will select the time point closest to the click.
624                                         Subtractive click will do nothing
625
626                                 When things are already selected, clicking on a selected point does
627                                         nothing (in both normal and add mode).  Add mode clicking on an unselected
628                                         point adds it to the set.  Normal clicking on an unselected point will
629                                         select only that one time point.  Subtractive clicking on any point
630                                         will remove it from the the set if it is included.
631                         */
632
633                         synfigapp::ValueDesc valdesc = property_value_desc().get_value();
634                         const Node::time_set *tset = get_times_from_vdesc(valdesc);
635
636                         bool clickfound = tset && get_closest_time(*tset,actual_time,pixel_width*cell_area.get_height(),stime);
637                         bool selectmode = mode & SELECT_MASK;
638
639                         //NOTE LATER ON WE SHOULD MAKE IT SO MULTIPLE VALUENODES CAN BE SELECTED AT ONCE
640                         //we want to jump to the value desc if we're not currently on it
641                         //      but only if we want to add the point
642                         if(clickfound && !(sel_value == valdesc))
643                         {
644                                 sel_value = valdesc;
645                                 sel_times.clear();
646                         }
647
648                         //now that we've made sure we're selecting the correct value, deal with the already selected points
649                         set<Time>::iterator foundi = clickfound ? sel_times.find(stime) : sel_times.end();
650                         bool found = foundi != sel_times.end();
651
652                         //remove all other points from our list... (only select the one we need)
653                         if(!selectmode && !found)
654                         {
655                                 sel_times.clear();
656                         }
657
658                         if(found && selectmode) //remove a single already selected point
659                         {
660                                 sel_times.erase(foundi);
661                         }else if(clickfound) //otherwise look at adding it
662                         {
663                                 //for replace the list was cleared earlier, and for add it wasn't so it works
664                                 sel_times.insert(stime);
665                         }
666                 }
667
668                 selection=false;
669                 try
670                 {
671                         iter=find_waypoint(selected_time,pixel_width*cell_area.get_height()/2);
672                         selected_waypoint=iter;
673                         selected=*iter;
674
675                         selection=true;
676                 }
677                 catch(int)
678                 {
679                         selection=false;
680                         selected=synfig::UniqueID::nil();
681                 }
682
683                 if((!sel_times.empty() || selection) && event->button.button==1)
684                 {
685                         dragging=true;
686                         drag_time=selected_time;
687                         actual_dragtime=actual_time;
688                 }
689                 //selected_time=iter->time;
690
691                 /*
692                 // Activepoint Selection
693                 if(parent_value_node)
694                 {
695                         const int index(property_value_desc().get_value().get_index());
696                         const synfig::ValueNode_DynamicList::ListEntry::ActivepointList& activepoint_list(parent_value_node->list[index].timing_info);
697                         synfig::ValueNode_DynamicList::ListEntry::ActivepointList::const_iterator iter;
698
699                         for(iter=activepoint_list.begin();iter!=activepoint_list.end();++iter)
700                         {
701                                 Time val=abs(iter->time-selected_time);
702                                 if(val<nearest)
703                                 {
704                                         nearest=val;
705                                         selected=*iter;
706                                         selection=true;
707                                 }
708                         }
709                         // Perhaps I sould signal if we selected this activepoint?
710                 }*/
711
712                         if(event->button.button==3)
713                         {
714                                 Time stime;
715                                 synfigapp::ValueDesc valdesc = property_value_desc().get_value();
716                                 const Node::time_set *tset = get_times_from_vdesc(valdesc);
717
718                                 bool clickfound = tset && get_closest_time(*tset,actual_time,pixel_width*cell_area.get_height(),stime);
719
720                                 etl::handle<synfig::Node> node;
721                                 if(valdesc.get_value(stime).get_type()==ValueBase::TYPE_CANVAS)
722                                 {
723                                         node=Canvas::Handle(valdesc.get_value(stime).get(Canvas::Handle()));
724                                 }
725                                 else //if(valdesc.is_value_node())
726                                 {
727                                         node=valdesc.get_value_node();
728                                 }
729
730                                 if(clickfound && node)
731                                 {
732                                         show_timepoint_menu(node, stime, actual_time<stime?SIDE_LEFT:SIDE_RIGHT);
733                                 }
734                         }
735
736                 break;
737         case GDK_MOTION_NOTIFY:
738                 //DEBUGPOINT();
739                 //if(selection && dragging)
740                 //      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();
741                 return true;
742
743                 break;
744         case GDK_BUTTON_RELEASE:
745                 {
746                         DEBUGPOINT();
747
748                         //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();
749                         dragging=false;
750
751                         /*if(event->button.button==3 && selection)
752                         {
753                                 signal_waypoint_clicked_(path,*selected_waypoint,event->button.button-1);
754                                 return true;
755                         }
756                         */
757
758                         //Time point stuff...
759                         if(event->button.button == 1)
760                         {
761                                 bool delmode = (mode & DELETE_MASK) && !(mode & COPY_MASK);
762                                 deltatime = actual_time - actual_dragtime;
763                                 if(sel_times.size() != 0 && (delmode || !deltatime.is_equal(Time(0))))
764                                 {
765                                         synfigapp::Action::ParamList param_list;
766                                         param_list.add("canvas",canvas_interface()->get_canvas());
767                                         param_list.add("canvas_interface",canvas_interface());
768
769                                         if(sel_value.get_value_type() == synfig::ValueBase::TYPE_CANVAS)
770                                         {
771                                                 param_list.add("addcanvas",sel_value.get_value().get(Canvas::Handle()));
772                                         }else
773                                         {
774                                                 param_list.add("addvaluedesc",sel_value);
775                                         }
776
777                                         set<Time>       newset;
778                                         std::set<synfig::Time>::iterator i = sel_times.begin(), end = sel_times.end();
779                                         for(; i != end; ++i)
780                                         {
781                                                 param_list.add("addtime",*i);
782
783                                                 newset.insert((*i + deltatime).round(get_canvas()->rend_desc().get_frame_rate()));
784                                         }
785
786                                         if(!delmode)
787                                                 param_list.add("deltatime",deltatime);
788                                 //      param_list.add("time",canvas_interface()->get_time());
789
790                                         if(mode & COPY_MASK) //copy
791                                         {
792                                                 etl::handle<studio::Instance>::cast_static(canvas_interface()->get_instance())
793                                                         ->process_action("timepoint_copy", param_list);
794                                         }else if(delmode) //DELETE
795                                         {
796                                                 etl::handle<studio::Instance>::cast_static(canvas_interface()->get_instance())
797                                                         ->process_action("timepoint_delete", param_list);
798                                         }else //MOVE
799                                         {
800                                                 etl::handle<studio::Instance>::cast_static(canvas_interface()->get_instance())
801                                                         ->process_action("timepoint_move", param_list);
802                                         }
803
804                                         //now replace all the selected with the new selected
805                                         sel_times = newset;
806                                 }
807                         }
808
809
810
811                         /*if(value_node && selection)
812                         {
813                                 if(selected_time==drag_time && event->button.button!=3)
814                                         signal_waypoint_clicked_(path,*selected_waypoint,event->button.button-1);
815                                 else
816                                 if(event->button.button==1)
817                                 {
818                                         synfig::Waypoint waypoint(*selected_waypoint);
819                                         Time newtime((waypoint.get_time()+(selected_time-drag_time)).round(canvas->rend_desc().get_frame_rate()));
820                                         if(waypoint.get_time()!=newtime)
821                                         {
822                                                 waypoint.set_time(newtime);
823                                                 signal_waypoint_changed_(waypoint,value_node);
824                                         }
825                                 }
826                         }*/
827
828                         //if(selection)
829                         //      selected_time=iter->time;
830                         //selected_time=iter->get_time();
831                         return true;
832                 }
833         default:
834                 //std::cerr<<"unknown event type "<<event->type<<std::endl;
835                 return false;
836                 break;
837         }
838
839
840
841         return false;
842 }
843
844
845
846 Glib::PropertyProxy<synfigapp::ValueDesc>
847 CellRenderer_TimeTrack::property_value_desc()
848 {
849         return Glib::PropertyProxy<synfigapp::ValueDesc>(this,"value_desc");
850 }
851
852 Glib::PropertyProxy<synfig::Canvas::Handle>
853 CellRenderer_TimeTrack::property_canvas()
854 {
855         return Glib::PropertyProxy<synfig::Canvas::Handle>(this,"canvas");
856 }
857
858 Glib::PropertyProxy<Gtk::Adjustment* >
859 CellRenderer_TimeTrack::property_adjustment()
860 {
861         return Glib::PropertyProxy<Gtk::Adjustment* >(this,"adjustment");
862 }
863
864 void
865 CellRenderer_TimeTrack::set_canvas_interface(etl::loose_handle<synfigapp::CanvasInterface>      h)
866 {
867         canvas_interface_ = h;
868 }
869
870 static void
871 set_waypoint_model(std::set<synfig::Waypoint, std::less<UniqueID> > waypoints, Waypoint::Model model, etl::loose_handle<synfigapp::CanvasInterface> canvas_interface)
872 {
873         // Create the action group
874         synfigapp::Action::PassiveGrouper group(canvas_interface->get_instance().get(),_("Change Waypoint Group"));
875
876         std::set<synfig::Waypoint, std::less<UniqueID> >::const_iterator iter;
877         for(iter=waypoints.begin();iter!=waypoints.end();++iter)
878         {
879                 Waypoint waypoint(*iter);
880                 waypoint.apply_model(model);
881
882                 synfigapp::Action::Handle action(synfigapp::Action::create("waypoint_set"));
883
884                 assert(action);
885
886                 action->set_param("canvas",canvas_interface->get_canvas());
887                 action->set_param("canvas_interface",canvas_interface);
888
889                 action->set_param("waypoint",waypoint);
890                 action->set_param("value_node",waypoint.get_parent_value_node());
891
892                 if(!canvas_interface->get_instance()->perform_action(action))
893                 {
894                         group.cancel();
895                         return;
896                 }
897         }
898 }
899
900 void
901 CellRenderer_TimeTrack::show_timepoint_menu(const etl::handle<synfig::Node>& node, const synfig::Time& time, Side side)
902 {
903         std::set<synfig::Waypoint, std::less<UniqueID> > waypoint_set;
904         int n;
905         n=synfig::waypoint_collect(waypoint_set,time,node);
906
907         Gtk::Menu* menu(manage(new Gtk::Menu()));
908
909         // Create the interpolation method menu
910         if(!waypoint_set.empty())
911         {
912                 Gtk::Menu* interp_menu(manage(new Gtk::Menu()));
913                 Waypoint::Model model;
914
915                 if(side==SIDE_LEFT)model.set_before(INTERPOLATION_TCB);
916                 else model.set_after(INTERPOLATION_TCB);
917                 interp_menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("TCB"),
918                         sigc::bind(
919                                 sigc::ptr_fun(set_waypoint_model),
920                                 waypoint_set,
921                                 model,
922                                 canvas_interface()
923                         )
924                 ));
925
926                 if(side==SIDE_LEFT)model.set_before(INTERPOLATION_LINEAR);
927                 else model.set_after(INTERPOLATION_LINEAR);
928                 interp_menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("Linear"),
929                         sigc::bind(
930                                 sigc::ptr_fun(set_waypoint_model),
931                                 waypoint_set,
932                                 model,
933                                 canvas_interface()
934                         )
935                 ));
936
937                 if(side==SIDE_LEFT)model.set_before(INTERPOLATION_HALT);
938                 else model.set_after(INTERPOLATION_HALT);
939                 interp_menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("Ease"),
940                         sigc::bind(
941                                 sigc::ptr_fun(set_waypoint_model),
942                                 waypoint_set,
943                                 model,
944                                 canvas_interface()
945                         )
946                 ));
947
948                 if(side==SIDE_LEFT)model.set_before(INTERPOLATION_CONSTANT);
949                 else model.set_after(INTERPOLATION_CONSTANT);
950                 interp_menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("Constant"),
951                         sigc::bind(
952                                 sigc::ptr_fun(set_waypoint_model),
953                                 waypoint_set,
954                                 model,
955                                 canvas_interface()
956                         )
957                 ));
958
959
960                 menu->items().push_back(
961                         Gtk::Menu_Helpers::MenuElem(
962                                 side==SIDE_LEFT?_("Change \"In\" Interp."):_("Change \"Out\" Interp."),
963                                 *interp_menu
964                         )
965                 );
966         }
967
968         menu->items().push_back(Gtk::Menu_Helpers::StockMenuElem(Gtk::StockID("gtk-jump-to"),
969                 sigc::bind(
970                         sigc::mem_fun(
971                                 *canvas_interface(),
972                                 &synfigapp::CanvasInterface::set_time
973                         ),
974                         time
975                 )
976         ));
977
978         if(!waypoint_set.empty())
979         {
980                 if(waypoint_set.size()==1)
981                 {
982                         delete menu;
983                         menu=0;
984                         signal_waypoint_clicked_(" ",*waypoint_set.begin(),2);
985                         return;
986                 }
987                 else
988                         synfig::info("Too many waypoints under me");
989         }
990         else
991                 synfig::info("ZERO waypoints under me");
992
993         if(menu)menu->popup(3,gtk_get_current_event_time());
994 }