1 /* === S Y N F I G ========================================================= */
2 /*! \file cellrenderer_timetrack.cpp
3 ** \brief Template Header
8 ** Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
9 ** Copyright (c) 2007 Chris Moore
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.
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.
22 /* ========================================================================= */
24 /* === H E A D E R S ======================================================= */
33 #include <gtkmm/label.h>
34 #include "cellrenderer_timetrack.h"
36 #include <gtkmm/spinbutton.h>
37 #include <gtkmm/combo.h>
38 #include <ETL/stringf>
39 #include "widget_value.h"
41 #include <gtkmm/menu.h>
42 #include <gtkmm/optionmenu.h>
43 #include "widget_time.h"
44 #include "widget_timeslider.h"
46 #include <synfigapp/canvasinterface.h>
49 #include <synfig/timepointcollect.h>
55 using namespace synfig;
58 using namespace studio;
60 /* === M A C R O S ========================================================= */
62 /* === G L O B A L S ======================================================= */
64 static char stipple_xpm[] = { 2, 0 };
66 //mode for modifier keys
70 SELECT_MASK = Gdk::CONTROL_MASK,
71 COPY_MASK = Gdk::SHIFT_MASK,
72 DELETE_MASK = Gdk::MOD1_MASK
75 /* === P R O C E D U R E S ================================================= */
77 /* === M E T H O D S ======================================================= */
79 CellRenderer_TimeTrack::CellRenderer_TimeTrack():
80 Glib::ObjectBase (typeid(CellRenderer_TimeTrack)),
82 adjustment_ (10,10,20,0,0,0),
84 property_valuedesc_ (*this,"value_desc",synfigapp::ValueDesc()),
85 property_canvas_ (*this,"canvas",synfig::Canvas::Handle()),
86 property_adjustment_(*this,"adjustment",&adjustment_),
87 property_enable_timing_info_(*this,"enable-timing-info", false)
93 CellRenderer_TimeTrack::~CellRenderer_TimeTrack()
95 if (getenv("SYNFIG_DEBUG_DESTRUCTORS"))
96 synfig::info("CellRenderer_TimeTrack::~CellRenderer_TimeTrack(): Deleted");
100 CellRenderer_TimeTrack::set_adjustment(Gtk::Adjustment &x)
102 property_adjustment_=&x;
103 // x.signal_value_changed().connect(sigc::mem_fun(*this,&Gtk::Widget::queue_draw));
106 synfig::Canvas::Handle
107 CellRenderer_TimeTrack::get_canvas()const
109 return const_cast<CellRenderer_TimeTrack*>(this)->property_canvas().get_value();
113 CellRenderer_TimeTrack::get_adjustment()
115 return (Gtk::Adjustment*)property_adjustment_;
118 const Gtk::Adjustment *
119 CellRenderer_TimeTrack::get_adjustment()const
121 return (const Gtk::Adjustment*)property_adjustment_;
125 CellRenderer_TimeTrack::is_selected(const Waypoint& waypoint)const
127 return selected==waypoint;
130 const synfig::Time get_time_offset_from_vdesc(const synfigapp::ValueDesc &v)
132 #ifdef ADJUST_WAYPOINTS_FOR_TIME_OFFSET
133 if(v.get_value_type() != synfig::ValueBase::TYPE_CANVAS)
134 return synfig::Time::zero();
136 synfig::Canvas::Handle canvasparam = v.get_value().get(Canvas::Handle());
138 return synfig::Time::zero();
140 if (!v.parent_is_layer_param())
141 return synfig::Time::zero();
143 synfig::Layer::Handle layer = v.get_layer();
145 if (layer->get_name()!="PasteCanvas")
146 return synfig::Time::zero();
148 return layer->get_param("time_offset").get(Time());
149 #else // ADJUST_WAYPOINTS_FOR_TIME_OFFSET
150 return synfig::Time::zero();
154 //kind of a hack... pointer is ugly
155 const synfig::Node::time_set *get_times_from_vdesc(const synfigapp::ValueDesc &v)
157 if(v.get_value_type() == synfig::ValueBase::TYPE_CANVAS)
159 synfig::Canvas::Handle canvasparam = v.get_value().get(Canvas::Handle());
162 return &canvasparam->get_times();
165 ValueNode *base_value = v.get_value_node().get();
167 ValueNode_DynamicList *parent_value_node =
168 v.parent_is_value_node() ?
169 dynamic_cast<ValueNode_DynamicList *>(v.get_parent_value_node().get()) :
172 //we want a dynamic list entry to override the normal...
173 if(parent_value_node)
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...
178 return &base_value->get_times();
183 bool get_closest_time(const synfig::Node::time_set &tset, const Time &t, const Time &range, Time &out)
185 Node::time_set::const_iterator i,j,end = tset.end();
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)
191 synfig::error(__FILE__":%d: tset.size() == 0",__LINE__);
195 //TODO add in RangeGet so it's not so damn hard to click on points
197 i = tset.upper_bound(t); //where t is the lower bound, t < [first,i)
200 double dist = Time::end();
205 closest = i->get_time();
206 dist = abs(i->get_time() - t);
209 if(j != end && (abs(j->get_time() - t) < dist) )
211 closest = j->get_time();
212 dist = abs(j->get_time() - t);
215 if( dist <= range/2 )
225 CellRenderer_TimeTrack::render_vfunc(
226 const Glib::RefPtr<Gdk::Drawable>& window,
228 const Gdk::Rectangle& /*background_area*/,
229 const Gdk::Rectangle& area_,
230 const Gdk::Rectangle& /*expose_area*/,
231 Gtk::CellRendererState /*flags*/)
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;
243 curr_time_color("#0000ff"),
244 inactive_color("#000000"),
245 keyframe_color("#a07f7f");
246 Gdk::Color activepoint_color[2];
248 activepoint_color[0]=Gdk::Color("#ff0000");
249 activepoint_color[1]=Gdk::Color("#00ff00");
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);
255 synfig::Canvas::Handle canvas(property_canvas().get_value());
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);
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());
265 // If the canvas is defined, then load up the keyframes
268 const synfig::KeyframeList& keyframe_list(canvas->keyframe_list());
269 synfig::KeyframeList::const_iterator iter;
271 for(iter=keyframe_list.begin();iter!=keyframe_list.end();++iter)
273 if(!iter->get_time().is_valid())
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())
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);
285 //render all the time points that exist
287 const synfig::Node::time_set *tset = get_times_from_vdesc(value_desc);
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();
294 float lower = adjustment->get_lower(),
295 upper = adjustment->get_upper();
297 Glib::RefPtr<Gdk::GC> gc = Gdk::GC::create(widget.get_window());
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);
303 bool valselected = sel_value.get_value_node() == base_value && !sel_times.empty();
305 float cfps = get_canvas()->rend_desc().get_frame_rate();
307 vector<Time> drawredafter;
309 Time diff = actual_time - actual_dragtime;//selected_time-drag_time;
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;
317 //if it found it... (might want to change comparison, and optimize
318 // sel_times.find to not produce an overall nlogn solution)
321 //not dragging... just draw as per normal
322 //if move dragging draw offset
323 //if copy dragging draw both...
325 if(valselected && sel_times.find(t_orig) != sel_times.end())
327 if(dragging) //skip if we're dragging because we'll render it later
329 if(mode & COPY_MASK) // draw both blue and red moved
331 drawredafter.push_back(t + diff.round(cfps));
332 gc->set_rgb_fg_color(Gdk::Color("#00EEEE"));
333 }else if(mode & DELETE_MASK) //it's just red...
335 gc->set_rgb_fg_color(Gdk::Color("#EE0000"));
337 }else //move - draw the red on top of the others...
339 drawredafter.push_back(t + diff.round(cfps));
344 gc->set_rgb_fg_color(Gdk::Color("#EE0000"));
349 gc->set_rgb_fg_color(Gdk::Color("#00EEEE"));
352 //synfig::info("Displaying time: %.3f s",(float)t);
353 const int x = (int)((t-lower)*area.get_width()/(upper-lower));
355 //should draw me a grey filled circle...
356 Gdk::Rectangle area2(
357 area.get_x() - area.get_height()/2 + x + 1,
362 render_time_point_to_window(window,area2,*i - time_offset,selected);
364 /*window->draw_arc(gc,true,
365 area.get_x() + x - area.get_height()/4, area.get_y() + area.get_height()/8,
366 area.get_height()/2, area.get_height()*3/4,
369 gc->set_rgb_fg_color(Gdk::Color("#000000"));
370 window->draw_arc(gc,false,
371 area.get_x() + x - area.get_height()/4, area.get_y() + area.get_height()/8,
372 area.get_height()/2, area.get_height()*3/4,
378 vector<Time>::iterator i = drawredafter.begin(), end = drawredafter.end();
381 //find the coordinate in the drawable space...
387 //synfig::info("Displaying time: %.3f s",(float)t);
388 const int x = (int)((t-lower)*area.get_width()/(upper-lower));
390 //should draw me a grey filled circle...
392 Gdk::Rectangle area2(
393 area.get_x() - area.get_height()/2 + x + 1,
398 render_time_point_to_window(window,area2,*i,true);
399 /* gc->set_rgb_fg_color(Gdk::Color("#EE0000"));
400 window->draw_arc(gc,true,
401 area.get_x() + x - area.get_height()/4, area.get_y() + area.get_height()/8,
402 area.get_height()/2, area.get_height()*3/4,
405 gc->set_rgb_fg_color(Gdk::Color("#000000"));
406 window->draw_arc(gc,false,
407 area.get_x() + x - area.get_height()/4, area.get_y() + area.get_height()/8,
408 area.get_height()/2, area.get_height()*3/4,
416 /* THIS IS NOW HANDLED ENTIRELY BY THE TIMEPOINT SYSTEM
417 // This this is an animated value node, then render the waypoints
420 //now render the actual waypoints
421 synfig::ValueNode_Animated::WaypointList::iterator iter;
423 iter=value_node->waypoint_list().begin();
424 iter!=value_node->waypoint_list().end();
428 if(!iter->get_time().is_valid())
432 if(is_selected(*iter))
434 Time t(iter->get_time());
438 t=(t+selected_time-drag_time).round(get_canvas()->rend_desc().get_frame_rate());
440 x=(int)((float)area.get_width()/(adjustment->get_upper()-adjustment->get_lower())*(t-adjustment->get_lower()));
441 shadow=Gtk::SHADOW_IN;
446 x=(int)((float)area.get_width()/(adjustment->get_upper()-adjustment->get_lower())*(iter->get_time()-adjustment->get_lower()));
447 shadow=Gtk::SHADOW_OUT;
452 widget.get_style()->paint_diamond(
453 Glib::RefPtr<Gdk::Window>::cast_static(window),
459 area.get_x()+x-area.get_height()/4,
460 area.get_y()+area.get_height()/4,
467 Gdk::Rectangle area(area_);
468 // If the parent of this value node is a dynamic list, then
469 // render the on and off times
470 if(parent_value_node)
472 const int index(property_value_desc().get_value().get_index());
473 const synfig::ValueNode_DynamicList::ListEntry& list_entry(parent_value_node->list[index]);
474 const synfig::ValueNode_DynamicList::ListEntry::ActivepointList& activepoint_list(list_entry.timing_info);
475 synfig::ValueNode_DynamicList::ListEntry::ActivepointList::const_iterator iter,next;
478 if(!activepoint_list.empty())
479 is_off=!activepoint_list.front().state;
484 for(next=activepoint_list.begin(),iter=next++;iter!=activepoint_list.end();iter=next++)
486 x=((int)((float)area.get_width()/(adjustment->get_upper()-adjustment->get_lower())*(iter->time-adjustment->get_lower())));
488 if(x>area.get_width())x=area.get_width();
490 bool status_at_time=0;
491 if(next!=activepoint_list.end())
493 status_at_time=!list_entry.status_at_time((iter->time+next->time)/2.0);
496 status_at_time=!list_entry.status_at_time(Time::end());
498 if(!is_off && status_at_time)
504 if(is_off && !status_at_time)
506 window->draw_rectangle(inactive_gc, true, area.get_x()+xstart, area.get_y(), x-xstart, area.get_height());
511 if(!is_off && iter!=activepoint_list.end() && next->state==false && iter->state==false)
516 else if(is_off && next!=activepoint_list.end() && iter->state==false && next->state==true)
518 window->draw_rectangle(inactive_gc, true, area.get_x()+xstart, area.get_y(), x-xstart, area.get_height());
521 else if(is_off && iter!=activepoint_list.end() && iter->state==true)
523 window->draw_rectangle(inactive_gc, true, area.get_x()+xstart, area.get_y(), prevx-xstart, area.get_height());
530 if(iter->time>=adjustment->get_lower() && iter->time<adjustment->get_upper())
535 gc->set_rgb_fg_color(activepoint_color[iter->state]);
536 window->draw_rectangle(gc, true, area.get_x()+x-w/2, area.get_y(), w, area.get_height());
542 window->draw_rectangle(inactive_gc, true, area.get_x()+xstart, area.get_y(), area.get_width()-xstart, area.get_height());
546 // Render a line that defines the current tick in time
548 gc->set_rgb_fg_color(curr_time_color);
550 const int x((int)((float)area.get_width()/(adjustment->get_upper()-adjustment->get_lower())*(adjustment->get_value()-adjustment->get_lower())));
552 if(adjustment->get_value()>=adjustment->get_lower() && adjustment->get_value()<adjustment->get_upper())
553 window->draw_rectangle(gc, true, area.get_x()+x, area.get_y(), 1, area.get_height());
557 synfig::ValueNode_Animated::WaypointList::iterator
558 CellRenderer_TimeTrack::find_waypoint(const synfig::Time& /*t*/,const synfig::Time& scope)
560 synfig::ValueNode_Animated *value_node=dynamic_cast<synfig::ValueNode_Animated*>(property_value_desc().get_value().get_value_node().get());
562 Time nearest(Time::end());
564 synfig::ValueNode_Animated::WaypointList::iterator iter,ret;
569 iter=value_node->waypoint_list().begin();
570 iter!=value_node->waypoint_list().end();
574 Time val=abs(iter->get_time()-selected_time);
582 if(nearest!=Time::end() && nearest<scope)
591 CellRenderer_TimeTrack::activate_vfunc(
593 Gtk::Widget& /*widget*/,
594 const Glib::ustring& treepath,
595 const Gdk::Rectangle& /*background_area*/,
596 const Gdk::Rectangle& cell_area,
597 Gtk::CellRendererState /*flags*/)
600 synfig::ValueNode_Animated::WaypointList::iterator iter;
601 Time nearest=1000000000;
602 Gtk::Adjustment *adjustment=get_adjustment();
604 // synfig::ValueNode_Animated *value_node=dynamic_cast<synfig::ValueNode_Animated*>(property_value_desc().get_value().get_value_node().get());
606 synfig::Canvas::Handle canvas(get_canvas());
608 synfig::ValueNode_DynamicList *parent_value_node(0);
609 if(property_value_desc().get_value().parent_is_value_node())
610 parent_value_node=dynamic_cast<synfig::ValueNode_DynamicList*>(property_value_desc().get_value().get_parent_value_node().get());
616 case GDK_MOTION_NOTIFY:
617 curr_time=((float)event->motion.x-(float)cell_area.get_x())/(float)cell_area.get_width()*(adjustment->get_upper()-adjustment->get_lower())+adjustment->get_lower();
621 Gdk::ModifierType mod;
622 Gdk::Event(event).get_state(mod);
626 case GDK_BUTTON_PRESS:
627 case GDK_BUTTON_RELEASE:
629 curr_time=((float)event->button.x-(float)cell_area.get_x())/(float)cell_area.get_width()*(adjustment->get_upper()-adjustment->get_lower())+adjustment->get_lower();
631 Gdk::ModifierType mod;
632 Gdk::Event(event).get_state(mod);
637 actual_time = curr_time;
639 curr_time=curr_time.round(canvas->rend_desc().get_frame_rate());
640 selected_time=curr_time;
642 Time pixel_width((adjustment->get_upper()-adjustment->get_lower())/cell_area.get_width());
646 case GDK_BUTTON_PRESS:
647 //selected_time=((float)event->button.x-(float)cell_area.get_x())/(float)cell_area.get_width()*(adjustment->get_upper()-adjustment->get_lower())+adjustment->get_lower();
649 //Deal with time point selection, but only if they aren't involved in the insanity...
650 if(/*!value_node && */event->button.button == 1)
654 /*! UI specification:
656 When nothing is selected, clicking on a point in either normal mode or
657 additive mode will select the time point closest to the click.
658 Subtractive click will do nothing
660 When things are already selected, clicking on a selected point does
661 nothing (in both normal and add mode). Add mode clicking on an unselected
662 point adds it to the set. Normal clicking on an unselected point will
663 select only that one time point. Subtractive clicking on any point
664 will remove it from the the set if it is included.
667 synfigapp::ValueDesc valdesc = property_value_desc().get_value();
668 const Node::time_set *tset = get_times_from_vdesc(valdesc);
669 const synfig::Time time_offset = get_time_offset_from_vdesc(valdesc);
671 bool clickfound = tset && get_closest_time(*tset,actual_time+time_offset,pixel_width*cell_area.get_height(),stime);
672 bool selectmode = mode & SELECT_MASK;
674 //NOTE LATER ON WE SHOULD MAKE IT SO MULTIPLE VALUENODES CAN BE SELECTED AT ONCE
675 //we want to jump to the value desc if we're not currently on it
676 // but only if we want to add the point
677 if(clickfound && !(sel_value == valdesc))
683 //now that we've made sure we're selecting the correct value, deal with the already selected points
684 set<Time>::iterator foundi = clickfound ? sel_times.find(stime) : sel_times.end();
685 bool found = foundi != sel_times.end();
687 //remove all other points from our list... (only select the one we need)
688 if(!selectmode && !found)
693 if(found && selectmode) //remove a single already selected point
695 sel_times.erase(foundi);
696 }else if(clickfound) //otherwise look at adding it
698 //for replace the list was cleared earlier, and for add it wasn't so it works
699 sel_times.insert(stime);
706 iter=find_waypoint(selected_time,pixel_width*cell_area.get_height()/2);
707 selected_waypoint=iter;
715 selected=synfig::UniqueID::nil();
718 if((!sel_times.empty() || selection) && event->button.button==1)
721 drag_time=selected_time;
722 actual_dragtime=actual_time;
724 //selected_time=iter->time;
727 // Activepoint Selection
728 if(parent_value_node)
730 const int index(property_value_desc().get_value().get_index());
731 const synfig::ValueNode_DynamicList::ListEntry::ActivepointList& activepoint_list(parent_value_node->list[index].timing_info);
732 synfig::ValueNode_DynamicList::ListEntry::ActivepointList::const_iterator iter;
734 for(iter=activepoint_list.begin();iter!=activepoint_list.end();++iter)
736 Time val=abs(iter->time-selected_time);
744 // Perhaps I should signal if we selected this activepoint?
747 if(event->button.button==3)
750 synfigapp::ValueDesc valdesc = property_value_desc().get_value();
751 const Node::time_set *tset = get_times_from_vdesc(valdesc);
752 synfig::Time time_offset = get_time_offset_from_vdesc(valdesc);
754 bool clickfound = tset && get_closest_time(*tset,actual_time+time_offset,pixel_width*cell_area.get_height(),stime);
756 etl::handle<synfig::Node> node;
757 if(valdesc.get_value(stime).get_type()==ValueBase::TYPE_CANVAS)
759 node=Canvas::Handle(valdesc.get_value(stime).get(Canvas::Handle()));
761 else //if(valdesc.is_value_node())
763 node=valdesc.get_value_node();
766 if(clickfound && node)
768 show_timepoint_menu(node, stime, time_offset, actual_time+time_offset<stime?SIDE_LEFT:SIDE_RIGHT);
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();
779 case GDK_BUTTON_RELEASE:
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();
784 /*if(event->button.button==3 && selection)
786 signal_waypoint_clicked_(path,*selected_waypoint,event->button.button-1);
791 //Time point stuff...
792 if(event->button.button == 1)
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))))
798 synfigapp::Action::ParamList param_list;
799 param_list.add("canvas",canvas_interface()->get_canvas());
800 param_list.add("canvas_interface",canvas_interface());
802 if(sel_value.get_value_type() == synfig::ValueBase::TYPE_CANVAS)
804 param_list.add("addcanvas",sel_value.get_value().get(Canvas::Handle()));
807 param_list.add("addvaluedesc",sel_value);
811 std::set<synfig::Time>::iterator i = sel_times.begin(), end = sel_times.end();
814 param_list.add("addtime",*i);
816 newset.insert((*i + deltatime).round(get_canvas()->rend_desc().get_frame_rate()));
820 param_list.add("deltatime",deltatime);
821 // param_list.add("time",canvas_interface()->get_time());
823 if(mode & COPY_MASK) //copy
825 etl::handle<studio::Instance>::cast_static(canvas_interface()->get_instance())
826 ->process_action("timepoint_copy", param_list);
827 }else if(delmode) //DELETE
829 etl::handle<studio::Instance>::cast_static(canvas_interface()->get_instance())
830 ->process_action("timepoint_delete", param_list);
833 etl::handle<studio::Instance>::cast_static(canvas_interface()->get_instance())
834 ->process_action("timepoint_move", param_list);
837 //now replace all the selected with the new selected
844 /*if(value_node && selection)
846 if(selected_time==drag_time && event->button.button!=3)
847 signal_waypoint_clicked_(path,*selected_waypoint,event->button.button-1);
849 if(event->button.button==1)
851 synfig::Waypoint waypoint(*selected_waypoint);
852 Time newtime((waypoint.get_time()+(selected_time-drag_time)).round(canvas->rend_desc().get_frame_rate()));
853 if(waypoint.get_time()!=newtime)
855 waypoint.set_time(newtime);
856 signal_waypoint_changed_(waypoint,value_node);
862 // selected_time=iter->time;
863 //selected_time=iter->get_time();
867 //std::cerr<<"unknown event type "<<event->type<<std::endl;
879 // The following three functions don't get documented correctly by
880 // doxygen 1.5.[23] because of a bug with any function whose name
881 // begins with 'property'. Fixed in doxygen 1.5.4 apparently. See
882 // http://bugzilla.gnome.org/show_bug.cgi?id=471185 .
883 Glib::PropertyProxy<synfigapp::ValueDesc>
884 CellRenderer_TimeTrack::property_value_desc()
886 return Glib::PropertyProxy<synfigapp::ValueDesc>(this,"value_desc");
889 Glib::PropertyProxy<synfig::Canvas::Handle>
890 CellRenderer_TimeTrack::property_canvas()
892 return Glib::PropertyProxy<synfig::Canvas::Handle>(this,"canvas");
895 Glib::PropertyProxy<Gtk::Adjustment* >
896 CellRenderer_TimeTrack::property_adjustment()
898 return Glib::PropertyProxy<Gtk::Adjustment* >(this,"adjustment");
902 CellRenderer_TimeTrack::set_canvas_interface(etl::loose_handle<synfigapp::CanvasInterface> h)
904 canvas_interface_ = h;
908 set_waypoint_model(std::set<synfig::Waypoint, std::less<UniqueID> > waypoints, Waypoint::Model model, etl::loose_handle<synfigapp::CanvasInterface> canvas_interface)
910 // Create the action group
911 synfigapp::Action::PassiveGrouper group(canvas_interface->get_instance().get(),_("Change Waypoint Group"));
913 std::set<synfig::Waypoint, std::less<UniqueID> >::const_iterator iter;
914 for(iter=waypoints.begin();iter!=waypoints.end();++iter)
916 Waypoint waypoint(*iter);
917 waypoint.apply_model(model);
919 synfigapp::Action::Handle action(synfigapp::Action::create("waypoint_set"));
923 action->set_param("canvas",canvas_interface->get_canvas());
924 action->set_param("canvas_interface",canvas_interface);
926 action->set_param("waypoint",waypoint);
927 action->set_param("value_node",waypoint.get_parent_value_node());
929 if(!canvas_interface->get_instance()->perform_action(action))
938 CellRenderer_TimeTrack::show_timepoint_menu(const etl::handle<synfig::Node>& node, const synfig::Time& time, const synfig::Time& time_offset, Side side)
940 std::set<synfig::Waypoint, std::less<UniqueID> > waypoint_set;
942 n=synfig::waypoint_collect(waypoint_set,time,node);
944 Gtk::Menu* menu(manage(new Gtk::Menu()));
945 menu->signal_hide().connect(sigc::bind(sigc::ptr_fun(&delete_widget), menu));
947 // Create the interpolation method menu
948 if(!waypoint_set.empty())
950 Gtk::Menu* interp_menu(manage(new Gtk::Menu()));
951 // no need to connect to signal_hide for this one - it will be deleted when its parent is deleted
952 Waypoint::Model model;
954 // note: each of the following 4 'if' blocks provokes these warnings:
955 // /usr/include/sigc++-2.0/sigc++/adaptors/bound_argument.h:57: warning:
956 // 'model.synfig::Waypoint::Model::temporal_tension' is used uninitialized in this function
957 // 'model.synfig::Waypoint::Model::bias' is used uninitialized in this function
958 // 'model.synfig::Waypoint::Model::continuity' is used uninitialized in this function
959 // 'model.synfig::Waypoint::Model::tension' is used uninitialized in this function
960 // 'model.synfig::Waypoint::Model::priority' is used uninitialized in this function
961 // I don't know if that matters or not.
963 if(side==SIDE_LEFT)model.set_before(INTERPOLATION_TCB);
964 else model.set_after(INTERPOLATION_TCB);
965 interp_menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("TCB"),
967 sigc::ptr_fun(set_waypoint_model),
974 if(side==SIDE_LEFT)model.set_before(INTERPOLATION_LINEAR);
975 else model.set_after(INTERPOLATION_LINEAR);
976 interp_menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("Linear"),
978 sigc::ptr_fun(set_waypoint_model),
985 if(side==SIDE_LEFT)model.set_before(INTERPOLATION_HALT);
986 else model.set_after(INTERPOLATION_HALT);
987 interp_menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("Ease"),
989 sigc::ptr_fun(set_waypoint_model),
996 if(side==SIDE_LEFT)model.set_before(INTERPOLATION_CONSTANT);
997 else model.set_after(INTERPOLATION_CONSTANT);
998 interp_menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("Constant"),
1000 sigc::ptr_fun(set_waypoint_model),
1008 menu->items().push_back(
1009 Gtk::Menu_Helpers::MenuElem(
1010 side==SIDE_LEFT?_("Change \"In\" Interp."):_("Change \"Out\" Interp."),
1016 menu->items().push_back(Gtk::Menu_Helpers::StockMenuElem(Gtk::StockID("gtk-jump-to"),
1019 *canvas_interface(),
1020 &synfigapp::CanvasInterface::set_time
1026 if(!waypoint_set.empty())
1028 // attempting to locate the valuenode for the clicked waypoint doesn't work if this is a Canvas parameter,
1029 // so act as if there were multiple waypoints in that case as a workaround
1030 if(waypoint_set.size()==1 && !Canvas::Handle::cast_dynamic(node))
1034 signal_waypoint_clicked_(" ",*waypoint_set.begin(),2);
1038 synfig::info("Too many waypoints under me");
1041 synfig::info("ZERO waypoints under me");
1043 if(menu)menu->popup(3,gtk_get_current_event_time());