c1e6c87dafcc4736895fa30a0f2d7770647974df
[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                 //DEBUGPOINT();
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                         DEBUGPOINT();
782
783                         //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();
784                         dragging=false;
785
786                         /*if(event->button.button==3 && selection)
787                         {
788                                 signal_waypoint_clicked_(path,*selected_waypoint,event->button.button-1);
789                                 return true;
790                         }
791                         */
792
793                         //Time point stuff...
794                         if(event->button.button == 1)
795                         {
796                                 bool delmode = (mode & DELETE_MASK) && !(mode & COPY_MASK);
797                                 deltatime = actual_time - actual_dragtime;
798                                 if(sel_times.size() != 0 && (delmode || !deltatime.is_equal(Time(0))))
799                                 {
800                                         synfigapp::Action::ParamList param_list;
801                                         param_list.add("canvas",canvas_interface()->get_canvas());
802                                         param_list.add("canvas_interface",canvas_interface());
803
804                                         if(sel_value.get_value_type() == synfig::ValueBase::TYPE_CANVAS)
805                                         {
806                                                 param_list.add("addcanvas",sel_value.get_value().get(Canvas::Handle()));
807                                         }else
808                                         {
809                                                 param_list.add("addvaluedesc",sel_value);
810                                         }
811
812                                         set<Time>       newset;
813                                         std::set<synfig::Time>::iterator i = sel_times.begin(), end = sel_times.end();
814                                         for(; i != end; ++i)
815                                         {
816                                                 param_list.add("addtime",*i);
817
818                                                 newset.insert((*i + deltatime).round(get_canvas()->rend_desc().get_frame_rate()));
819                                         }
820
821                                         if(!delmode)
822                                                 param_list.add("deltatime",deltatime);
823                                 //      param_list.add("time",canvas_interface()->get_time());
824
825                                         if(mode & COPY_MASK) //copy
826                                         {
827                                                 etl::handle<studio::Instance>::cast_static(canvas_interface()->get_instance())
828                                                         ->process_action("timepoint_copy", param_list);
829                                         }else if(delmode) //DELETE
830                                         {
831                                                 etl::handle<studio::Instance>::cast_static(canvas_interface()->get_instance())
832                                                         ->process_action("timepoint_delete", param_list);
833                                         }else //MOVE
834                                         {
835                                                 etl::handle<studio::Instance>::cast_static(canvas_interface()->get_instance())
836                                                         ->process_action("timepoint_move", param_list);
837                                         }
838
839                                         //now replace all the selected with the new selected
840                                         sel_times = newset;
841                                 }
842                         }
843
844
845
846                         /*if(value_node && selection)
847                         {
848                                 if(selected_time==drag_time && event->button.button!=3)
849                                         signal_waypoint_clicked_(path,*selected_waypoint,event->button.button-1);
850                                 else
851                                 if(event->button.button==1)
852                                 {
853                                         synfig::Waypoint waypoint(*selected_waypoint);
854                                         Time newtime((waypoint.get_time()+(selected_time-drag_time)).round(canvas->rend_desc().get_frame_rate()));
855                                         if(waypoint.get_time()!=newtime)
856                                         {
857                                                 waypoint.set_time(newtime);
858                                                 signal_waypoint_changed_(waypoint,value_node);
859                                         }
860                                 }
861                         }*/
862
863                         //if(selection)
864                         //      selected_time=iter->time;
865                         //selected_time=iter->get_time();
866                         return true;
867                 }
868         default:
869                 //std::cerr<<"unknown event type "<<event->type<<std::endl;
870                 return false;
871                 break;
872         }
873
874
875
876         return false;
877 }
878
879
880
881 // The following three functions don't get documented correctly by
882 // doxygen 1.5.[23] because of a bug with any function whose name
883 // begins with 'property'.  Fixed in doxygen 1.5.4 apparently.  See
884 // http://bugzilla.gnome.org/show_bug.cgi?id=471185 .
885 Glib::PropertyProxy<synfigapp::ValueDesc>
886 CellRenderer_TimeTrack::property_value_desc()
887 {
888         return Glib::PropertyProxy<synfigapp::ValueDesc>(this,"value_desc");
889 }
890
891 Glib::PropertyProxy<synfig::Canvas::Handle>
892 CellRenderer_TimeTrack::property_canvas()
893 {
894         return Glib::PropertyProxy<synfig::Canvas::Handle>(this,"canvas");
895 }
896
897 Glib::PropertyProxy<Gtk::Adjustment* >
898 CellRenderer_TimeTrack::property_adjustment()
899 {
900         return Glib::PropertyProxy<Gtk::Adjustment* >(this,"adjustment");
901 }
902
903 void
904 CellRenderer_TimeTrack::set_canvas_interface(etl::loose_handle<synfigapp::CanvasInterface>      h)
905 {
906         canvas_interface_ = h;
907 }
908
909 static void
910 set_waypoint_model(std::set<synfig::Waypoint, std::less<UniqueID> > waypoints, Waypoint::Model model, etl::loose_handle<synfigapp::CanvasInterface> canvas_interface)
911 {
912         // Create the action group
913         synfigapp::Action::PassiveGrouper group(canvas_interface->get_instance().get(),_("Change Waypoint Group"));
914
915         std::set<synfig::Waypoint, std::less<UniqueID> >::const_iterator iter;
916         for(iter=waypoints.begin();iter!=waypoints.end();++iter)
917         {
918                 Waypoint waypoint(*iter);
919                 waypoint.apply_model(model);
920
921                 synfigapp::Action::Handle action(synfigapp::Action::create("waypoint_set"));
922
923                 assert(action);
924
925                 action->set_param("canvas",canvas_interface->get_canvas());
926                 action->set_param("canvas_interface",canvas_interface);
927
928                 action->set_param("waypoint",waypoint);
929                 action->set_param("value_node",waypoint.get_parent_value_node());
930
931                 if(!canvas_interface->get_instance()->perform_action(action))
932                 {
933                         group.cancel();
934                         return;
935                 }
936         }
937 }
938
939 void
940 CellRenderer_TimeTrack::show_timepoint_menu(const etl::handle<synfig::Node>& node, const synfig::Time& time, const synfig::Time& time_offset, Side side)
941 {
942         std::set<synfig::Waypoint, std::less<UniqueID> > waypoint_set;
943         int n;
944         n=synfig::waypoint_collect(waypoint_set,time,node);
945
946         Gtk::Menu* menu(manage(new Gtk::Menu()));
947         menu->signal_hide().connect(sigc::bind(sigc::ptr_fun(&delete_widget), menu));
948
949         // Create the interpolation method menu
950         if(!waypoint_set.empty())
951         {
952                 Gtk::Menu* interp_menu(manage(new Gtk::Menu()));
953                 // no need to connect to signal_hide for this one - it will be deleted when its parent is deleted
954                 Waypoint::Model model;
955
956                 // note: each of the following 4 'if' blocks provokes these warnings:
957                 //  /usr/include/sigc++-2.0/sigc++/adaptors/bound_argument.h:57: warning:
958                 //  'model.synfig::Waypoint::Model::temporal_tension' is used uninitialized in this function
959                 //      'model.synfig::Waypoint::Model::bias' is used uninitialized in this function
960                 //      'model.synfig::Waypoint::Model::continuity' is used uninitialized in this function
961                 //      'model.synfig::Waypoint::Model::tension' is used uninitialized in this function
962                 //      'model.synfig::Waypoint::Model::priority' is used uninitialized in this function
963                 // I don't know if that matters or not.
964
965                 if(side==SIDE_LEFT)model.set_before(INTERPOLATION_TCB);
966                 else model.set_after(INTERPOLATION_TCB);
967                 interp_menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("TCB"),
968                         sigc::bind(
969                                 sigc::ptr_fun(set_waypoint_model),
970                                 waypoint_set,
971                                 model,
972                                 canvas_interface()
973                         )
974                 ));
975
976                 if(side==SIDE_LEFT)model.set_before(INTERPOLATION_LINEAR);
977                 else model.set_after(INTERPOLATION_LINEAR);
978                 interp_menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("Linear"),
979                         sigc::bind(
980                                 sigc::ptr_fun(set_waypoint_model),
981                                 waypoint_set,
982                                 model,
983                                 canvas_interface()
984                         )
985                 ));
986
987                 if(side==SIDE_LEFT)model.set_before(INTERPOLATION_HALT);
988                 else model.set_after(INTERPOLATION_HALT);
989                 interp_menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("Ease"),
990                         sigc::bind(
991                                 sigc::ptr_fun(set_waypoint_model),
992                                 waypoint_set,
993                                 model,
994                                 canvas_interface()
995                         )
996                 ));
997
998                 if(side==SIDE_LEFT)model.set_before(INTERPOLATION_CONSTANT);
999                 else model.set_after(INTERPOLATION_CONSTANT);
1000                 interp_menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("Constant"),
1001                         sigc::bind(
1002                                 sigc::ptr_fun(set_waypoint_model),
1003                                 waypoint_set,
1004                                 model,
1005                                 canvas_interface()
1006                         )
1007                 ));
1008
1009
1010                 menu->items().push_back(
1011                         Gtk::Menu_Helpers::MenuElem(
1012                                 side==SIDE_LEFT?_("Change \"In\" Interp."):_("Change \"Out\" Interp."),
1013                                 *interp_menu
1014                         )
1015                 );
1016         }
1017
1018         menu->items().push_back(Gtk::Menu_Helpers::StockMenuElem(Gtk::StockID("gtk-jump-to"),
1019                 sigc::bind(
1020                         sigc::mem_fun(
1021                                 *canvas_interface(),
1022                                 &synfigapp::CanvasInterface::set_time
1023                         ),
1024                         time - time_offset
1025                 )
1026         ));
1027
1028         if(!waypoint_set.empty())
1029         {
1030                 // attempting to locate the valuenode for the clicked waypoint doesn't work if this is a Canvas parameter,
1031                 // so act as if there were multiple waypoints in that case as a workaround
1032                 if(waypoint_set.size()==1 && !Canvas::Handle::cast_dynamic(node))
1033                 {
1034                         delete menu;
1035                         menu=0;
1036                         signal_waypoint_clicked_(" ",*waypoint_set.begin(),2);
1037                         return;
1038                 }
1039                 else
1040                         synfig::info("Too many waypoints under me");
1041         }
1042         else
1043                 synfig::info("ZERO waypoints under me");
1044
1045         if(menu)menu->popup(3,gtk_get_current_event_time());
1046 }