/* === S Y N F I G ========================================================= */
/*! \file timemodel.cpp
** \brief TimeModel Implementation File
**
** $Id$
**
** \legal
** Copyright (c) 2004 Adrian Bentley
** ......... ... 2018 Ivan Mahonin
**
** 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 <config.h>
#endif
#include <algorithm>
#include <synfig/general.h>
#include "app.h"
#include "helpers.h"
#include "timemodel.h"
#include <gui/localization.h>
#endif
/* === U S I N G =========================================================== */
using namespace synfig;
using namespace studio;
/* === M A C R O S ========================================================= */
/* === G L O B A L S ======================================================= */
/* === P R O C E D U R E S ================================================= */
/* === M E T H O D S ======================================================= */
/* === E N T R Y P O I N T ================================================= */
TimeModel::TimeModel():
in_sync(),
fps(),
play_bounds_enabled(),
play_repeat(),
full_time_adjustment_(Gtk::Adjustment::create(0.0, 0.0, 0.0)),
scroll_time_adjustment_(Gtk::Adjustment::create(0.0, 0.0, 0.0)),
play_bounds_adjustment_(Gtk::Adjustment::create(0.0, 0.0, 0.0))
{
full_time_adjustment_->signal_changed().connect(
sigc::bind(sigc::mem_fun(*this, &TimeModel::on_changed), &full_time_adjustment_) );
full_time_adjustment_->signal_value_changed().connect(
sigc::bind(sigc::mem_fun(*this, &TimeModel::on_value_changed), &full_time_adjustment_) );
scroll_time_adjustment_->signal_changed().connect(
sigc::bind(sigc::mem_fun(*this, &TimeModel::on_changed), &scroll_time_adjustment_) );
scroll_time_adjustment_->signal_value_changed().connect(
sigc::bind(sigc::mem_fun(*this, &TimeModel::on_value_changed), &scroll_time_adjustment_) );
play_bounds_adjustment_->signal_changed().connect(
sigc::bind(sigc::mem_fun(*this, &TimeModel::on_changed), &play_bounds_adjustment_) );
play_bounds_adjustment_->signal_value_changed().connect(
sigc::bind(sigc::mem_fun(*this, &TimeModel::on_value_changed), &play_bounds_adjustment_) );
}
void
TimeModel::on_changed(Glib::RefPtr<Gtk::Adjustment> *source)
{
if (in_sync) return;
if (source == &play_bounds_adjustment_)
set_play_bounds(Time((*source)->get_lower()), Time((*source)->get_upper()));
sync();
}
void
TimeModel::on_value_changed(Glib::RefPtr<Gtk::Adjustment> *source)
{
if (in_sync) return;
if (source == &scroll_time_adjustment_) {
Time lower((*source)->get_value());
Time upper = lower + get_page_size();
set_visible_bounds(lower, upper);
} else
if (source == &play_bounds_adjustment_) {
Time t = round_time( Time((*source)->get_value()) );
if (play_time == play_bounds_lower && t <= play_bounds_lower) return;
if (play_time == play_bounds_upper && t >= play_bounds_upper) return;
set_time(t);
} else {
set_time(Time((*source)->get_value()));
}
sync();
}
void
TimeModel::sync()
{
in_sync = true;
try {
double precision = real_low_precision<double>();
Time step_increment = get_step_increment();
Time page_increment = get_page_increment();
Time page_size = get_page_size();
// raise events only when all changes will done
FreezeNotify freeze_full_time(full_time_adjustment());
FreezeNotify freeze_scroll_time(scroll_time_adjustment());
FreezeNotify freeze_play_bounds(play_bounds_adjustment());
configure_adjustment(
full_time_adjustment(),
(double)time,
(double)lower,
(double)upper,
(double)step_increment,
(double)page_increment,
(double)page_size,
precision );
configure_adjustment(
scroll_time_adjustment(),
(double)visible_lower,
(double)lower,
(double)upper,
(double)step_increment,
(double)page_increment,
(double)page_size,
precision );
configure_adjustment(
play_bounds_adjustment(),
(double)time,
(double)play_bounds_lower,
(double)play_bounds_upper,
(double)step_increment,
(double)step_increment,
0.0,
precision );
} catch(...) {
in_sync = false;
throw;
}
in_sync = false;
}
bool
TimeModel::set_time_silent(Time time, bool *is_play_time_changed)
{
time = round_time(time);
Time t = std::max(lower, std::min(upper, time));
Time pt = std::max(play_bounds_lower, std::min(play_bounds_upper, t));
if (this->play_time != pt) {
this->play_time = pt;
if (is_play_time_changed) *is_play_time_changed = true;
}
if (this->time == t) return false;
this->time = t;
return true;
}
bool
TimeModel::set_bounds_silent(Time lower, Time upper, float fps)
{
if (approximate_less_or_equal_lp(fps, 0.f)) fps = 0.f;
// try to keep minimum one frame range
Time step = Time(fps ? 1.0/fps : 0.0);
lower = round_time(lower);
upper = std::max(round_time(lower + step), round_time(upper));
if (this->lower == lower && this->upper == upper && this->fps == fps)
return false;
Time play_bounds_lower = this->play_bounds_lower <= this->lower ? lower : this->play_bounds_lower;
Time play_bounds_upper = this->play_bounds_upper >= this->upper ? upper : this->play_bounds_upper;
this->lower = lower;
this->upper = upper;
this->fps = fps;
set_visible_bounds_silent(visible_lower, visible_upper);
set_play_bounds_silent(play_bounds_lower, play_bounds_upper, play_bounds_enabled, play_repeat);
set_time_silent(time, NULL);
return true;
}
bool
TimeModel::set_visible_bounds_silent(Time lower, Time upper)
{
// try to keep duration, minimum duration is a one frame
Time duration = std::max(Time(fps ? 1.0/fps : 0.0), upper - lower);
// this->lower bound have priority when this->upper < this->lower
lower = std::max(this->lower, std::min(this->upper, lower));
upper = std::max(lower, std::min(this->upper, upper));
if (duration > Time() && this->lower < this->upper) {
Time t = lower + duration;
if (t > upper)
upper = std::max(lower, std::min(this->upper, t));
t = upper - duration;
if (t < lower)
lower = std::max(this->lower, t);
}
if (visible_lower == lower && visible_upper == upper) return false;
visible_lower = lower;
visible_upper = upper;
return true;
}
bool
TimeModel::set_play_bounds_silent(Time lower, Time upper, bool enabled, bool repeat) {
// this->lower bound have priority when this->upper < this->lower
lower = std::max(this->lower, std::min(this->upper, round_time(lower)));
upper = std::max(lower, std::min(this->upper, round_time(upper)));
// try to keep minimum two frame range
if (fps && this->lower < this->upper) {
Time step = Time(2.0/fps);
Time t = round_time(lower + step);
if (t > upper)
upper = std::max(lower, std::min(this->upper, t));
t = round_time(upper - step);
if (t < lower)
lower = std::max(this->lower, t);
}
if ( play_bounds_lower == lower && play_bounds_upper == upper
&& play_bounds_enabled == enabled && play_repeat == repeat )
return false;
play_bounds_lower = lower;
play_bounds_upper = upper;
play_bounds_enabled = enabled;
play_repeat = repeat;
set_time_silent(time, NULL);
return true;
}
void
TimeModel::set_play_bounds_lower(const Time &lower) {
// upper bound will be fixed to be >= (lower + two frames)
Time step = Time(fps ? 2.0/fps : 0.0);
Time t = round_time(lower);
set_play_bounds(t, std::max(play_bounds_upper, t + step));
}
void
TimeModel::set_play_bounds_upper(const Time &upper) {
// lower bound will be fixed to be <= (upper - two frames)
Time step = Time(fps ? 2.0/fps : 0.0);
Time t = round_time(upper);
set_play_bounds(std::min(play_bounds_lower, t - step), t);
}
void
TimeModel::set_play_repeat(bool repeat)
{
if (play_repeat == repeat) return;
play_repeat = repeat;
play_bounds_changed();
}
void
TimeModel::set_play_bounds_enabled(bool enabled)
{
if (play_bounds_enabled == enabled) return;
play_bounds_enabled = enabled;
play_bounds_changed();
}
Real
TimeModel::get_zoom() const
{ return (Real)get_size()/(Real)get_page_size(); }
void
TimeModel::set_zoom(Real x, const Time ¢er)
{
x = std::max(Real(1.0), x);
Time size = get_size();
Time page_size = size/x;
Time lower = center - page_size*0.5;
Time upper = lower + page_size;
set_visible_bounds(lower, upper);
}
void
TimeModel::zoom(Real x, const Time ¢er)
{
// relative zoom in/out
x = std::max(real_low_precision<Real>(), x);
x = 1.0/x;
Time lower = (visible_lower - center)*x + center;
Time upper = (visible_upper - center)*x + center;
set_visible_bounds(lower, upper);
}
bool
TimeModel::almost_equal(const synfig::Time &a, const synfig::Time &b, const synfig::Time &range) const
{ return (a < b ? b - a : a - b) <= std::max(Time(), range) + get_frame_duration()*0.49999; }
Time
TimeModel::get_frame_duration() const
{ return fps ? Time(1.0/fps).round(fps) : Time(); }
Time
TimeModel::get_page_increment() const
{
Time s = get_step_increment();
Time p = get_page_size();
return std::max(s, std::min(p*0.8, p - s));
}