/*** Olive - Non-Linear Video Editor Copyright (C) 2021 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 . ***/ #include "seekablewidget.h" #include #include #include #include "common/qtutils.h" #include "core.h" namespace olive { SeekableWidget::SeekableWidget(QWidget* parent) : TimelineScaledWidget(parent), timeline_points_(nullptr), scroll_(0), snap_service_(nullptr), dragging_(false) { QFontMetrics fm = fontMetrics(); text_height_ = fm.height(); // Set width of playhead marker playhead_width_ = QtUtils::QFontMetricsWidth(fm, "H"); setContextMenuPolicy(Qt::CustomContextMenu); } void SeekableWidget::ConnectTimelinePoints(TimelinePoints *points) { if (timeline_points_) { disconnect(timeline_points_->workarea(), &TimelineWorkArea::RangeChanged, this, static_cast(&SeekableWidget::update)); disconnect(timeline_points_->workarea(), &TimelineWorkArea::EnabledChanged, this, static_cast(&SeekableWidget::update)); disconnect(timeline_points_->markers(), &TimelineMarkerList::MarkerAdded, this, static_cast(&SeekableWidget::update)); disconnect(timeline_points_->markers(), &TimelineMarkerList::MarkerRemoved, this, static_cast(&SeekableWidget::update)); foreach(Marker* marker_widget, marker_map_.values()) { marker_widget->deleteLater(); } } timeline_points_ = points; if (timeline_points_) { connect(timeline_points_->workarea(), &TimelineWorkArea::RangeChanged, this, static_cast(&SeekableWidget::update)); connect(timeline_points_->workarea(), &TimelineWorkArea::EnabledChanged, this, static_cast(&SeekableWidget::update)); connect(timeline_points_->markers(), &TimelineMarkerList::MarkerAdded, this, static_cast(&SeekableWidget::update)); connect(timeline_points_->markers(), &TimelineMarkerList::MarkerRemoved, this, static_cast(&SeekableWidget::update)); connect(timeline_points_->markers(), &TimelineMarkerList::MarkerAdded, this, &SeekableWidget::addMarker); connect(timeline_points_->markers(), &TimelineMarkerList::MarkerRemoved, this, &SeekableWidget::removeMarker); } if (timeline_points() && !timeline_points()->markers()->list().isEmpty()) { foreach (TimelineMarker *marker, timeline_points()->markers()->list()) { if (!marker_map_.keys().contains(marker)) { addMarker(marker); } } } updateMarkerPositions(); update(); } void SeekableWidget::SetSnapService(SnapService *service) { snap_service_ = service; } void SeekableWidget::DeleteSelected() { foreach (TimelineMarker *marker, GetActiveTimelineMarkers()) { //marker_map_.value(marker)->deleteLater(); //marker_map_.take(marker); timeline_points()->markers()->RemoveMarker(marker); // GetActiveMarkers().value(marker)->deleteLater(); } } const int &SeekableWidget::GetScroll() const { return scroll_; } void SeekableWidget::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton) { SeekToScreenPoint(event->pos().x()); dragging_ = true; DeselectAllMarkers(); } } void SeekableWidget::mouseMoveEvent(QMouseEvent *event) { if (event->buttons() & Qt::LeftButton) { SeekToScreenPoint(event->pos().x()); } } void SeekableWidget::mouseReleaseEvent(QMouseEvent *event) { Q_UNUSED(event) if (snap_service_) { snap_service_->HideSnaps(); } dragging_ = false; } void SeekableWidget::ScaleChangedEvent(const double &) { updateMarkerPositions(); update(); } TimelinePoints *SeekableWidget::timeline_points() const { return timeline_points_; } void SeekableWidget::SetTime(const rational &r) { time_ = r; updateMarkerPositions(); update(); } void SeekableWidget::SetScroll(int s) { scroll_ = s; updateMarkerPositions(); update(); } QVector SeekableWidget::GetActiveTimelineMarkers() { QVector active_timelineMarkers; foreach (TimelineMarker *marker, timeline_points()->markers()->list()) { if (marker->active()) { active_timelineMarkers.append(marker); } } return active_timelineMarkers; } void SeekableWidget::DeselectAllMarkers() { foreach(TimelineMarker* marker, timeline_points()->markers()->list()) { marker->set_active(false); } } void SeekableWidget::addMarker(TimelineMarker* marker) { if (!marker_map_.contains(marker)) { Marker *marker_widget = new Marker(this); marker_map_.insert(marker, marker_widget); connect(marker_widget, &Marker::ColorChanged, this, &SeekableWidget::SetMarkerColor); connect(marker, &TimelineMarker::ColorChanged, marker_widget, &Marker::SetColor); connect(marker_widget, &Marker::ActiveChanged, marker, &TimelineMarker::set_active); connect(marker, &TimelineMarker::ActiveChanged, marker_widget, &Marker::SetActive); marker_widget->move(TimeToScreen(marker->time().in()), 20); marker_widget->SetColor(marker->color()); marker_widget->show(); } } void SeekableWidget::removeMarker(TimelineMarker *marker) { marker_map_.value(marker)->deleteLater(); marker_map_.take(marker); } void SeekableWidget::SetMarkerColor(int c) { foreach(TimelineMarker* marker, GetActiveTimelineMarkers()) { marker->set_color(c); } } void SeekableWidget::updateMarkerPositions() { foreach (TimelineMarker* marker, marker_map_.keys()) { Marker *m = marker_map_.value(marker); m->move(TimeToScreen(marker->time().in()), 20); } } int SeekableWidget::TimeToScreen(const rational &time) const { return qFloor(TimeToScene(time)) - scroll_; } rational SeekableWidget::ScreenToTime(int x) const { return qMax(rational(0), SceneToTime(x + scroll_)); } void SeekableWidget::SeekToScreenPoint(int screen) { if (timebase().isNull()) { return; } rational playhead_time = ScreenToTime(screen); if (Core::instance()->snapping() && snap_service_) { rational movement; snap_service_->SnapPoint({playhead_time}, &movement, SnapService::kSnapAll & ~SnapService::kSnapToPlayhead); playhead_time += movement; } if (playhead_time != GetTime()) { SetTime(playhead_time); emit TimeChanged(playhead_time); } } void SeekableWidget::DrawTimelinePoints(QPainter* p, int marker_bottom) { if (!timeline_points()) { return; } // Draw in/out workarea if (timeline_points()->workarea()->enabled()) { int workarea_left = qMax(0, TimeToScreen(timeline_points()->workarea()->in())); int workarea_right; if (timeline_points()->workarea()->out() == TimelineWorkArea::kResetOut) { workarea_right = width(); } else { workarea_right = qMin(width(), TimeToScreen(timeline_points()->workarea()->out())); } p->fillRect(workarea_left, 0, workarea_right - workarea_left, height(), palette().highlight()); } // Draw markers if (marker_bottom > 0 && !timeline_points()->markers()->list().isEmpty()) { int marker_top = marker_bottom - text_height_; // FIXME: Hardcoded marker colors p->setPen(Qt::black); p->setBrush(Qt::green); foreach (TimelineMarker* marker, timeline_points()->markers()->list()) { int marker_left = TimeToScreen(marker->time().in()); int marker_right = TimeToScreen(marker->time().out()); if (marker_left >= width() || marker_right < 0) { continue; } if (marker->time().length() == 0) { // Single point in time marker //DrawPlayhead(p, marker_left, marker_bottom); } else { // Marker range int rect_left = qMax(0, marker_left); int rect_right = qMin(width(), marker_right); QRect marker_rect(rect_left, marker_top, rect_right - rect_left, marker_bottom - marker_top); p->drawRect(marker_rect); if (!marker->name().isEmpty()) { p->drawText(marker_rect, marker->name()); } } } } } void SeekableWidget::DrawPlayhead(QPainter *p, int x, int y) { int half_width = playhead_width_ / 2; if (x + half_width < 0 || x - half_width > width()) { return; } p->setRenderHint(QPainter::Antialiasing); int half_text_height = text_height() / 3; 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); p->setRenderHint(QPainter::Antialiasing, false); } }