1 /* === S Y N F I G ========================================================= */
2 /*! \file widget_timeslider.cpp
3 ** \brief Time Slider Widget Implementation File
8 ** Copyright (c) 2004 Adrian Bentley
10 ** This package is free software; you can redistribute it and/or
11 ** modify it under the terms of the GNU General Public License as
12 ** published by the Free Software Foundation; either version 2 of
13 ** the License, or (at your option) any later version.
15 ** This package is distributed in the hope that it will be useful,
16 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 ** General Public License for more details.
21 /* ========================================================================= */
23 /* === H E A D E R S ======================================================= */
32 #include "widget_timeslider.h"
40 /* === U S I N G =========================================================== */
44 using namespace synfig;
46 using studio::Widget_Timeslider;
48 /* === M A C R O S ========================================================= */
50 /* === G L O B A L S ======================================================= */
51 const double zoominfactor = 0.75;
52 const double zoomoutfactor = 1/zoominfactor;
54 /* === P R O C E D U R E S ================================================= */
56 Gdk::Color get_interp_color(synfig::Interpolation x)
60 case INTERPOLATION_TCB:
61 return Gdk::Color("#00B000");
65 case INTERPOLATION_LINEAR:
66 return Gdk::Color("#B0B000");
69 case INTERPOLATION_CONSTANT:
70 return Gdk::Color("#C70000");
73 case INTERPOLATION_HALT:
74 return Gdk::Color("#00b0b0");
77 case INTERPOLATION_MANUAL:
78 return Gdk::Color("#B000B0");
81 case INTERPOLATION_UNDEFINED: default:
82 return Gdk::Color("#808080");
88 color_darken(Gdk::Color x, float amount)
92 x.get_green_p()*amount,
99 studio::render_time_point_to_window(
100 const Glib::RefPtr<Gdk::Drawable>& window,
101 const Gdk::Rectangle& area,
102 const synfig::TimePoint &tp,
106 Glib::RefPtr<Gdk::GC> gc(Gdk::GC::create(window));
107 const Gdk::Color black("#000000");
110 gc->set_line_attributes(2,Gdk::LINE_SOLID,Gdk::CAP_BUTT,Gdk::JOIN_MITER);
112 gc->set_line_attributes(1,Gdk::LINE_SOLID,Gdk::CAP_BUTT,Gdk::JOIN_MITER);
115 std::vector<Gdk::Point> points;
117 /*- BEFORE ------------------------------------- */
119 color=get_interp_color(tp.get_before());
120 color=color_darken(color,1.0f);
121 if(selected)color=color_darken(color,1.3f);
122 gc->set_rgb_fg_color(color);
124 switch(tp.get_before())
126 case INTERPOLATION_TCB:
137 gc->set_rgb_fg_color(black);
150 case INTERPOLATION_HALT:
161 gc->set_rgb_fg_color(black);
174 case INTERPOLATION_LINEAR:
176 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()));
177 points.push_back(Gdk::Point(area.get_x(),area.get_y()+area.get_height()));
178 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()));
179 window->draw_polygon(gc,true,points);
180 gc->set_rgb_fg_color(black);
181 window->draw_lines(gc,points);
184 case INTERPOLATION_CONSTANT:
186 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()));
187 points.push_back(Gdk::Point(area.get_x()+area.get_width()/4,area.get_y()));
188 points.push_back(Gdk::Point(area.get_x()+area.get_width()/4,area.get_y()+area.get_height()/2));
189 points.push_back(Gdk::Point(area.get_x(),area.get_y()+area.get_height()/2));
190 points.push_back(Gdk::Point(area.get_x(),area.get_y()+area.get_height()));
191 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()));
192 window->draw_polygon(gc,true,points);
193 gc->set_rgb_fg_color(black);
194 window->draw_lines(gc,points);
197 case INTERPOLATION_UNDEFINED: default:
199 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()));
200 points.push_back(Gdk::Point(area.get_x()+area.get_width()/3,area.get_y()));
201 points.push_back(Gdk::Point(area.get_x(),area.get_y()+area.get_height()/3));
202 points.push_back(Gdk::Point(area.get_x(),area.get_y()+area.get_height()*2/3));
203 points.push_back(Gdk::Point(area.get_x()+area.get_width()/3,area.get_y()+area.get_height()));
204 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()));
205 window->draw_polygon(gc,true,points);
206 gc->set_rgb_fg_color(black);
207 window->draw_lines(gc,points);
211 /*- AFTER -------------------------------------- */
213 color=get_interp_color(tp.get_after());
214 color=color_darken(color,0.8f);
215 if(selected)color=color_darken(color,1.3f);
216 gc->set_rgb_fg_color(color);
219 switch(tp.get_after())
221 case INTERPOLATION_TCB:
232 gc->set_rgb_fg_color(black);
245 case INTERPOLATION_HALT:
250 area.get_y()-area.get_height(),
256 gc->set_rgb_fg_color(black);
261 area.get_y()-area.get_height(),
269 case INTERPOLATION_LINEAR:
271 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()));
272 points.push_back(Gdk::Point(area.get_x()+area.get_width(),area.get_y()));
273 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()));
274 window->draw_polygon(gc,true,points);
275 gc->set_rgb_fg_color(black);
276 window->draw_lines(gc,points);
279 case INTERPOLATION_CONSTANT:
281 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()));
282 points.push_back(Gdk::Point(area.get_x()+area.get_width(),area.get_y()));
283 points.push_back(Gdk::Point(area.get_x()+area.get_width(),area.get_y()+area.get_height()/2));
284 points.push_back(Gdk::Point(area.get_x()+area.get_width()*3/4,area.get_y()+area.get_height()/2));
285 points.push_back(Gdk::Point(area.get_x()+area.get_width()*3/4,area.get_y()+area.get_height()));
286 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()));
287 window->draw_polygon(gc,true,points);
288 gc->set_rgb_fg_color(black);
289 window->draw_lines(gc,points);
292 case INTERPOLATION_UNDEFINED: default:
294 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()));
295 points.push_back(Gdk::Point(area.get_x()+area.get_width()*2/3,area.get_y()));
296 points.push_back(Gdk::Point(area.get_x()+area.get_width(),area.get_y()+area.get_height()/3));
297 points.push_back(Gdk::Point(area.get_x()+area.get_width(),area.get_y()+area.get_height()*2/3));
298 points.push_back(Gdk::Point(area.get_x()+area.get_width()*2/3,area.get_y()+area.get_height()));
299 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()));
300 window->draw_polygon(gc,true,points);
301 gc->set_rgb_fg_color(black);
302 window->draw_lines(gc,points);
308 /* === M E T H O D S ======================================================= */
310 /* === E N T R Y P O I N T ================================================= */
311 double defaultfps = 0;
312 const int fullheight = 20;
314 Widget_Timeslider::Widget_Timeslider()
315 :layout(Pango::Layout::create(get_pango_context())),
316 adj_default(0,0,2,1/defaultfps,10/defaultfps),
318 //invalidated(false),
323 set_size_request(-1,fullheight);
326 add_events( Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK
327 | Gdk::BUTTON_MOTION_MASK | Gdk::SCROLL_MASK );
329 set_time_adjustment(&adj_default);
333 Widget_Timeslider::~Widget_Timeslider()
337 void Widget_Timeslider::set_time_adjustment(Gtk::Adjustment *x)
339 //disconnect old connections
340 time_value_change.disconnect();
341 time_other_change.disconnect();
343 //connect update function to new adjustment
348 time_value_change = x->signal_value_changed().connect(sigc::mem_fun(*this,&Widget_Timeslider::queue_draw));
349 time_other_change = x->signal_changed().connect(sigc::mem_fun(*this,&Widget_Timeslider::queue_draw));
350 //invalidated = true;
355 void Widget_Timeslider::set_global_fps(float d)
361 //update everything since we need to redraw already
362 //invalidated = true;
368 /*void Widget_Timeslider::update_times()
372 start = adj_timescale->get_lower();
373 end = adj_timescale->get_upper();
374 current = adj_timescale->get_value();
378 void Widget_Timeslider::refresh()
386 }else if(adj_timescale)
388 double l = adj_timescale->get_lower(),
389 u = adj_timescale->get_upper(),
390 v = adj_timescale->get_value();
392 bool invalid = (l != start) || (u != end) || (v != current);
398 if(invalid) queue_draw();
402 bool Widget_Timeslider::redraw(bool doublebuffer)
404 Glib::RefPtr<Gdk::Window> window = get_window();
406 if(!window) return false;
408 Glib::RefPtr<Gdk::GC> gc = Gdk::GC::create(window);
409 if(!gc) return false;
411 //synfig::info("Drawing Timeslider");
412 //clear and update to current values
413 //invalidated = false;
416 //draw grey rectangle
417 Gdk::Color c("#7f7f7f");
418 gc->set_rgb_fg_color(c);
419 gc->set_background(c);
421 //Get the data for the window and the params to draw it...
422 int w = get_width(), h = get_height();
424 window->draw_rectangle(gc,true,0,0,w,h);
426 const double EPSILON = 1e-6;
427 if(!adj_timescale || w == 0) return true;
429 //Get the time information since we now know it's valid
430 double start = adj_timescale->get_lower(),
431 end = adj_timescale->get_upper(),
432 current = adj_timescale->get_value();
434 if(end-start < EPSILON) return true;
436 //synfig::info("Drawing Lines");
438 //draw all the time stuff
439 double dtdp = (end - start)/get_width();
440 double dpdt = 1/dtdp;
444 //Draw the time line...
445 double tpx = (current-start)*dpdt;
446 gc->set_rgb_fg_color(Gdk::Color("#ffaf00"));
447 window->draw_line(gc,round_to_int(tpx),0,round_to_int(tpx),fullheight);
449 //normal line/text color
450 gc->set_rgb_fg_color(Gdk::Color("#333333"));
452 //draw these lines... (always 5 between) maybe 6?
453 const int subdiv = 4;
456 //..., 3m, 2m, 1m30s, 1m, 30s, 20s, 10s, 5s, 3s, 2s, 1s, 0.5s
459 { 1.0/fps,subdiv/fps,0.25,0.5, 1, 2, 3, 5, 10, 20, 30, 60, 90, 120, 180, 300, 600, 1200, 1800, 2700, 3600 };
460 //{ 3600, 2700, 1800, 1200, 600, 300, 180, 120, 90, 60, 30, 20, 10, 5, 3, 2, 1, 0.5 };
461 const int ranges_size = sizeof(ranges)/sizeof(double);
463 double lowerrange = dtdp*75, upperrange = dtdp*150;
464 double midrange = (lowerrange + upperrange)/2;
466 //find most ideal scale
467 double scale = ranges[0];
469 double *val = binary_find(ranges, ranges+ranges_size, midrange);
470 double *after = val+1;
472 if(val >= ranges+ranges_size)
474 val = ranges+ranges_size-1;
477 if(after >= ranges+ranges_size)
479 after = ranges+ranges_size-1;
484 double diff = abs(scale - midrange), diff2 = abs(*after - midrange);
489 //synfig::info("Range found: (l %.2lf,u %.2lf - m %.2lf) -> %.2lf",lowerrange,upperrange,midrange,scale);
491 //search around this area to get the right one
494 //get first valid line and it's position in pixel space
500 double subr = scale / subdiv;
502 //get it's position inside...
503 time = ceil(start/subr)*subr - start;
506 //absolute time of the line to be drawn
510 double t = (time/scale - floor(time/scale))*subdiv; // the difference from the big mark in 0:1
511 //sdindex = (int)floor(t + 0.5); //get how far through the range it is...
512 sdindex = round_to_int(t); //get how far through the range it is...
514 //synfig::info("Extracted fr %.2lf -> %d", t, sdindex);
517 //synfig::info("Initial values: %.4lf t, %.1lf pixels, %d i", time,pixel,sdindex);
520 const int heightbig = 12;
521 const int heightsmall = 4;
523 int width = get_width();
524 while( pixel < width )
526 int xpx = round_to_int(pixel);
531 window->draw_line(gc,xpx,0,xpx,heightbig);
532 //round the time to nearest frame and draw the text
533 Time tm((double)time);
534 if(get_global_fps()) tm.round(get_global_fps());
535 Glib::ustring timecode(tm.get_string(get_global_fps(),App::get_time_format()));
537 //gc->set_rgb_fg_color(Gdk::Color("#000000"));
538 layout->set_text(timecode);
539 window->draw_layout(gc,xpx+2,heightsmall,layout);
542 window->draw_line(gc,xpx,0,xpx,heightsmall);
545 //increment time and position
546 pixel += subr / dtdp;
550 if(++sdindex >= subdiv) sdindex -= subdiv;
556 bool Widget_Timeslider::on_motion_notify_event(GdkEventMotion* event) //for dragging
558 if(!adj_timescale) return false;
560 Gdk::ModifierType mod = Gdk::ModifierType(event->state);
564 //NOTE: we might want to address the possibility of dragging with both buttons held down
566 if(mod & Gdk::BUTTON2_MASK)
569 //we need this for scrolling by dragging
570 double curx = event->x;
572 double start = adj_timescale->get_lower(),
573 end = adj_timescale->get_upper();
578 if(event->time-last_event_time<30)
581 last_event_time=event->time;
583 if(abs(lastx - curx) < 1 && end != start) return true;
584 //translate the window and correct it
586 //update our stuff so we are operating correctly
587 //invalidated = true;
590 //Note: Use inverse of mouse movement because of conceptual space relationship
591 double diff = lastx - curx; //curx - lastx;
593 //NOTE: This might be incorrect...
594 //fraction to move...
595 double dpx = (end - start)/get_width();
604 //But clamp to bounds if they exist...
605 //HACK - bounds should not be required for this slider
608 if(start < adj_bounds->get_lower())
610 diff = adj_bounds->get_lower() - start;
615 if(end > adj_bounds->get_upper())
617 diff = adj_bounds->get_upper() - end;
623 //synfig::info("Scrolling timerange to (%.4f,%.4f)",start,end);
625 adj_timescale->set_lower(start);
626 adj_timescale->set_upper(end);
628 adj_timescale->changed();
639 if(mod & Gdk::BUTTON1_MASK)
641 double curx = event->x;
643 //get time from drag...
644 double start = adj_timescale->get_lower(),
645 end = adj_timescale->get_upper(),
646 current = adj_timescale->get_value();
647 double t = start + curx*(end - start)/get_width();
649 //snap it to fps - if they exist...
652 t = floor(t*fps + 0.5)/fps;
658 adj_timescale->set_value(t);
660 //Fixed this to actually do what it's supposed to...
661 if(event->time-last_event_time>50)
663 adj_timescale->value_changed();
664 last_event_time = event->time;
674 bool Widget_Timeslider::on_scroll_event(GdkEventScroll* event) //for zooming
676 if(!adj_timescale) return false;
678 //Update so we are calculating based on current values
681 //figure out if we should center ourselves on the current time
684 //we want to zoom in on the time value if control is held down
685 if(Gdk::ModifierType(event->state) & Gdk::CONTROL_MASK)
690 switch(event->direction)
692 case GDK_SCROLL_UP: //zoom in
698 case GDK_SCROLL_DOWN: //zoom out
712 void Widget_Timeslider::zoom_in(bool centerontime)
714 if(!adj_timescale) return;
716 double start = adj_timescale->get_lower(),
717 end = adj_timescale->get_upper(),
718 current = adj_timescale->get_value();
720 double focuspoint = centerontime ? current : (start + end)/2;
722 //calculate new beginning and end
723 end = focuspoint + (end-focuspoint)*zoominfactor;
724 start = focuspoint + (start-focuspoint)*zoominfactor;
726 //synfig::info("Zooming in timerange to (%.4f,%.4f)",start,end);
729 if(start < adj_bounds->get_lower())
731 start = adj_bounds->get_lower();
734 if(end > adj_bounds->get_upper())
736 end = adj_bounds->get_upper();
741 adj_timescale->set_lower(start);
742 adj_timescale->set_upper(end);
744 //call changed function
745 adj_timescale->changed();
748 void Widget_Timeslider::zoom_out(bool centerontime)
750 if(!adj_timescale) return;
752 double start = adj_timescale->get_lower(),
753 end = adj_timescale->get_upper(),
754 current = adj_timescale->get_value();
756 double focuspoint = centerontime ? current : (start + end)/2;
758 //calculate new beginning and end
759 end = focuspoint + (end-focuspoint)*zoomoutfactor;
760 start = focuspoint + (start-focuspoint)*zoomoutfactor;
762 //synfig::info("Zooming out timerange to (%.4f,%.4f)",start,end);
765 if(start < adj_bounds->get_lower())
767 start = adj_bounds->get_lower();
770 if(end > adj_bounds->get_upper())
772 end = adj_bounds->get_upper();
777 adj_timescale->set_lower(start);
778 adj_timescale->set_upper(end);
780 //call changed function
781 adj_timescale->changed();
784 bool Widget_Timeslider::on_button_press_event(GdkEventButton *event) //for clicking
786 switch(event->button)
791 double start = adj_timescale->get_lower(),
792 end = adj_timescale->get_upper(),
793 current = adj_timescale->get_value();
795 double w = get_width();
796 double t = start + (end - start) * event->x / w;
798 t = floor(t*fps + 0.5)/fps;
800 /*synfig::info("Clicking time from %.3lf to %.3lf [(%.2lf,%.2lf) %.2lf / %.2lf ... %.2lf",
801 current, vt, start, end, event->x, w, fps);*/
809 adj_timescale->set_value(current);
810 adj_timescale->value_changed();
837 bool Widget_Timeslider::on_button_release_event(GdkEventButton *event) //end drag
839 switch(event->button)