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 synfig::info("CellRenderer_TimeTrack::~CellRenderer_TimeTrack(): deleted");
99 CellRenderer_TimeTrack::set_adjustment(Gtk::Adjustment &x)
101 property_adjustment_=&x;
102 // x.signal_value_changed().connect(sigc::mem_fun(*this,&Gtk::Widget::queue_draw));
105 synfig::Canvas::Handle
106 CellRenderer_TimeTrack::get_canvas()const
108 return const_cast<CellRenderer_TimeTrack*>(this)->property_canvas().get_value();
112 CellRenderer_TimeTrack::get_adjustment()
114 return (Gtk::Adjustment*)property_adjustment_;
117 const Gtk::Adjustment *
118 CellRenderer_TimeTrack::get_adjustment()const
120 return (const Gtk::Adjustment*)property_adjustment_;
124 CellRenderer_TimeTrack::is_selected(const Waypoint& waypoint)const
126 return selected==waypoint;
129 const synfig::Time get_time_offset_from_vdesc(const synfigapp::ValueDesc &v)
131 #ifdef ADJUST_WAYPOINTS_FOR_TIME_OFFSET
132 if(v.get_value_type() != synfig::ValueBase::TYPE_CANVAS)
133 return synfig::Time::zero();
135 synfig::Canvas::Handle canvasparam = v.get_value().get(Canvas::Handle());
137 return synfig::Time::zero();
139 if (!v.parent_is_layer_param())
140 return synfig::Time::zero();
142 synfig::Layer::Handle layer = v.get_layer();
144 if (layer->get_name()!="PasteCanvas")
145 return synfig::Time::zero();
147 return layer->get_param("time_offset").get(Time());
148 #else // ADJUST_WAYPOINTS_FOR_TIME_OFFSET
149 return synfig::Time::zero();
153 //kind of a hack... pointer is ugly
154 const synfig::Node::time_set *get_times_from_vdesc(const synfigapp::ValueDesc &v)
156 if(v.get_value_type() == synfig::ValueBase::TYPE_CANVAS)
158 synfig::Canvas::Handle canvasparam = v.get_value().get(Canvas::Handle());
161 return &canvasparam->get_times();
164 ValueNode *base_value = v.get_value_node().get();
166 ValueNode_DynamicList *parent_value_node =
167 v.parent_is_value_node() ?
168 dynamic_cast<ValueNode_DynamicList *>(v.get_parent_value_node().get()) :
171 //we want a dynamic list entry to override the normal...
172 if(parent_value_node)
174 return &parent_value_node->list[v.get_index()].get_times();
175 }else if(base_value) //don't render stuff if it's just animated...
177 return &base_value->get_times();
182 bool get_closest_time(const synfig::Node::time_set &tset, const Time &t, const Time &range, Time &out)
184 Node::time_set::const_iterator i,j,end = tset.end();
186 // stop the crash mentioned in bug #1689282
187 // doesn't solve the underlying problem though, I don't think
188 if (tset.size() == 0)
190 synfig::error(__FILE__":%d: tset.size() == 0",__LINE__);
194 //TODO add in RangeGet so it's not so damn hard to click on points
196 i = tset.upper_bound(t); //where t is the lower bound, t < [first,i)
199 double dist = Time::end();
204 closest = i->get_time();
205 dist = abs(i->get_time() - t);
208 if(j != end && (abs(j->get_time() - t) < dist) )
210 closest = j->get_time();
211 dist = abs(j->get_time() - t);
214 if( dist <= range/2 )
224 CellRenderer_TimeTrack::render_vfunc(
225 const Glib::RefPtr<Gdk::Drawable>& window,
227 const Gdk::Rectangle& /*background_area*/,
228 const Gdk::Rectangle& area_,
229 const Gdk::Rectangle& /*expose_area*/,
230 Gtk::CellRendererState /*flags*/)
235 Glib::RefPtr<Gdk::GC> gc(Gdk::GC::create(window));
236 Glib::RefPtr<Gdk::GC> inactive_gc(Gdk::GC::create(window));
237 Gtk::Adjustment *adjustment=get_adjustment();
238 // Gtk::StateType state = Gtk::STATE_ACTIVE;
239 // Gtk::ShadowType shadow;
242 curr_time_color("#0000ff"),
243 inactive_color("#000000"),
244 keyframe_color("#a07f7f");
245 Gdk::Color activepoint_color[2];
247 activepoint_color[0]=Gdk::Color("#ff0000");
248 activepoint_color[1]=Gdk::Color("#00ff00");
250 inactive_gc->set_rgb_fg_color(inactive_color);
251 inactive_gc->set_stipple(Gdk::Bitmap::create(stipple_xpm,2,2));
252 inactive_gc->set_fill(Gdk::STIPPLED);
254 synfig::Canvas::Handle canvas(property_canvas().get_value());
256 synfigapp::ValueDesc value_desc = property_value_desc().get_value();
257 synfig::ValueNode *base_value = value_desc.get_value_node().get();
258 // synfig::ValueNode_Animated *value_node=dynamic_cast<synfig::ValueNode_Animated*>(base_value);
260 synfig::ValueNode_DynamicList *parent_value_node(0);
261 if(property_value_desc().get_value().parent_is_value_node())
262 parent_value_node=dynamic_cast<synfig::ValueNode_DynamicList*>(property_value_desc().get_value().get_parent_value_node().get());
264 // If the canvas is defined, then load up the keyframes
267 const synfig::KeyframeList& keyframe_list(canvas->keyframe_list());
268 synfig::KeyframeList::const_iterator iter;
270 for(iter=keyframe_list.begin();iter!=keyframe_list.end();++iter)
272 if(!iter->get_time().is_valid())
275 const int x((int)((float)area_.get_width()/(adjustment->get_upper()-adjustment->get_lower())*(iter->get_time()-adjustment->get_lower())));
276 if(iter->get_time()>=adjustment->get_lower() && iter->get_time()<adjustment->get_upper())
278 gc->set_rgb_fg_color(keyframe_color);
279 window->draw_rectangle(gc, true, area_.get_x()+x, area_.get_y(), 1, area_.get_height()+1);
284 //render all the time points that exist
286 const synfig::Node::time_set *tset = get_times_from_vdesc(value_desc);
290 const synfig::Time time_offset = get_time_offset_from_vdesc(value_desc);
291 synfig::Node::time_set::const_iterator i = tset->begin(), end = tset->end();
293 float lower = adjustment->get_lower(),
294 upper = adjustment->get_upper();
296 Glib::RefPtr<Gdk::GC> gc = Gdk::GC::create(widget.get_window());
298 Gdk::Rectangle area(area_);
299 gc->set_clip_rectangle(area);
300 gc->set_line_attributes(1,Gdk::LINE_SOLID,Gdk::CAP_BUTT,Gdk::JOIN_MITER);
302 bool valselected = sel_value.get_value_node() == base_value && !sel_times.empty();
304 float cfps = get_canvas()->rend_desc().get_frame_rate();
306 vector<Time> drawredafter;
308 Time diff = actual_time - actual_dragtime;//selected_time-drag_time;
311 //find the coordinate in the drawable space...
312 Time t_orig = i->get_time();
313 if(!t_orig.is_valid()) continue;
314 Time t = t_orig - time_offset;
316 //if it found it... (might want to change comparison, and optimize
317 // sel_times.find to not produce an overall nlogn solution)
320 //not dragging... just draw as per normal
321 //if move dragging draw offset
322 //if copy dragging draw both...
324 if(valselected && sel_times.find(t_orig) != sel_times.end())
326 if(dragging) //skip if we're dragging because we'll render it later
328 if(mode & COPY_MASK) // draw both blue and red moved
330 drawredafter.push_back(t + diff.round(cfps));
331 gc->set_rgb_fg_color(Gdk::Color("#00EEEE"));
332 }else if(mode & DELETE_MASK) //it's just red...
334 gc->set_rgb_fg_color(Gdk::Color("#EE0000"));
336 }else //move - draw the red on top of the others...
338 drawredafter.push_back(t + diff.round(cfps));
343 gc->set_rgb_fg_color(Gdk::Color("#EE0000"));
348 gc->set_rgb_fg_color(Gdk::Color("#00EEEE"));
351 //synfig::info("Displaying time: %.3f s",(float)t);
352 const int x = (int)((t-lower)*area.get_width()/(upper-lower));
354 //should draw me a grey filled circle...
355 Gdk::Rectangle area2(
356 area.get_x() - area.get_height()/2 + x + 1,
361 render_time_point_to_window(window,area2,*i - time_offset,selected);
363 /*window->draw_arc(gc,true,
364 area.get_x() + x - area.get_height()/4, area.get_y() + area.get_height()/8,
365 area.get_height()/2, area.get_height()*3/4,
368 gc->set_rgb_fg_color(Gdk::Color("#000000"));
369 window->draw_arc(gc,false,
370 area.get_x() + x - area.get_height()/4, area.get_y() + area.get_height()/8,
371 area.get_height()/2, area.get_height()*3/4,
377 vector<Time>::iterator i = drawredafter.begin(), end = drawredafter.end();
380 //find the coordinate in the drawable space...
386 //synfig::info("Displaying time: %.3f s",(float)t);
387 const int x = (int)((t-lower)*area.get_width()/(upper-lower));
389 //should draw me a grey filled circle...
391 Gdk::Rectangle area2(
392 area.get_x() - area.get_height()/2 + x + 1,
397 render_time_point_to_window(window,area2,*i,true);
398 /* gc->set_rgb_fg_color(Gdk::Color("#EE0000"));
399 window->draw_arc(gc,true,
400 area.get_x() + x - area.get_height()/4, area.get_y() + area.get_height()/8,
401 area.get_height()/2, area.get_height()*3/4,
404 gc->set_rgb_fg_color(Gdk::Color("#000000"));
405 window->draw_arc(gc,false,
406 area.get_x() + x - area.get_height()/4, area.get_y() + area.get_height()/8,
407 area.get_height()/2, area.get_height()*3/4,
415 /* THIS IS NOW HANDLED ENTIRELY BY THE TIMEPOINT SYSTEM
416 // This this is an animated value node, then render the waypoints
419 //now render the actual waypoints
420 synfig::ValueNode_Animated::WaypointList::iterator iter;
422 iter=value_node->waypoint_list().begin();
423 iter!=value_node->waypoint_list().end();
427 if(!iter->get_time().is_valid())
431 if(is_selected(*iter))
433 Time t(iter->get_time());
437 t=(t+selected_time-drag_time).round(get_canvas()->rend_desc().get_frame_rate());
439 x=(int)((float)area.get_width()/(adjustment->get_upper()-adjustment->get_lower())*(t-adjustment->get_lower()));
440 shadow=Gtk::SHADOW_IN;
445 x=(int)((float)area.get_width()/(adjustment->get_upper()-adjustment->get_lower())*(iter->get_time()-adjustment->get_lower()));
446 shadow=Gtk::SHADOW_OUT;
451 widget.get_style()->paint_diamond(
452 Glib::RefPtr<Gdk::Window>::cast_static(window),
458 area.get_x()+x-area.get_height()/4,
459 area.get_y()+area.get_height()/4,
466 Gdk::Rectangle area(area_);
467 // If the parent of this value node is a dynamic list, then
468 // render the on and off times
469 if(parent_value_node)
471 const int index(property_value_desc().get_value().get_index());
472 const synfig::ValueNode_DynamicList::ListEntry& list_entry(parent_value_node->list[index]);
473 const synfig::ValueNode_DynamicList::ListEntry::ActivepointList& activepoint_list(list_entry.timing_info);
474 synfig::ValueNode_DynamicList::ListEntry::ActivepointList::const_iterator iter,next;
477 if(!activepoint_list.empty())
478 is_off=!activepoint_list.front().state;
483 for(next=activepoint_list.begin(),iter=next++;iter!=activepoint_list.end();iter=next++)
485 x=((int)((float)area.get_width()/(adjustment->get_upper()-adjustment->get_lower())*(iter->time-adjustment->get_lower())));
487 if(x>area.get_width())x=area.get_width();
489 bool status_at_time=0;
490 if(next!=activepoint_list.end())
492 status_at_time=!list_entry.status_at_time((iter->time+next->time)/2.0);
495 status_at_time=!list_entry.status_at_time(Time::end());
497 if(!is_off && status_at_time)
503 if(is_off && !status_at_time)
505 window->draw_rectangle(inactive_gc, true, area.get_x()+xstart, area.get_y(), x-xstart, area.get_height());
510 if(!is_off && iter!=activepoint_list.end() && next->state==false && iter->state==false)
515 else if(is_off && next!=activepoint_list.end() && iter->state==false && next->state==true)
517 window->draw_rectangle(inactive_gc, true, area.get_x()+xstart, area.get_y(), x-xstart, area.get_height());
520 else if(is_off && iter!=activepoint_list.end() && iter->state==true)
522 window->draw_rectangle(inactive_gc, true, area.get_x()+xstart, area.get_y(), prevx-xstart, area.get_height());
529 if(iter->time>=adjustment->get_lower() && iter->time<adjustment->get_upper())
534 gc->set_rgb_fg_color(activepoint_color[iter->state]);
535 window->draw_rectangle(gc, true, area.get_x()+x-w/2, area.get_y(), w, area.get_height());
541 window->draw_rectangle(inactive_gc, true, area.get_x()+xstart, area.get_y(), area.get_width()-xstart, area.get_height());
545 // Render a line that defines the current tick in time
547 gc->set_rgb_fg_color(curr_time_color);
549 const int x((int)((float)area.get_width()/(adjustment->get_upper()-adjustment->get_lower())*(adjustment->get_value()-adjustment->get_lower())));
551 if(adjustment->get_value()>=adjustment->get_lower() && adjustment->get_value()<adjustment->get_upper())
552 window->draw_rectangle(gc, true, area.get_x()+x, area.get_y(), 1, area.get_height());
556 synfig::ValueNode_Animated::WaypointList::iterator
557 CellRenderer_TimeTrack::find_waypoint(const synfig::Time& /*t*/,const synfig::Time& scope)
559 synfig::ValueNode_Animated *value_node=dynamic_cast<synfig::ValueNode_Animated*>(property_value_desc().get_value().get_value_node().get());
561 Time nearest(Time::end());
563 synfig::ValueNode_Animated::WaypointList::iterator iter,ret;
568 iter=value_node->waypoint_list().begin();
569 iter!=value_node->waypoint_list().end();
573 Time val=abs(iter->get_time()-selected_time);
581 if(nearest!=Time::end() && nearest<scope)
590 CellRenderer_TimeTrack::activate_vfunc(
592 Gtk::Widget& /*widget*/,
593 const Glib::ustring& treepath,
594 const Gdk::Rectangle& /*background_area*/,
595 const Gdk::Rectangle& cell_area,
596 Gtk::CellRendererState /*flags*/)
599 synfig::ValueNode_Animated::WaypointList::iterator iter;
600 Time nearest=1000000000;
601 Gtk::Adjustment *adjustment=get_adjustment();
603 // synfig::ValueNode_Animated *value_node=dynamic_cast<synfig::ValueNode_Animated*>(property_value_desc().get_value().get_value_node().get());
605 synfig::Canvas::Handle canvas(get_canvas());
607 synfig::ValueNode_DynamicList *parent_value_node(0);
608 if(property_value_desc().get_value().parent_is_value_node())
609 parent_value_node=dynamic_cast<synfig::ValueNode_DynamicList*>(property_value_desc().get_value().get_parent_value_node().get());
615 case GDK_MOTION_NOTIFY:
616 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();
620 Gdk::ModifierType mod;
621 Gdk::Event(event).get_state(mod);
625 case GDK_BUTTON_PRESS:
626 case GDK_BUTTON_RELEASE:
628 curr_time=((float)event->button.x-(float)cell_area.get_x())/(float)cell_area.get_width()*(adjustment->get_upper()-adjustment->get_lower())+adjustment->get_lower();
630 Gdk::ModifierType mod;
631 Gdk::Event(event).get_state(mod);
636 actual_time = curr_time;
638 curr_time=curr_time.round(canvas->rend_desc().get_frame_rate());
639 selected_time=curr_time;
641 Time pixel_width((adjustment->get_upper()-adjustment->get_lower())/cell_area.get_width());
645 case GDK_BUTTON_PRESS:
646 //selected_time=((float)event->button.x-(float)cell_area.get_x())/(float)cell_area.get_width()*(adjustment->get_upper()-adjustment->get_lower())+adjustment->get_lower();
648 //Deal with time point selection, but only if they aren't involved in the insanity...
649 if(/*!value_node && */event->button.button == 1)
653 /*! UI specification:
655 When nothing is selected, clicking on a point in either normal mode or
656 additive mode will select the time point closest to the click.
657 Subtractive click will do nothing
659 When things are already selected, clicking on a selected point does
660 nothing (in both normal and add mode). Add mode clicking on an unselected
661 point adds it to the set. Normal clicking on an unselected point will
662 select only that one time point. Subtractive clicking on any point
663 will remove it from the the set if it is included.
666 synfigapp::ValueDesc valdesc = property_value_desc().get_value();
667 const Node::time_set *tset = get_times_from_vdesc(valdesc);
668 const synfig::Time time_offset = get_time_offset_from_vdesc(valdesc);
670 bool clickfound = tset && get_closest_time(*tset,actual_time+time_offset,pixel_width*cell_area.get_height(),stime);
671 bool selectmode = mode & SELECT_MASK;
673 //NOTE LATER ON WE SHOULD MAKE IT SO MULTIPLE VALUENODES CAN BE SELECTED AT ONCE
674 //we want to jump to the value desc if we're not currently on it
675 // but only if we want to add the point
676 if(clickfound && !(sel_value == valdesc))
682 //now that we've made sure we're selecting the correct value, deal with the already selected points
683 set<Time>::iterator foundi = clickfound ? sel_times.find(stime) : sel_times.end();
684 bool found = foundi != sel_times.end();
686 //remove all other points from our list... (only select the one we need)
687 if(!selectmode && !found)
692 if(found && selectmode) //remove a single already selected point
694 sel_times.erase(foundi);
695 }else if(clickfound) //otherwise look at adding it
697 //for replace the list was cleared earlier, and for add it wasn't so it works
698 sel_times.insert(stime);
705 iter=find_waypoint(selected_time,pixel_width*cell_area.get_height()/2);
706 selected_waypoint=iter;
714 selected=synfig::UniqueID::nil();
717 if((!sel_times.empty() || selection) && event->button.button==1)
720 drag_time=selected_time;
721 actual_dragtime=actual_time;
723 //selected_time=iter->time;
726 // Activepoint Selection
727 if(parent_value_node)
729 const int index(property_value_desc().get_value().get_index());
730 const synfig::ValueNode_DynamicList::ListEntry::ActivepointList& activepoint_list(parent_value_node->list[index].timing_info);
731 synfig::ValueNode_DynamicList::ListEntry::ActivepointList::const_iterator iter;
733 for(iter=activepoint_list.begin();iter!=activepoint_list.end();++iter)
735 Time val=abs(iter->time-selected_time);
743 // Perhaps I should signal if we selected this activepoint?
746 if(event->button.button==3)
749 synfigapp::ValueDesc valdesc = property_value_desc().get_value();
750 const Node::time_set *tset = get_times_from_vdesc(valdesc);
751 synfig::Time time_offset = get_time_offset_from_vdesc(valdesc);
753 bool clickfound = tset && get_closest_time(*tset,actual_time+time_offset,pixel_width*cell_area.get_height(),stime);
755 etl::handle<synfig::Node> node;
756 if(valdesc.get_value(stime).get_type()==ValueBase::TYPE_CANVAS)
758 node=Canvas::Handle(valdesc.get_value(stime).get(Canvas::Handle()));
760 else //if(valdesc.is_value_node())
762 node=valdesc.get_value_node();
765 if(clickfound && node)
767 show_timepoint_menu(node, stime, time_offset, actual_time+time_offset<stime?SIDE_LEFT:SIDE_RIGHT);
772 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:
783 //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();
786 /*if(event->button.button==3 && selection)
788 signal_waypoint_clicked_(path,*selected_waypoint,event->button.button-1);
793 //Time point stuff...
794 if(event->button.button == 1)
796 bool delmode = (mode & DELETE_MASK) && !(mode & COPY_MASK);
797 deltatime = actual_time - actual_dragtime;
798 if(sel_times.size() != 0 && (delmode || !deltatime.is_equal(Time(0))))
800 synfigapp::Action::ParamList param_list;
801 param_list.add("canvas",canvas_interface()->get_canvas());
802 param_list.add("canvas_interface",canvas_interface());
804 if(sel_value.get_value_type() == synfig::ValueBase::TYPE_CANVAS)
806 param_list.add("addcanvas",sel_value.get_value().get(Canvas::Handle()));
809 param_list.add("addvaluedesc",sel_value);
813 std::set<synfig::Time>::iterator i = sel_times.begin(), end = sel_times.end();
816 param_list.add("addtime",*i);
818 newset.insert((*i + deltatime).round(get_canvas()->rend_desc().get_frame_rate()));
822 param_list.add("deltatime",deltatime);
823 // param_list.add("time",canvas_interface()->get_time());
825 if(mode & COPY_MASK) //copy
827 etl::handle<studio::Instance>::cast_static(canvas_interface()->get_instance())
828 ->process_action("timepoint_copy", param_list);
829 }else if(delmode) //DELETE
831 etl::handle<studio::Instance>::cast_static(canvas_interface()->get_instance())
832 ->process_action("timepoint_delete", param_list);
835 etl::handle<studio::Instance>::cast_static(canvas_interface()->get_instance())
836 ->process_action("timepoint_move", param_list);
839 //now replace all the selected with the new selected
846 /*if(value_node && selection)
848 if(selected_time==drag_time && event->button.button!=3)
849 signal_waypoint_clicked_(path,*selected_waypoint,event->button.button-1);
851 if(event->button.button==1)
853 synfig::Waypoint waypoint(*selected_waypoint);
854 Time newtime((waypoint.get_time()+(selected_time-drag_time)).round(canvas->rend_desc().get_frame_rate()));
855 if(waypoint.get_time()!=newtime)
857 waypoint.set_time(newtime);
858 signal_waypoint_changed_(waypoint,value_node);
864 // selected_time=iter->time;
865 //selected_time=iter->get_time();
869 //std::cerr<<"unknown event type "<<event->type<<std::endl;
881 // The following three functions don't get documented correctly by
882 // doxygen 1.5.[23] because of a bug with any function whose name
883 // begins with 'property'. Fixed in doxygen 1.5.4 apparently. See
884 // http://bugzilla.gnome.org/show_bug.cgi?id=471185 .
885 Glib::PropertyProxy<synfigapp::ValueDesc>
886 CellRenderer_TimeTrack::property_value_desc()
888 return Glib::PropertyProxy<synfigapp::ValueDesc>(this,"value_desc");
891 Glib::PropertyProxy<synfig::Canvas::Handle>
892 CellRenderer_TimeTrack::property_canvas()
894 return Glib::PropertyProxy<synfig::Canvas::Handle>(this,"canvas");
897 Glib::PropertyProxy<Gtk::Adjustment* >
898 CellRenderer_TimeTrack::property_adjustment()
900 return Glib::PropertyProxy<Gtk::Adjustment* >(this,"adjustment");
904 CellRenderer_TimeTrack::set_canvas_interface(etl::loose_handle<synfigapp::CanvasInterface> h)
906 canvas_interface_ = h;
910 set_waypoint_model(std::set<synfig::Waypoint, std::less<UniqueID> > waypoints, Waypoint::Model model, etl::loose_handle<synfigapp::CanvasInterface> canvas_interface)
912 // Create the action group
913 synfigapp::Action::PassiveGrouper group(canvas_interface->get_instance().get(),_("Change Waypoint Group"));
915 std::set<synfig::Waypoint, std::less<UniqueID> >::const_iterator iter;
916 for(iter=waypoints.begin();iter!=waypoints.end();++iter)
918 Waypoint waypoint(*iter);
919 waypoint.apply_model(model);
921 synfigapp::Action::Handle action(synfigapp::Action::create("waypoint_set"));
925 action->set_param("canvas",canvas_interface->get_canvas());
926 action->set_param("canvas_interface",canvas_interface);
928 action->set_param("waypoint",waypoint);
929 action->set_param("value_node",waypoint.get_parent_value_node());
931 if(!canvas_interface->get_instance()->perform_action(action))
940 CellRenderer_TimeTrack::show_timepoint_menu(const etl::handle<synfig::Node>& node, const synfig::Time& time, const synfig::Time& time_offset, Side side)
942 std::set<synfig::Waypoint, std::less<UniqueID> > waypoint_set;
944 n=synfig::waypoint_collect(waypoint_set,time,node);
946 Gtk::Menu* menu(manage(new Gtk::Menu()));
947 menu->signal_hide().connect(sigc::bind(sigc::ptr_fun(&delete_widget), menu));
949 // Create the interpolation method menu
950 if(!waypoint_set.empty())
952 Gtk::Menu* interp_menu(manage(new Gtk::Menu()));
953 // no need to connect to signal_hide for this one - it will be deleted when its parent is deleted
954 Waypoint::Model model;
956 // note: each of the following 4 'if' blocks provokes these warnings:
957 // /usr/include/sigc++-2.0/sigc++/adaptors/bound_argument.h:57: warning:
958 // 'model.synfig::Waypoint::Model::temporal_tension' is used uninitialized in this function
959 // 'model.synfig::Waypoint::Model::bias' is used uninitialized in this function
960 // 'model.synfig::Waypoint::Model::continuity' is used uninitialized in this function
961 // 'model.synfig::Waypoint::Model::tension' is used uninitialized in this function
962 // 'model.synfig::Waypoint::Model::priority' is used uninitialized in this function
963 // I don't know if that matters or not.
965 if(side==SIDE_LEFT)model.set_before(INTERPOLATION_TCB);
966 else model.set_after(INTERPOLATION_TCB);
967 interp_menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("TCB"),
969 sigc::ptr_fun(set_waypoint_model),
976 if(side==SIDE_LEFT)model.set_before(INTERPOLATION_LINEAR);
977 else model.set_after(INTERPOLATION_LINEAR);
978 interp_menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("Linear"),
980 sigc::ptr_fun(set_waypoint_model),
987 if(side==SIDE_LEFT)model.set_before(INTERPOLATION_HALT);
988 else model.set_after(INTERPOLATION_HALT);
989 interp_menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("Ease"),
991 sigc::ptr_fun(set_waypoint_model),
998 if(side==SIDE_LEFT)model.set_before(INTERPOLATION_CONSTANT);
999 else model.set_after(INTERPOLATION_CONSTANT);
1000 interp_menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("Constant"),
1002 sigc::ptr_fun(set_waypoint_model),
1010 menu->items().push_back(
1011 Gtk::Menu_Helpers::MenuElem(
1012 side==SIDE_LEFT?_("Change \"In\" Interp."):_("Change \"Out\" Interp."),
1018 menu->items().push_back(Gtk::Menu_Helpers::StockMenuElem(Gtk::StockID("gtk-jump-to"),
1021 *canvas_interface(),
1022 &synfigapp::CanvasInterface::set_time
1028 if(!waypoint_set.empty())
1030 // attempting to locate the valuenode for the clicked waypoint doesn't work if this is a Canvas parameter,
1031 // so act as if there were multiple waypoints in that case as a workaround
1032 if(waypoint_set.size()==1 && !Canvas::Handle::cast_dynamic(node))
1036 signal_waypoint_clicked_(" ",*waypoint_set.begin(),2);
1040 synfig::info("Too many waypoints under me");
1043 synfig::info("ZERO waypoints under me");
1045 if(menu)menu->popup(3,gtk_get_current_event_time());