Improve the waypoint context menus. Now it's possible to delete and duplicate comple...
[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 "general.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         if (getenv("SYNFIG_DEBUG_DESTRUCTORS"))
94                 synfig::info("CellRenderer_TimeTrack::~CellRenderer_TimeTrack(): Deleted");
95 }
96
97 void
98 CellRenderer_TimeTrack::set_adjustment(Gtk::Adjustment &x)
99 {
100         property_adjustment_=&x;
101 //      x.signal_value_changed().connect(sigc::mem_fun(*this,&Gtk::Widget::queue_draw));
102 }
103
104 synfig::Canvas::Handle
105 CellRenderer_TimeTrack::get_canvas()const
106 {
107         return const_cast<CellRenderer_TimeTrack*>(this)->property_canvas().get_value();
108 }
109
110 Gtk::Adjustment *
111 CellRenderer_TimeTrack::get_adjustment()
112 {
113         return (Gtk::Adjustment*)property_adjustment_;
114 }
115
116 const Gtk::Adjustment *
117 CellRenderer_TimeTrack::get_adjustment()const
118 {
119         return (const Gtk::Adjustment*)property_adjustment_;
120 }
121
122 bool
123 CellRenderer_TimeTrack::is_selected(const Waypoint& waypoint)const
124 {
125         return selected==waypoint;
126 }
127
128 const synfig::Time get_time_offset_from_vdesc(const synfigapp::ValueDesc &v)
129 {
130 #ifdef ADJUST_WAYPOINTS_FOR_TIME_OFFSET
131         if(v.get_value_type() != synfig::ValueBase::TYPE_CANVAS)
132                 return synfig::Time::zero();
133
134         synfig::Canvas::Handle canvasparam = v.get_value().get(Canvas::Handle());
135         if(!canvasparam)
136                 return synfig::Time::zero();
137
138         if (!v.parent_is_layer_param())
139                 return synfig::Time::zero();
140
141         synfig::Layer::Handle layer = v.get_layer();
142
143         if (layer->get_name()!="PasteCanvas")
144                 return synfig::Time::zero();
145
146         return layer->get_param("time_offset").get(Time());
147 #else // ADJUST_WAYPOINTS_FOR_TIME_OFFSET
148         return synfig::Time::zero();
149 #endif
150 }
151
152 //kind of a hack... pointer is ugly
153 const synfig::Node::time_set *get_times_from_vdesc(const synfigapp::ValueDesc &v)
154 {
155         if(v.get_value_type() == synfig::ValueBase::TYPE_CANVAS)
156         {
157                 synfig::Canvas::Handle canvasparam = v.get_value().get(Canvas::Handle());
158
159                 if(canvasparam)
160                         return &canvasparam->get_times();
161         }
162
163         ValueNode *base_value = v.get_value_node().get();
164
165         ValueNode_DynamicList *parent_value_node =
166                         v.parent_is_value_node() ?
167                                 dynamic_cast<ValueNode_DynamicList *>(v.get_parent_value_node().get()) :
168                                 0;
169
170         //we want a dynamic list entry to override the normal...
171         if(parent_value_node)
172         {
173                 return &parent_value_node->list[v.get_index()].get_times();
174         }else if(base_value) //don't render stuff if it's just animated...
175         {
176                 return &base_value->get_times();
177         }
178         return 0;
179 }
180
181 bool get_closest_time(const synfig::Node::time_set &tset, const Time &t, const Time &range, Time &out)
182 {
183         Node::time_set::const_iterator  i,j,end = tset.end();
184
185         // stop the crash mentioned in bug #1689282
186         // doesn't solve the underlying problem though, I don't think
187         if (tset.size() == 0)
188         {
189                 synfig::error(__FILE__":%d: tset.size() == 0",__LINE__);
190                 return false;
191         }
192
193         //TODO add in RangeGet so it's not so damn hard to click on points
194
195         i = tset.upper_bound(t); //where t is the lower bound, t < [first,i)
196         j = i; --j;
197
198         double dist = Time::end();
199         double closest = 0;
200
201         if(i != end)
202         {
203                 closest = i->get_time();
204                 dist = abs(i->get_time() - t);
205         }
206
207         if(j != end && (abs(j->get_time() - t) < dist) )
208         {
209                 closest = j->get_time();
210                 dist = abs(j->get_time() - t);
211         }
212
213         if( dist <= range/2 )
214         {
215                 out = closest;
216                 return true;
217         }
218
219         return false;
220 }
221
222 void
223 CellRenderer_TimeTrack::render_vfunc(
224                 const Glib::RefPtr<Gdk::Drawable>& window,
225                 Gtk::Widget& widget,
226                 const Gdk::Rectangle& /*background_area*/,
227                 const Gdk::Rectangle& area_,
228                 const Gdk::Rectangle& /*expose_area*/,
229                 Gtk::CellRendererState /*flags*/)
230 {
231         if(!window)
232                 return;
233
234         Glib::RefPtr<Gdk::GC> gc(Gdk::GC::create(window));
235         Glib::RefPtr<Gdk::GC> inactive_gc(Gdk::GC::create(window));
236         Gtk::Adjustment *adjustment=get_adjustment();
237         // Gtk::StateType state = Gtk::STATE_ACTIVE;
238         // Gtk::ShadowType shadow;
239
240         Gdk::Color
241                 curr_time_color("#0000ff"),
242                 inactive_color("#000000"),
243                 keyframe_color("#a07f7f");
244         Gdk::Color activepoint_color[2];
245
246         activepoint_color[0]=Gdk::Color("#ff0000");
247         activepoint_color[1]=Gdk::Color("#00ff00");
248
249         inactive_gc->set_rgb_fg_color(inactive_color);
250         inactive_gc->set_stipple(Gdk::Bitmap::create(stipple_xpm,2,2));
251         inactive_gc->set_fill(Gdk::STIPPLED);
252
253         synfig::Canvas::Handle canvas(property_canvas().get_value());
254
255         synfigapp::ValueDesc value_desc = property_value_desc().get_value();
256         synfig::ValueNode *base_value = value_desc.get_value_node().get();
257         // synfig::ValueNode_Animated *value_node=dynamic_cast<synfig::ValueNode_Animated*>(base_value);
258
259         synfig::ValueNode_DynamicList *parent_value_node(0);
260         if(property_value_desc().get_value().parent_is_value_node())
261                 parent_value_node=dynamic_cast<synfig::ValueNode_DynamicList*>(property_value_desc().get_value().get_parent_value_node().get());
262
263         // If the canvas is defined, then load up the keyframes
264         if(canvas)
265         {
266                 const synfig::KeyframeList& keyframe_list(canvas->keyframe_list());
267                 synfig::KeyframeList::const_iterator iter;
268
269                 for(iter=keyframe_list.begin();iter!=keyframe_list.end();++iter)
270                 {
271                         if(!iter->get_time().is_valid())
272                                 continue;
273
274                         const int x((int)((float)area_.get_width()/(adjustment->get_upper()-adjustment->get_lower())*(iter->get_time()-adjustment->get_lower())));
275                         if(iter->get_time()>=adjustment->get_lower() && iter->get_time()<adjustment->get_upper())
276                         {
277                                 gc->set_rgb_fg_color(keyframe_color);
278                                 window->draw_rectangle(gc, true, area_.get_x()+x, area_.get_y(), 1, area_.get_height()+1);
279                         }
280                 }
281         }
282
283         //render all the time points that exist
284         {
285                 const synfig::Node::time_set *tset = get_times_from_vdesc(value_desc);
286
287                 if(tset)
288                 {
289                         const synfig::Time time_offset = get_time_offset_from_vdesc(value_desc);
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_orig = i->get_time();
312                                 if(!t_orig.is_valid()) continue;
313                                 Time t = t_orig - time_offset;
314
315                                 //if it found it... (might want to change comparison, and optimize
316                                 //                                       sel_times.find to not produce an overall nlogn solution)
317
318                                 bool selected=false;
319                                 //not dragging... just draw as per normal
320                                 //if move dragging draw offset
321                                 //if copy dragging draw both...
322
323                                 if(valselected && sel_times.find(t_orig) != sel_times.end())
324                                 {
325                                         if(dragging) //skip if we're dragging because we'll render it later
326                                         {
327                                                 if(mode & COPY_MASK) // draw both blue and red moved
328                                                 {
329                                                         drawredafter.push_back(t + diff.round(cfps));
330                                                         gc->set_rgb_fg_color(Gdk::Color("#00EEEE"));
331                                                 }else if(mode & DELETE_MASK) //it's just red...
332                                                 {
333                                                         gc->set_rgb_fg_color(Gdk::Color("#EE0000"));
334                                                         selected=true;
335                                                 }else //move - draw the red on top of the others...
336                                                 {
337                                                         drawredafter.push_back(t + diff.round(cfps));
338                                                         continue;
339                                                 }
340                                         }else
341                                         {
342                                                 gc->set_rgb_fg_color(Gdk::Color("#EE0000"));
343                                                 selected=true;
344                                         }
345                                 }else
346                                 {
347                                         gc->set_rgb_fg_color(Gdk::Color("#00EEEE"));
348                                 }
349
350                                 //synfig::info("Displaying time: %.3f s",(float)t);
351                                 const int x = (int)((t-lower)*area.get_width()/(upper-lower));
352
353                                 //should draw me a grey filled circle...
354                                 Gdk::Rectangle area2(
355                                         area.get_x() - area.get_height()/2 + x + 1,
356                                         area.get_y() + 1,
357                                         area.get_height()-2,
358                                         area.get_height()-2
359                                 );
360                                 render_time_point_to_window(window,area2,*i - time_offset,selected);
361
362                                 /*window->draw_arc(gc,true,
363                                 area.get_x() + x - area.get_height()/4, area.get_y() + area.get_height()/8,
364                                 area.get_height()/2, area.get_height()*3/4,
365                                 0, 64*360);
366
367                                 gc->set_rgb_fg_color(Gdk::Color("#000000"));
368                                 window->draw_arc(gc,false,
369                                 area.get_x() + x - area.get_height()/4, area.get_y() + area.get_height()/8,
370                                 area.get_height()/2, area.get_height()*3/4,
371                                 0, 64*360);
372                                 */
373                         }
374
375                         {
376                                 vector<Time>::iterator i = drawredafter.begin(), end = drawredafter.end();
377                                 for(; i != end; ++i)
378                                 {
379                                         //find the coordinate in the drawable space...
380                                         Time t = *i;
381
382                                         if(!t.is_valid())
383                                                 continue;
384
385                                         //synfig::info("Displaying time: %.3f s",(float)t);
386                                         const int x = (int)((t-lower)*area.get_width()/(upper-lower));
387
388                                         //should draw me a grey filled circle...
389
390                                         Gdk::Rectangle area2(
391                                                 area.get_x() - area.get_height()/2 + x + 1,
392                                                 area.get_y() + 1,
393                                                 area.get_height()-2,
394                                                 area.get_height()-2
395                                         );
396                                         render_time_point_to_window(window,area2,*i,true);
397 /*                                      gc->set_rgb_fg_color(Gdk::Color("#EE0000"));
398                                         window->draw_arc(gc,true,
399                                         area.get_x() + x - area.get_height()/4, area.get_y() + area.get_height()/8,
400                                         area.get_height()/2, area.get_height()*3/4,
401                                         0, 64*360);
402
403                                         gc->set_rgb_fg_color(Gdk::Color("#000000"));
404                                         window->draw_arc(gc,false,
405                                         area.get_x() + x - area.get_height()/4, area.get_y() + area.get_height()/8,
406                                         area.get_height()/2, area.get_height()*3/4,
407                                         0, 64*360);
408 */
409                                 }
410                         }
411                 }
412         }
413
414         /* THIS IS NOW HANDLED ENTIRELY BY THE TIMEPOINT SYSTEM
415         // This this is an animated value node, then render the waypoints
416         if(value_node)
417         {
418                 //now render the actual waypoints
419                 synfig::ValueNode_Animated::WaypointList::iterator iter;
420                 for(
421                         iter=value_node->waypoint_list().begin();
422                         iter!=value_node->waypoint_list().end();
423                         iter++
424                 )
425                 {
426                         if(!iter->get_time().is_valid())
427                                 continue;
428                         int x;
429                         bool selected=false;
430                         if(is_selected(*iter))
431                         {
432                                 Time t(iter->get_time());
433
434
435                                 if(dragging)
436                                         t=(t+selected_time-drag_time).round(get_canvas()->rend_desc().get_frame_rate());
437
438                                 x=(int)((float)area.get_width()/(adjustment->get_upper()-adjustment->get_lower())*(t-adjustment->get_lower()));
439                                 shadow=Gtk::SHADOW_IN;
440                                 selected=true;
441                         }
442                         else
443                         {
444                                 x=(int)((float)area.get_width()/(adjustment->get_upper()-adjustment->get_lower())*(iter->get_time()-adjustment->get_lower()));
445                                 shadow=Gtk::SHADOW_OUT;
446                                 selected=false;
447                         }
448
449
450                         widget.get_style()->paint_diamond(
451                                 Glib::RefPtr<Gdk::Window>::cast_static(window),
452                                 state,
453                                 shadow,
454                                 area,
455                                 widget,
456                                 "solid",
457                                 area.get_x()+x-area.get_height()/4,
458                                 area.get_y()+area.get_height()/4,
459                                 area.get_height()/2,
460                                 area.get_height()/2
461                         );
462                 }
463         }
464         */
465                 Gdk::Rectangle area(area_);
466         // If the parent of this value node is a dynamic list, then
467         // render the on and off times
468         if(parent_value_node)
469         {
470                 const int index(property_value_desc().get_value().get_index());
471                 const synfig::ValueNode_DynamicList::ListEntry& list_entry(parent_value_node->list[index]);
472                 const synfig::ValueNode_DynamicList::ListEntry::ActivepointList& activepoint_list(list_entry.timing_info);
473                 synfig::ValueNode_DynamicList::ListEntry::ActivepointList::const_iterator iter,next;
474
475                 bool is_off(false);
476                 if(!activepoint_list.empty())
477                         is_off=!activepoint_list.front().state;
478
479                 int xstart(0);
480
481                 int x=0,prevx=0;
482                 for(next=activepoint_list.begin(),iter=next++;iter!=activepoint_list.end();iter=next++)
483                 {
484                         x=((int)((float)area.get_width()/(adjustment->get_upper()-adjustment->get_lower())*(iter->time-adjustment->get_lower())));
485                         if(x<0)x=0;
486                         if(x>area.get_width())x=area.get_width();
487
488                         bool status_at_time=0;
489                         if(next!=activepoint_list.end())
490                         {
491                                 status_at_time=!list_entry.status_at_time((iter->time+next->time)/2.0);
492                         }
493                         else
494                                 status_at_time=!list_entry.status_at_time(Time::end());
495
496                         if(!is_off && status_at_time)
497                         {
498                                 xstart=x;
499                                 is_off=true;
500                         }
501                         else
502                         if(is_off && !status_at_time)
503                         {
504                                 window->draw_rectangle(inactive_gc, true, area.get_x()+xstart, area.get_y(), x-xstart, area.get_height());
505                                 is_off=false;
506                         }
507
508                         /*
509                         if(!is_off && iter!=activepoint_list.end() && next->state==false && iter->state==false)
510                         {
511                                 xstart=x;
512                                 is_off=true;
513                         }
514                         else if(is_off && next!=activepoint_list.end() && iter->state==false && next->state==true)
515                         {
516                                 window->draw_rectangle(inactive_gc, true, area.get_x()+xstart, area.get_y(), x-xstart, area.get_height());
517                                 is_off=false;
518                         }
519                         else if(is_off && iter!=activepoint_list.end() && iter->state==true)
520                         {
521                                 window->draw_rectangle(inactive_gc, true, area.get_x()+xstart, area.get_y(), prevx-xstart, area.get_height());
522                                 is_off=false;
523                         }
524                         */
525
526
527
528                         if(iter->time>=adjustment->get_lower() && iter->time<adjustment->get_upper())
529                         {
530                                 int w(1);
531                                 if(selected==*iter)
532                                         w=3;
533                                 gc->set_rgb_fg_color(activepoint_color[iter->state]);
534                                 window->draw_rectangle(gc, true, area.get_x()+x-w/2, area.get_y(), w, area.get_height());
535                         }
536                         prevx=x;
537                 }
538                 if(is_off)
539                 {
540                         window->draw_rectangle(inactive_gc, true, area.get_x()+xstart, area.get_y(), area.get_width()-xstart, area.get_height());
541                 }
542         }
543
544         // Render a line that defines the current tick in time
545         {
546                 gc->set_rgb_fg_color(curr_time_color);
547
548                 const int x((int)((float)area.get_width()/(adjustment->get_upper()-adjustment->get_lower())*(adjustment->get_value()-adjustment->get_lower())));
549
550                 if(adjustment->get_value()>=adjustment->get_lower() && adjustment->get_value()<adjustment->get_upper())
551                         window->draw_rectangle(gc, true, area.get_x()+x, area.get_y(), 1, area.get_height());
552         }
553 }
554
555 synfig::ValueNode_Animated::WaypointList::iterator
556 CellRenderer_TimeTrack::find_waypoint(const synfig::Time& /*t*/,const synfig::Time& scope)
557 {
558         synfig::ValueNode_Animated *value_node=dynamic_cast<synfig::ValueNode_Animated*>(property_value_desc().get_value().get_value_node().get());
559
560     Time nearest(Time::end());
561
562         synfig::ValueNode_Animated::WaypointList::iterator iter,ret;
563
564         if(value_node)
565         {
566                 for(
567                         iter=value_node->waypoint_list().begin();
568                         iter!=value_node->waypoint_list().end();
569                         iter++
570                         )
571                 {
572                         Time val=abs(iter->get_time()-selected_time);
573                         if(val<nearest)
574                         {
575                                 nearest=val;
576                                 ret=iter;
577                         }
578                 }
579
580                 if(nearest!=Time::end() && nearest<scope)
581                 {
582                         return ret;
583                 }
584         }
585         throw int();
586 }
587
588 bool
589 CellRenderer_TimeTrack::activate_vfunc(
590         GdkEvent* event,
591         Gtk::Widget& /*widget*/,
592         const Glib::ustring& treepath,
593         const Gdk::Rectangle& /*background_area*/,
594         const Gdk::Rectangle& cell_area,
595         Gtk::CellRendererState /*flags*/)
596 {
597         path=treepath;
598         synfig::ValueNode_Animated::WaypointList::iterator iter;
599     Time nearest=1000000000;
600         Gtk::Adjustment *adjustment=get_adjustment();
601
602         // synfig::ValueNode_Animated *value_node=dynamic_cast<synfig::ValueNode_Animated*>(property_value_desc().get_value().get_value_node().get());
603
604         synfig::Canvas::Handle canvas(get_canvas());
605
606         synfig::ValueNode_DynamicList *parent_value_node(0);
607         if(property_value_desc().get_value().parent_is_value_node())
608                 parent_value_node=dynamic_cast<synfig::ValueNode_DynamicList*>(property_value_desc().get_value().get_parent_value_node().get());
609
610         Time deltatime = 0;
611         Time curr_time;
612         switch(event->type)
613         {
614         case GDK_MOTION_NOTIFY:
615                 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();
616
617                 mode = NONE;
618                 {
619                         Gdk::ModifierType mod;
620                         Gdk::Event(event).get_state(mod);
621                         mode = mod;
622                 }
623                 break;
624         case GDK_BUTTON_PRESS:
625         case GDK_BUTTON_RELEASE:
626         default:
627                 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();
628                 {
629                         Gdk::ModifierType mod;
630                         Gdk::Event(event).get_state(mod);
631                         mode = mod;
632                 }
633                 break;
634         }
635         actual_time = curr_time;
636         if(canvas)
637                 curr_time=curr_time.round(canvas->rend_desc().get_frame_rate());
638         selected_time=curr_time;
639
640     Time pixel_width((adjustment->get_upper()-adjustment->get_lower())/cell_area.get_width());
641
642     switch(event->type)
643     {
644         case GDK_BUTTON_PRESS:
645                 //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();
646
647                 //Deal with time point selection, but only if they aren't involved in the insanity...
648                 if(/*!value_node && */event->button.button == 1)
649                 {
650                         Time stime;
651
652                         /*!     UI specification:
653
654                                 When nothing is selected, clicking on a point in either normal mode or
655                                         additive mode will select the time point closest to the click.
656                                         Subtractive click will do nothing
657
658                                 When things are already selected, clicking on a selected point does
659                                         nothing (in both normal and add mode).  Add mode clicking on an unselected
660                                         point adds it to the set.  Normal clicking on an unselected point will
661                                         select only that one time point.  Subtractive clicking on any point
662                                         will remove it from the the set if it is included.
663                         */
664
665                         synfigapp::ValueDesc valdesc = property_value_desc().get_value();
666                         const Node::time_set *tset = get_times_from_vdesc(valdesc);
667                         const synfig::Time time_offset = get_time_offset_from_vdesc(valdesc);
668
669                         bool clickfound = tset && get_closest_time(*tset,actual_time+time_offset,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 should 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                                 synfig::Time time_offset = get_time_offset_from_vdesc(valdesc);
751
752                                 bool clickfound = tset && get_closest_time(*tset,actual_time+time_offset,pixel_width*cell_area.get_height(),stime);
753
754                                 etl::handle<synfig::Node> node;
755                                 if(valdesc.get_value(stime).get_type()==ValueBase::TYPE_CANVAS)
756                                 {
757                                         node=Canvas::Handle(valdesc.get_value(stime).get(Canvas::Handle()));
758                                 }
759                                 else //if(valdesc.is_value_node())
760                                 {
761                                         node=valdesc.get_value_node();
762                                 }
763
764                                 if(clickfound && node)
765                                 {
766                                         show_timepoint_menu(node, stime, time_offset, actual_time+time_offset<stime?Waypoint::SIDE_LEFT:Waypoint::SIDE_RIGHT);
767                                 }
768                         }
769
770                 break;
771         case GDK_MOTION_NOTIFY:
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                         //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();
780                         dragging=false;
781
782                         /*if(event->button.button==3 && selection)
783                         {
784                                 signal_waypoint_clicked_cellrenderer_(path,*selected_waypoint,event->button.button-1);
785                                 return true;
786                         }
787                         */
788
789                         //Time point stuff...
790                         if(event->button.button == 1)
791                         {
792                                 bool delmode = (mode & DELETE_MASK) && !(mode & COPY_MASK);
793                                 deltatime = actual_time - actual_dragtime;
794                                 if(sel_times.size() != 0 && (delmode || !deltatime.is_equal(Time(0))))
795                                 {
796                                         synfigapp::Action::ParamList param_list;
797                                         param_list.add("canvas",canvas_interface()->get_canvas());
798                                         param_list.add("canvas_interface",canvas_interface());
799
800                                         if(sel_value.get_value_type() == synfig::ValueBase::TYPE_CANVAS)
801                                         {
802                                                 param_list.add("addcanvas",sel_value.get_value().get(Canvas::Handle()));
803                                         }else
804                                         {
805                                                 param_list.add("addvaluedesc",sel_value);
806                                         }
807
808                                         set<Time>       newset;
809                                         std::set<synfig::Time>::iterator i = sel_times.begin(), end = sel_times.end();
810                                         for(; i != end; ++i)
811                                         {
812                                                 param_list.add("addtime",*i);
813
814                                                 newset.insert((*i + deltatime).round(get_canvas()->rend_desc().get_frame_rate()));
815                                         }
816
817                                         if(!delmode)
818                                                 param_list.add("deltatime",deltatime);
819                                 //      param_list.add("time",canvas_interface()->get_time());
820
821                                         if(mode & COPY_MASK) //copy
822                                         {
823                                                 etl::handle<studio::Instance>::cast_static(canvas_interface()->get_instance())
824                                                         ->process_action("timepoint_copy", param_list);
825                                         }else if(delmode) //DELETE
826                                         {
827                                                 etl::handle<studio::Instance>::cast_static(canvas_interface()->get_instance())
828                                                         ->process_action("timepoint_delete", param_list);
829                                         }else //MOVE
830                                         {
831                                                 etl::handle<studio::Instance>::cast_static(canvas_interface()->get_instance())
832                                                         ->process_action("timepoint_move", param_list);
833                                         }
834
835                                         //now replace all the selected with the new selected
836                                         sel_times = newset;
837                                 }
838                         }
839
840
841
842                         /*if(value_node && selection)
843                         {
844                                 if(selected_time==drag_time && event->button.button!=3)
845                                         signal_waypoint_clicked_cellrenderer_(path,*selected_waypoint,event->button.button-1);
846                                 else
847                                 if(event->button.button==1)
848                                 {
849                                         synfig::Waypoint waypoint(*selected_waypoint);
850                                         Time newtime((waypoint.get_time()+(selected_time-drag_time)).round(canvas->rend_desc().get_frame_rate()));
851                                         if(waypoint.get_time()!=newtime)
852                                         {
853                                                 waypoint.set_time(newtime);
854                                                 signal_waypoint_changed_(waypoint,value_node);
855                                         }
856                                 }
857                         }*/
858
859                         //if(selection)
860                         //      selected_time=iter->time;
861                         //selected_time=iter->get_time();
862                         return true;
863                 }
864         default:
865                 //std::cerr<<"unknown event type "<<event->type<<std::endl;
866                 return false;
867                 break;
868         }
869
870
871
872         return false;
873 }
874
875
876
877 // The following three functions don't get documented correctly by
878 // doxygen 1.5.[23] because of a bug with any function whose name
879 // begins with 'property'.  Fixed in doxygen 1.5.4 apparently.  See
880 // http://bugzilla.gnome.org/show_bug.cgi?id=471185 .
881 Glib::PropertyProxy<synfigapp::ValueDesc>
882 CellRenderer_TimeTrack::property_value_desc()
883 {
884         return Glib::PropertyProxy<synfigapp::ValueDesc>(this,"value_desc");
885 }
886
887 Glib::PropertyProxy<synfig::Canvas::Handle>
888 CellRenderer_TimeTrack::property_canvas()
889 {
890         return Glib::PropertyProxy<synfig::Canvas::Handle>(this,"canvas");
891 }
892
893 Glib::PropertyProxy<Gtk::Adjustment* >
894 CellRenderer_TimeTrack::property_adjustment()
895 {
896         return Glib::PropertyProxy<Gtk::Adjustment* >(this,"adjustment");
897 }
898
899 void
900 CellRenderer_TimeTrack::set_canvas_interface(etl::loose_handle<synfigapp::CanvasInterface>      h)
901 {
902         canvas_interface_ = h;
903 }
904
905 void
906 CellRenderer_TimeTrack::show_timepoint_menu(const etl::handle<synfig::Node>& node, const synfig::Time& time, const synfig::Time& time_offset, Waypoint::Side side)
907 {
908         signal_waypoint_clicked_cellrenderer_(node,time,time_offset,2,side);
909 }