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