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
9 ** Copyright (c) 2007, 2008 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 "widget_timeslider.h"
43 /* === U S I N G =========================================================== */
47 using namespace synfig
;
49 using studio::Widget_Timeslider
;
51 /* === M A C R O S ========================================================= */
53 /* === G L O B A L S ======================================================= */
54 const double zoominfactor
= 0.75;
55 const double zoomoutfactor
= 1/zoominfactor
;
57 /* === P R O C E D U R E S ================================================= */
59 Gdk::Color
get_interp_color(synfig::Interpolation x
)
63 case INTERPOLATION_TCB
:
64 return Gdk::Color("#00B000");
68 case INTERPOLATION_LINEAR
:
69 return Gdk::Color("#B0B000");
72 case INTERPOLATION_CONSTANT
:
73 return Gdk::Color("#C70000");
76 case INTERPOLATION_HALT
:
77 return Gdk::Color("#00b0b0");
80 case INTERPOLATION_MANUAL
:
81 return Gdk::Color("#B000B0");
84 case INTERPOLATION_UNDEFINED
: default:
85 return Gdk::Color("#808080");
91 color_darken(Gdk::Color x
, float amount
)
93 double red
= x
.get_red_p() * amount
;
94 double green
= x
.get_green_p() * amount
;
95 double blue
= x
.get_blue_p() * amount
;
97 x
.set_rgb_p( red
> 1 ? 1 : red
,
98 green
> 1 ? 1 : green
,
105 studio::render_time_point_to_window(
106 const Glib::RefPtr
<Gdk::Drawable
>& window
,
107 const Gdk::Rectangle
& area
,
108 const synfig::TimePoint
&tp
,
112 Glib::RefPtr
<Gdk::GC
> gc(Gdk::GC::create(window
));
113 const Gdk::Color
black("#000000");
116 gc
->set_line_attributes(2,Gdk::LINE_SOLID
,Gdk::CAP_BUTT
,Gdk::JOIN_MITER
);
118 gc
->set_line_attributes(1,Gdk::LINE_SOLID
,Gdk::CAP_BUTT
,Gdk::JOIN_MITER
);
121 std::vector
<Gdk::Point
> points
;
123 /*- BEFORE ------------------------------------- */
125 color
=get_interp_color(tp
.get_before());
126 color
=color_darken(color
,1.0f
);
127 if(selected
)color
=color_darken(color
,1.3f
);
128 gc
->set_rgb_fg_color(color
);
130 switch(tp
.get_before())
132 case INTERPOLATION_TCB
:
143 gc
->set_rgb_fg_color(black
);
156 case INTERPOLATION_HALT
:
167 gc
->set_rgb_fg_color(black
);
180 case INTERPOLATION_LINEAR
:
182 points
.push_back(Gdk::Point(area
.get_x()+area
.get_width()/2,area
.get_y()));
183 points
.push_back(Gdk::Point(area
.get_x(),area
.get_y()+area
.get_height()));
184 points
.push_back(Gdk::Point(area
.get_x()+area
.get_width()/2,area
.get_y()+area
.get_height()));
185 window
->draw_polygon(gc
,true,points
);
186 gc
->set_rgb_fg_color(black
);
187 window
->draw_lines(gc
,points
);
190 case INTERPOLATION_CONSTANT
:
192 points
.push_back(Gdk::Point(area
.get_x()+area
.get_width()/2,area
.get_y()));
193 points
.push_back(Gdk::Point(area
.get_x()+area
.get_width()/4,area
.get_y()));
194 points
.push_back(Gdk::Point(area
.get_x()+area
.get_width()/4,area
.get_y()+area
.get_height()/2));
195 points
.push_back(Gdk::Point(area
.get_x(),area
.get_y()+area
.get_height()/2));
196 points
.push_back(Gdk::Point(area
.get_x(),area
.get_y()+area
.get_height()));
197 points
.push_back(Gdk::Point(area
.get_x()+area
.get_width()/2,area
.get_y()+area
.get_height()));
198 window
->draw_polygon(gc
,true,points
);
199 gc
->set_rgb_fg_color(black
);
200 window
->draw_lines(gc
,points
);
203 case INTERPOLATION_UNDEFINED
: default:
205 points
.push_back(Gdk::Point(area
.get_x()+area
.get_width()/2,area
.get_y()));
206 points
.push_back(Gdk::Point(area
.get_x()+area
.get_width()/3,area
.get_y()));
207 points
.push_back(Gdk::Point(area
.get_x(),area
.get_y()+area
.get_height()/3));
208 points
.push_back(Gdk::Point(area
.get_x(),area
.get_y()+area
.get_height()-area
.get_height()/3));
209 points
.push_back(Gdk::Point(area
.get_x()+area
.get_width()/3,area
.get_y()+area
.get_height()));
210 points
.push_back(Gdk::Point(area
.get_x()+area
.get_width()/2,area
.get_y()+area
.get_height()));
211 window
->draw_polygon(gc
,true,points
);
212 gc
->set_rgb_fg_color(black
);
213 window
->draw_lines(gc
,points
);
217 /*- AFTER -------------------------------------- */
219 color
=get_interp_color(tp
.get_after());
220 color
=color_darken(color
,0.8f
);
221 if(selected
)color
=color_darken(color
,1.3f
);
222 gc
->set_rgb_fg_color(color
);
224 switch(tp
.get_after())
226 case INTERPOLATION_TCB
:
237 gc
->set_rgb_fg_color(black
);
250 case INTERPOLATION_HALT
:
255 area
.get_y()-area
.get_height(),
261 gc
->set_rgb_fg_color(black
);
266 area
.get_y()-area
.get_height(),
274 case INTERPOLATION_LINEAR
:
276 points
.push_back(Gdk::Point(area
.get_x()+area
.get_width()/2,area
.get_y()));
277 points
.push_back(Gdk::Point(area
.get_x()+area
.get_width(),area
.get_y()));
278 points
.push_back(Gdk::Point(area
.get_x()+area
.get_width()/2,area
.get_y()+area
.get_height()));
279 window
->draw_polygon(gc
,true,points
);
280 gc
->set_rgb_fg_color(black
);
281 window
->draw_lines(gc
,points
);
284 case INTERPOLATION_CONSTANT
:
286 points
.push_back(Gdk::Point(area
.get_x()+area
.get_width()/2,area
.get_y()));
287 points
.push_back(Gdk::Point(area
.get_x()+area
.get_width(),area
.get_y()));
288 points
.push_back(Gdk::Point(area
.get_x()+area
.get_width(),area
.get_y()+area
.get_height()/2));
289 points
.push_back(Gdk::Point(area
.get_x()+area
.get_width()-area
.get_width()/4,area
.get_y()+area
.get_height()/2));
290 points
.push_back(Gdk::Point(area
.get_x()+area
.get_width()-area
.get_width()/4,area
.get_y()+area
.get_height()));
291 points
.push_back(Gdk::Point(area
.get_x()+area
.get_width()/2,area
.get_y()+area
.get_height()));
292 window
->draw_polygon(gc
,true,points
);
293 gc
->set_rgb_fg_color(black
);
294 window
->draw_lines(gc
,points
);
297 case INTERPOLATION_UNDEFINED
: default:
299 points
.push_back(Gdk::Point(area
.get_x()+area
.get_width()/2,area
.get_y()));
300 points
.push_back(Gdk::Point(area
.get_x()+area
.get_width()-area
.get_width()/3,area
.get_y()));
301 points
.push_back(Gdk::Point(area
.get_x()+area
.get_width(),area
.get_y()+area
.get_height()/3));
302 points
.push_back(Gdk::Point(area
.get_x()+area
.get_width(),area
.get_y()+area
.get_height()-area
.get_height()/3));
303 points
.push_back(Gdk::Point(area
.get_x()+area
.get_width()-area
.get_width()/3,area
.get_y()+area
.get_height()));
304 points
.push_back(Gdk::Point(area
.get_x()+area
.get_width()/2,area
.get_y()+area
.get_height()));
305 window
->draw_polygon(gc
,true,points
);
306 gc
->set_rgb_fg_color(black
);
307 window
->draw_lines(gc
,points
);
313 /* === M E T H O D S ======================================================= */
315 /* === E N T R Y P O I N T ================================================= */
316 double defaultfps
= 24;
317 const int fullheight
= 20;
319 Widget_Timeslider::Widget_Timeslider()
320 :layout(Pango::Layout::create(get_pango_context())),
321 adj_default(0,0,2,1/defaultfps
,10/defaultfps
),
323 //invalidated(false),
328 set_size_request(-1,fullheight
);
331 add_events( Gdk::BUTTON_PRESS_MASK
| Gdk::BUTTON_RELEASE_MASK
332 | Gdk::BUTTON_MOTION_MASK
| Gdk::SCROLL_MASK
);
334 set_time_adjustment(&adj_default
);
338 Widget_Timeslider::~Widget_Timeslider()
342 void Widget_Timeslider::set_time_adjustment(Gtk::Adjustment
*x
)
344 //disconnect old connections
345 time_value_change
.disconnect();
346 time_other_change
.disconnect();
348 //connect update function to new adjustment
353 time_value_change
= x
->signal_value_changed().connect(sigc::mem_fun(*this,&Widget_Timeslider::queue_draw
));
354 time_other_change
= x
->signal_changed().connect(sigc::mem_fun(*this,&Widget_Timeslider::queue_draw
));
355 //invalidated = true;
360 void Widget_Timeslider::set_global_fps(float d
)
366 //update everything since we need to redraw already
367 //invalidated = true;
373 /*void Widget_Timeslider::update_times()
377 start = adj_timescale->get_lower();
378 end = adj_timescale->get_upper();
379 current = adj_timescale->get_value();
383 void Widget_Timeslider::refresh()
391 }else if(adj_timescale)
393 double l = adj_timescale->get_lower(),
394 u = adj_timescale->get_upper(),
395 v = adj_timescale->get_value();
397 bool invalid = (l != start) || (u != end) || (v != current);
403 if(invalid) queue_draw();
407 bool Widget_Timeslider::redraw(bool /*doublebuffer*/)
409 Glib::RefPtr
<Gdk::Window
> window
= get_window();
411 if(!window
) return false;
413 Glib::RefPtr
<Gdk::GC
> gc
= Gdk::GC::create(window
);
414 if(!gc
) return false;
416 //synfig::info("Drawing Timeslider");
417 //clear and update to current values
418 //invalidated = false;
421 //draw grey rectangle
422 Gdk::Color
c("#7f7f7f");
423 gc
->set_rgb_fg_color(c
);
424 gc
->set_background(c
);
426 //Get the data for the window and the params to draw it...
427 int w
= get_width(), h
= get_height();
429 window
->draw_rectangle(gc
,true,0,0,w
,h
);
431 const double EPSILON
= 1e-6;
432 if(!adj_timescale
|| w
== 0) return true;
434 //Get the time information since we now know it's valid
435 double start
= adj_timescale
->get_lower(),
436 end
= adj_timescale
->get_upper(),
437 current
= adj_timescale
->get_value();
439 if(end
-start
< EPSILON
) return true;
441 //synfig::info("Drawing Lines");
443 //draw all the time stuff
444 double dtdp
= (end
- start
)/get_width();
445 double dpdt
= 1/dtdp
;
449 //Draw the time line...
450 double tpx
= (current
-start
)*dpdt
;
451 gc
->set_rgb_fg_color(Gdk::Color("#ffaf00"));
452 window
->draw_line(gc
,round_to_int(tpx
),0,round_to_int(tpx
),fullheight
);
454 //normal line/text color
455 gc
->set_rgb_fg_color(Gdk::Color("#333333"));
457 int ifps
= round_to_int(fps
);
458 if (ifps
< 1) ifps
= 1;
460 std::vector
<double> ranges
;
462 unsigned int pos
= 0;
464 // build a list of all the factors of the frame rate
465 for (int i
= 1; i
*i
<= ifps
; i
++)
468 ranges
.insert(ranges
.begin()+pos
, i
/fps
);
470 ranges
.insert(ranges
.begin()+pos
+1, ifps
/i
/fps
);
474 // fill in any gaps where one factor is more than 2 times the previous
475 std::vector
<double>::iterator iter
, next
;
477 for (pos
= 0; pos
< ranges
.size()-1; pos
++)
479 iter
= ranges
.begin()+pos
;
482 ranges
.insert(next
, *iter
*2);
485 double more_ranges
[] = {
486 2, 3, 5, 10, 20, 30, 60, 90, 120, 180,
487 300, 600, 1200, 1800, 2700, 3600, 3600*2,
488 3600*4, 3600*8, 3600*16, 3600*32, 3600*64,
489 3600*128, 3600*256, 3600*512, 3600*1024 };
491 ranges
.insert(ranges
.end(), more_ranges
, more_ranges
+ sizeof(more_ranges
)/sizeof(double));
493 double lowerrange
= dtdp
*140, upperrange
= dtdp
*280;
494 double midrange
= (lowerrange
+ upperrange
)/2;
496 //find most ideal scale
498 next
= binary_find(ranges
.begin(), ranges
.end(), midrange
);
501 if (iter
== ranges
.end()) iter
--;
502 if (next
== ranges
.end()) next
--;
504 if (abs(*next
- midrange
) < abs(*iter
- midrange
))
509 // subdivide into this many tick marks (8 or less)
510 int subdiv
= round_to_int(scale
* ifps
);
514 const int ideal
= subdiv
;
516 // find a number of tick marks that nicely divides the scale
517 // (5 minutes divided by 6 is 50s, but that's not 'nice' -
518 // 5 ticks of 1m each is much simpler than 6 ticks of 50s)
519 for (subdiv
= 8; subdiv
> 0; subdiv
--)
520 if ((ideal
<= ifps
*2 && (ideal
% (subdiv
)) == 0) ||
521 (ideal
<= ifps
*2*60 && (ideal
% (subdiv
*ifps
)) == 0) ||
522 (ideal
<= ifps
*2*60*60 && (ideal
% (subdiv
*ifps
*60 )) == 0) ||
523 (true && (ideal
% (subdiv
*ifps
*60*60)) == 0))
526 // if we didn't find anything, use 4 ticks
531 time_per_tickmark
= scale
/ subdiv
;
533 //get first valid line and its position in pixel space
539 double subr
= scale
/ subdiv
;
541 //get its position inside...
542 time
= ceil(start
/subr
)*subr
- start
;
545 //absolute time of the line to be drawn
549 double t
= (time
/scale
- floor(time
/scale
))*subdiv
; // the difference from the big mark in 0:1
550 //sdindex = (int)floor(t + 0.5); //get how far through the range it is...
551 sdindex
= round_to_int(t
); //get how far through the range it is...
552 if (sdindex
== subdiv
) sdindex
= 0;
554 //synfig::info("Extracted fr %.2lf -> %d", t, sdindex);
557 //synfig::info("Initial values: %.4lf t, %.1lf pixels, %d i", time,pixel,sdindex);
560 const int heightbig
= 12;
561 const int heightsmall
= 4;
563 int width
= get_width();
564 while( pixel
< width
)
566 int xpx
= round_to_int(pixel
);
571 window
->draw_line(gc
,xpx
,0,xpx
,heightbig
);
572 //round the time to nearest frame and draw the text
573 Time
tm((double)time
);
574 if(get_global_fps()) tm
.round(get_global_fps());
575 Glib::ustring
timecode(tm
.get_string(get_global_fps(),App::get_time_format()));
577 //gc->set_rgb_fg_color(Gdk::Color("#000000"));
578 layout
->set_text(timecode
);
579 window
->draw_layout(gc
,xpx
+2,heightsmall
,layout
);
582 window
->draw_line(gc
,xpx
,0,xpx
,heightsmall
);
585 //increment time and position
586 pixel
+= subr
/ dtdp
;
590 if(++sdindex
>= subdiv
) sdindex
-= subdiv
;
596 bool Widget_Timeslider::on_motion_notify_event(GdkEventMotion
* event
) //for dragging
598 if(!adj_timescale
) return false;
600 Gdk::ModifierType mod
= Gdk::ModifierType(event
->state
);
604 //NOTE: we might want to address the possibility of dragging with both buttons held down
606 if(mod
& Gdk::BUTTON2_MASK
)
609 //we need this for scrolling by dragging
610 double curx
= event
->x
;
612 double start
= adj_timescale
->get_lower(),
613 end
= adj_timescale
->get_upper();
617 if(event
->time
-last_event_time
<30)
620 last_event_time
=event
->time
;
622 if(abs(lastx
- curx
) < 1 && end
!= start
) return true;
623 //translate the window and correct it
625 //update our stuff so we are operating correctly
626 //invalidated = true;
629 //Note: Use inverse of mouse movement because of conceptual space relationship
630 double diff
= lastx
- curx
; //curx - lastx;
632 //NOTE: This might be incorrect...
633 //fraction to move...
634 double dpx
= (end
- start
)/get_width();
643 //But clamp to bounds if they exist...
644 //HACK - bounds should not be required for this slider
647 if(start
< adj_bounds
->get_lower())
649 diff
= adj_bounds
->get_lower() - start
;
654 if(end
> adj_bounds
->get_upper())
656 diff
= adj_bounds
->get_upper() - end
;
662 //synfig::info("Scrolling timerange to (%.4f,%.4f)",start,end);
664 adj_timescale
->set_lower(start
);
665 adj_timescale
->set_upper(end
);
667 adj_timescale
->changed();
678 if(mod
& Gdk::BUTTON1_MASK
)
680 double curx
= event
->x
;
682 //get time from drag...
683 double start
= adj_timescale
->get_lower(),
684 end
= adj_timescale
->get_upper(),
685 current
= adj_timescale
->get_value();
686 double t
= start
+ curx
*(end
- start
)/get_width();
688 //snap it to fps - if they exist...
691 t
= floor(t
*fps
+ 0.5)/fps
;
697 adj_timescale
->set_value(t
);
699 //Fixed this to actually do what it's supposed to...
700 if(event
->time
-last_event_time
>50)
702 adj_timescale
->value_changed();
703 last_event_time
= event
->time
;
713 bool Widget_Timeslider::on_scroll_event(GdkEventScroll
* event
) //for zooming
715 if(!adj_timescale
) return false;
717 //Update so we are calculating based on current values
720 //figure out if we should center ourselves on the current time
723 //we want to zoom in on the time value if control is held down
724 if(Gdk::ModifierType(event
->state
) & Gdk::CONTROL_MASK
)
727 switch(event
->direction
)
729 case GDK_SCROLL_UP
: //zoom in
733 case GDK_SCROLL_DOWN
: //zoom out
737 case GDK_SCROLL_RIGHT
:
738 case GDK_SCROLL_LEFT
:
740 double t
= adj_timescale
->get_value();
742 double start
= adj_timescale
->get_lower();
743 double end
= adj_timescale
->get_upper();
744 double lower
= adj_bounds
->get_lower();
745 double upper
= adj_bounds
->get_upper();
746 double adj
= time_per_tickmark
;
748 if( event
->direction
== GDK_SCROLL_RIGHT
)
750 // step forward one tick
753 // don't go past the end of time
757 // if we are already in the right half of the slider
758 if ((t
-start
)*2 > (end
-start
))
760 // if we can't scroll the background left one whole tick, scroll it to the end
761 if (end
> upper
- (t
-orig_t
))
763 adj_timescale
->set_lower(upper
- (end
-start
));
764 adj_timescale
->set_upper(upper
);
766 // else scroll the background left
769 adj_timescale
->set_lower(start
+ (t
-orig_t
));
770 adj_timescale
->set_upper(start
+ (t
-orig_t
) + (end
-start
));
776 // step backwards one tick
779 // don't go past the start of time
783 // if we are already in the left half of the slider
784 if ((t
-start
)*2 < (end
-start
))
786 // if we can't scroll the background right one whole tick, scroll it to the beginning
787 if (start
< lower
+ (orig_t
-t
))
789 adj_timescale
->set_lower(lower
);
790 adj_timescale
->set_upper(lower
+ (end
-start
));
792 // else scroll the background right
795 adj_timescale
->set_lower(start
- (orig_t
-t
));
796 adj_timescale
->set_upper(start
- (orig_t
-t
) + (end
-start
));
803 adj_timescale
->set_value(t
);
804 adj_timescale
->value_changed();
813 void Widget_Timeslider::zoom_in(bool centerontime
)
815 if(!adj_timescale
) return;
817 double start
= adj_timescale
->get_lower(),
818 end
= adj_timescale
->get_upper(),
819 current
= adj_timescale
->get_value();
821 double focuspoint
= centerontime
? current
: (start
+ end
)/2;
823 //calculate new beginning and end
824 end
= focuspoint
+ (end
-focuspoint
)*zoominfactor
;
825 start
= focuspoint
+ (start
-focuspoint
)*zoominfactor
;
827 //synfig::info("Zooming in timerange to (%.4f,%.4f)",start,end);
830 if(start
< adj_bounds
->get_lower())
832 start
= adj_bounds
->get_lower();
835 if(end
> adj_bounds
->get_upper())
837 end
= adj_bounds
->get_upper();
842 adj_timescale
->set_lower(start
);
843 adj_timescale
->set_upper(end
);
845 //call changed function
846 adj_timescale
->changed();
849 void Widget_Timeslider::zoom_out(bool centerontime
)
851 if(!adj_timescale
) return;
853 double start
= adj_timescale
->get_lower(),
854 end
= adj_timescale
->get_upper(),
855 current
= adj_timescale
->get_value();
857 double focuspoint
= centerontime
? current
: (start
+ end
)/2;
859 //calculate new beginning and end
860 end
= focuspoint
+ (end
-focuspoint
)*zoomoutfactor
;
861 start
= focuspoint
+ (start
-focuspoint
)*zoomoutfactor
;
863 //synfig::info("Zooming out timerange to (%.4f,%.4f)",start,end);
866 if(start
< adj_bounds
->get_lower())
868 start
= adj_bounds
->get_lower();
871 if(end
> adj_bounds
->get_upper())
873 end
= adj_bounds
->get_upper();
878 adj_timescale
->set_lower(start
);
879 adj_timescale
->set_upper(end
);
881 //call changed function
882 adj_timescale
->changed();
885 bool Widget_Timeslider::on_button_press_event(GdkEventButton
*event
) //for clicking
887 switch(event
->button
)
892 double start
= adj_timescale
->get_lower(),
893 end
= adj_timescale
->get_upper(),
894 current
= adj_timescale
->get_value();
896 double w
= get_width();
897 double t
= start
+ (end
- start
) * event
->x
/ w
;
899 t
= floor(t
*fps
+ 0.5)/fps
;
901 /*synfig::info("Clicking time from %.3lf to %.3lf [(%.2lf,%.2lf) %.2lf / %.2lf ... %.2lf",
902 current, vt, start, end, event->x, w, fps);*/
910 adj_timescale
->set_value(current
);
911 adj_timescale
->value_changed();
938 bool Widget_Timeslider::on_button_release_event(GdkEventButton
*event
) //end drag
940 switch(event
->button
)