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:
773 //if(selection && dragging)
774 // selected_time=((float)event->motion.x-(float)cell_area.get_x())/(float)cell_area.get_width()*(adjustment->get_upper()-adjustment->get_lower())+adjustment->get_lower();
778 case GDK_BUTTON_RELEASE:
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();
783 /*if(event->button.button==3 && selection)
785 signal_waypoint_clicked_(path,*selected_waypoint,event->button.button-1);
790 //Time point stuff...
791 if(event->button.button == 1)
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))))
797 synfigapp::Action::ParamList param_list;
798 param_list.add("canvas",canvas_interface()->get_canvas());
799 param_list.add("canvas_interface",canvas_interface());
801 if(sel_value.get_value_type() == synfig::ValueBase::TYPE_CANVAS)
803 param_list.add("addcanvas",sel_value.get_value().get(Canvas::Handle()));
806 param_list.add("addvaluedesc",sel_value);
810 std::set<synfig::Time>::iterator i = sel_times.begin(), end = sel_times.end();
813 param_list.add("addtime",*i);
815 newset.insert((*i + deltatime).round(get_canvas()->rend_desc().get_frame_rate()));
819 param_list.add("deltatime",deltatime);
820 // param_list.add("time",canvas_interface()->get_time());
822 if(mode & COPY_MASK) //copy
824 etl::handle<studio::Instance>::cast_static(canvas_interface()->get_instance())
825 ->process_action("timepoint_copy", param_list);
826 }else if(delmode) //DELETE
828 etl::handle<studio::Instance>::cast_static(canvas_interface()->get_instance())
829 ->process_action("timepoint_delete", param_list);
832 etl::handle<studio::Instance>::cast_static(canvas_interface()->get_instance())
833 ->process_action("timepoint_move", param_list);
836 //now replace all the selected with the new selected
843 /*if(value_node && selection)
845 if(selected_time==drag_time && event->button.button!=3)
846 signal_waypoint_clicked_(path,*selected_waypoint,event->button.button-1);
848 if(event->button.button==1)
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)
854 waypoint.set_time(newtime);
855 signal_waypoint_changed_(waypoint,value_node);
861 // selected_time=iter->time;
862 //selected_time=iter->get_time();
866 //std::cerr<<"unknown event type "<<event->type<<std::endl;
878 // The following three functions don't get documented correctly by
879 // doxygen 1.5.[23] because of a bug with any function whose name
880 // begins with 'property'. Fixed in doxygen 1.5.4 apparently. See
881 // http://bugzilla.gnome.org/show_bug.cgi?id=471185 .
882 Glib::PropertyProxy<synfigapp::ValueDesc>
883 CellRenderer_TimeTrack::property_value_desc()
885 return Glib::PropertyProxy<synfigapp::ValueDesc>(this,"value_desc");
888 Glib::PropertyProxy<synfig::Canvas::Handle>
889 CellRenderer_TimeTrack::property_canvas()
891 return Glib::PropertyProxy<synfig::Canvas::Handle>(this,"canvas");
894 Glib::PropertyProxy<Gtk::Adjustment* >
895 CellRenderer_TimeTrack::property_adjustment()
897 return Glib::PropertyProxy<Gtk::Adjustment* >(this,"adjustment");
901 CellRenderer_TimeTrack::set_canvas_interface(etl::loose_handle<synfigapp::CanvasInterface> h)
903 canvas_interface_ = h;
907 set_waypoint_model(std::set<synfig::Waypoint, std::less<UniqueID> > waypoints, Waypoint::Model model, etl::loose_handle<synfigapp::CanvasInterface> canvas_interface)
909 // Create the action group
910 synfigapp::Action::PassiveGrouper group(canvas_interface->get_instance().get(),_("Change Waypoint Group"));
912 std::set<synfig::Waypoint, std::less<UniqueID> >::const_iterator iter;
913 for(iter=waypoints.begin();iter!=waypoints.end();++iter)
915 Waypoint waypoint(*iter);
916 waypoint.apply_model(model);
918 synfigapp::Action::Handle action(synfigapp::Action::create("waypoint_set"));
922 action->set_param("canvas",canvas_interface->get_canvas());
923 action->set_param("canvas_interface",canvas_interface);
925 action->set_param("waypoint",waypoint);
926 action->set_param("value_node",waypoint.get_parent_value_node());
928 if(!canvas_interface->get_instance()->perform_action(action))
937 CellRenderer_TimeTrack::show_timepoint_menu(const etl::handle<synfig::Node>& node, const synfig::Time& time, const synfig::Time& time_offset, Side side)
939 std::set<synfig::Waypoint, std::less<UniqueID> > waypoint_set;
941 n=synfig::waypoint_collect(waypoint_set,time,node);
943 Gtk::Menu* menu(manage(new Gtk::Menu()));
944 menu->signal_hide().connect(sigc::bind(sigc::ptr_fun(&delete_widget), menu));
946 // Create the interpolation method menu
947 if(!waypoint_set.empty())
949 Gtk::Menu* interp_menu(manage(new Gtk::Menu()));
950 // no need to connect to signal_hide for this one - it will be deleted when its parent is deleted
951 Waypoint::Model model;
953 // note: each of the following 4 'if' blocks provokes these warnings:
954 // /usr/include/sigc++-2.0/sigc++/adaptors/bound_argument.h:57: warning:
955 // 'model.synfig::Waypoint::Model::temporal_tension' is used uninitialized in this function
956 // 'model.synfig::Waypoint::Model::bias' is used uninitialized in this function
957 // 'model.synfig::Waypoint::Model::continuity' is used uninitialized in this function
958 // 'model.synfig::Waypoint::Model::tension' is used uninitialized in this function
959 // 'model.synfig::Waypoint::Model::priority' is used uninitialized in this function
960 // I don't know if that matters or not.
962 if(side==SIDE_LEFT)model.set_before(INTERPOLATION_TCB);
963 else model.set_after(INTERPOLATION_TCB);
964 interp_menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("TCB"),
966 sigc::ptr_fun(set_waypoint_model),
973 if(side==SIDE_LEFT)model.set_before(INTERPOLATION_LINEAR);
974 else model.set_after(INTERPOLATION_LINEAR);
975 interp_menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("Linear"),
977 sigc::ptr_fun(set_waypoint_model),
984 if(side==SIDE_LEFT)model.set_before(INTERPOLATION_HALT);
985 else model.set_after(INTERPOLATION_HALT);
986 interp_menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("Ease"),
988 sigc::ptr_fun(set_waypoint_model),
995 if(side==SIDE_LEFT)model.set_before(INTERPOLATION_CONSTANT);
996 else model.set_after(INTERPOLATION_CONSTANT);
997 interp_menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("Constant"),
999 sigc::ptr_fun(set_waypoint_model),
1007 menu->items().push_back(
1008 Gtk::Menu_Helpers::MenuElem(
1009 side==SIDE_LEFT?_("Change \"In\" Interp."):_("Change \"Out\" Interp."),
1015 menu->items().push_back(Gtk::Menu_Helpers::StockMenuElem(Gtk::StockID("gtk-jump-to"),
1018 *canvas_interface(),
1019 &synfigapp::CanvasInterface::set_time
1025 if(!waypoint_set.empty())
1027 // attempting to locate the valuenode for the clicked waypoint doesn't work if this is a Canvas parameter,
1028 // so act as if there were multiple waypoints in that case as a workaround
1029 if(waypoint_set.size()==1 && !Canvas::Handle::cast_dynamic(node))
1033 signal_waypoint_clicked_(" ",*waypoint_set.begin(),2);
1037 synfig::info("Too many waypoints under me");
1040 synfig::info("ZERO waypoints under me");
1042 if(menu)menu->popup(3,gtk_get_current_event_time());