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