Typos.
[synfig.git] / synfig-studio / trunk / src / gtkmm / cellrenderer_timetrack.cpp
1 /* === S Y N F I G ========================================================= */
2 /*!     \file cellrenderer_timetrack.cpp
3 **      \brief Template Header
4 **
5 **      $Id$
6 **
7 **      \legal
8 **      Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
9 **      Copyright (c) 2007 Chris Moore
10 **
11 **      This package is free software; you can redistribute it and/or
12 **      modify it under the terms of the GNU General Public License as
13 **      published by the Free Software Foundation; either version 2 of
14 **      the License, or (at your option) any later version.
15 **
16 **      This package is distributed in the hope that it will be useful,
17 **      but WITHOUT ANY WARRANTY; without even the implied warranty of
18 **      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 **      General Public License for more details.
20 **      \endlegal
21 */
22 /* ========================================================================= */
23
24 /* === H E A D E R S ======================================================= */
25
26 #ifdef USING_PCH
27 #       include "pch.h"
28 #else
29 #ifdef HAVE_CONFIG_H
30 #       include <config.h>
31 #endif
32
33 #include <gtkmm/label.h>
34 #include "cellrenderer_timetrack.h"
35 #include <gtk/gtk.h>
36 #include <gtkmm/spinbutton.h>
37 #include <gtkmm/combo.h>
38 #include <ETL/stringf>
39 #include "widget_value.h"
40 #include "app.h"
41 #include <gtkmm/menu.h>
42 #include <gtkmm/optionmenu.h>
43 #include "widget_time.h"
44 #include "widget_timeslider.h"
45
46 #include <synfigapp/canvasinterface.h>
47 #include "instance.h"
48
49 #include <synfig/timepointcollect.h>
50
51 #endif
52
53 using namespace synfig;
54 using namespace std;
55 using namespace etl;
56 using namespace studio;
57
58 /* === M A C R O S ========================================================= */
59
60 /* === G L O B A L S ======================================================= */
61
62 static char stipple_xpm[] = { 2, 0 };
63
64 //mode for modifier keys
65 enum MODMODE
66 {
67         NONE = 0,
68         SELECT_MASK = Gdk::CONTROL_MASK,
69         COPY_MASK = Gdk::SHIFT_MASK,
70         DELETE_MASK = Gdk::MOD1_MASK
71 };
72
73 /* === P R O C E D U R E S ================================================= */
74
75 /* === M E T H O D S ======================================================= */
76
77 CellRenderer_TimeTrack::CellRenderer_TimeTrack():
78         Glib::ObjectBase        (typeid(CellRenderer_TimeTrack)),
79         Gtk::CellRenderer       (),
80         adjustment_                     (10,10,20,0,0,0),
81
82         property_valuedesc_     (*this,"value_desc",synfigapp::ValueDesc()),
83         property_canvas_        (*this,"canvas",synfig::Canvas::Handle()),
84         property_adjustment_(*this,"adjustment",&adjustment_),
85         property_enable_timing_info_(*this,"enable-timing-info", false)
86 {
87                 dragging=false;
88         selection=false;
89 }
90
91 CellRenderer_TimeTrack::~CellRenderer_TimeTrack()
92 {
93         synfig::info("CellRenderer_TimeTrack::~CellRenderer_TimeTrack(): deleted");
94 }
95
96 void
97 CellRenderer_TimeTrack::set_adjustment(Gtk::Adjustment &x)
98 {
99         property_adjustment_=&x;
100 //      x.signal_value_changed().connect(sigc::mem_fun(*this,&Gtk::Widget::queue_draw));
101 }
102
103 synfig::Canvas::Handle
104 CellRenderer_TimeTrack::get_canvas()const
105 {
106         return const_cast<CellRenderer_TimeTrack*>(this)->property_canvas().get_value();
107 }
108
109 Gtk::Adjustment *
110 CellRenderer_TimeTrack::get_adjustment()
111 {
112         return (Gtk::Adjustment*)property_adjustment_;
113 }
114
115 const Gtk::Adjustment *
116 CellRenderer_TimeTrack::get_adjustment()const
117 {
118         return (const Gtk::Adjustment*)property_adjustment_;
119 }
120
121 bool
122 CellRenderer_TimeTrack::is_selected(const Waypoint& waypoint)const
123 {
124         return selected==waypoint;
125 }
126
127 //kind of a hack... pointer is ugly
128 const synfig::Node::time_set *get_times_from_vdesc(const synfigapp::ValueDesc &v)
129 {
130         if(v.get_value_type() == synfig::ValueBase::TYPE_CANVAS)
131         {
132                 synfig::Canvas::Handle canvasparam = v.get_value().get(Canvas::Handle());
133
134                 if(canvasparam)
135                 {
136 #ifdef ADJUST_WAYPOINTS_FOR_TIME_OFFSET // see node.h
137                         synfig::Time::value_type time_offset = 0;
138                         if (v.parent_is_layer_param())
139                         {
140                                 synfig::Layer::Handle layer = v.get_layer();
141                                 if (layer->get_name()=="PasteCanvas")
142                                         time_offset = layer->get_param("time_offset").get(Time());
143                         }
144
145                         const Node::time_set *times = &canvasparam->get_times();
146
147                         if (time_offset)
148                         {
149                                 //! \todo this is a memory leak - blame the 'kind of hack' above
150                                 Node::time_set *tmp = new Node::time_set;
151                                 Node::time_set::iterator i = times->begin(), end = times->end();
152                                 for (; i != end; ++i)
153                                         tmp->insert(*i - time_offset);
154                                 return tmp;
155                         }
156
157                         return times;
158 #else // ADJUST_WAYPOINTS_FOR_TIME_OFFSET
159                         return &canvasparam->get_times();
160 #endif
161                 }
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                         synfig::Node::time_set::const_iterator  i = tset->begin(), end = tset->end();
291
292                         float   lower = adjustment->get_lower(),
293                                         upper = adjustment->get_upper();
294
295                         Glib::RefPtr<Gdk::GC>   gc = Gdk::GC::create(widget.get_window());
296
297                         Gdk::Rectangle area(area_);
298                         gc->set_clip_rectangle(area);
299                         gc->set_line_attributes(1,Gdk::LINE_SOLID,Gdk::CAP_BUTT,Gdk::JOIN_MITER);
300
301                         bool valselected = sel_value.get_value_node() == base_value && !sel_times.empty();
302
303                         float cfps = get_canvas()->rend_desc().get_frame_rate();
304
305                         vector<Time>    drawredafter;
306
307                         Time diff = actual_time - actual_dragtime;//selected_time-drag_time;
308                         for(; i != end; ++i)
309                         {
310                                 //find the coordinate in the drawable space...
311                                 Time t = i->get_time();
312
313                                 if(!t.is_valid())
314                                         continue;
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) != 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,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
669                         bool clickfound = tset && get_closest_time(*tset,actual_time,pixel_width*cell_area.get_height(),stime);
670                         bool selectmode = mode & SELECT_MASK;
671
672                         //NOTE LATER ON WE SHOULD MAKE IT SO MULTIPLE VALUENODES CAN BE SELECTED AT ONCE
673                         //we want to jump to the value desc if we're not currently on it
674                         //      but only if we want to add the point
675                         if(clickfound && !(sel_value == valdesc))
676                         {
677                                 sel_value = valdesc;
678                                 sel_times.clear();
679                         }
680
681                         //now that we've made sure we're selecting the correct value, deal with the already selected points
682                         set<Time>::iterator foundi = clickfound ? sel_times.find(stime) : sel_times.end();
683                         bool found = foundi != sel_times.end();
684
685                         //remove all other points from our list... (only select the one we need)
686                         if(!selectmode && !found)
687                         {
688                                 sel_times.clear();
689                         }
690
691                         if(found && selectmode) //remove a single already selected point
692                         {
693                                 sel_times.erase(foundi);
694                         }else if(clickfound) //otherwise look at adding it
695                         {
696                                 //for replace the list was cleared earlier, and for add it wasn't so it works
697                                 sel_times.insert(stime);
698                         }
699                 }
700
701                 selection=false;
702                 try
703                 {
704                         iter=find_waypoint(selected_time,pixel_width*cell_area.get_height()/2);
705                         selected_waypoint=iter;
706                         selected=*iter;
707
708                         selection=true;
709                 }
710                 catch(int)
711                 {
712                         selection=false;
713                         selected=synfig::UniqueID::nil();
714                 }
715
716                 if((!sel_times.empty() || selection) && event->button.button==1)
717                 {
718                         dragging=true;
719                         drag_time=selected_time;
720                         actual_dragtime=actual_time;
721                 }
722                 //selected_time=iter->time;
723
724                 /*
725                 // Activepoint Selection
726                 if(parent_value_node)
727                 {
728                         const int index(property_value_desc().get_value().get_index());
729                         const synfig::ValueNode_DynamicList::ListEntry::ActivepointList& activepoint_list(parent_value_node->list[index].timing_info);
730                         synfig::ValueNode_DynamicList::ListEntry::ActivepointList::const_iterator iter;
731
732                         for(iter=activepoint_list.begin();iter!=activepoint_list.end();++iter)
733                         {
734                                 Time val=abs(iter->time-selected_time);
735                                 if(val<nearest)
736                                 {
737                                         nearest=val;
738                                         selected=*iter;
739                                         selection=true;
740                                 }
741                         }
742                         // Perhaps I sould signal if we selected this activepoint?
743                 }*/
744
745                         if(event->button.button==3)
746                         {
747                                 Time stime;
748                                 synfigapp::ValueDesc valdesc = property_value_desc().get_value();
749                                 const Node::time_set *tset = get_times_from_vdesc(valdesc);
750
751                                 bool clickfound = tset && get_closest_time(*tset,actual_time,pixel_width*cell_area.get_height(),stime);
752
753                                 etl::handle<synfig::Node> node;
754                                 if(valdesc.get_value(stime).get_type()==ValueBase::TYPE_CANVAS)
755                                 {
756                                         node=Canvas::Handle(valdesc.get_value(stime).get(Canvas::Handle()));
757                                 }
758                                 else //if(valdesc.is_value_node())
759                                 {
760                                         node=valdesc.get_value_node();
761                                 }
762
763                                 if(clickfound && node)
764                                 {
765                                         show_timepoint_menu(node, stime, actual_time<stime?SIDE_LEFT:SIDE_RIGHT);
766                                 }
767                         }
768
769                 break;
770         case GDK_MOTION_NOTIFY:
771                 //DEBUGPOINT();
772                 //if(selection && dragging)
773                 //      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();
774                 return true;
775
776                 break;
777         case GDK_BUTTON_RELEASE:
778                 {
779                         DEBUGPOINT();
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 Glib::PropertyProxy<synfigapp::ValueDesc>
880 CellRenderer_TimeTrack::property_value_desc()
881 {
882         return Glib::PropertyProxy<synfigapp::ValueDesc>(this,"value_desc");
883 }
884
885 Glib::PropertyProxy<synfig::Canvas::Handle>
886 CellRenderer_TimeTrack::property_canvas()
887 {
888         return Glib::PropertyProxy<synfig::Canvas::Handle>(this,"canvas");
889 }
890
891 Glib::PropertyProxy<Gtk::Adjustment* >
892 CellRenderer_TimeTrack::property_adjustment()
893 {
894         return Glib::PropertyProxy<Gtk::Adjustment* >(this,"adjustment");
895 }
896
897 void
898 CellRenderer_TimeTrack::set_canvas_interface(etl::loose_handle<synfigapp::CanvasInterface>      h)
899 {
900         canvas_interface_ = h;
901 }
902
903 static void
904 set_waypoint_model(std::set<synfig::Waypoint, std::less<UniqueID> > waypoints, Waypoint::Model model, etl::loose_handle<synfigapp::CanvasInterface> canvas_interface)
905 {
906         // Create the action group
907         synfigapp::Action::PassiveGrouper group(canvas_interface->get_instance().get(),_("Change Waypoint Group"));
908
909         std::set<synfig::Waypoint, std::less<UniqueID> >::const_iterator iter;
910         for(iter=waypoints.begin();iter!=waypoints.end();++iter)
911         {
912                 Waypoint waypoint(*iter);
913                 waypoint.apply_model(model);
914
915                 synfigapp::Action::Handle action(synfigapp::Action::create("waypoint_set"));
916
917                 assert(action);
918
919                 action->set_param("canvas",canvas_interface->get_canvas());
920                 action->set_param("canvas_interface",canvas_interface);
921
922                 action->set_param("waypoint",waypoint);
923                 action->set_param("value_node",waypoint.get_parent_value_node());
924
925                 if(!canvas_interface->get_instance()->perform_action(action))
926                 {
927                         group.cancel();
928                         return;
929                 }
930         }
931 }
932
933 void
934 CellRenderer_TimeTrack::show_timepoint_menu(const etl::handle<synfig::Node>& node, const synfig::Time& time, Side side)
935 {
936         std::set<synfig::Waypoint, std::less<UniqueID> > waypoint_set;
937         int n;
938         n=synfig::waypoint_collect(waypoint_set,time,node);
939
940         Gtk::Menu* menu(manage(new Gtk::Menu()));
941
942         // Create the interpolation method menu
943         if(!waypoint_set.empty())
944         {
945                 Gtk::Menu* interp_menu(manage(new Gtk::Menu()));
946                 Waypoint::Model model;
947
948                 // note: each of the following 4 'if' blocks provokes these warnings:
949                 //  /usr/include/sigc++-2.0/sigc++/adaptors/bound_argument.h:57: warning:
950                 //  'model.synfig::Waypoint::Model::temporal_tension' is used uninitialized in this function
951                 //      'model.synfig::Waypoint::Model::bias' is used uninitialized in this function
952                 //      'model.synfig::Waypoint::Model::continuity' is used uninitialized in this function
953                 //      'model.synfig::Waypoint::Model::tension' is used uninitialized in this function
954                 //      'model.synfig::Waypoint::Model::priority' is used uninitialized in this function
955                 // I don't know if that matters or not.
956
957                 if(side==SIDE_LEFT)model.set_before(INTERPOLATION_TCB);
958                 else model.set_after(INTERPOLATION_TCB);
959                 interp_menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("TCB"),
960                         sigc::bind(
961                                 sigc::ptr_fun(set_waypoint_model),
962                                 waypoint_set,
963                                 model,
964                                 canvas_interface()
965                         )
966                 ));
967
968                 if(side==SIDE_LEFT)model.set_before(INTERPOLATION_LINEAR);
969                 else model.set_after(INTERPOLATION_LINEAR);
970                 interp_menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("Linear"),
971                         sigc::bind(
972                                 sigc::ptr_fun(set_waypoint_model),
973                                 waypoint_set,
974                                 model,
975                                 canvas_interface()
976                         )
977                 ));
978
979                 if(side==SIDE_LEFT)model.set_before(INTERPOLATION_HALT);
980                 else model.set_after(INTERPOLATION_HALT);
981                 interp_menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("Ease"),
982                         sigc::bind(
983                                 sigc::ptr_fun(set_waypoint_model),
984                                 waypoint_set,
985                                 model,
986                                 canvas_interface()
987                         )
988                 ));
989
990                 if(side==SIDE_LEFT)model.set_before(INTERPOLATION_CONSTANT);
991                 else model.set_after(INTERPOLATION_CONSTANT);
992                 interp_menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("Constant"),
993                         sigc::bind(
994                                 sigc::ptr_fun(set_waypoint_model),
995                                 waypoint_set,
996                                 model,
997                                 canvas_interface()
998                         )
999                 ));
1000
1001
1002                 menu->items().push_back(
1003                         Gtk::Menu_Helpers::MenuElem(
1004                                 side==SIDE_LEFT?_("Change \"In\" Interp."):_("Change \"Out\" Interp."),
1005                                 *interp_menu
1006                         )
1007                 );
1008         }
1009
1010         menu->items().push_back(Gtk::Menu_Helpers::StockMenuElem(Gtk::StockID("gtk-jump-to"),
1011                 sigc::bind(
1012                         sigc::mem_fun(
1013                                 *canvas_interface(),
1014                                 &synfigapp::CanvasInterface::set_time
1015                         ),
1016                         time
1017                 )
1018         ));
1019
1020         if(!waypoint_set.empty())
1021         {
1022                 if(waypoint_set.size()==1)
1023                 {
1024                         delete menu;
1025                         menu=0;
1026                         signal_waypoint_clicked_(" ",*waypoint_set.begin(),2);
1027                         return;
1028                 }
1029                 else
1030                         synfig::info("Too many waypoints under me");
1031         }
1032         else
1033                 synfig::info("ZERO waypoints under me");
1034
1035         if(menu)menu->popup(3,gtk_get_current_event_time());
1036 }