dc9717f50c3a1f45911212390316e23be11617d8
[synfig.git] / synfig-studio / 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, 2008 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 <gtkmm/spinbutton.h>
36 #include <gtkmm/combo.h>
37 #include <ETL/stringf>
38 #include "widget_value.h"
39 #include "app.h"
40 #include <gtkmm/menu.h>
41 #include <gtkmm/optionmenu.h>
42 #include "widget_time.h"
43 #include "widget_timeslider.h"
44
45 #include <synfigapp/canvasinterface.h>
46 #include "instance.h"
47
48 #include "general.h"
49
50 #endif
51
52 using namespace synfig;
53 using namespace std;
54 using namespace etl;
55 using namespace studio;
56
57 /* === M A C R O S ========================================================= */
58
59 /* === G L O B A L S ======================================================= */
60
61 static char stipple_xpm[] = { 2, 0 };
62
63 //mode for modifier keys
64 enum MODMODE
65 {
66         NONE = 0,
67         SELECT_MASK = Gdk::CONTROL_MASK,
68         COPY_MASK = Gdk::SHIFT_MASK,
69         DELETE_MASK = Gdk::MOD1_MASK
70 };
71
72 /* === P R O C E D U R E S ================================================= */
73
74 /* === M E T H O D S ======================================================= */
75
76 CellRenderer_TimeTrack::CellRenderer_TimeTrack():
77         Glib::ObjectBase        (typeid(CellRenderer_TimeTrack)),
78         Gtk::CellRenderer       (),
79         adjustment_                     (10,10,20,0,0,0),
80
81         property_valuedesc_     (*this,"value_desc",synfigapp::ValueDesc()),
82         property_canvas_        (*this,"canvas",synfig::Canvas::Handle()),
83         property_adjustment_(*this,"adjustment",&adjustment_),
84         property_enable_timing_info_(*this,"enable-timing-info", false)
85 {
86         dragging=false;
87         selection=false;
88 }
89
90 CellRenderer_TimeTrack::~CellRenderer_TimeTrack()
91 {
92         if (getenv("SYNFIG_DEBUG_DESTRUCTORS"))
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 const synfig::Time get_time_offset_from_vdesc(const synfigapp::ValueDesc &v)
128 {
129 #ifdef ADJUST_WAYPOINTS_FOR_TIME_OFFSET
130         if(getenv("SYNFIG_SHOW_CANVAS_PARAM_WAYPOINTS") ||
131            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(!getenv("SYNFIG_SHOW_CANVAS_PARAM_WAYPOINTS") &&
156            v.get_value_type() == synfig::ValueBase::TYPE_CANVAS)
157         {
158                 synfig::Canvas::Handle canvasparam = v.get_value().get(Canvas::Handle());
159
160                 if(canvasparam)
161                         return &canvasparam->get_times();
162         }
163
164         ValueNode *base_value = v.get_value_node().get();
165
166         ValueNode_DynamicList *parent_value_node =
167                         v.parent_is_value_node() ?
168                                 dynamic_cast<ValueNode_DynamicList *>(v.get_parent_value_node().get()) :
169                                 0;
170
171         //we want a dynamic list entry to override the normal...
172         if(parent_value_node)
173         {
174                 return &parent_value_node->list[v.get_index()].get_times();
175         }else if(base_value) //don't render stuff if it's just animated...
176         {
177                 return &base_value->get_times();
178         }
179         return 0;
180 }
181
182 bool get_closest_time(const synfig::Node::time_set &tset, const Time &t, const Time &range, Time &out)
183 {
184         Node::time_set::const_iterator  i,j,end = tset.end();
185
186         // stop the crash mentioned in bug #1689282
187         // doesn't solve the underlying problem though, I don't think
188         if (tset.size() == 0)
189         {
190                 synfig::error(__FILE__":%d: tset.size() == 0",__LINE__);
191                 return false;
192         }
193
194         //TODO add in RangeGet so it's not so damn hard to click on points
195
196         i = tset.upper_bound(t); //where t is the lower bound, t < [first,i)
197         j = i; --j;
198
199         double dist = Time::end();
200         double closest = 0;
201
202         if(i != end)
203         {
204                 closest = i->get_time();
205                 dist = abs(i->get_time() - t);
206         }
207
208         if(j != end && (abs(j->get_time() - t) < dist) )
209         {
210                 closest = j->get_time();
211                 dist = abs(j->get_time() - t);
212         }
213
214         if( dist <= range/2 )
215         {
216                 out = closest;
217                 return true;
218         }
219
220         return false;
221 }
222
223 void
224 CellRenderer_TimeTrack::render_vfunc(
225                 const Glib::RefPtr<Gdk::Drawable>& window,
226                 Gtk::Widget& widget,
227                 const Gdk::Rectangle& /*background_area*/,
228                 const Gdk::Rectangle& area_,
229                 const Gdk::Rectangle& /*expose_area*/,
230                 Gtk::CellRendererState /*flags*/)
231 {
232         if(!window)
233                 return;
234
235         Glib::RefPtr<Gdk::GC> gc(Gdk::GC::create(window));
236         Glib::RefPtr<Gdk::GC> inactive_gc(Gdk::GC::create(window));
237         Gtk::Adjustment *adjustment=get_adjustment();
238         // Gtk::StateType state = Gtk::STATE_ACTIVE;
239         // Gtk::ShadowType shadow;
240
241         Gdk::Color
242                 curr_time_color("#0000ff"),
243                 inactive_color("#000000"),
244                 keyframe_color("#a07f7f");
245         Gdk::Color activepoint_color[2];
246
247         activepoint_color[0]=Gdk::Color("#ff0000");
248         activepoint_color[1]=Gdk::Color("#00ff00");
249
250         inactive_gc->set_rgb_fg_color(inactive_color);
251         inactive_gc->set_stipple(Gdk::Bitmap::create(stipple_xpm,2,2));
252         inactive_gc->set_fill(Gdk::STIPPLED);
253
254         synfig::Canvas::Handle canvas(property_canvas().get_value());
255
256         synfigapp::ValueDesc value_desc = property_value_desc().get_value();
257         synfig::ValueNode *base_value = value_desc.get_value_node().get();
258         // synfig::ValueNode_Animated *value_node=dynamic_cast<synfig::ValueNode_Animated*>(base_value);
259
260         synfig::ValueNode_DynamicList *parent_value_node(0);
261         if(property_value_desc().get_value().parent_is_value_node())
262                 parent_value_node=dynamic_cast<synfig::ValueNode_DynamicList*>(property_value_desc().get_value().get_parent_value_node().get());
263
264         // If the canvas is defined, then load up the keyframes
265         if(canvas)
266         {
267                 const synfig::KeyframeList& keyframe_list(canvas->keyframe_list());
268                 synfig::KeyframeList::const_iterator iter;
269
270                 for(iter=keyframe_list.begin();iter!=keyframe_list.end();++iter)
271                 {
272                         if(!iter->get_time().is_valid())
273                                 continue;
274
275                         const int x((int)((float)area_.get_width()/(adjustment->get_upper()-adjustment->get_lower())*(iter->get_time()-adjustment->get_lower())));
276                         if(iter->get_time()>=adjustment->get_lower() && iter->get_time()<adjustment->get_upper())
277                         {
278                                 gc->set_rgb_fg_color(keyframe_color);
279                                 window->draw_rectangle(gc, true, area_.get_x()+x, area_.get_y(), 1, area_.get_height()+1);
280                         }
281                 }
282         }
283
284         //render all the time points that exist
285         {
286                 const synfig::Node::time_set *tset = get_times_from_vdesc(value_desc);
287
288                 if(tset)
289                 {
290                         const synfig::Time time_offset = get_time_offset_from_vdesc(value_desc);
291                         synfig::Node::time_set::const_iterator  i = tset->begin(), end = tset->end();
292
293                         float   lower = adjustment->get_lower(),
294                                         upper = adjustment->get_upper();
295
296                         Glib::RefPtr<Gdk::GC>   gc = Gdk::GC::create(widget.get_window());
297
298                         Gdk::Rectangle area(area_);
299                         gc->set_clip_rectangle(area);
300                         gc->set_line_attributes(1,Gdk::LINE_SOLID,Gdk::CAP_BUTT,Gdk::JOIN_MITER);
301
302                         bool valselected = sel_value.get_value_node() == base_value && !sel_times.empty();
303
304                         float cfps = get_canvas()->rend_desc().get_frame_rate();
305
306                         vector<Time>    drawredafter;
307
308                         Time diff = actual_time - actual_dragtime;//selected_time-drag_time;
309                         for(; i != end; ++i)
310                         {
311                                 //find the coordinate in the drawable space...
312                                 Time t_orig = i->get_time();
313                                 if(!t_orig.is_valid()) continue;
314                                 Time t = t_orig - time_offset;
315                                 if(t<adjustment->get_lower() || t>adjustment->get_upper()) continue;
316
317                                 //if it found it... (might want to change comparison, and optimize
318                                 //                                       sel_times.find to not produce an overall nlogn solution)
319
320                                 bool selected=false;
321                                 //not dragging... just draw as per normal
322                                 //if move dragging draw offset
323                                 //if copy dragging draw both...
324
325                                 if(valselected && sel_times.find(t_orig) != sel_times.end())
326                                 {
327                                         if(dragging) //skip if we're dragging because we'll render it later
328                                         {
329                                                 if(mode & COPY_MASK) // draw both blue and red moved
330                                                 {
331                                                         drawredafter.push_back(t + diff.round(cfps));
332                                                         gc->set_rgb_fg_color(Gdk::Color("#00EEEE"));
333                                                 }else if(mode & DELETE_MASK) //it's just red...
334                                                 {
335                                                         gc->set_rgb_fg_color(Gdk::Color("#EE0000"));
336                                                         selected=true;
337                                                 }else //move - draw the red on top of the others...
338                                                 {
339                                                         drawredafter.push_back(t + diff.round(cfps));
340                                                         continue;
341                                                 }
342                                         }else
343                                         {
344                                                 gc->set_rgb_fg_color(Gdk::Color("#EE0000"));
345                                                 selected=true;
346                                         }
347                                 }else
348                                 {
349                                         gc->set_rgb_fg_color(Gdk::Color("#00EEEE"));
350                                 }
351
352                                 //synfig::info("Displaying time: %.3f s",(float)t);
353                                 const int x = (int)((t-lower)*area.get_width()/(upper-lower));
354
355                                 //should draw me a grey filled circle...
356                                 Gdk::Rectangle area2(
357                                         area.get_x() - area.get_height()/2 + x + 1,
358                                         area.get_y() + 1,
359                                         area.get_height()-2,
360                                         area.get_height()-2
361                                 );
362                                 render_time_point_to_window(window,area2,*i - time_offset,selected);
363
364                                 /*window->draw_arc(gc,true,
365                                 area.get_x() + x - area.get_height()/4, area.get_y() + area.get_height()/8,
366                                 area.get_height()/2, area.get_height()*3/4,
367                                 0, 64*360);
368
369                                 gc->set_rgb_fg_color(Gdk::Color("#000000"));
370                                 window->draw_arc(gc,false,
371                                 area.get_x() + x - area.get_height()/4, area.get_y() + area.get_height()/8,
372                                 area.get_height()/2, area.get_height()*3/4,
373                                 0, 64*360);
374                                 */
375                         }
376
377                         {
378                                 vector<Time>::iterator i = drawredafter.begin(), end = drawredafter.end();
379                                 for(; i != end; ++i)
380                                 {
381                                         //find the coordinate in the drawable space...
382                                         Time t = *i;
383
384                                         if(!t.is_valid())
385                                                 continue;
386
387                                         //synfig::info("Displaying time: %.3f s",(float)t);
388                                         const int x = (int)((t-lower)*area.get_width()/(upper-lower));
389
390                                         //should draw me a grey filled circle...
391
392                                         Gdk::Rectangle area2(
393                                                 area.get_x() - area.get_height()/2 + x + 1,
394                                                 area.get_y() + 1,
395                                                 area.get_height()-2,
396                                                 area.get_height()-2
397                                         );
398                                         render_time_point_to_window(window,area2,*i,true);
399 /*                                      gc->set_rgb_fg_color(Gdk::Color("#EE0000"));
400                                         window->draw_arc(gc,true,
401                                         area.get_x() + x - area.get_height()/4, area.get_y() + area.get_height()/8,
402                                         area.get_height()/2, area.get_height()*3/4,
403                                         0, 64*360);
404
405                                         gc->set_rgb_fg_color(Gdk::Color("#000000"));
406                                         window->draw_arc(gc,false,
407                                         area.get_x() + x - area.get_height()/4, area.get_y() + area.get_height()/8,
408                                         area.get_height()/2, area.get_height()*3/4,
409                                         0, 64*360);
410 */
411                                 }
412                         }
413                 }
414         }
415
416         /* THIS IS NOW HANDLED ENTIRELY BY THE TIMEPOINT SYSTEM
417         // This this is an animated value node, then render the waypoints
418         if(value_node)
419         {
420                 //now render the actual waypoints
421                 synfig::ValueNode_Animated::WaypointList::iterator iter;
422                 for(
423                         iter=value_node->waypoint_list().begin();
424                         iter!=value_node->waypoint_list().end();
425                         iter++
426                 )
427                 {
428                         if(!iter->get_time().is_valid())
429                                 continue;
430                         int x;
431                         bool selected=false;
432                         if(is_selected(*iter))
433                         {
434                                 Time t(iter->get_time());
435
436
437                                 if(dragging)
438                                         t=(t+selected_time-drag_time).round(get_canvas()->rend_desc().get_frame_rate());
439
440                                 x=(int)((float)area.get_width()/(adjustment->get_upper()-adjustment->get_lower())*(t-adjustment->get_lower()));
441                                 shadow=Gtk::SHADOW_IN;
442                                 selected=true;
443                         }
444                         else
445                         {
446                                 x=(int)((float)area.get_width()/(adjustment->get_upper()-adjustment->get_lower())*(iter->get_time()-adjustment->get_lower()));
447                                 shadow=Gtk::SHADOW_OUT;
448                                 selected=false;
449                         }
450
451
452                         widget.get_style()->paint_diamond(
453                                 Glib::RefPtr<Gdk::Window>::cast_static(window),
454                                 state,
455                                 shadow,
456                                 area,
457                                 widget,
458                                 "solid",
459                                 area.get_x()+x-area.get_height()/4,
460                                 area.get_y()+area.get_height()/4,
461                                 area.get_height()/2,
462                                 area.get_height()/2
463                         );
464                 }
465         }
466         */
467                 Gdk::Rectangle area(area_);
468         // If the parent of this value node is a dynamic list, then
469         // render the on and off times
470         if(parent_value_node)
471         {
472                 const int index(property_value_desc().get_value().get_index());
473                 const synfig::ValueNode_DynamicList::ListEntry& list_entry(parent_value_node->list[index]);
474                 const synfig::ValueNode_DynamicList::ListEntry::ActivepointList& activepoint_list(list_entry.timing_info);
475                 synfig::ValueNode_DynamicList::ListEntry::ActivepointList::const_iterator iter,next;
476
477                 bool is_off(false);
478                 if(!activepoint_list.empty())
479                         is_off=!activepoint_list.front().state;
480
481                 int xstart(0);
482
483                 int x=0,prevx=0;
484                 for(next=activepoint_list.begin(),iter=next++;iter!=activepoint_list.end();iter=next++)
485                 {
486                         x=((int)((float)area.get_width()/(adjustment->get_upper()-adjustment->get_lower())*(iter->time-adjustment->get_lower())));
487                         if(x<0)x=0;
488                         if(x>area.get_width())x=area.get_width();
489
490                         bool status_at_time=0;
491                         if(next!=activepoint_list.end())
492                         {
493                                 status_at_time=!list_entry.status_at_time((iter->time+next->time)/2.0);
494                         }
495                         else
496                                 status_at_time=!list_entry.status_at_time(Time::end());
497
498                         if(!is_off && status_at_time)
499                         {
500                                 xstart=x;
501                                 is_off=true;
502                         }
503                         else
504                         if(is_off && !status_at_time)
505                         {
506                                 window->draw_rectangle(inactive_gc, true, area.get_x()+xstart, area.get_y(), x-xstart, area.get_height());
507                                 is_off=false;
508                         }
509
510                         /*
511                         if(!is_off && iter!=activepoint_list.end() && next->state==false && iter->state==false)
512                         {
513                                 xstart=x;
514                                 is_off=true;
515                         }
516                         else if(is_off && next!=activepoint_list.end() && iter->state==false && next->state==true)
517                         {
518                                 window->draw_rectangle(inactive_gc, true, area.get_x()+xstart, area.get_y(), x-xstart, area.get_height());
519                                 is_off=false;
520                         }
521                         else if(is_off && iter!=activepoint_list.end() && iter->state==true)
522                         {
523                                 window->draw_rectangle(inactive_gc, true, area.get_x()+xstart, area.get_y(), prevx-xstart, area.get_height());
524                                 is_off=false;
525                         }
526                         */
527
528
529
530                         if(iter->time>=adjustment->get_lower() && iter->time<adjustment->get_upper())
531                         {
532                                 int w(1);
533                                 if(selected==*iter)
534                                         w=3;
535                                 gc->set_rgb_fg_color(activepoint_color[iter->state]);
536                                 window->draw_rectangle(gc, true, area.get_x()+x-w/2, area.get_y(), w, area.get_height());
537                         }
538                         prevx=x;
539                 }
540                 if(is_off)
541                 {
542                         window->draw_rectangle(inactive_gc, true, area.get_x()+xstart, area.get_y(), area.get_width()-xstart, area.get_height());
543                 }
544         }
545
546         // Render a line that defines the current tick in time
547         {
548                 gc->set_rgb_fg_color(curr_time_color);
549
550                 const int x((int)((float)area.get_width()/(adjustment->get_upper()-adjustment->get_lower())*(adjustment->get_value()-adjustment->get_lower())));
551
552                 if(adjustment->get_value()>=adjustment->get_lower() && adjustment->get_value()<adjustment->get_upper())
553                         window->draw_rectangle(gc, true, area.get_x()+x, area.get_y(), 1, area.get_height());
554         }
555 }
556
557 synfig::ValueNode_Animated::WaypointList::iterator
558 CellRenderer_TimeTrack::find_waypoint(const synfig::Time& /*t*/,const synfig::Time& scope)
559 {
560         synfig::ValueNode_Animated *value_node=dynamic_cast<synfig::ValueNode_Animated*>(property_value_desc().get_value().get_value_node().get());
561
562     Time nearest(Time::end());
563
564         synfig::ValueNode_Animated::WaypointList::iterator iter,ret;
565
566         if(value_node)
567         {
568                 for(
569                         iter=value_node->waypoint_list().begin();
570                         iter!=value_node->waypoint_list().end();
571                         iter++
572                         )
573                 {
574                         Time val=abs(iter->get_time()-selected_time);
575                         if(val<nearest)
576                         {
577                                 nearest=val;
578                                 ret=iter;
579                         }
580                 }
581
582                 if(nearest!=Time::end() && nearest<scope)
583                 {
584                         return ret;
585                 }
586         }
587         throw int();
588 }
589
590 bool
591 CellRenderer_TimeTrack::activate_vfunc(
592         GdkEvent* event,
593         Gtk::Widget& /*widget*/,
594         const Glib::ustring& treepath,
595         const Gdk::Rectangle& /*background_area*/,
596         const Gdk::Rectangle& cell_area,
597         Gtk::CellRendererState /*flags*/)
598 {
599         path=treepath;
600         synfig::ValueNode_Animated::WaypointList::iterator iter;
601     Time nearest=1000000000;
602         Gtk::Adjustment *adjustment=get_adjustment();
603
604         // synfig::ValueNode_Animated *value_node=dynamic_cast<synfig::ValueNode_Animated*>(property_value_desc().get_value().get_value_node().get());
605
606         synfig::Canvas::Handle canvas(get_canvas());
607
608         synfig::ValueNode_DynamicList *parent_value_node(0);
609         if(property_value_desc().get_value().parent_is_value_node())
610                 parent_value_node=dynamic_cast<synfig::ValueNode_DynamicList*>(property_value_desc().get_value().get_parent_value_node().get());
611
612         Time deltatime = 0;
613         Time curr_time;
614         switch(event->type)
615         {
616         case GDK_MOTION_NOTIFY:
617                 curr_time=((float)event->motion.x-(float)cell_area.get_x())/(float)cell_area.get_width()*(adjustment->get_upper()-adjustment->get_lower())+adjustment->get_lower();
618
619                 mode = NONE;
620                 {
621                         Gdk::ModifierType mod;
622                         Gdk::Event(event).get_state(mod);
623                         mode = mod;
624                 }
625                 break;
626         case GDK_BUTTON_PRESS:
627         case GDK_BUTTON_RELEASE:
628         default:
629                 curr_time=((float)event->button.x-(float)cell_area.get_x())/(float)cell_area.get_width()*(adjustment->get_upper()-adjustment->get_lower())+adjustment->get_lower();
630                 {
631                         Gdk::ModifierType mod;
632                         Gdk::Event(event).get_state(mod);
633                         mode = mod;
634                 }
635                 break;
636         }
637         actual_time = curr_time;
638         if(canvas)
639                 curr_time=curr_time.round(canvas->rend_desc().get_frame_rate());
640         selected_time=curr_time;
641
642     Time pixel_width((adjustment->get_upper()-adjustment->get_lower())/cell_area.get_width());
643
644     switch(event->type)
645     {
646         case GDK_BUTTON_PRESS:
647                 //selected_time=((float)event->button.x-(float)cell_area.get_x())/(float)cell_area.get_width()*(adjustment->get_upper()-adjustment->get_lower())+adjustment->get_lower();
648
649                 //Deal with time point selection, but only if they aren't involved in the insanity...
650                 if(/*!value_node && */event->button.button == 1)
651                 {
652                         Time stime;
653
654                         /*!     UI specification:
655
656                                 When nothing is selected, clicking on a point in either normal mode or
657                                         additive mode will select the time point closest to the click.
658                                         Subtractive click will do nothing
659
660                                 When things are already selected, clicking on a selected point does
661                                         nothing (in both normal and add mode).  Add mode clicking on an unselected
662                                         point adds it to the set.  Normal clicking on an unselected point will
663                                         select only that one time point.  Subtractive clicking on any point
664                                         will remove it from the the set if it is included.
665                         */
666
667                         synfigapp::ValueDesc valdesc = property_value_desc().get_value();
668                         const Node::time_set *tset = get_times_from_vdesc(valdesc);
669                         const synfig::Time time_offset = get_time_offset_from_vdesc(valdesc);
670
671                         bool clickfound = tset && get_closest_time(*tset,actual_time+time_offset,pixel_width*cell_area.get_height(),stime);
672                         bool selectmode = mode & SELECT_MASK;
673
674                         //NOTE LATER ON WE SHOULD MAKE IT SO MULTIPLE VALUENODES CAN BE SELECTED AT ONCE
675                         //we want to jump to the value desc if we're not currently on it
676                         //      but only if we want to add the point
677                         if(clickfound && !(sel_value == valdesc))
678                         {
679                                 sel_value = valdesc;
680                                 sel_times.clear();
681                         }
682
683                         //now that we've made sure we're selecting the correct value, deal with the already selected points
684                         set<Time>::iterator foundi = clickfound ? sel_times.find(stime) : sel_times.end();
685                         bool found = foundi != sel_times.end();
686
687                         //remove all other points from our list... (only select the one we need)
688                         if(!selectmode && !found)
689                         {
690                                 sel_times.clear();
691                         }
692
693                         if(found && selectmode) //remove a single already selected point
694                         {
695                                 sel_times.erase(foundi);
696                         }else if(clickfound) //otherwise look at adding it
697                         {
698                                 //for replace the list was cleared earlier, and for add it wasn't so it works
699                                 sel_times.insert(stime);
700                         }
701                 }
702
703                 selection=false;
704                 try
705                 {
706                         iter=find_waypoint(selected_time,pixel_width*cell_area.get_height()/2);
707                         selected_waypoint=iter;
708                         selected=*iter;
709
710                         selection=true;
711                 }
712                 catch(int)
713                 {
714                         selection=false;
715                         selected=synfig::UniqueID::nil();
716                 }
717
718                 if((!sel_times.empty() || selection) && event->button.button==1)
719                 {
720                         dragging=true;
721                         drag_time=selected_time;
722                         actual_dragtime=actual_time;
723                 }
724                 //selected_time=iter->time;
725
726                 /*
727                 // Activepoint Selection
728                 if(parent_value_node)
729                 {
730                         const int index(property_value_desc().get_value().get_index());
731                         const synfig::ValueNode_DynamicList::ListEntry::ActivepointList& activepoint_list(parent_value_node->list[index].timing_info);
732                         synfig::ValueNode_DynamicList::ListEntry::ActivepointList::const_iterator iter;
733
734                         for(iter=activepoint_list.begin();iter!=activepoint_list.end();++iter)
735                         {
736                                 Time val=abs(iter->time-selected_time);
737                                 if(val<nearest)
738                                 {
739                                         nearest=val;
740                                         selected=*iter;
741                                         selection=true;
742                                 }
743                         }
744                         // Perhaps I should signal if we selected this activepoint?
745                 }*/
746
747                         if(event->button.button==3)
748                         {
749                                 Time stime;
750                                 synfigapp::ValueDesc valdesc = property_value_desc().get_value();
751                                 const Node::time_set *tset = get_times_from_vdesc(valdesc);
752                                 synfig::Time time_offset = get_time_offset_from_vdesc(valdesc);
753
754                                 bool clickfound = tset && get_closest_time(*tset,actual_time+time_offset,pixel_width*cell_area.get_height(),stime);
755
756                                 etl::handle<synfig::Node> node;
757                                 if(!getenv("SYNFIG_SHOW_CANVAS_PARAM_WAYPOINTS") &&
758                                    valdesc.get_value(stime).get_type()==ValueBase::TYPE_CANVAS)
759                                 {
760                                         node=Canvas::Handle(valdesc.get_value(stime).get(Canvas::Handle()));
761                                 }
762                                 else //if(valdesc.is_value_node())
763                                 {
764                                         node=valdesc.get_value_node();
765                                 }
766
767                                 if(clickfound && node)
768                                         signal_waypoint_clicked_cellrenderer()(node, stime, time_offset, 2);
769                         }
770
771                 break;
772         case GDK_MOTION_NOTIFY:
773                 //if(selection && dragging)
774                 //      selected_time=((float)event->motion.x-(float)cell_area.get_x())/(float)cell_area.get_width()*(adjustment->get_upper()-adjustment->get_lower())+adjustment->get_lower();
775                 return true;
776
777                 break;
778         case GDK_BUTTON_RELEASE:
779                 {
780                         //selected_time=((float)event->button.x-(float)cell_area.get_x())/(float)cell_area.get_width()*(adjustment->get_upper()-adjustment->get_lower())+adjustment->get_lower();
781                         dragging=false;
782
783                         /*if(event->button.button==3 && selection)
784                         {
785                                 signal_waypoint_clicked_cellrenderer()(path,*selected_waypoint,event->button.button-1);
786                                 return true;
787                         }
788                         */
789
790                         //Time point stuff...
791                         if(event->button.button == 1)
792                         {
793                                 bool delmode = (mode & DELETE_MASK) && !(mode & COPY_MASK);
794                                 deltatime = actual_time - actual_dragtime;
795                                 if(sel_times.size() != 0 && (delmode || !deltatime.is_equal(Time(0))))
796                                 {
797                                         synfigapp::Action::ParamList param_list;
798                                         param_list.add("canvas",canvas_interface()->get_canvas());
799                                         param_list.add("canvas_interface",canvas_interface());
800
801                                         if(!getenv("SYNFIG_SHOW_CANVAS_PARAM_WAYPOINTS") &&
802                                            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("TimepointsCopy", param_list);
827                                         }else if(delmode) //DELETE
828                                         {
829                                                 etl::handle<studio::Instance>::cast_static(canvas_interface()->get_instance())
830                                                         ->process_action("TimepointsDelete", param_list);
831                                         }else //MOVE
832                                         {
833                                                 etl::handle<studio::Instance>::cast_static(canvas_interface()->get_instance())
834                                                         ->process_action("TimepointsMove", 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_cellrenderer()(path,*selected_waypoint,event->button.button-1);
848                                 else
849                                 if(event->button.button==1)
850                                 {
851                                         synfig::Waypoint waypoint(*selected_waypoint);
852                                         Time newtime((waypoint.get_time()+(selected_time-drag_time)).round(canvas->rend_desc().get_frame_rate()));
853                                         if(waypoint.get_time()!=newtime)
854                                         {
855                                                 waypoint.set_time(newtime);
856                                                 signal_waypoint_changed_(waypoint,value_node);
857                                         }
858                                 }
859                         }*/
860
861                         //if(selection)
862                         //      selected_time=iter->time;
863                         //selected_time=iter->get_time();
864                         return true;
865                 }
866         default:
867                 //std::cerr<<"unknown event type "<<event->type<<std::endl;
868                 return false;
869                 break;
870         }
871
872
873
874         return false;
875 }
876
877
878
879 // The following three functions don't get documented correctly by
880 // doxygen 1.5.[23] because of a bug with any function whose name
881 // begins with 'property'.  Fixed in doxygen 1.5.4 apparently.  See
882 // http://bugzilla.gnome.org/show_bug.cgi?id=471185 .
883 Glib::PropertyProxy<synfigapp::ValueDesc>
884 CellRenderer_TimeTrack::property_value_desc()
885 {
886         return Glib::PropertyProxy<synfigapp::ValueDesc>(this,"value_desc");
887 }
888
889 Glib::PropertyProxy<synfig::Canvas::Handle>
890 CellRenderer_TimeTrack::property_canvas()
891 {
892         return Glib::PropertyProxy<synfig::Canvas::Handle>(this,"canvas");
893 }
894
895 Glib::PropertyProxy<Gtk::Adjustment* >
896 CellRenderer_TimeTrack::property_adjustment()
897 {
898         return Glib::PropertyProxy<Gtk::Adjustment* >(this,"adjustment");
899 }
900
901 void
902 CellRenderer_TimeTrack::set_canvas_interface(etl::loose_handle<synfigapp::CanvasInterface>      h)
903 {
904         canvas_interface_ = h;
905 }