Typo.
[synfig.git] / synfig-studio / trunk / src / gtkmm / cellrenderer_timetrack.cpp
1 /* === S Y N F I G ========================================================= */
2 /*!     \file cellrenderer_timetrack.cpp
3 **      \brief Template Header
4 **
5 **      $Id$
6 **
7 **      \legal
8 **      Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
9 **      Copyright (c) 2007 Chris Moore
10 **
11 **      This package is free software; you can redistribute it and/or
12 **      modify it under the terms of the GNU General Public License as
13 **      published by the Free Software Foundation; either version 2 of
14 **      the License, or (at your option) any later version.
15 **
16 **      This package is distributed in the hope that it will be useful,
17 **      but WITHOUT ANY WARRANTY; without even the implied warranty of
18 **      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 **      General Public License for more details.
20 **      \endlegal
21 */
22 /* ========================================================================= */
23
24 /* === H E A D E R S ======================================================= */
25
26 #ifdef USING_PCH
27 #       include "pch.h"
28 #else
29 #ifdef HAVE_CONFIG_H
30 #       include <config.h>
31 #endif
32
33 #include <gtkmm/label.h>
34 #include "cellrenderer_timetrack.h"
35 #include <gtk/gtk.h>
36 #include <gtkmm/spinbutton.h>
37 #include <gtkmm/combo.h>
38 #include <ETL/stringf>
39 #include "widget_value.h"
40 #include "app.h"
41 #include <gtkmm/menu.h>
42 #include <gtkmm/optionmenu.h>
43 #include "widget_time.h"
44 #include "widget_timeslider.h"
45
46 #include <synfigapp/canvasinterface.h>
47 #include "instance.h"
48
49 #include <synfig/timepointcollect.h>
50
51 #endif
52
53 using namespace synfig;
54 using namespace std;
55 using namespace etl;
56 using namespace studio;
57
58 /* === M A C R O S ========================================================= */
59
60 /* === G L O B A L S ======================================================= */
61
62 static char stipple_xpm[] = { 2, 0 };
63
64 //mode for modifier keys
65 enum MODMODE
66 {
67         NONE = 0,
68         SELECT_MASK = Gdk::CONTROL_MASK,
69         COPY_MASK = Gdk::SHIFT_MASK,
70         DELETE_MASK = Gdk::MOD1_MASK
71 };
72
73 /* === P R O C E D U R E S ================================================= */
74
75 /* === M E T H O D S ======================================================= */
76
77 CellRenderer_TimeTrack::CellRenderer_TimeTrack():
78         Glib::ObjectBase        (typeid(CellRenderer_TimeTrack)),
79         Gtk::CellRenderer       (),
80         adjustment_                     (10,10,20,0,0,0),
81
82         property_valuedesc_     (*this,"value_desc",synfigapp::ValueDesc()),
83         property_canvas_        (*this,"canvas",synfig::Canvas::Handle()),
84         property_adjustment_(*this,"adjustment",&adjustment_),
85         property_enable_timing_info_(*this,"enable-timing-info", false)
86 {
87                 dragging=false;
88         selection=false;
89 }
90
91 CellRenderer_TimeTrack::~CellRenderer_TimeTrack()
92 {
93         synfig::info("CellRenderer_TimeTrack::~CellRenderer_TimeTrack(): deleted");
94 }
95
96 void
97 CellRenderer_TimeTrack::set_adjustment(Gtk::Adjustment &x)
98 {
99         property_adjustment_=&x;
100 //      x.signal_value_changed().connect(sigc::mem_fun(*this,&Gtk::Widget::queue_draw));
101 }
102
103 synfig::Canvas::Handle
104 CellRenderer_TimeTrack::get_canvas()const
105 {
106         return const_cast<CellRenderer_TimeTrack*>(this)->property_canvas().get_value();
107 }
108
109 Gtk::Adjustment *
110 CellRenderer_TimeTrack::get_adjustment()
111 {
112         return (Gtk::Adjustment*)property_adjustment_;
113 }
114
115 const Gtk::Adjustment *
116 CellRenderer_TimeTrack::get_adjustment()const
117 {
118         return (const Gtk::Adjustment*)property_adjustment_;
119 }
120
121 bool
122 CellRenderer_TimeTrack::is_selected(const Waypoint& waypoint)const
123 {
124         return selected==waypoint;
125 }
126
127 const synfig::Time get_time_offset_from_vdesc(const synfigapp::ValueDesc &v)
128 {
129 #ifdef ADJUST_WAYPOINTS_FOR_TIME_OFFSET
130         if(v.get_value_type() != synfig::ValueBase::TYPE_CANVAS)
131                 return synfig::Time::zero();
132
133         synfig::Canvas::Handle canvasparam = v.get_value().get(Canvas::Handle());
134         if(!canvasparam)
135                 return synfig::Time::zero();
136
137         if (!v.parent_is_layer_param())
138                 return synfig::Time::zero();
139
140         synfig::Layer::Handle layer = v.get_layer();
141
142         if (layer->get_name()!="PasteCanvas")
143                 return synfig::Time::zero();
144
145         return layer->get_param("time_offset").get(Time());
146 #else // ADJUST_WAYPOINTS_FOR_TIME_OFFSET
147         return synfig::Time::zero();
148 #endif
149 }
150
151 //kind of a hack... pointer is ugly
152 const synfig::Node::time_set *get_times_from_vdesc(const synfigapp::ValueDesc &v)
153 {
154         if(v.get_value_type() == synfig::ValueBase::TYPE_CANVAS)
155         {
156                 synfig::Canvas::Handle canvasparam = v.get_value().get(Canvas::Handle());
157
158                 if(canvasparam)
159                         return &canvasparam->get_times();
160         }
161
162         ValueNode *base_value = v.get_value_node().get();
163
164         ValueNode_DynamicList *parent_value_node =
165                         v.parent_is_value_node() ?
166                                 dynamic_cast<ValueNode_DynamicList *>(v.get_parent_value_node().get()) :
167                                 0;
168
169         //we want a dynamic list entry to override the normal...
170         if(parent_value_node)
171         {
172                 return &parent_value_node->list[v.get_index()].get_times();
173         }else if(base_value) //don't render stuff if it's just animated...
174         {
175                 return &base_value->get_times();
176         }
177         return 0;
178 }
179
180 bool get_closest_time(const synfig::Node::time_set &tset, const Time &t, const Time &range, Time &out)
181 {
182         Node::time_set::const_iterator  i,j,end = tset.end();
183
184         // stop the crash mentioned in bug #1689282
185         // doesn't solve the underlying problem though, I don't think
186         if (tset.size() == 0)
187         {
188                 synfig::error(__FILE__":%d: tset.size() == 0",__LINE__);
189                 return false;
190         }
191
192         //TODO add in RangeGet so it's not so damn hard to click on points
193
194         i = tset.upper_bound(t); //where t is the lower bound, t < [first,i)
195         j = i; --j;
196
197         double dist = Time::end();
198         double closest = 0;
199
200         if(i != end)
201         {
202                 closest = i->get_time();
203                 dist = abs(i->get_time() - t);
204         }
205
206         if(j != end && (abs(j->get_time() - t) < dist) )
207         {
208                 closest = j->get_time();
209                 dist = abs(j->get_time() - t);
210         }
211
212         if( dist <= range/2 )
213         {
214                 out = closest;
215                 return true;
216         }
217
218         return false;
219 }
220
221 void
222 CellRenderer_TimeTrack::render_vfunc(
223                 const Glib::RefPtr<Gdk::Drawable>& window,
224                 Gtk::Widget& widget,
225                 const Gdk::Rectangle& /*background_area*/,
226                 const Gdk::Rectangle& area_,
227                 const Gdk::Rectangle& /*expose_area*/,
228                 Gtk::CellRendererState /*flags*/)
229 {
230         if(!window)
231                 return;
232
233         Glib::RefPtr<Gdk::GC> gc(Gdk::GC::create(window));
234         Glib::RefPtr<Gdk::GC> inactive_gc(Gdk::GC::create(window));
235         Gtk::Adjustment *adjustment=get_adjustment();
236         // Gtk::StateType state = Gtk::STATE_ACTIVE;
237         // Gtk::ShadowType shadow;
238
239         Gdk::Color
240                 curr_time_color("#0000ff"),
241                 inactive_color("#000000"),
242                 keyframe_color("#a07f7f");
243         Gdk::Color activepoint_color[2];
244
245         activepoint_color[0]=Gdk::Color("#ff0000");
246         activepoint_color[1]=Gdk::Color("#00ff00");
247
248         inactive_gc->set_rgb_fg_color(inactive_color);
249         inactive_gc->set_stipple(Gdk::Bitmap::create(stipple_xpm,2,2));
250         inactive_gc->set_fill(Gdk::STIPPLED);
251
252         synfig::Canvas::Handle canvas(property_canvas().get_value());
253
254         synfigapp::ValueDesc value_desc = property_value_desc().get_value();
255         synfig::ValueNode *base_value = value_desc.get_value_node().get();
256         // synfig::ValueNode_Animated *value_node=dynamic_cast<synfig::ValueNode_Animated*>(base_value);
257
258         synfig::ValueNode_DynamicList *parent_value_node(0);
259         if(property_value_desc().get_value().parent_is_value_node())
260                 parent_value_node=dynamic_cast<synfig::ValueNode_DynamicList*>(property_value_desc().get_value().get_parent_value_node().get());
261
262         // If the canvas is defined, then load up the keyframes
263         if(canvas)
264         {
265                 const synfig::KeyframeList& keyframe_list(canvas->keyframe_list());
266                 synfig::KeyframeList::const_iterator iter;
267
268                 for(iter=keyframe_list.begin();iter!=keyframe_list.end();++iter)
269                 {
270                         if(!iter->get_time().is_valid())
271                                 continue;
272
273                         const int x((int)((float)area_.get_width()/(adjustment->get_upper()-adjustment->get_lower())*(iter->get_time()-adjustment->get_lower())));
274                         if(iter->get_time()>=adjustment->get_lower() && iter->get_time()<adjustment->get_upper())
275                         {
276                                 gc->set_rgb_fg_color(keyframe_color);
277                                 window->draw_rectangle(gc, true, area_.get_x()+x, area_.get_y(), 1, area_.get_height()+1);
278                         }
279                 }
280         }
281
282         //render all the time points that exist
283         {
284                 const synfig::Node::time_set *tset = get_times_from_vdesc(value_desc);
285
286                 if(tset)
287                 {
288                         const synfig::Time time_offset = get_time_offset_from_vdesc(value_desc);
289                         synfig::Node::time_set::const_iterator  i = tset->begin(), end = tset->end();
290
291                         float   lower = adjustment->get_lower(),
292                                         upper = adjustment->get_upper();
293
294                         Glib::RefPtr<Gdk::GC>   gc = Gdk::GC::create(widget.get_window());
295
296                         Gdk::Rectangle area(area_);
297                         gc->set_clip_rectangle(area);
298                         gc->set_line_attributes(1,Gdk::LINE_SOLID,Gdk::CAP_BUTT,Gdk::JOIN_MITER);
299
300                         bool valselected = sel_value.get_value_node() == base_value && !sel_times.empty();
301
302                         float cfps = get_canvas()->rend_desc().get_frame_rate();
303
304                         vector<Time>    drawredafter;
305
306                         Time diff = actual_time - actual_dragtime;//selected_time-drag_time;
307                         for(; i != end; ++i)
308                         {
309                                 //find the coordinate in the drawable space...
310                                 Time t_orig = i->get_time();
311                                 if(!t_orig.is_valid()) continue;
312                                 Time t = t_orig - time_offset;
313
314                                 //if it found it... (might want to change comparison, and optimize
315                                 //                                       sel_times.find to not produce an overall nlogn solution)
316
317                                 bool selected=false;
318                                 //not dragging... just draw as per normal
319                                 //if move dragging draw offset
320                                 //if copy dragging draw both...
321
322                                 if(valselected && sel_times.find(t_orig) != sel_times.end())
323                                 {
324                                         if(dragging) //skip if we're dragging because we'll render it later
325                                         {
326                                                 if(mode & COPY_MASK) // draw both blue and red moved
327                                                 {
328                                                         drawredafter.push_back(t + diff.round(cfps));
329                                                         gc->set_rgb_fg_color(Gdk::Color("#00EEEE"));
330                                                 }else if(mode & DELETE_MASK) //it's just red...
331                                                 {
332                                                         gc->set_rgb_fg_color(Gdk::Color("#EE0000"));
333                                                         selected=true;
334                                                 }else //move - draw the red on top of the others...
335                                                 {
336                                                         drawredafter.push_back(t + diff.round(cfps));
337                                                         continue;
338                                                 }
339                                         }else
340                                         {
341                                                 gc->set_rgb_fg_color(Gdk::Color("#EE0000"));
342                                                 selected=true;
343                                         }
344                                 }else
345                                 {
346                                         gc->set_rgb_fg_color(Gdk::Color("#00EEEE"));
347                                 }
348
349                                 //synfig::info("Displaying time: %.3f s",(float)t);
350                                 const int x = (int)((t-lower)*area.get_width()/(upper-lower));
351
352                                 //should draw me a grey filled circle...
353                                 Gdk::Rectangle area2(
354                                         area.get_x() - area.get_height()/2 + x + 1,
355                                         area.get_y() + 1,
356                                         area.get_height()-2,
357                                         area.get_height()-2
358                                 );
359                                 render_time_point_to_window(window,area2,*i - time_offset,selected);
360
361                                 /*window->draw_arc(gc,true,
362                                 area.get_x() + x - area.get_height()/4, area.get_y() + area.get_height()/8,
363                                 area.get_height()/2, area.get_height()*3/4,
364                                 0, 64*360);
365
366                                 gc->set_rgb_fg_color(Gdk::Color("#000000"));
367                                 window->draw_arc(gc,false,
368                                 area.get_x() + x - area.get_height()/4, area.get_y() + area.get_height()/8,
369                                 area.get_height()/2, area.get_height()*3/4,
370                                 0, 64*360);
371                                 */
372                         }
373
374                         {
375                                 vector<Time>::iterator i = drawredafter.begin(), end = drawredafter.end();
376                                 for(; i != end; ++i)
377                                 {
378                                         //find the coordinate in the drawable space...
379                                         Time t = *i;
380
381                                         if(!t.is_valid())
382                                                 continue;
383
384                                         //synfig::info("Displaying time: %.3f s",(float)t);
385                                         const int x = (int)((t-lower)*area.get_width()/(upper-lower));
386
387                                         //should draw me a grey filled circle...
388
389                                         Gdk::Rectangle area2(
390                                                 area.get_x() - area.get_height()/2 + x + 1,
391                                                 area.get_y() + 1,
392                                                 area.get_height()-2,
393                                                 area.get_height()-2
394                                         );
395                                         render_time_point_to_window(window,area2,*i,true);
396 /*                                      gc->set_rgb_fg_color(Gdk::Color("#EE0000"));
397                                         window->draw_arc(gc,true,
398                                         area.get_x() + x - area.get_height()/4, area.get_y() + area.get_height()/8,
399                                         area.get_height()/2, area.get_height()*3/4,
400                                         0, 64*360);
401
402                                         gc->set_rgb_fg_color(Gdk::Color("#000000"));
403                                         window->draw_arc(gc,false,
404                                         area.get_x() + x - area.get_height()/4, area.get_y() + area.get_height()/8,
405                                         area.get_height()/2, area.get_height()*3/4,
406                                         0, 64*360);
407 */
408                                 }
409                         }
410                 }
411         }
412
413         /* THIS IS NOW HANDLED ENTIRELY BY THE TIMEPOINT SYSTEM
414         // This this is an animated value node, then render the waypoints
415         if(value_node)
416         {
417                 //now render the actual waypoints
418                 synfig::ValueNode_Animated::WaypointList::iterator iter;
419                 for(
420                         iter=value_node->waypoint_list().begin();
421                         iter!=value_node->waypoint_list().end();
422                         iter++
423                 )
424                 {
425                         if(!iter->get_time().is_valid())
426                                 continue;
427                         int x;
428                         bool selected=false;
429                         if(is_selected(*iter))
430                         {
431                                 Time t(iter->get_time());
432
433
434                                 if(dragging)
435                                         t=(t+selected_time-drag_time).round(get_canvas()->rend_desc().get_frame_rate());
436
437                                 x=(int)((float)area.get_width()/(adjustment->get_upper()-adjustment->get_lower())*(t-adjustment->get_lower()));
438                                 shadow=Gtk::SHADOW_IN;
439                                 selected=true;
440                         }
441                         else
442                         {
443                                 x=(int)((float)area.get_width()/(adjustment->get_upper()-adjustment->get_lower())*(iter->get_time()-adjustment->get_lower()));
444                                 shadow=Gtk::SHADOW_OUT;
445                                 selected=false;
446                         }
447
448
449                         widget.get_style()->paint_diamond(
450                                 Glib::RefPtr<Gdk::Window>::cast_static(window),
451                                 state,
452                                 shadow,
453                                 area,
454                                 widget,
455                                 "solid",
456                                 area.get_x()+x-area.get_height()/4,
457                                 area.get_y()+area.get_height()/4,
458                                 area.get_height()/2,
459                                 area.get_height()/2
460                         );
461                 }
462         }
463         */
464                 Gdk::Rectangle area(area_);
465         // If the parent of this value node is a dynamic list, then
466         // render the on and off times
467         if(parent_value_node)
468         {
469                 const int index(property_value_desc().get_value().get_index());
470                 const synfig::ValueNode_DynamicList::ListEntry& list_entry(parent_value_node->list[index]);
471                 const synfig::ValueNode_DynamicList::ListEntry::ActivepointList& activepoint_list(list_entry.timing_info);
472                 synfig::ValueNode_DynamicList::ListEntry::ActivepointList::const_iterator iter,next;
473
474                 bool is_off(false);
475                 if(!activepoint_list.empty())
476                         is_off=!activepoint_list.front().state;
477
478                 int xstart(0);
479
480                 int x=0,prevx=0;
481                 for(next=activepoint_list.begin(),iter=next++;iter!=activepoint_list.end();iter=next++)
482                 {
483                         x=((int)((float)area.get_width()/(adjustment->get_upper()-adjustment->get_lower())*(iter->time-adjustment->get_lower())));
484                         if(x<0)x=0;
485                         if(x>area.get_width())x=area.get_width();
486
487                         bool status_at_time=0;
488                         if(next!=activepoint_list.end())
489                         {
490                                 status_at_time=!list_entry.status_at_time((iter->time+next->time)/2.0);
491                         }
492                         else
493                                 status_at_time=!list_entry.status_at_time(Time::end());
494
495                         if(!is_off && status_at_time)
496                         {
497                                 xstart=x;
498                                 is_off=true;
499                         }
500                         else
501                         if(is_off && !status_at_time)
502                         {
503                                 window->draw_rectangle(inactive_gc, true, area.get_x()+xstart, area.get_y(), x-xstart, area.get_height());
504                                 is_off=false;
505                         }
506
507                         /*
508                         if(!is_off && iter!=activepoint_list.end() && next->state==false && iter->state==false)
509                         {
510                                 xstart=x;
511                                 is_off=true;
512                         }
513                         else if(is_off && next!=activepoint_list.end() && iter->state==false && next->state==true)
514                         {
515                                 window->draw_rectangle(inactive_gc, true, area.get_x()+xstart, area.get_y(), x-xstart, area.get_height());
516                                 is_off=false;
517                         }
518                         else if(is_off && iter!=activepoint_list.end() && iter->state==true)
519                         {
520                                 window->draw_rectangle(inactive_gc, true, area.get_x()+xstart, area.get_y(), prevx-xstart, area.get_height());
521                                 is_off=false;
522                         }
523                         */
524
525
526
527                         if(iter->time>=adjustment->get_lower() && iter->time<adjustment->get_upper())
528                         {
529                                 int w(1);
530                                 if(selected==*iter)
531                                         w=3;
532                                 gc->set_rgb_fg_color(activepoint_color[iter->state]);
533                                 window->draw_rectangle(gc, true, area.get_x()+x-w/2, area.get_y(), w, area.get_height());
534                         }
535                         prevx=x;
536                 }
537                 if(is_off)
538                 {
539                         window->draw_rectangle(inactive_gc, true, area.get_x()+xstart, area.get_y(), area.get_width()-xstart, area.get_height());
540                 }
541         }
542
543         // Render a line that defines the current tick in time
544         {
545                 gc->set_rgb_fg_color(curr_time_color);
546
547                 const int x((int)((float)area.get_width()/(adjustment->get_upper()-adjustment->get_lower())*(adjustment->get_value()-adjustment->get_lower())));
548
549                 if(adjustment->get_value()>=adjustment->get_lower() && adjustment->get_value()<adjustment->get_upper())
550                         window->draw_rectangle(gc, true, area.get_x()+x, area.get_y(), 1, area.get_height());
551         }
552 }
553
554 synfig::ValueNode_Animated::WaypointList::iterator
555 CellRenderer_TimeTrack::find_waypoint(const synfig::Time& /*t*/,const synfig::Time& scope)
556 {
557         synfig::ValueNode_Animated *value_node=dynamic_cast<synfig::ValueNode_Animated*>(property_value_desc().get_value().get_value_node().get());
558
559     Time nearest(Time::end());
560
561         synfig::ValueNode_Animated::WaypointList::iterator iter,ret;
562
563         if(value_node)
564         {
565                 for(
566                         iter=value_node->waypoint_list().begin();
567                         iter!=value_node->waypoint_list().end();
568                         iter++
569                         )
570                 {
571                         Time val=abs(iter->get_time()-selected_time);
572                         if(val<nearest)
573                         {
574                                 nearest=val;
575                                 ret=iter;
576                         }
577                 }
578
579                 if(nearest!=Time::end() && nearest<scope)
580                 {
581                         return ret;
582                 }
583         }
584         throw int();
585 }
586
587 bool
588 CellRenderer_TimeTrack::activate_vfunc(
589         GdkEvent* event,
590         Gtk::Widget& /*widget*/,
591         const Glib::ustring& treepath,
592         const Gdk::Rectangle& /*background_area*/,
593         const Gdk::Rectangle& cell_area,
594         Gtk::CellRendererState /*flags*/)
595 {
596         path=treepath;
597         synfig::ValueNode_Animated::WaypointList::iterator iter;
598     Time nearest=1000000000;
599         Gtk::Adjustment *adjustment=get_adjustment();
600
601         // synfig::ValueNode_Animated *value_node=dynamic_cast<synfig::ValueNode_Animated*>(property_value_desc().get_value().get_value_node().get());
602
603         synfig::Canvas::Handle canvas(get_canvas());
604
605         synfig::ValueNode_DynamicList *parent_value_node(0);
606         if(property_value_desc().get_value().parent_is_value_node())
607                 parent_value_node=dynamic_cast<synfig::ValueNode_DynamicList*>(property_value_desc().get_value().get_parent_value_node().get());
608
609         Time deltatime = 0;
610         Time curr_time;
611         switch(event->type)
612         {
613         case GDK_MOTION_NOTIFY:
614                 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();
615
616                 mode = NONE;
617                 {
618                         Gdk::ModifierType mod;
619                         Gdk::Event(event).get_state(mod);
620                         mode = mod;
621                 }
622                 break;
623         case GDK_BUTTON_PRESS:
624         case GDK_BUTTON_RELEASE:
625         default:
626                 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();
627                 {
628                         Gdk::ModifierType mod;
629                         Gdk::Event(event).get_state(mod);
630                         mode = mod;
631                 }
632                 break;
633         }
634         actual_time = curr_time;
635         if(canvas)
636                 curr_time=curr_time.round(canvas->rend_desc().get_frame_rate());
637         selected_time=curr_time;
638
639     Time pixel_width((adjustment->get_upper()-adjustment->get_lower())/cell_area.get_width());
640
641     switch(event->type)
642     {
643         case GDK_BUTTON_PRESS:
644                 //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();
645
646                 //Deal with time point selection, but only if they aren't involved in the insanity...
647                 if(/*!value_node && */event->button.button == 1)
648                 {
649                         Time stime;
650
651                         /*!     UI specification:
652
653                                 When nothing is selected, clicking on a point in either normal mode or
654                                         additive mode will select the time point closest to the click.
655                                         Subtractive click will do nothing
656
657                                 When things are already selected, clicking on a selected point does
658                                         nothing (in both normal and add mode).  Add mode clicking on an unselected
659                                         point adds it to the set.  Normal clicking on an unselected point will
660                                         select only that one time point.  Subtractive clicking on any point
661                                         will remove it from the the set if it is included.
662                         */
663
664                         synfigapp::ValueDesc valdesc = property_value_desc().get_value();
665                         const Node::time_set *tset = get_times_from_vdesc(valdesc);
666                         const synfig::Time time_offset = get_time_offset_from_vdesc(valdesc);
667
668                         bool clickfound = tset && get_closest_time(*tset,actual_time+time_offset,pixel_width*cell_area.get_height(),stime);
669                         bool selectmode = mode & SELECT_MASK;
670
671                         //NOTE LATER ON WE SHOULD MAKE IT SO MULTIPLE VALUENODES CAN BE SELECTED AT ONCE
672                         //we want to jump to the value desc if we're not currently on it
673                         //      but only if we want to add the point
674                         if(clickfound && !(sel_value == valdesc))
675                         {
676                                 sel_value = valdesc;
677                                 sel_times.clear();
678                         }
679
680                         //now that we've made sure we're selecting the correct value, deal with the already selected points
681                         set<Time>::iterator foundi = clickfound ? sel_times.find(stime) : sel_times.end();
682                         bool found = foundi != sel_times.end();
683
684                         //remove all other points from our list... (only select the one we need)
685                         if(!selectmode && !found)
686                         {
687                                 sel_times.clear();
688                         }
689
690                         if(found && selectmode) //remove a single already selected point
691                         {
692                                 sel_times.erase(foundi);
693                         }else if(clickfound) //otherwise look at adding it
694                         {
695                                 //for replace the list was cleared earlier, and for add it wasn't so it works
696                                 sel_times.insert(stime);
697                         }
698                 }
699
700                 selection=false;
701                 try
702                 {
703                         iter=find_waypoint(selected_time,pixel_width*cell_area.get_height()/2);
704                         selected_waypoint=iter;
705                         selected=*iter;
706
707                         selection=true;
708                 }
709                 catch(int)
710                 {
711                         selection=false;
712                         selected=synfig::UniqueID::nil();
713                 }
714
715                 if((!sel_times.empty() || selection) && event->button.button==1)
716                 {
717                         dragging=true;
718                         drag_time=selected_time;
719                         actual_dragtime=actual_time;
720                 }
721                 //selected_time=iter->time;
722
723                 /*
724                 // Activepoint Selection
725                 if(parent_value_node)
726                 {
727                         const int index(property_value_desc().get_value().get_index());
728                         const synfig::ValueNode_DynamicList::ListEntry::ActivepointList& activepoint_list(parent_value_node->list[index].timing_info);
729                         synfig::ValueNode_DynamicList::ListEntry::ActivepointList::const_iterator iter;
730
731                         for(iter=activepoint_list.begin();iter!=activepoint_list.end();++iter)
732                         {
733                                 Time val=abs(iter->time-selected_time);
734                                 if(val<nearest)
735                                 {
736                                         nearest=val;
737                                         selected=*iter;
738                                         selection=true;
739                                 }
740                         }
741                         // Perhaps I should signal if we selected this activepoint?
742                 }*/
743
744                         if(event->button.button==3)
745                         {
746                                 Time stime;
747                                 synfigapp::ValueDesc valdesc = property_value_desc().get_value();
748                                 const Node::time_set *tset = get_times_from_vdesc(valdesc);
749
750                                 bool clickfound = tset && get_closest_time(*tset,actual_time+get_time_offset_from_vdesc(valdesc),pixel_width*cell_area.get_height(),stime);
751
752                                 etl::handle<synfig::Node> node;
753                                 if(valdesc.get_value(stime).get_type()==ValueBase::TYPE_CANVAS)
754                                 {
755                                         node=Canvas::Handle(valdesc.get_value(stime).get(Canvas::Handle()));
756                                 }
757                                 else //if(valdesc.is_value_node())
758                                 {
759                                         node=valdesc.get_value_node();
760                                 }
761
762                                 if(clickfound && node)
763                                 {
764                                         show_timepoint_menu(node, stime, actual_time<stime?SIDE_LEFT:SIDE_RIGHT);
765                                 }
766                         }
767
768                 break;
769         case GDK_MOTION_NOTIFY:
770                 //DEBUGPOINT();
771                 //if(selection && dragging)
772                 //      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();
773                 return true;
774
775                 break;
776         case GDK_BUTTON_RELEASE:
777                 {
778                         DEBUGPOINT();
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_(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(sel_value.get_value_type() == synfig::ValueBase::TYPE_CANVAS)
802                                         {
803                                                 param_list.add("addcanvas",sel_value.get_value().get(Canvas::Handle()));
804                                         }else
805                                         {
806                                                 param_list.add("addvaluedesc",sel_value);
807                                         }
808
809                                         set<Time>       newset;
810                                         std::set<synfig::Time>::iterator i = sel_times.begin(), end = sel_times.end();
811                                         for(; i != end; ++i)
812                                         {
813                                                 param_list.add("addtime",*i);
814
815                                                 newset.insert((*i + deltatime).round(get_canvas()->rend_desc().get_frame_rate()));
816                                         }
817
818                                         if(!delmode)
819                                                 param_list.add("deltatime",deltatime);
820                                 //      param_list.add("time",canvas_interface()->get_time());
821
822                                         if(mode & COPY_MASK) //copy
823                                         {
824                                                 etl::handle<studio::Instance>::cast_static(canvas_interface()->get_instance())
825                                                         ->process_action("timepoint_copy", param_list);
826                                         }else if(delmode) //DELETE
827                                         {
828                                                 etl::handle<studio::Instance>::cast_static(canvas_interface()->get_instance())
829                                                         ->process_action("timepoint_delete", param_list);
830                                         }else //MOVE
831                                         {
832                                                 etl::handle<studio::Instance>::cast_static(canvas_interface()->get_instance())
833                                                         ->process_action("timepoint_move", param_list);
834                                         }
835
836                                         //now replace all the selected with the new selected
837                                         sel_times = newset;
838                                 }
839                         }
840
841
842
843                         /*if(value_node && selection)
844                         {
845                                 if(selected_time==drag_time && event->button.button!=3)
846                                         signal_waypoint_clicked_(path,*selected_waypoint,event->button.button-1);
847                                 else
848                                 if(event->button.button==1)
849                                 {
850                                         synfig::Waypoint waypoint(*selected_waypoint);
851                                         Time newtime((waypoint.get_time()+(selected_time-drag_time)).round(canvas->rend_desc().get_frame_rate()));
852                                         if(waypoint.get_time()!=newtime)
853                                         {
854                                                 waypoint.set_time(newtime);
855                                                 signal_waypoint_changed_(waypoint,value_node);
856                                         }
857                                 }
858                         }*/
859
860                         //if(selection)
861                         //      selected_time=iter->time;
862                         //selected_time=iter->get_time();
863                         return true;
864                 }
865         default:
866                 //std::cerr<<"unknown event type "<<event->type<<std::endl;
867                 return false;
868                 break;
869         }
870
871
872
873         return false;
874 }
875
876
877
878 Glib::PropertyProxy<synfigapp::ValueDesc>
879 CellRenderer_TimeTrack::property_value_desc()
880 {
881         return Glib::PropertyProxy<synfigapp::ValueDesc>(this,"value_desc");
882 }
883
884 Glib::PropertyProxy<synfig::Canvas::Handle>
885 CellRenderer_TimeTrack::property_canvas()
886 {
887         return Glib::PropertyProxy<synfig::Canvas::Handle>(this,"canvas");
888 }
889
890 Glib::PropertyProxy<Gtk::Adjustment* >
891 CellRenderer_TimeTrack::property_adjustment()
892 {
893         return Glib::PropertyProxy<Gtk::Adjustment* >(this,"adjustment");
894 }
895
896 void
897 CellRenderer_TimeTrack::set_canvas_interface(etl::loose_handle<synfigapp::CanvasInterface>      h)
898 {
899         canvas_interface_ = h;
900 }
901
902 static void
903 set_waypoint_model(std::set<synfig::Waypoint, std::less<UniqueID> > waypoints, Waypoint::Model model, etl::loose_handle<synfigapp::CanvasInterface> canvas_interface)
904 {
905         // Create the action group
906         synfigapp::Action::PassiveGrouper group(canvas_interface->get_instance().get(),_("Change Waypoint Group"));
907
908         std::set<synfig::Waypoint, std::less<UniqueID> >::const_iterator iter;
909         for(iter=waypoints.begin();iter!=waypoints.end();++iter)
910         {
911                 Waypoint waypoint(*iter);
912                 waypoint.apply_model(model);
913
914                 synfigapp::Action::Handle action(synfigapp::Action::create("waypoint_set"));
915
916                 assert(action);
917
918                 action->set_param("canvas",canvas_interface->get_canvas());
919                 action->set_param("canvas_interface",canvas_interface);
920
921                 action->set_param("waypoint",waypoint);
922                 action->set_param("value_node",waypoint.get_parent_value_node());
923
924                 if(!canvas_interface->get_instance()->perform_action(action))
925                 {
926                         group.cancel();
927                         return;
928                 }
929         }
930 }
931
932 void
933 CellRenderer_TimeTrack::show_timepoint_menu(const etl::handle<synfig::Node>& node, const synfig::Time& time, Side side)
934 {
935         std::set<synfig::Waypoint, std::less<UniqueID> > waypoint_set;
936         int n;
937         n=synfig::waypoint_collect(waypoint_set,time,node);
938
939         Gtk::Menu* menu(manage(new Gtk::Menu()));
940
941         // Create the interpolation method menu
942         if(!waypoint_set.empty())
943         {
944                 Gtk::Menu* interp_menu(manage(new Gtk::Menu()));
945                 Waypoint::Model model;
946
947                 // note: each of the following 4 'if' blocks provokes these warnings:
948                 //  /usr/include/sigc++-2.0/sigc++/adaptors/bound_argument.h:57: warning:
949                 //  'model.synfig::Waypoint::Model::temporal_tension' is used uninitialized in this function
950                 //      'model.synfig::Waypoint::Model::bias' is used uninitialized in this function
951                 //      'model.synfig::Waypoint::Model::continuity' is used uninitialized in this function
952                 //      'model.synfig::Waypoint::Model::tension' is used uninitialized in this function
953                 //      'model.synfig::Waypoint::Model::priority' is used uninitialized in this function
954                 // I don't know if that matters or not.
955
956                 if(side==SIDE_LEFT)model.set_before(INTERPOLATION_TCB);
957                 else model.set_after(INTERPOLATION_TCB);
958                 interp_menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("TCB"),
959                         sigc::bind(
960                                 sigc::ptr_fun(set_waypoint_model),
961                                 waypoint_set,
962                                 model,
963                                 canvas_interface()
964                         )
965                 ));
966
967                 if(side==SIDE_LEFT)model.set_before(INTERPOLATION_LINEAR);
968                 else model.set_after(INTERPOLATION_LINEAR);
969                 interp_menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("Linear"),
970                         sigc::bind(
971                                 sigc::ptr_fun(set_waypoint_model),
972                                 waypoint_set,
973                                 model,
974                                 canvas_interface()
975                         )
976                 ));
977
978                 if(side==SIDE_LEFT)model.set_before(INTERPOLATION_HALT);
979                 else model.set_after(INTERPOLATION_HALT);
980                 interp_menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("Ease"),
981                         sigc::bind(
982                                 sigc::ptr_fun(set_waypoint_model),
983                                 waypoint_set,
984                                 model,
985                                 canvas_interface()
986                         )
987                 ));
988
989                 if(side==SIDE_LEFT)model.set_before(INTERPOLATION_CONSTANT);
990                 else model.set_after(INTERPOLATION_CONSTANT);
991                 interp_menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("Constant"),
992                         sigc::bind(
993                                 sigc::ptr_fun(set_waypoint_model),
994                                 waypoint_set,
995                                 model,
996                                 canvas_interface()
997                         )
998                 ));
999
1000
1001                 menu->items().push_back(
1002                         Gtk::Menu_Helpers::MenuElem(
1003                                 side==SIDE_LEFT?_("Change \"In\" Interp."):_("Change \"Out\" Interp."),
1004                                 *interp_menu
1005                         )
1006                 );
1007         }
1008
1009         menu->items().push_back(Gtk::Menu_Helpers::StockMenuElem(Gtk::StockID("gtk-jump-to"),
1010                 sigc::bind(
1011                         sigc::mem_fun(
1012                                 *canvas_interface(),
1013                                 &synfigapp::CanvasInterface::set_time
1014                         ),
1015                         time
1016                 )
1017         ));
1018
1019         if(!waypoint_set.empty())
1020         {
1021                 if(waypoint_set.size()==1)
1022                 {
1023                         delete menu;
1024                         menu=0;
1025                         signal_waypoint_clicked_(" ",*waypoint_set.begin(),2);
1026                         return;
1027                 }
1028                 else
1029                         synfig::info("Too many waypoints under me");
1030         }
1031         else
1032                 synfig::info("ZERO waypoints under me");
1033
1034         if(menu)menu->popup(3,gtk_get_current_event_time());
1035 }