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