/* === S Y N F I G ========================================================= */ /*! \file widget_keyframe_list.cpp ** \brief A custom widget to manage keyframes in the timeline. ** ** $Id$ ** ** \legal ** Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley ** Copyright (c) 2007 Chris Moore ** Copyright (c) 2009 Carlos López ** Copyright (c) 2012-2013 Konstantin Dmitriev ** ** This package is free software; you can redistribute it and/or ** modify it under the terms of the GNU General Public License as ** published by the Free Software Foundation; either version 2 of ** the License, or (at your option) any later version. ** ** This package is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** General Public License for more details. ** \endlegal */ /* ========================================================================= */ /* === H E A D E R S ======================================================= */ #ifdef USING_PCH # include "pch.h" #else #ifdef HAVE_CONFIG_H # include #endif #include "widget_keyframe_list.h" #include "app.h" #include #include #include #include "general.h" #endif /* === U S I N G =========================================================== */ using namespace std; using namespace etl; using namespace synfig; using namespace studio; /* === M A C R O S ========================================================= */ #define WIDGET_KEYFRAME_LIST_DEFAULT_FPS 24.0 /* === G L O B A L S ======================================================= */ /* === P R O C E D U R E S ================================================= */ /* === M E T H O D S ======================================================= */ Widget_Keyframe_List::Widget_Keyframe_List(): adj_default(Gtk::Adjustment::create(0,0,2,1/WIDGET_KEYFRAME_LIST_DEFAULT_FPS,10/WIDGET_KEYFRAME_LIST_DEFAULT_FPS)), kf_list_(&default_kf_list_), time_ratio("4f", WIDGET_KEYFRAME_LIST_DEFAULT_FPS) { editable_=true; fps=WIDGET_KEYFRAME_LIST_DEFAULT_FPS; set_size_request(-1,64); //! The widget respond to mouse button press and release and to //! left button motion add_events(Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK); add_events(Gdk::BUTTON1_MOTION_MASK /*| Gdk::BUTTON3_MOTION_MASK*/); add_events(Gdk::POINTER_MOTION_MASK); set_time_adjustment(adj_default); queue_draw(); //! Create the window of the moving tooltip moving_tooltip_ = Gtk::manage(new Gtk::Window(Gtk::WINDOW_POPUP)); moving_tooltip_->set_resizable(false); moving_tooltip_->set_name("gtk-tooltips"); moving_tooltip_->set_border_width (4); moving_tooltip_->set_default_size(10, 10); moving_tooltip_->set_type_hint(Gdk::WINDOW_TYPE_HINT_TOOLTIP); moving_tooltip_label_ = Gtk::manage(new Gtk::Label()); moving_tooltip_label_->set_alignment(0.5, 0.5); moving_tooltip_label_->show(); moving_tooltip_->add(*moving_tooltip_label_); } Widget_Keyframe_List::~Widget_Keyframe_List() { } void Widget_Keyframe_List::draw_arrow( const Cairo::RefPtr &cr, double x, double y, double width, double height, bool fill, const synfig::Color &color ) { cr->set_source_rgba(color.get_r(), color.get_g(), color.get_b(), color.get_a()); cr->set_line_width(1.0); cr->move_to(x, y); cr->line_to(x - 0.5*width, y - height); cr->line_to(x + 0.5*width, y - height); cr->close_path(); if (fill) cr->fill(); else cr->stroke(); } bool Widget_Keyframe_List::on_draw(const Cairo::RefPtr &cr) { //!Check if the window we want draw is ready Glib::RefPtr window = get_window(); if(!window) return true; const double h(get_height()); const double w(get_width()); const double y(h - 2); const double ah(h - 4); const double aw(2*ah); //!Boundaries of the drawing area in time units. synfig::Time top(adj_timescale->get_upper()); synfig::Time bottom(adj_timescale->get_lower()); //! The style of the widget. Glib::RefPtr style = get_style_context(); Gtk::StateFlags state = style->get_state(); //TODO hardcoded colors //! Colors Color background(0.46, 0.55, 0.70, 1.0); Color normal(0.0, 0.0, 0.0, 1.0); Color selected(1.0, 1.0, 1.0, 1.0); Color drag_old_position(1.0, 1.0, 1.0, 0.6); Color drag_new_position(1.0, 1.0, 1.0, 1.0); //! Draw a background cr->set_source_rgba(background.get_r(), background.get_g(), background.get_b(), background.get_a()); cr->rectangle(0.0, 0.0, w, h); cr->fill(); cr->restore(); if(!editable_) { return true; //needs fixing! } //!Returns if there are not keyframes to draw. if (kf_list_->empty()) return false; //!Loop all the keyframes synfig::KeyframeList::iterator iter,selected_iter; bool show_selected(false); for(iter=kf_list_->begin();iter!=kf_list_->end();iter++) { if (iter->get_time()>top || iter->get_time()get_time()-bottom) * (w/(top-bottom)); draw_arrow(cr, x, y, aw, ah, iter->active(), normal); } else { selected_iter=iter; show_selected=true; } } // we do this so that we can be sure that // the selected keyframe is shown on top if(show_selected) { if (!dragging_) { const double x = (double)(selected_iter->get_time()-bottom) * (w/(top-bottom)); draw_arrow(cr, x, y, aw, ah, selected_iter->active(), selected); } else { const double prev_x = (double)(selected_iter->get_time()-bottom) * (w/(top-bottom)); const double new_x = (double)(dragging_kf_time-bottom) * (w/(top-bottom)); draw_arrow(cr, prev_x, y, aw, ah, selected_iter->active(), drag_old_position); draw_arrow(cr, new_x, y, aw, ah, selected_iter->active(), drag_new_position); } } return true; } void Widget_Keyframe_List::set_kf_list(synfig::KeyframeList* x) { kf_list_=x; set_selected_keyframe(selected_none); selected_=false; dragging_=false; } void Widget_Keyframe_List::set_selected_keyframe(const synfig::Keyframe &x) { if (x == selected_none) return; if (x == selected_kf) { // synfig::Keyframe::operator== only on uniqueid::operator== // \see synfig::UniqueID::operator== // In all case, refresh keyframe description to do not loose it selected_kf.set_description(x.get_description()); // refresh keyframe time also. selected_kf.set_time(x.get_time()); // and activation status selected_kf.set_active(x.active()); return; } selected_kf=x; selected_=true; dragging_kf_time=selected_kf.get_time(); if(canvas_interface_) canvas_interface_->signal_keyframe_selected()(selected_kf, (void*)this); dragging_=false; queue_draw(); } void Widget_Keyframe_List::on_keyframe_selected(synfig::Keyframe keyframe, void* emitter) { if((void*)this == emitter) return; if (keyframe == selected_kf) return; selected_kf=keyframe; selected_=true; dragging_kf_time=selected_kf.get_time(); dragging_=false; queue_draw(); } bool Widget_Keyframe_List::perform_move_kf(bool delta=false) { if(!selected_) return false; if(dragging_kf_time == selected_kf.get_time()) return false; // change this checking if not sticked to integer frames Time selected_kf_time(selected_kf.get_time()); Time prev, next; kf_list_->find_prev_next(selected_kf_time, prev, next); // Not possible to set delta to the first keyframe // perform normal movement // As suggested by Zelgadis it is better to not perform anything. if (prev==Time::begin() && delta==true) { synfig::info(_("Not possible to ALT-drag the first keyframe")); return false; } if(!delta) { synfigapp::Action::Handle action(synfigapp::Action::create("KeyframeSet")); if(!action) return false; selected_kf.set_time(dragging_kf_time); action->set_param("canvas",canvas_interface_->get_canvas()); action->set_param("canvas_interface",canvas_interface_); action->set_param("keyframe",selected_kf); try { canvas_interface_->get_instance()->perform_action(action); canvas_interface_->signal_keyframe_selected()(selected_kf, (void*)this); } catch(...) { return false; } } else { // find prev from selected kf time including deactivated kf Keyframe prev_kf(*kf_list_->find_prev(selected_kf_time, false)); Time prev_kf_time(prev_kf.get_time()); if (prev_kf_time >= dragging_kf_time) //Not allowed { synfig::warning(_("Delta set not allowed")); synfig::info("Widget_Keyframe_List::perform_move_kf(%i)::prev_kf_time=%s", delta, prev_kf_time.get_string().c_str()); synfig::info("Widget_Keyframe_List::perform_move_kf(%i)::dragging_kf_time=%s", delta, dragging_kf_time.get_string().c_str()); return false; } else { Time old_delta_time(selected_kf_time-prev_kf_time); Time new_delta_time(dragging_kf_time-prev_kf_time); Time change_delta(new_delta_time-old_delta_time); synfigapp::Action::Handle action(synfigapp::Action::create("KeyframeSetDelta")); if(!action) return false; action->set_param("canvas",canvas_interface_->get_canvas()); action->set_param("canvas_interface",canvas_interface_); action->set_param("keyframe",prev_kf); action->set_param("delta",change_delta); canvas_interface_->get_instance()->perform_action(action); } } queue_draw(); return true; } bool Widget_Keyframe_List::on_event(GdkEvent *event) { //Do not respond mouse events if the list is empty if(!kf_list_->size()) return true; const int x(static_cast(event->button.x)); //const int y(static_cast(event->button.y)); //!Boundaries of the drawing area in time units. synfig::Time top(adj_timescale->get_upper()); synfig::Time bottom(adj_timescale->get_lower()); //!pos is the [0,1] relative horizontal place on the widget float pos((float)x/(get_width())); if(pos<0.0f)pos=0.0f; if(pos>1.0f)pos=1.0f; //! The time where the event x is synfig::Time t((float)(bottom+pos*(top-bottom))); //! here the guts of the event switch(event->type) { case GDK_MOTION_NOTIFY: if(editable_) { // here is captured mouse motion // AND left or right mouse button pressed if (event->motion.state & (GDK_BUTTON1_MASK /*| GDK_BUTTON3_MASK*/)) { if (!selected_) return true; // stick to integer frames. It can be optional in the future if(fps) t = floor(t*fps + 0.5)/fps; dragging_kf_time=t; dragging_=true; queue_draw(); //! Moving tooltip displaying the dragging time { int x_root = static_cast(event->button.x_root); int x_origin; int y_origin; get_window()->get_origin (x_origin, y_origin); Glib::ustring tooltip_label (_("Time : ")); tooltip_label.append( dragging_kf_time.get_string(fps,App::get_time_format()) ); tooltip_label.append("\n"); tooltip_label.append( _("Old Time : ") ); tooltip_label.append(selected_kf.get_time().get_string(fps,App::get_time_format())); moving_tooltip_label_->set_text (tooltip_label); if(!moving_tooltip_->get_visible ()) { //! Show the tooltip and fix his y coordinate (up to the widget) moving_tooltip_->show(); moving_tooltip_y_ = y_origin - moving_tooltip_->get_height(); } //! Move the tooltip to a nice position moving_tooltip_->move(x_root, moving_tooltip_y_); } return true; } // here is captured mouse motion // AND NOT left or right mouse button pressed else { Glib::ustring ttip=""; synfig::Time p_t,n_t; kf_list_->find_prev_next(t, p_t, n_t); if( (p_t==Time::begin() && n_t==Time::end()) || ((t-p_t)>time_ratio && (n_t-t)>time_ratio) ) { ttip = _("Click and drag keyframes"); } else if ((t-p_t)<(n_t-t)) { synfig::Keyframe kf(*kf_list_->find_prev(t)); synfig::String kf_name(kf.get_description().c_str()); ttip = (kf_name.length() == 0)? _("No name") : kf_name.c_str(); } else { synfig::Keyframe kf(*kf_list_->find_next(t)); synfig::String kf_name(kf.get_description().c_str()); ttip = (kf_name.length() == 0)? _("No name") : kf_name.c_str(); } set_tooltip_text(ttip); dragging_=false; queue_draw(); return true; } } break; case GDK_BUTTON_PRESS: changed_=false; dragging_=false; if(event->button.button==1 || event->button.button==3) { if(editable_) { synfig::Time prev_t,next_t; kf_list_->find_prev_next(t, prev_t, next_t, false); if( (prev_t==Time::begin() && next_t==Time::end()) || ((t-prev_t)>time_ratio && (next_t-t)>time_ratio) ) { switch(event->button.button) { case 1: set_selected_keyframe(selected_none); selected_=false; queue_draw(); break; case 3: Gtk::Menu* menu = dynamic_cast(App::ui_manager()->get_widget("/menu-keyframe")); if(menu) { menu->popup(event->button.button,gtk_get_current_event_time()); } break; } } else if ((t-prev_t)<(next_t-t)) { switch(event->button.button) { case 1: set_selected_keyframe(*(kf_list_->find_prev(t, false))); queue_draw(); selected_=true; break; case 3: set_selected_keyframe(*(kf_list_->find_prev(t, false))); queue_draw(); selected_=true; Gtk::Menu* menu = dynamic_cast(App::ui_manager()->get_widget("/menu-keyframe")); if(menu) { menu->popup(event->button.button,gtk_get_current_event_time()); } break; } } else { switch(event->button.button) { case 1: set_selected_keyframe(*(kf_list_->find_next(t, false))); queue_draw(); selected_=true; break; case 3: set_selected_keyframe(*(kf_list_->find_next(t, false))); queue_draw(); selected_=true; Gtk::Menu* menu = dynamic_cast(App::ui_manager()->get_widget("/menu-keyframe")); if(menu) { menu->popup(event->button.button,gtk_get_current_event_time()); } break; } } return true; } else { return false; } } break; case GDK_2BUTTON_PRESS: if(event->button.button==1) { if(selected_ && editable_ && canvas_interface_) { canvas_interface_->signal_keyframe_properties()(); } } break; case GDK_BUTTON_RELEASE: if(editable_ && (event->button.button==1 /*|| event->button.button==3*/)) { // stick to integer frames. if(fps) t = floor(t*fps + 0.5)/fps; bool stat=false; if(dragging_) { //if (event->button.button==3) if(event->button.state & GDK_MOD1_MASK) { stat=perform_move_kf(true); } else { stat=perform_move_kf(false); } moving_tooltip_->hide(); } dragging_=false; return stat; } break; default: break; } return false; } void Widget_Keyframe_List::set_time_adjustment(const Glib::RefPtr &x) { //disconnect old connections time_value_change.disconnect(); time_other_change.disconnect(); //connect update function to new adjustment adj_timescale = x; if(x) { time_value_change = x->signal_value_changed().connect(sigc::mem_fun(*this,&Widget_Keyframe_List::queue_draw)); time_other_change = x->signal_changed().connect(sigc::mem_fun(*this,&Widget_Keyframe_List::queue_draw)); } } void Widget_Keyframe_List::set_fps(float d) { if(fps != d) { fps = d; //update everything since we need to redraw already queue_draw(); } } void Widget_Keyframe_List::set_canvas_interface(etl::loose_handle h) { canvas_interface_=h; // Store the values used from the canvas interface. if (canvas_interface_) { set_fps(canvas_interface_->get_canvas()->rend_desc().get_frame_rate()); set_kf_list(&canvas_interface_->get_canvas()->keyframe_list()); canvas_interface_->signal_keyframe_added().connect( sigc::hide_return( sigc::hide( sigc::mem_fun(*this,&studio::Widget_Keyframe_List::queue_draw) ) ) ); canvas_interface_->signal_keyframe_changed().connect( sigc::hide_return( sigc::hide( sigc::mem_fun(*this,&studio::Widget_Keyframe_List::queue_draw) ) ) ); canvas_interface_->signal_keyframe_removed().connect( sigc::hide_return( sigc::hide( sigc::mem_fun(*this,&studio::Widget_Keyframe_List::queue_draw) ) ) ); canvas_interface_->signal_keyframe_selected().connect( sigc::mem_fun(*this,&studio::Widget_Keyframe_List::on_keyframe_selected) ); } }