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