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