390 lines
9.7 KiB
C++
390 lines
9.7 KiB
C++
/***
|
|
|
|
Olive - Non-Linear Video Editor
|
|
Copyright (C) 2019 Olive Team
|
|
|
|
This program 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 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program 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.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
***/
|
|
|
|
#include "timeruler.h"
|
|
|
|
#include <QDebug>
|
|
#include <QMouseEvent>
|
|
#include <QPainter>
|
|
#include <QtMath>
|
|
|
|
#include "common/timecodefunctions.h"
|
|
#include "common/qtversionabstraction.h"
|
|
#include "config/config.h"
|
|
#include "core.h"
|
|
|
|
TimeRuler::TimeRuler(bool text_visible, bool cache_status_visible, QWidget* parent) :
|
|
QWidget(parent),
|
|
scroll_(0),
|
|
text_visible_(text_visible),
|
|
centered_text_(true),
|
|
scale_(1.0),
|
|
time_(0),
|
|
show_cache_status_(cache_status_visible)
|
|
{
|
|
QFontMetrics fm = fontMetrics();
|
|
|
|
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum);
|
|
|
|
// Text height is used to calculate widget height
|
|
text_height_ = fm.height();
|
|
cache_status_height_ = text_height_ / 4;
|
|
|
|
// Get the "minimum" space allowed between two line markers on the ruler (in screen pixels)
|
|
// Mediocre but reliable way of scaling UI objects by font/DPI size
|
|
minimum_gap_between_lines_ = QFontMetricsWidth(fm, "H");
|
|
|
|
// Set width of playhead marker
|
|
playhead_width_ = minimum_gap_between_lines_;
|
|
|
|
// Text visibility affects height, so we set that here
|
|
UpdateHeight();
|
|
}
|
|
|
|
const double &TimeRuler::scale()
|
|
{
|
|
return scale_;
|
|
}
|
|
|
|
void TimeRuler::SetScale(const double &d)
|
|
{
|
|
scale_ = d;
|
|
|
|
update();
|
|
}
|
|
|
|
void TimeRuler::SetTimebase(const rational &r)
|
|
{
|
|
timebase_ = r;
|
|
|
|
timebase_dbl_ = timebase_.toDouble();
|
|
|
|
timebase_flipped_dbl_ = timebase_.flipped().toDouble();
|
|
|
|
update();
|
|
}
|
|
|
|
const int64_t &TimeRuler::GetTime()
|
|
{
|
|
return time_;
|
|
}
|
|
|
|
void TimeRuler::SetCacheStatusLength(const rational &length)
|
|
{
|
|
cache_length_ = length;
|
|
|
|
dirty_cache_ranges_.RemoveTimeRange(TimeRange(length, RATIONAL_MAX));
|
|
|
|
update();
|
|
}
|
|
|
|
void TimeRuler::SetTime(const int64_t &r)
|
|
{
|
|
time_ = r;
|
|
|
|
update();
|
|
}
|
|
|
|
void TimeRuler::SetScroll(int s)
|
|
{
|
|
scroll_ = s;
|
|
|
|
update();
|
|
}
|
|
|
|
void TimeRuler::CacheInvalidatedRange(const rational& in, const rational& out)
|
|
{
|
|
dirty_cache_ranges_.InsertTimeRange(TimeRange(in, out));
|
|
|
|
update();
|
|
}
|
|
|
|
void TimeRuler::CacheTimeReady(const rational &time)
|
|
{
|
|
dirty_cache_ranges_.RemoveTimeRange(TimeRange(time, time + timebase_));
|
|
|
|
update();
|
|
}
|
|
|
|
void TimeRuler::paintEvent(QPaintEvent *)
|
|
{
|
|
// Nothing to paint if the timebase is invalid
|
|
if (timebase_.isNull()) {
|
|
return;
|
|
}
|
|
|
|
QPainter p(this);
|
|
|
|
double width_of_frame = timebase_dbl_ * scale_;
|
|
double width_of_second = 0;
|
|
do {
|
|
width_of_second += timebase_dbl_;
|
|
} while (width_of_second < 1.0);
|
|
width_of_second *= scale_;
|
|
double width_of_minute = width_of_second * 60;
|
|
double width_of_hour = width_of_minute * 60;
|
|
double width_of_day = width_of_hour * 24;
|
|
|
|
double long_interval, short_interval;
|
|
int long_rate;
|
|
|
|
// Used for comparison, even if one unit can technically fit, we have to fit at least two for it to matter
|
|
int doubled_gap = minimum_gap_between_lines_ * 2;
|
|
|
|
if (width_of_day < doubled_gap) {
|
|
long_interval = -1;
|
|
short_interval = width_of_day;
|
|
} else if (width_of_hour < doubled_gap) {
|
|
long_interval = width_of_day;
|
|
long_rate = 24;
|
|
short_interval = width_of_hour;
|
|
} else if (width_of_minute < doubled_gap) {
|
|
long_interval = width_of_hour;
|
|
long_rate = 60;
|
|
short_interval = width_of_minute;
|
|
} else if (width_of_second < doubled_gap) {
|
|
long_interval = width_of_minute;
|
|
long_rate = 60;
|
|
short_interval = width_of_second;
|
|
} else if (width_of_frame < doubled_gap) {
|
|
long_interval = width_of_second;
|
|
long_rate = qRound(timebase_flipped_dbl_);
|
|
short_interval = width_of_frame;
|
|
} else {
|
|
// FIXME: Implement this...
|
|
long_interval = width_of_second;
|
|
short_interval = width_of_frame;
|
|
}
|
|
|
|
if (short_interval < minimum_gap_between_lines_) {
|
|
if (long_interval == -1) {
|
|
do {
|
|
short_interval *= 2;
|
|
} while (short_interval < minimum_gap_between_lines_);
|
|
} else {
|
|
int div;
|
|
|
|
short_interval = long_interval;
|
|
|
|
for (div=long_rate;div>0;div--) {
|
|
if (long_rate%div == 0) {
|
|
// This division produces a whole number
|
|
double test_frame_width = long_interval / static_cast<double>(div);
|
|
|
|
if (test_frame_width >= minimum_gap_between_lines_) {
|
|
short_interval = test_frame_width;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set line color to main text color
|
|
p.setBrush(Qt::NoBrush);
|
|
p.setPen(palette().text().color());
|
|
|
|
// Calculate line dimensions
|
|
QFontMetrics fm = p.fontMetrics();
|
|
int line_bottom = height();
|
|
|
|
if (show_cache_status_) {
|
|
line_bottom -= cache_status_height_;
|
|
}
|
|
|
|
int long_height = fm.height();
|
|
int short_height = long_height/2;
|
|
int long_y = line_bottom - long_height;
|
|
int short_y = line_bottom - short_height;
|
|
|
|
// Draw long lines
|
|
int last_long_unit = -1;
|
|
int last_short_unit = -1;
|
|
int last_text_draw = INT_MIN;
|
|
|
|
// FIXME: Hardcoded number
|
|
const int kAverageTextWidth = 200;
|
|
|
|
for (int i=-kAverageTextWidth;i<width()+kAverageTextWidth;i++) {
|
|
double screen_pt = static_cast<double>(i + scroll_);
|
|
|
|
if (long_interval > -1) {
|
|
int this_long_unit = qFloor(screen_pt/long_interval);
|
|
if (this_long_unit != last_long_unit) {
|
|
int line_y = long_y;
|
|
|
|
if (text_visible_) {
|
|
QRect text_rect;
|
|
Qt::Alignment text_align;
|
|
QString timecode_str = Timecode::timestamp_to_timecode(ScreenToUnit(i), timebase_, Timecode::CurrentDisplay());
|
|
int timecode_width = QFontMetricsWidth(fm, timecode_str);
|
|
int timecode_left;
|
|
|
|
if (centered_text_) {
|
|
text_rect = QRect(i - kAverageTextWidth/2, 0, kAverageTextWidth, fm.height());
|
|
text_align = Qt::AlignCenter;
|
|
timecode_left = i - timecode_width/2;
|
|
} else {
|
|
text_rect = QRect(i, 0, kAverageTextWidth, fm.height());
|
|
text_align = Qt::AlignLeft | Qt::AlignVCenter;
|
|
timecode_left = i;
|
|
|
|
// Add gap to left between line and text
|
|
timecode_str.prepend(' ');
|
|
}
|
|
|
|
if (timecode_left > last_text_draw) {
|
|
p.drawText(text_rect,
|
|
text_align,
|
|
timecode_str);
|
|
|
|
last_text_draw = timecode_left + timecode_width;
|
|
|
|
if (!centered_text_) {
|
|
line_y = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
p.drawLine(i, line_y, i, line_bottom);
|
|
last_long_unit = this_long_unit;
|
|
}
|
|
}
|
|
|
|
if (short_interval > -1) {
|
|
int this_short_unit = qFloor(screen_pt/short_interval);
|
|
if (this_short_unit != last_short_unit) {
|
|
p.drawLine(i, short_y, i, line_bottom);
|
|
last_short_unit = this_short_unit;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If cache status is enabled
|
|
if (show_cache_status_) {
|
|
int cache_screen_length = qMin(TimeToScreen(cache_length_), width());
|
|
|
|
if (cache_screen_length > 0) {
|
|
int cache_y = height() - cache_status_height_;
|
|
|
|
p.fillRect(0, cache_y, cache_screen_length , cache_status_height_, Qt::green);
|
|
|
|
foreach (const TimeRange& range, dirty_cache_ranges_) {
|
|
int range_left = TimeToScreen(range.in());
|
|
int range_right = TimeToScreen(range.out());
|
|
|
|
if (range_left >= width() || range_right < 0) {
|
|
continue;
|
|
}
|
|
|
|
p.fillRect(qMax(0, range_left), cache_y, qMin(width(), range_right) - range_left, cache_status_height_, Qt::red);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Draw the playhead if it's on screen at the moment
|
|
int playhead_pos = UnitToScreen(time_);
|
|
if (playhead_pos + playhead_width_ >= 0 && playhead_pos - playhead_width_ < width()) {
|
|
p.setPen(Qt::NoPen);
|
|
p.setBrush(style_.PlayheadColor());
|
|
DrawPlayhead(&p, playhead_pos, line_bottom);
|
|
}
|
|
}
|
|
|
|
void TimeRuler::mousePressEvent(QMouseEvent *event)
|
|
{
|
|
SeekToScreenPoint(event->pos().x());
|
|
}
|
|
|
|
void TimeRuler::mouseMoveEvent(QMouseEvent *event)
|
|
{
|
|
if (event->buttons() & Qt::LeftButton) {
|
|
SeekToScreenPoint(event->pos().x());
|
|
}
|
|
}
|
|
|
|
void TimeRuler::DrawPlayhead(QPainter *p, int x, int y)
|
|
{
|
|
p->setRenderHint(QPainter::Antialiasing);
|
|
|
|
int half_text_height = text_height_ / 3;
|
|
int half_width = playhead_width_ / 2;
|
|
|
|
QPoint points[] = {
|
|
QPoint(x, y),
|
|
QPoint(x - half_width, y - half_text_height),
|
|
QPoint(x - half_width, y - text_height_),
|
|
QPoint(x + 1 + half_width, y - text_height_),
|
|
QPoint(x + 1 + half_width, y - half_text_height),
|
|
QPoint(x + 1, y),
|
|
};
|
|
|
|
p->drawPolygon(points, 6);
|
|
}
|
|
|
|
int TimeRuler::CacheStatusHeight() const
|
|
{
|
|
return fontMetrics().height() / 4;
|
|
}
|
|
|
|
double TimeRuler::ScreenToUnitFloat(int screen)
|
|
{
|
|
return (screen + scroll_) / scale_ / timebase_dbl_;
|
|
}
|
|
|
|
int64_t TimeRuler::ScreenToUnit(int screen)
|
|
{
|
|
return qFloor(ScreenToUnitFloat(screen));
|
|
}
|
|
|
|
int TimeRuler::UnitToScreen(int64_t unit)
|
|
{
|
|
return qFloor(static_cast<double>(unit) * scale_ * timebase_dbl_) - scroll_;
|
|
}
|
|
|
|
int TimeRuler::TimeToScreen(const rational &time)
|
|
{
|
|
return qFloor(time.toDouble() * scale_) - scroll_;
|
|
}
|
|
|
|
void TimeRuler::SeekToScreenPoint(int screen)
|
|
{
|
|
int64_t timestamp = qMax(0, qRound(ScreenToUnitFloat(screen)));
|
|
|
|
SetTime(timestamp);
|
|
|
|
emit TimeChanged(timestamp);
|
|
}
|
|
|
|
void TimeRuler::UpdateHeight()
|
|
{
|
|
int height = text_height_;
|
|
|
|
if (text_visible_) {
|
|
height += text_height_;
|
|
}
|
|
|
|
if (show_cache_status_) {
|
|
height += cache_status_height_;
|
|
}
|
|
|
|
setFixedHeight(height);
|
|
}
|