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)
90 double red = x.get_red_p() * amount;
91 double green = x.get_green_p() * amount;
92 double blue = x.get_blue_p() * amount;
94 x.set_rgb_p( red > 1 ? 1 : red,
95 green > 1 ? 1 : green,
102 studio::render_time_point_to_window(
103 const Glib::RefPtr<Gdk::Drawable>& window,
104 const Gdk::Rectangle& area,
105 const synfig::TimePoint &tp,
109 Glib::RefPtr<Gdk::GC> gc(Gdk::GC::create(window));
110 const Gdk::Color black("#000000");
113 gc->set_line_attributes(2,Gdk::LINE_SOLID,Gdk::CAP_BUTT,Gdk::JOIN_MITER);
115 gc->set_line_attributes(1,Gdk::LINE_SOLID,Gdk::CAP_BUTT,Gdk::JOIN_MITER);
118 std::vector<Gdk::Point> points;
120 /*- BEFORE ------------------------------------- */
122 color=get_interp_color(tp.get_before());
123 color=color_darken(color,1.0f);
124 if(selected)color=color_darken(color,1.3f);
125 gc->set_rgb_fg_color(color);
127 switch(tp.get_before())
129 case INTERPOLATION_TCB:
140 gc->set_rgb_fg_color(black);
153 case INTERPOLATION_HALT:
164 gc->set_rgb_fg_color(black);
177 case INTERPOLATION_LINEAR:
179 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()));
180 points.push_back(Gdk::Point(area.get_x(),area.get_y()+area.get_height()));
181 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()));
182 window->draw_polygon(gc,true,points);
183 gc->set_rgb_fg_color(black);
184 window->draw_lines(gc,points);
187 case INTERPOLATION_CONSTANT:
189 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()));
190 points.push_back(Gdk::Point(area.get_x()+area.get_width()/4,area.get_y()));
191 points.push_back(Gdk::Point(area.get_x()+area.get_width()/4,area.get_y()+area.get_height()/2));
192 points.push_back(Gdk::Point(area.get_x(),area.get_y()+area.get_height()/2));
193 points.push_back(Gdk::Point(area.get_x(),area.get_y()+area.get_height()));
194 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()));
195 window->draw_polygon(gc,true,points);
196 gc->set_rgb_fg_color(black);
197 window->draw_lines(gc,points);
200 case INTERPOLATION_UNDEFINED: default:
202 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()));
203 points.push_back(Gdk::Point(area.get_x()+area.get_width()/3,area.get_y()));
204 points.push_back(Gdk::Point(area.get_x(),area.get_y()+area.get_height()/3));
205 points.push_back(Gdk::Point(area.get_x(),area.get_y()+area.get_height()*2/3));
206 points.push_back(Gdk::Point(area.get_x()+area.get_width()/3,area.get_y()+area.get_height()));
207 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()));
208 window->draw_polygon(gc,true,points);
209 gc->set_rgb_fg_color(black);
210 window->draw_lines(gc,points);
214 /*- AFTER -------------------------------------- */
216 color=get_interp_color(tp.get_after());
217 color=color_darken(color,0.8f);
218 if(selected)color=color_darken(color,1.3f);
219 gc->set_rgb_fg_color(color);
222 switch(tp.get_after())
224 case INTERPOLATION_TCB:
235 gc->set_rgb_fg_color(black);
248 case INTERPOLATION_HALT:
253 area.get_y()-area.get_height(),
259 gc->set_rgb_fg_color(black);
264 area.get_y()-area.get_height(),
272 case INTERPOLATION_LINEAR:
274 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()));
275 points.push_back(Gdk::Point(area.get_x()+area.get_width(),area.get_y()));
276 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()));
277 window->draw_polygon(gc,true,points);
278 gc->set_rgb_fg_color(black);
279 window->draw_lines(gc,points);
282 case INTERPOLATION_CONSTANT:
284 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()));
285 points.push_back(Gdk::Point(area.get_x()+area.get_width(),area.get_y()));
286 points.push_back(Gdk::Point(area.get_x()+area.get_width(),area.get_y()+area.get_height()/2));
287 points.push_back(Gdk::Point(area.get_x()+area.get_width()*3/4,area.get_y()+area.get_height()/2));
288 points.push_back(Gdk::Point(area.get_x()+area.get_width()*3/4,area.get_y()+area.get_height()));
289 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()));
290 window->draw_polygon(gc,true,points);
291 gc->set_rgb_fg_color(black);
292 window->draw_lines(gc,points);
295 case INTERPOLATION_UNDEFINED: default:
297 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()));
298 points.push_back(Gdk::Point(area.get_x()+area.get_width()*2/3,area.get_y()));
299 points.push_back(Gdk::Point(area.get_x()+area.get_width(),area.get_y()+area.get_height()/3));
300 points.push_back(Gdk::Point(area.get_x()+area.get_width(),area.get_y()+area.get_height()*2/3));
301 points.push_back(Gdk::Point(area.get_x()+area.get_width()*2/3,area.get_y()+area.get_height()));
302 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()));
303 window->draw_polygon(gc,true,points);
304 gc->set_rgb_fg_color(black);
305 window->draw_lines(gc,points);
311 /* === M E T H O D S ======================================================= */
313 /* === E N T R Y P O I N T ================================================= */
314 double defaultfps = 0;
315 const int fullheight = 20;
317 Widget_Timeslider::Widget_Timeslider()
318 :layout(Pango::Layout::create(get_pango_context())),
319 adj_default(0,0,2,1/defaultfps,10/defaultfps),
321 //invalidated(false),
326 set_size_request(-1,fullheight);
329 add_events( Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK
330 | Gdk::BUTTON_MOTION_MASK | Gdk::SCROLL_MASK );
332 set_time_adjustment(&adj_default);
336 Widget_Timeslider::~Widget_Timeslider()
340 void Widget_Timeslider::set_time_adjustment(Gtk::Adjustment *x)
342 //disconnect old connections
343 time_value_change.disconnect();
344 time_other_change.disconnect();
346 //connect update function to new adjustment
351 time_value_change = x->signal_value_changed().connect(sigc::mem_fun(*this,&Widget_Timeslider::queue_draw));
352 time_other_change = x->signal_changed().connect(sigc::mem_fun(*this,&Widget_Timeslider::queue_draw));
353 //invalidated = true;
358 void Widget_Timeslider::set_global_fps(float d)
364 //update everything since we need to redraw already
365 //invalidated = true;
371 /*void Widget_Timeslider::update_times()
375 start = adj_timescale->get_lower();
376 end = adj_timescale->get_upper();
377 current = adj_timescale->get_value();
381 void Widget_Timeslider::refresh()
389 }else if(adj_timescale)
391 double l = adj_timescale->get_lower(),
392 u = adj_timescale->get_upper(),
393 v = adj_timescale->get_value();
395 bool invalid = (l != start) || (u != end) || (v != current);
401 if(invalid) queue_draw();
405 bool Widget_Timeslider::redraw(bool doublebuffer)
407 Glib::RefPtr<Gdk::Window> window = get_window();
409 if(!window) return false;
411 Glib::RefPtr<Gdk::GC> gc = Gdk::GC::create(window);
412 if(!gc) return false;
414 //synfig::info("Drawing Timeslider");
415 //clear and update to current values
416 //invalidated = false;
419 //draw grey rectangle
420 Gdk::Color c("#7f7f7f");
421 gc->set_rgb_fg_color(c);
422 gc->set_background(c);
424 //Get the data for the window and the params to draw it...
425 int w = get_width(), h = get_height();
427 window->draw_rectangle(gc,true,0,0,w,h);
429 const double EPSILON = 1e-6;
430 if(!adj_timescale || w == 0) return true;
432 //Get the time information since we now know it's valid
433 double start = adj_timescale->get_lower(),
434 end = adj_timescale->get_upper(),
435 current = adj_timescale->get_value();
437 if(end-start < EPSILON) return true;
439 //synfig::info("Drawing Lines");
441 //draw all the time stuff
442 double dtdp = (end - start)/get_width();
443 double dpdt = 1/dtdp;
447 //Draw the time line...
448 double tpx = (current-start)*dpdt;
449 gc->set_rgb_fg_color(Gdk::Color("#ffaf00"));
450 window->draw_line(gc,round_to_int(tpx),0,round_to_int(tpx),fullheight);
452 //normal line/text color
453 gc->set_rgb_fg_color(Gdk::Color("#333333"));
455 //draw these lines... (always 5 between) maybe 6?
456 const int subdiv = 4;
459 //..., 3m, 2m, 1m30s, 1m, 30s, 20s, 10s, 5s, 3s, 2s, 1s, 0.5s
462 { 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 };
463 //{ 3600, 2700, 1800, 1200, 600, 300, 180, 120, 90, 60, 30, 20, 10, 5, 3, 2, 1, 0.5 };
464 const int ranges_size = sizeof(ranges)/sizeof(double);
466 double lowerrange = dtdp*75, upperrange = dtdp*150;
467 double midrange = (lowerrange + upperrange)/2;
469 //find most ideal scale
470 double scale = ranges[0];
472 double *val = binary_find(ranges, ranges+ranges_size, midrange);
473 double *after = val+1;
475 if(val >= ranges+ranges_size)
477 val = ranges+ranges_size-1;
480 if(after >= ranges+ranges_size)
482 after = ranges+ranges_size-1;
487 double diff = abs(scale - midrange), diff2 = abs(*after - midrange);
492 //synfig::info("Range found: (l %.2lf,u %.2lf - m %.2lf) -> %.2lf",lowerrange,upperrange,midrange,scale);
494 //search around this area to get the right one
497 //get first valid line and its position in pixel space
503 double subr = scale / subdiv;
505 //get its position inside...
506 time = ceil(start/subr)*subr - start;
509 //absolute time of the line to be drawn
513 double t = (time/scale - floor(time/scale))*subdiv; // the difference from the big mark in 0:1
514 //sdindex = (int)floor(t + 0.5); //get how far through the range it is...
515 sdindex = round_to_int(t); //get how far through the range it is...
517 //synfig::info("Extracted fr %.2lf -> %d", t, sdindex);
520 //synfig::info("Initial values: %.4lf t, %.1lf pixels, %d i", time,pixel,sdindex);
523 const int heightbig = 12;
524 const int heightsmall = 4;
526 int width = get_width();
527 while( pixel < width )
529 int xpx = round_to_int(pixel);
534 window->draw_line(gc,xpx,0,xpx,heightbig);
535 //round the time to nearest frame and draw the text
536 Time tm((double)time);
537 if(get_global_fps()) tm.round(get_global_fps());
538 Glib::ustring timecode(tm.get_string(get_global_fps(),App::get_time_format()));
540 //gc->set_rgb_fg_color(Gdk::Color("#000000"));
541 layout->set_text(timecode);
542 window->draw_layout(gc,xpx+2,heightsmall,layout);
545 window->draw_line(gc,xpx,0,xpx,heightsmall);
548 //increment time and position
549 pixel += subr / dtdp;
553 if(++sdindex >= subdiv) sdindex -= subdiv;
559 bool Widget_Timeslider::on_motion_notify_event(GdkEventMotion* event) //for dragging
561 if(!adj_timescale) return false;
563 Gdk::ModifierType mod = Gdk::ModifierType(event->state);
567 //NOTE: we might want to address the possibility of dragging with both buttons held down
569 if(mod & Gdk::BUTTON2_MASK)
572 //we need this for scrolling by dragging
573 double curx = event->x;
575 double start = adj_timescale->get_lower(),
576 end = adj_timescale->get_upper();
581 if(event->time-last_event_time<30)
584 last_event_time=event->time;
586 if(abs(lastx - curx) < 1 && end != start) return true;
587 //translate the window and correct it
589 //update our stuff so we are operating correctly
590 //invalidated = true;
593 //Note: Use inverse of mouse movement because of conceptual space relationship
594 double diff = lastx - curx; //curx - lastx;
596 //NOTE: This might be incorrect...
597 //fraction to move...
598 double dpx = (end - start)/get_width();
607 //But clamp to bounds if they exist...
608 //HACK - bounds should not be required for this slider
611 if(start < adj_bounds->get_lower())
613 diff = adj_bounds->get_lower() - start;
618 if(end > adj_bounds->get_upper())
620 diff = adj_bounds->get_upper() - end;
626 //synfig::info("Scrolling timerange to (%.4f,%.4f)",start,end);
628 adj_timescale->set_lower(start);
629 adj_timescale->set_upper(end);
631 adj_timescale->changed();
642 if(mod & Gdk::BUTTON1_MASK)
644 double curx = event->x;
646 //get time from drag...
647 double start = adj_timescale->get_lower(),
648 end = adj_timescale->get_upper(),
649 current = adj_timescale->get_value();
650 double t = start + curx*(end - start)/get_width();
652 //snap it to fps - if they exist...
655 t = floor(t*fps + 0.5)/fps;
661 adj_timescale->set_value(t);
663 //Fixed this to actually do what it's supposed to...
664 if(event->time-last_event_time>50)
666 adj_timescale->value_changed();
667 last_event_time = event->time;
677 bool Widget_Timeslider::on_scroll_event(GdkEventScroll* event) //for zooming
679 if(!adj_timescale) return false;
681 //Update so we are calculating based on current values
684 //figure out if we should center ourselves on the current time
687 //we want to zoom in on the time value if control is held down
688 if(Gdk::ModifierType(event->state) & Gdk::CONTROL_MASK)
693 switch(event->direction)
695 case GDK_SCROLL_UP: //zoom in
701 case GDK_SCROLL_DOWN: //zoom out
715 void Widget_Timeslider::zoom_in(bool centerontime)
717 if(!adj_timescale) return;
719 double start = adj_timescale->get_lower(),
720 end = adj_timescale->get_upper(),
721 current = adj_timescale->get_value();
723 double focuspoint = centerontime ? current : (start + end)/2;
725 //calculate new beginning and end
726 end = focuspoint + (end-focuspoint)*zoominfactor;
727 start = focuspoint + (start-focuspoint)*zoominfactor;
729 //synfig::info("Zooming in timerange to (%.4f,%.4f)",start,end);
732 if(start < adj_bounds->get_lower())
734 start = adj_bounds->get_lower();
737 if(end > adj_bounds->get_upper())
739 end = adj_bounds->get_upper();
744 adj_timescale->set_lower(start);
745 adj_timescale->set_upper(end);
747 //call changed function
748 adj_timescale->changed();
751 void Widget_Timeslider::zoom_out(bool centerontime)
753 if(!adj_timescale) return;
755 double start = adj_timescale->get_lower(),
756 end = adj_timescale->get_upper(),
757 current = adj_timescale->get_value();
759 double focuspoint = centerontime ? current : (start + end)/2;
761 //calculate new beginning and end
762 end = focuspoint + (end-focuspoint)*zoomoutfactor;
763 start = focuspoint + (start-focuspoint)*zoomoutfactor;
765 //synfig::info("Zooming out timerange to (%.4f,%.4f)",start,end);
768 if(start < adj_bounds->get_lower())
770 start = adj_bounds->get_lower();
773 if(end > adj_bounds->get_upper())
775 end = adj_bounds->get_upper();
780 adj_timescale->set_lower(start);
781 adj_timescale->set_upper(end);
783 //call changed function
784 adj_timescale->changed();
787 bool Widget_Timeslider::on_button_press_event(GdkEventButton *event) //for clicking
789 switch(event->button)
794 double start = adj_timescale->get_lower(),
795 end = adj_timescale->get_upper(),
796 current = adj_timescale->get_value();
798 double w = get_width();
799 double t = start + (end - start) * event->x / w;
801 t = floor(t*fps + 0.5)/fps;
803 /*synfig::info("Clicking time from %.3lf to %.3lf [(%.2lf,%.2lf) %.2lf / %.2lf ... %.2lf",
804 current, vt, start, end, event->x, w, fps);*/
812 adj_timescale->set_value(current);
813 adj_timescale->value_changed();
840 bool Widget_Timeslider::on_button_release_event(GdkEventButton *event) //end drag
842 switch(event->button)