refactor: workspace layout — crates/, app at root, legacy C++ removed

Single mechanical restructure commit:
- root Cargo.toml = oakapp bin + workspace; one cargo build produces
  oakapp, oak-cli, oak-worker, liboakengine.dylib
- app/rust/src -> src/ (app at repo root, no rust/ nesting)
- src/<mod>/rust -> crates/oak<mod>; src/oakcore-rs -> crates/oakcore;
  src/bindings/oakotio -> crates/oakotio; src/engine/rust ->
  crates/oakengine (keeps cdylib+staticlib+rlib)
- public C headers include/<mod>/ -> crates/oakengine/include/<mod>/
- OFX SDK headers vendored into crates/oakplugin/ofx/ (HostSupport gone)
- legacy deleted: old src/ C++ modules, engine/, core/, ffmpeg_bridge/,
  app/ (Qt), cli/worker C++, root CMakeLists, third_party/KDDockWidgets
  submodule, otio-install, all build-* output (~40GB)
- oakstorage kept but excluded from the workspace (skeleton w/ todos);
  gpui excluded (own workspace)
- verified: cargo build green, cargo test --workspace 1845/0
  (with the documented OCIO_RS_* env override for the homebrew OCIO)
This commit is contained in:
2026-08-10 20:24:25 +08:00
parent f8540e3892
commit 013a175707
4212 changed files with 8331 additions and 2274987 deletions
-26
View File
@@ -1,26 +0,0 @@
# Olive - Non-Linear Video Editor
# Copyright (C) 2022 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/>.
set(OLIVE_SOURCES
${OLIVE_SOURCES}
widget/timeruler/markerpainting.h
widget/timeruler/markerpainting.cpp
widget/timeruler/seekablewidget.h
widget/timeruler/seekablewidget.cpp
widget/timeruler/timeruler.h
widget/timeruler/timeruler.cpp
PARENT_SCOPE
)
-131
View File
@@ -1,131 +0,0 @@
/***
Oak - Non-Linear Video Editor
Copyright (C) 2026 Oak 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/>.
***/
#ifndef OAK_MARKERHANDLE_H
#define OAK_MARKERHANDLE_H
#include <olive/core/core.h>
#include <QByteArray>
#include <QString>
#include "oakengine/timeline.h"
namespace olive
{
using olive::core::Rational;
using olive::core::TimeRange;
/**
* @brief Facade accessors for marker handles held by the ruler/scrollbar
* widgets.
*
* The widgets keep OakEngineMarker* / OakEngineMarkerList* as opaque
* identity handles (selection, drawing, hit-testing). All engine data and
* mutations go through the liboakengine C ABI (oakengine/timeline.h); the
* handle itself is only an identity. Marker times are rational seconds
* (num/den pairs), not frame timestamps.
*/
inline TimeRange marker_time(const OakEngineMarker *marker)
{
int64_t in_num = 0, in_den = 1, out_num = 0, out_den = 1;
oakengine_marker_get_time(marker, &in_num, &in_den,
&out_num, &out_den);
return TimeRange(Rational(int(in_num), int(in_den)),
Rational(int(out_num), int(out_den)));
}
inline QString marker_name(const OakEngineMarker *marker)
{
const int size =
oakengine_marker_get_name(marker, nullptr, 0);
QByteArray buf(size + 1, '\0');
oakengine_marker_get_name(marker, buf.data(),
int(buf.size()));
return QString::fromUtf8(buf.constData());
}
inline int marker_color(const OakEngineMarker *marker)
{
return oakengine_marker_get_color(marker);
}
inline bool marker_has_sibling_at_time(const OakEngineMarker *marker,
const Rational &time)
{
return oakengine_marker_has_sibling_at_time(
marker, time.numerator(), time.denominator()) != 0;
}
inline void marker_set_time_live(OakEngineMarker *marker,
const TimeRange &range)
{
oakengine_marker_set_time_live(
marker, range.in().numerator(), range.in().denominator(),
range.out().numerator(), range.out().denominator());
}
/**
* @brief ADL customization points for
* TimeBasedViewSelectionManager<OakEngineMarker>.
*
* The selection manager template calls these unqualified; these overloads
* route marker access through the facade (see
* widget/keyframeview/keyframehandle.h for the keyframe equivalent).
* Markers drag both their in and out points, hence selection_time_end().
* selection_time_target_parent() returns nullptr: marker drags never pass
* a time target.
*/
inline Rational selection_time(OakEngineMarker *marker)
{
return marker_time(marker).in();
}
inline Rational selection_time_end(OakEngineMarker *marker)
{
return marker_time(marker).out();
}
inline void selection_set_time(OakEngineMarker *marker, const Rational &time)
{
// Move the in-point keeping the range length (was
// TimelineMarker::set_time(const Rational &))
const TimeRange range = marker_time(marker);
const Rational length = range.out() - range.in();
marker_set_time_live(marker, TimeRange(time, time + length));
}
inline bool selection_has_sibling_at_time(OakEngineMarker *marker,
const Rational &time)
{
return marker_has_sibling_at_time(marker, time);
}
inline OakEngineNode *selection_time_target_parent(OakEngineMarker *marker)
{
Q_UNUSED(marker)
return nullptr;
}
} // namespace olive
#endif // OAK_MARKERHANDLE_H
-115
View File
@@ -1,115 +0,0 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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 "markerpainting.h"
#include <QApplication>
#include <QPalette>
#include "oakutil/qtutils.h"
#include "common/colorcodingapp.h"
namespace olive
{
namespace MarkerPainting
{
int height(const QFontMetrics &fm)
{
return fm.height();
}
QRect draw(QPainter *p, const QPoint &pt, int max_right, double scale,
bool selected, const QString &name, int color,
const core::Rational &in, const core::Rational &out)
{
QFontMetrics fm = p->fontMetrics();
int marker_height = height(fm);
int marker_width = QtUtils::q_font_metrics_width(fm, QStringLiteral("H"));
int half_width = marker_width / 2;
QColor c = QtUtils::to_q_color(AppColorCoding::get_color(color));
if (selected) {
p->setPen(Qt::white);
p->setBrush(c.lighter());
} else {
p->setPen(Qt::black);
p->setBrush(c);
}
int top = pt.y() - marker_height;
QTextOption op(Qt::AlignLeft | Qt::AlignVCenter);
op.setWrapMode(QTextOption::NoWrap);
if (out != in) {
QRect marker_rect(pt.x(), top, (out - in).to_double() * scale,
marker_height);
p->drawRect(marker_rect);
if (!name.isEmpty()) {
p->setPen(AppColorCoding::get_ui_selector_color(
AppColorCoding::get_color(color)));
p->drawText(marker_rect.adjusted(marker_width / 4, 0, 0, 0), name,
op);
}
return marker_rect;
} else {
int half_marker_height = marker_height / 3;
int left = pt.x() - half_width;
int right = pt.x() + half_width;
int center_y = pt.y() - half_marker_height;
QPoint points[] = {
pt,
QPoint(left, center_y),
QPoint(left, top),
QPoint(right, top),
QPoint(right, center_y),
pt,
};
p->setRenderHint(QPainter::Antialiasing);
p->drawPolygon(points, 6);
if (!name.isEmpty() && max_right != -1) {
QRect text_rect(right, top, max_right - right, marker_height);
int padding = QtUtils::q_font_metrics_width(p->fontMetrics(),
QStringLiteral(" "));
text_rect.adjust(padding, 0, -padding - half_width, 0);
p->setPen(qApp->palette().text().color());
p->drawText(text_rect, name, op);
}
return QRect(left, top, marker_width, marker_height);
}
}
}
}
-61
View File
@@ -1,61 +0,0 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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/>.
***/
#ifndef OAK_MARKERPAINTING_H
#define OAK_MARKERPAINTING_H
#include <olive/core/core.h>
#include <QFontMetrics>
#include <QPainter>
#include <QPoint>
#include <QRect>
#include <QString>
namespace olive
{
/**
* @brief Marker painting helpers (pure UI code, moved app-side from the
* engine's TimelineMarker::draw()/get_marker_height()).
*
* The caller passes the marker's data by value (name, color index and
* in/out times) so no engine types are needed for drawing.
*/
namespace MarkerPainting
{
/// Height of a marker in pixels for the given font (was
/// TimelineMarker::get_marker_height()).
int height(const QFontMetrics &fm);
/// Draw a marker at `pt` (bottom-center anchor) and return its bounding
/// rect (was TimelineMarker::draw()). `max_right` of -1 disables the
/// label text; `scale` is pixels per second for ranged markers.
QRect draw(QPainter *p, const QPoint &pt, int max_right, double scale,
bool selected, const QString &name, int color,
const core::Rational &in, const core::Rational &out);
}
}
#endif // OAK_MARKERPAINTING_H
-830
View File
@@ -1,830 +0,0 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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 "seekablewidget.h"
#include <QInputDialog>
#include <QMouseEvent>
#include <QPainter>
#include <QtMath>
#include "oakutil/qtutils.h"
#include "oakutil/oaknode.h"
#include "oakutil/range.h"
#include "core.h"
#include "dialog/markerproperties/markerpropertiesdialog.h"
#include "markerhandle.h"
#include "markerpainting.h"
#include "oakengine/node.h"
#include "oakengine/serializer.h"
#include "oakengine/timeline.h"
#include "oakengine/viewer.h"
#include "oakengine/undo.h"
#include "widget/colorlabelmenu/colorlabelmenu.h"
#include "widget/menu/menushared.h"
#include "widget/timebased/timebasedwidget.h"
namespace olive
{
#define super TimeBasedView
SeekableWidget::SeekableWidget(QWidget *parent)
: super(parent)
, markers_(nullptr)
, workarea_(nullptr)
, dragging_(false)
, ignore_next_focus_out_(false)
, selection_manager_(this)
, resize_item_(nullptr)
, marker_top_(0)
, marker_bottom_(0)
, marker_editing_enabled_(true)
, bridge_(new EngineEventBridge(this))
{
QFontMetrics fm = fontMetrics();
text_height_ = fm.height();
// Set width of playhead marker
playhead_width_ = QtUtils::q_font_metrics_width(fm, "H");
setContextMenuPolicy(Qt::CustomContextMenu);
setFocusPolicy(Qt::ClickFocus);
setMouseTracking(true);
selection_manager_.set_snap_mask(TimeBasedWidget::k_snap_all);
set_is_timeline_axes(true);
}
void SeekableWidget::set_markers(OakEngineMarkerList *markers)
{
// Unsubscribe old marker list events via bridge
for (int64_t id : marker_list_subs_) {
bridge_->unsubscribe(id);
}
marker_list_subs_.clear();
if (markers_) {
selection_manager_.clear_selection();
}
markers_ = markers;
if (markers_) {
// Subscribe to marker list events via bridge instead of direct marker list signals
marker_list_subs_.append(bridge_->subscribe(
markers_,
OAKENGINE_EVENT_MARKER_LIST_MARKER_ADDED));
marker_list_subs_.append(bridge_->subscribe(
markers_,
OAKENGINE_EVENT_MARKER_LIST_MARKER_REMOVED));
marker_list_subs_.append(bridge_->subscribe(
markers_,
OAKENGINE_EVENT_MARKER_LIST_MARKER_MODIFIED));
// Wire the bridge signals once — bridge_ outlives individual marker
// list subscriptions, re-connecting on every set_markers() would
// stack duplicate viewport updates.
if (!marker_connects_done_) {
marker_connects_done_ = true;
connect(bridge_, &EngineEventBridge::marker_list_marker_added, this,
[this](OakEngineMarkerList *, OakEngineMarker *) {
viewport()->update();
});
connect(bridge_, &EngineEventBridge::marker_list_marker_removed, this,
[this](OakEngineMarkerList *, OakEngineMarker *) {
viewport()->update();
});
connect(bridge_, &EngineEventBridge::marker_list_marker_modified, this,
[this](OakEngineMarkerList *, OakEngineMarker *) {
viewport()->update();
});
}
}
viewport()->update();
}
void SeekableWidget::set_work_area(OakEngineWorkarea *workarea)
{
if (workarea_) {
selection_manager_.clear_selection();
if (workarea_range_sub_) {
bridge_->unsubscribe(workarea_range_sub_);
workarea_range_sub_ = 0;
}
if (workarea_enabled_sub_) {
bridge_->unsubscribe(workarea_enabled_sub_);
workarea_enabled_sub_ = 0;
}
}
workarea_ = workarea;
if (workarea_) {
workarea_range_sub_ = bridge_->subscribe(
reinterpret_cast<void *>(workarea_),
OAKENGINE_EVENT_WORKAREA_RANGE_CHANGED);
workarea_enabled_sub_ = bridge_->subscribe(
reinterpret_cast<void *>(workarea_),
OAKENGINE_EVENT_WORKAREA_ENABLED_CHANGED);
connect(bridge_, &EngineEventBridge::workarea_range_changed, viewport(),
static_cast<void (QWidget::*)()>(&QWidget::update));
connect(bridge_, &EngineEventBridge::workarea_enabled_changed, viewport(),
static_cast<void (QWidget::*)()>(&QWidget::update));
}
viewport()->update();
}
void SeekableWidget::delete_selected()
{
if (!selection_manager_.is_dragging()) {
const auto &selected = selection_manager_.get_selected_objects();
if (selected.empty()) {
return;
}
OakEngineNode *viewer_node =
get_viewer_node();
if (oakengine_node_is_sequence(viewer_node)) {
// Batch removal through the liboakengine C ABI facade (one
// undoable command). The facade family only wraps sequences;
// markers of other viewer nodes (e.g. footage viewers) keep
// the old per-marker command path below.
QVector<int64_t> times;
times.reserve(int(selected.size()));
for (OakEngineMarker *marker : selected) {
times.append(Timecode::time_to_timestamp(
marker_time(marker).in(), timebase(), Timecode::k_round));
}
oakengine_sequence_marker_remove_many(
reinterpret_cast<OakEngineSequence *>(viewer_node),
times.constData(), times.size());
return;
}
// Remove each marker (undoable individually)
for (auto *m : selected) {
oakengine_marker_remove(m);
}
}
}
bool SeekableWidget::copy_selected(bool cut)
{
if (!selection_manager_.get_selected_objects().empty()) {
const auto &selected = selection_manager_.get_selected_objects();
std::vector<const OakEngineMarker *> markers;
markers.reserve(selected.size());
for (auto *m : selected) {
markers.push_back(m);
}
OakEngineClipboard *cb = oakengine_clipboard_create(
OAKENGINE_CLIPBOARD_MARKERS, nullptr, nullptr);
oakengine_clipboard_set_markers(
cb, markers.data(), static_cast<int>(markers.size()));
oakengine_clipboard_copy(cb);
oakengine_clipboard_free(cb);
if (cut) {
delete_selected();
}
return true;
} else {
return false;
}
}
bool SeekableWidget::paste_markers()
{
OakEngineNode *viewer =
get_viewer_node();
OakEngineProject *project = oak::Node(viewer).project().handle();
OakEngineClipboard *cb = oakengine_clipboard_create(
OAKENGINE_CLIPBOARD_MARKERS, project, nullptr);
int result_code;
oakengine_clipboard_paste(
cb, OAKENGINE_CLIPBOARD_MARKERS, project, &result_code, nullptr, 0);
if (result_code == OAKENGINE_OK) {
int count = oakengine_clipboard_get_loaded_marker_count(cb);
if (count > 0) {
// Collect the pasted markers
std::vector<OakEngineMarker *> markers;
markers.reserve(count);
for (int i = 0; i < count; i++) {
markers.push_back(
oakengine_clipboard_get_loaded_marker_at(cb, i));
}
// Normalize markers to start at playhead
Rational min = RATIONAL_MAX;
for (auto it = markers.cbegin(); it != markers.cend(); it++) {
min = std::min(min, marker_time(*it).in());
}
int64_t ph_num = 0, ph_den = 1;
oakengine_viewer_get_playhead(viewer, &ph_num, &ph_den);
min -= Rational(int(ph_num), int(ph_den));
for (auto it = markers.cbegin(); it != markers.cend(); it++) {
OakEngineMarker *m = *it;
Rational new_in = marker_time(m).in() - min;
oakengine_marker_set_time_live(
m,
new_in.numerator(), new_in.denominator(),
new_in.numerator(), new_in.denominator());
if (OakEngineMarker *existing =
oakengine_marker_list_marker_at_time(
markers_, new_in.numerator(),
new_in.denominator())) {
oakengine_marker_remove(existing);
}
// Re-add the clipboard marker to the list (undoable)
oakengine_marker_list_add_existing(markers_, m);
}
oakengine_clipboard_free(cb);
return true;
}
}
oakengine_clipboard_free(cb);
return false;
}
void SeekableWidget::mousePressEvent(QMouseEvent *event)
{
OakEngineMarker *initial;
if (hand_press(event)) {
return;
} else if (event->modifiers() & Qt::ControlModifier) {
selection_manager_.rubber_band_start(event);
} else if (marker_editing_enabled_ &&
(initial = selection_manager_.mouse_press(event))) {
selection_manager_.drag_start(initial, event);
} else if (resize_item_) {
// Handle selection, even though we won't be using it for dragging
if (!(event->modifiers() & Qt::ShiftModifier)) {
selection_manager_.clear_selection();
}
// resize_item_ is either the workarea or a marker (see
// find_resize_handle); the handles are opaque here, so tell
// them apart by comparing against workarea_ instead of dynamic_cast.
if (resize_item_ != workarea_) {
selection_manager_.select(
static_cast<OakEngineMarker *>(resize_item_));
}
dragging_ = true;
resize_start_ = mapToScene(event->pos());
} else if (!selection_manager_.get_object_at_point(event->pos()) &&
event->button() == Qt::LeftButton) {
seek_to_scene_point(mapToScene(event->pos()).x());
dragging_ = true;
deselect_all_markers();
}
}
void SeekableWidget::mouseMoveEvent(QMouseEvent *event)
{
if (hand_move(event)) {
return;
} else if (selection_manager_.is_rubber_banding()) {
selection_manager_.rubber_band_move(event->pos());
viewport()->update();
} else if (selection_manager_.is_dragging()) {
selection_manager_.drag_move(event->pos());
} else if (dragging_) {
QPointF scene = mapToScene(event->pos());
if (resize_item_) {
drag_resize_handle(scene);
} else {
seek_to_scene_point(scene.x());
}
} else {
// Look for resize points
if (!last_playhead_shape_.containsPoint(event->pos(),
Qt::OddEvenFill) &&
!selection_manager_.get_object_at_point(event->pos()) &&
find_resize_handle(event)) {
setCursor(Qt::SizeHorCursor);
} else {
unsetCursor();
clear_resize_handle();
}
}
if (event->buttons()) {
// Signal cursor pos in case we should scroll to catch up to it
emit drag_moved(event->pos().x(), event->pos().y());
}
}
void SeekableWidget::mouseReleaseEvent(QMouseEvent *event)
{
if (hand_release(event)) {
return;
}
if (selection_manager_.is_rubber_banding()) {
selection_manager_.rubber_band_stop();
return;
}
if (selection_manager_.is_dragging()) {
void *command = oakengine_undo_command_create_multi();
selection_manager_.drag_stop(command);
oakengine_undo_push(
command, tr("Moved %1 Marker(s)").arg(selection_manager_.get_selected_objects().size()).toUtf8().constData());
}
if (get_snap_service()) {
get_snap_service()->hide_snaps();
}
if (resize_item_) {
commit_resize_handle();
resize_item_ = nullptr;
}
dragging_ = false;
emit drag_released();
}
void SeekableWidget::mouseDoubleClickEvent(QMouseEvent *event)
{
super::mouseDoubleClickEvent(event);
if (selection_manager_.get_object_at_point(event->pos()) &&
!selection_manager_.get_selected_objects().empty()) {
show_marker_properties();
}
}
void SeekableWidget::focusOutEvent(QFocusEvent *event)
{
super::focusOutEvent(event);
if (ignore_next_focus_out_) {
ignore_next_focus_out_ = false;
} else {
// Deselect everything when we lose focus
deselect_all_markers();
}
}
void SeekableWidget::draw_markers(QPainter *p, int marker_bottom)
{
selection_manager_.clear_drawn_objects();
// Draw markers
const int marker_count =
markers_ ? oakengine_marker_list_count(markers_) : 0;
if (marker_count > 0 && marker_bottom > 0) {
int lim_left = get_left_limit();
int lim_right = get_right_limit();
for (int i = 0; i < marker_count; i++) {
OakEngineMarker *marker =
oakengine_marker_list_at(markers_, i);
const TimeRange range = marker_time(marker);
int marker_right = time_to_scene(range.out());
if (marker_right < lim_left) {
continue;
}
int marker_left = time_to_scene(range.in());
if (marker_left >= lim_right) {
break;
}
int max_marker_right = lim_right;
{
// Check if there's a marker next
if (i + 1 < marker_count) {
OakEngineMarker *next =
oakengine_marker_list_at(markers_, i + 1);
max_marker_right =
std::min(max_marker_right,
int(time_to_scene(marker_time(next).in())));
}
}
QRect marker_rect = MarkerPainting::draw(
p, QPoint(marker_left, marker_bottom), max_marker_right,
get_scale(), selection_manager_.is_selected(marker),
marker_name(marker), marker_color(marker),
range.in(), range.out());
marker_top_ = marker_rect.top();
selection_manager_.declare_drawn_object(marker, marker_rect);
}
}
marker_bottom_ = marker_bottom;
}
void SeekableWidget::draw_work_area(QPainter *p)
{
// Fetch workarea state through the C ABI (opaque handle on this side)
int64_t wa_in_num = 0, wa_in_den = 1, wa_out_num = 0, wa_out_den = 1;
int wa_enabled = 0;
const bool have_workarea =
workarea_ &&
oakengine_workarea_get(
workarea_, &wa_in_num,
&wa_in_den, &wa_out_num, &wa_out_den,
&wa_enabled) == OAKENGINE_OK &&
wa_enabled;
// Draw in/out workarea
if (have_workarea) {
const Rational wa_in{int(wa_in_num), int(wa_in_den)};
const Rational wa_out{int(wa_out_num), int(wa_out_den)};
int lim_left = get_left_limit();
int lim_right = get_right_limit();
int workarea_left = qMax(qreal(lim_left), time_to_scene(wa_in));
int workarea_right;
if (wa_out == RATIONAL_MAX) {
workarea_right = lim_right;
} else {
workarea_right =
qMin(qreal(lim_right), time_to_scene(wa_out));
}
QColor translucent_highlight = palette().highlight().color();
translucent_highlight.setAlpha(96);
p->fillRect(workarea_left, 0, workarea_right - workarea_left, height(),
translucent_highlight);
}
}
void SeekableWidget::deselect_all_markers()
{
selection_manager_.clear_selection();
viewport()->update();
}
void SeekableWidget::set_marker_color(int c)
{
QVector<OakEngineMarker *> oak_markers;
for (OakEngineMarker *marker :
selection_manager_.get_selected_objects()) {
oak_markers.append(marker);
}
oakengine_marker_set_properties(
oak_markers.data(), oak_markers.size(), c, nullptr, 0, 0, 0, 0, 0,
nullptr);
}
void SeekableWidget::show_marker_properties()
{
MarkerPropertiesDialog mpd(selection_manager_.get_selected_objects(),
timebase(), this);
ignore_next_focus_out_ = true;
mpd.exec();
}
void SeekableWidget::TimebaseChangedEvent(const Rational &t)
{
super::TimebaseChangedEvent(t);
selection_manager_.set_timebase(t);
}
void SeekableWidget::seek_to_scene_point(qreal scene)
{
if (timebase().isNull()) {
return;
}
Rational playhead_time = qMax(Rational(0), scene_to_time(scene));
if (Core::instance()->snapping() && get_snap_service()) {
Rational movement;
get_snap_service()->snap_point({ playhead_time }, &movement,
TimeBasedWidget::k_snap_all &
~TimeBasedWidget::k_snap_to_playhead);
playhead_time += movement;
}
OakEngineNode *viewer =
get_viewer_node();
int64_t ph_num = 0, ph_den = 1;
oakengine_viewer_get_playhead(viewer, &ph_num, &ph_den);
if (viewer &&
playhead_time != Rational(int(ph_num), int(ph_den))) {
oakengine_viewer_set_playhead(
viewer,
playhead_time.numerator(), playhead_time.denominator());
}
}
void SeekableWidget::SelectionManagerSelectEvent(void *obj)
{
super::SelectionManagerSelectEvent(obj);
viewport()->update();
}
void SeekableWidget::SelectionManagerDeselectEvent(void *obj)
{
super::SelectionManagerDeselectEvent(obj);
viewport()->update();
}
void SeekableWidget::CatchUpScrollEvent()
{
super::CatchUpScrollEvent();
this->selection_manager_.force_drag_update();
}
void SeekableWidget::draw_playhead(QPainter *p, int x, int y)
{
int half_width = playhead_width_ / 2;
{
int test = x - this->get_scroll();
if (test + half_width < 0 || test - half_width > width()) {
return;
}
}
p->setRenderHint(QPainter::Antialiasing);
int half_text_height = text_height() / 3;
last_playhead_shape_ = QPolygon({
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(last_playhead_shape_);
p->setRenderHint(QPainter::Antialiasing, false);
}
int SeekableWidget::get_left_limit() const
{
return get_scroll();
}
int SeekableWidget::get_right_limit() const
{
return get_left_limit() + width();
}
bool SeekableWidget::show_context_menu(const QPoint &p)
{
if (marker_editing_enabled_ && selection_manager_.get_object_at_point(p) &&
!selection_manager_.get_selected_objects().empty()) {
// Show marker-specific menu
Menu m;
ColorLabelMenu color_coding_menu;
connect(&color_coding_menu, &ColorLabelMenu::color_selected, this,
&SeekableWidget::set_marker_color);
m.addMenu(&color_coding_menu);
m.addSeparator();
MenuShared::instance()->add_items_for_edit_menu(&m, false);
m.addSeparator();
QAction *properties_action = m.addAction(tr("Properties"));
connect(properties_action, &QAction::triggered, this,
&SeekableWidget::show_marker_properties);
ignore_next_focus_out_ = true;
m.exec(mapToGlobal(p));
return true;
} else {
return false;
}
}
bool SeekableWidget::find_resize_handle(QMouseEvent *event)
{
if (!marker_editing_enabled_) {
return false;
}
clear_resize_handle();
QPointF scene = mapToScene(event->pos());
const int border = 10;
Rational min = scene_to_time_no_grid(scene.x() - border);
Rational max = scene_to_time_no_grid(scene.x() + border);
// Test for workarea (state fetched through the C ABI; the handle is
// opaque on this side)
int64_t wa_in_num = 0, wa_in_den = 1, wa_out_num = 0, wa_out_den = 1;
int wa_enabled = 0;
const bool have_workarea =
workarea_ &&
oakengine_workarea_get(
workarea_, &wa_in_num,
&wa_in_den, &wa_out_num, &wa_out_den,
&wa_enabled) == OAKENGINE_OK &&
wa_enabled;
if (have_workarea) {
const Rational wa_in{int(wa_in_num), int(wa_in_den)};
const Rational wa_out{int(wa_out_num), int(wa_out_den)};
if (wa_in >= min && wa_in < max) {
resize_mode_ = k_resize_in;
} else if (wa_out >= min && wa_out < max) {
resize_mode_ = k_resize_out;
}
if (resize_mode_ != k_resize_none) {
resize_item_ = workarea_;
resize_item_range_ = TimeRange(wa_in, wa_out);
resize_snap_mask_ = TimeBasedWidget::k_snap_all &
~TimeBasedWidget::k_snap_to_workarea;
}
}
if (resize_mode_ == k_resize_none &&
event->pos().y() >= marker_top_ &&
event->pos().y() < marker_bottom_) {
if (markers_) {
// Check for markers
const int marker_count =
oakengine_marker_list_count(markers_);
for (int i = 0; i < marker_count; i++) {
OakEngineMarker *m =
oakengine_marker_list_at(markers_, i);
const TimeRange m_time = marker_time(m);
if (m_time.in() != m_time.out()) {
if (m_time.in() >= min && m_time.in() < max) {
resize_mode_ = k_resize_in;
} else if (m_time.out() >= min &&
m_time.out() < max) {
resize_mode_ = k_resize_out;
}
if (resize_mode_ != k_resize_none) {
resize_item_ = m;
resize_item_range_ = m_time;
resize_snap_mask_ = TimeBasedWidget::k_snap_all;
break;
}
}
}
}
}
return resize_item_;
}
void SeekableWidget::clear_resize_handle()
{
resize_item_ = nullptr;
resize_mode_ = k_resize_none;
}
void SeekableWidget::drag_resize_handle(const QPointF &scene)
{
qreal diff = scene.x() - resize_start_.x();
Rational proposed_time;
if (resize_mode_ == k_resize_in) {
proposed_time = qMax(Rational(0), qMin(resize_item_range_.out(),
resize_item_range_.in() +
scene_to_time_no_grid(diff)));
} else {
proposed_time =
qMax(resize_item_range_.in(),
resize_item_range_.out() + scene_to_time_no_grid(diff));
}
Rational presnap_time = proposed_time;
if (Core::instance()->snapping() && get_snap_service()) {
Rational movement;
get_snap_service()->snap_point({ proposed_time }, &movement,
resize_snap_mask_);
proposed_time += movement;
}
TimeRange new_range = resize_item_range_;
if (resize_mode_ == k_resize_in) {
// Markers should not have the same time as anything else
// NOTE: This code is largely duplicated from TimeBasedViewSelectionManager::DragMove. Not ideal,
// but I'm not sure if there's a good way to re-use that code
// resize_item_ is either the workarea or a marker; the handles are
// opaque here, so compare against workarea_ instead of
// dynamic_cast.
if (resize_item_ != workarea_) {
OakEngineMarker *marker =
static_cast<OakEngineMarker *>(resize_item_);
if (markers_ &&
oakengine_marker_list_marker_at_time(
markers_, proposed_time.numerator(),
proposed_time.denominator()) != marker) {
proposed_time = presnap_time;
if (get_snap_service()) {
get_snap_service()->hide_snaps();
}
}
while (markers_ &&
oakengine_marker_list_marker_at_time(
markers_, proposed_time.numerator(),
proposed_time.denominator()) != marker &&
oakengine_marker_list_marker_at_time(
markers_, proposed_time.numerator(),
proposed_time.denominator())) {
proposed_time += Rational(1, 1000);
}
}
new_range.set_in(proposed_time);
} else {
new_range.set_out(proposed_time);
}
if (resize_item_ != workarea_) {
oakengine_marker_set_time_live(
static_cast<OakEngineMarker *>(resize_item_),
new_range.in().numerator(), new_range.in().denominator(),
new_range.out().numerator(), new_range.out().denominator());
} else {
oakengine_workarea_set_range(
workarea_,
new_range.in().numerator(), new_range.in().denominator(),
new_range.out().numerator(), new_range.out().denominator());
}
}
void SeekableWidget::commit_resize_handle()
{
// resize_item_ is either the workarea or a marker (see
// find_resize_handle); compare against workarea_ instead of
// dynamic_cast since the handles are opaque here.
if (resize_item_ != workarea_) {
OakEngineMarker *marker =
static_cast<OakEngineMarker *>(resize_item_);
oakengine_marker_set_properties(
&marker, 1, -1, nullptr, 1,
resize_item_range_.in().numerator(),
resize_item_range_.in().denominator(),
resize_item_range_.out().numerator(),
resize_item_range_.out().denominator(),
nullptr);
} else {
void *wa_cmd = oakengine_undo_command_create(
tr("Changed Workarea Length").toUtf8().constData(),
nullptr, nullptr, nullptr, nullptr);
oakengine_undo_push(wa_cmd,
tr("Changed Workarea Length").toUtf8().constData());
}
}
}
-191
View File
@@ -1,191 +0,0 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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/>.
***/
#ifndef OAK_SEEKABLEWIDGET_H
#define OAK_SEEKABLEWIDGET_H
#include <QHBoxLayout>
#include <QScrollBar>
#include "widget/menu/menu.h"
#include "widget/timebased/timebasedviewselectionmanager.h"
#include "engineeventbridge.h"
namespace olive
{
class SeekableWidget : public TimeBasedView {
Q_OBJECT
public:
SeekableWidget(QWidget *parent = nullptr);
int get_scroll() const
{
return horizontalScrollBar()->value();
}
OakEngineMarkerList *get_markers() const
{
return markers_;
}
OakEngineWorkarea *get_work_area() const
{
return workarea_;
}
void set_markers(OakEngineMarkerList *markers);
void set_work_area(OakEngineWorkarea *workarea);
virtual bool is_dragging_playhead() const override
{
return dragging_;
}
bool is_marker_editing_enabled() const
{
return marker_editing_enabled_;
}
void set_marker_editing_enabled(bool e)
{
marker_editing_enabled_ = e;
}
void delete_selected();
bool copy_selected(bool cut);
bool paste_markers();
void deselect_all_markers();
void seek_to_scene_point(qreal scene);
bool has_items_selected() const
{
return !selection_manager_.get_selected_objects().empty();
}
const std::vector<OakEngineMarker *> &get_selected_markers() const
{
return selection_manager_.get_selected_objects();
}
virtual void SelectionManagerSelectEvent(void *obj) override;
virtual void SelectionManagerDeselectEvent(void *obj) override;
virtual void CatchUpScrollEvent() override;
public slots:
void set_scroll(int i)
{
horizontalScrollBar()->setValue(i);
}
virtual void TimebaseChangedEvent(const Rational &) override;
signals:
void drag_moved(int x, int y);
void drag_released();
protected:
virtual void mousePressEvent(QMouseEvent *event) override;
virtual void mouseMoveEvent(QMouseEvent *event) override;
virtual void mouseReleaseEvent(QMouseEvent *event) override;
virtual void mouseDoubleClickEvent(QMouseEvent *event) override;
virtual void focusOutEvent(QFocusEvent *event) override;
void draw_markers(QPainter *p, int marker_bottom = 0);
void draw_work_area(QPainter *p);
void draw_playhead(QPainter *p, int x, int y);
inline const int &text_height() const
{
return text_height_;
}
inline const int &playhead_width() const
{
return playhead_width_;
}
int get_left_limit() const;
int get_right_limit() const;
protected slots:
virtual bool show_context_menu(const QPoint &p);
private:
enum ResizeMode { k_resize_none, k_resize_in, k_resize_out };
bool find_resize_handle(QMouseEvent *event);
void clear_resize_handle();
void drag_resize_handle(const QPointF &scene_pos);
void commit_resize_handle();
OakEngineMarkerList *markers_;
OakEngineWorkarea *workarea_;
int text_height_;
int playhead_width_;
bool dragging_;
bool ignore_next_focus_out_;
TimeBasedViewSelectionManager<OakEngineMarker> selection_manager_;
void *resize_item_;
ResizeMode resize_mode_;
TimeRange resize_item_range_;
QPointF resize_start_;
uint32_t resize_snap_mask_;
int marker_top_;
int marker_bottom_;
bool marker_editing_enabled_;
QVector<int64_t> marker_list_subs_;
bool marker_connects_done_ = false;
EngineEventBridge *bridge_ = nullptr;
int64_t workarea_range_sub_ = 0;
int64_t workarea_enabled_sub_ = 0;
QPolygon last_playhead_shape_;
private slots:
void set_marker_color(int c);
void show_marker_properties();
};
}
#endif // OAK_SEEKABLEWIDGET_H
-411
View File
@@ -1,411 +0,0 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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 <QPainter>
#include "oakutil/qtutils.h"
#include "common/configwrapper.h"
#include "core.h"
#include "oakengine/viewer.h"
#include "markerpainting.h"
#include "widget/menu/menu.h"
#include "widget/menu/menushared.h"
#include "widget/viewer/vieweroutpututils.h"
namespace olive
{
#define super SeekableWidget
TimeRuler::TimeRuler(bool text_visible, bool cache_status_visible,
QWidget *parent)
: super(parent)
, text_visible_(text_visible)
, centered_text_(true)
, show_cache_status_(cache_status_visible)
, playback_cache_(nullptr)
{
QFontMetrics fm = fontMetrics();
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum);
// Text height is used to calculate widget height
// 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_ = QtUtils::q_font_metrics_width(fm, "H");
// Text visibility affects height, so we set that here
update_height();
// Force update if the default timecode display mode changes
connect(Core::instance(), &Core::timecode_display_changed, this,
static_cast<void (TimeRuler::*)()>(&TimeRuler::update));
// Connect context menu
connect(this, &TimeRuler::customContextMenuRequested, this,
&TimeRuler::show_context_menu);
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
//horizontalScrollBar()->setVisible(false);
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setBackgroundRole(QPalette::Window);
setFrameShape(QFrame::NoFrame);
// NOTE: One day it might be preferable to use AlignBottom because the lines are anchored to
// the bottom of the widget. However, for now this makes sense since we just ported this
// from a QWidget's paintEvent.
setAlignment(Qt::AlignLeft | Qt::AlignTop);
bridge_ = new EngineEventBridge(this);
connect(bridge_, &EngineEventBridge::playback_cache_invalidated,
viewport(), [this]() { viewport()->update(); });
connect(bridge_, &EngineEventBridge::playback_cache_validated,
viewport(), [this]() { viewport()->update(); });
}
void TimeRuler::set_centered_text(bool c)
{
centered_text_ = c;
update();
}
void TimeRuler::set_playback_cache(void *cache)
{
if (!show_cache_status_) {
return;
}
if (playback_cache_) {
bridge_->unsubscribe(cache_sub_invalidated_);
bridge_->unsubscribe(cache_sub_validated_);
cache_sub_invalidated_ = 0;
cache_sub_validated_ = 0;
}
playback_cache_ = cache;
if (playback_cache_) {
cache_sub_invalidated_ = bridge_->subscribe(
playback_cache_, OAKENGINE_EVENT_PLAYBACK_CACHE_INVALIDATED);
cache_sub_validated_ = bridge_->subscribe(
playback_cache_, OAKENGINE_EVENT_PLAYBACK_CACHE_VALIDATED);
}
update();
}
void TimeRuler::drawForeground(QPainter *p, const QRectF &rect)
{
// Nothing to paint if the timebase is invalid
if (timebase().isNull()) {
return;
}
// Draw timeline points if connected
int marker_height = MarkerPainting::height(p->fontMetrics());
draw_work_area(p);
draw_markers(p, marker_height);
double width_of_frame = timebase_dbl() * get_scale();
double width_of_second = 0;
do {
width_of_second += timebase_dbl();
} while (width_of_second < 1.0);
width_of_second *= get_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 = 0;
// 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 <= 0) {
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 -= oakengine_playback_cache_indicator_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 k_average_text_width = 200;
for (int i = get_scroll() - k_average_text_width;
i < get_scroll() + width() + k_average_text_width; i++) {
double screen_pt = static_cast<double>(i);
if (long_interval > -1) {
int this_long_unit = std::floor(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 =
QString::fromStdString(Timecode::time_to_timecode(
scene_to_time(i), timebase(),
Core::instance()->get_timecode_display()));
int timecode_width =
QtUtils::q_font_metrics_width(fm, timecode_str);
int timecode_left;
if (centered_text_) {
text_rect = QRect(i - k_average_text_width / 2,
marker_height, k_average_text_width,
fm.height());
text_align = Qt::AlignCenter;
timecode_left = i - timecode_width / 2;
} else {
text_rect = QRect(i, marker_height, k_average_text_width,
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, static_cast<int>(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 = std::floor(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_ && playback_cache_ &&
oakengine_playback_cache_has_validated_ranges(playback_cache_)) {
// FIXME: Hardcoded to get video length, if we ever need audio length, this will have to change
int h = oakengine_playback_cache_indicator_height();
QRect cache_rect(0, height() - h, width(), h);
OakEngineNode *cache_parent =
oakengine_playback_cache_parent(playback_cache_);
int64_t len_num = 0, len_den = 1;
if (oakengine_viewer_from_node(cache_parent) &&
oakengine_viewer_get_video_length(cache_parent, &len_num,
&len_den) == OAKENGINE_OK &&
len_den != 0) {
int right = time_to_scene(Rational(static_cast<int>(len_num),
static_cast<int>(len_den)));
cache_rect.setWidth(std::max(0, right));
}
if (cache_rect.width() > 0) {
// App-side equivalent of PlaybackCache::draw(): the
// oakengine_playback_cache_draw() C ABI anchors the strip at
// the painter viewport's top edge and cannot reproduce this
// bottom-anchored indicator, so the validated ranges are
// painted directly from oakengine_playback_cache_valid_ranges()
// (same fill logic as the engine's draw()).
const Rational start = scene_to_time(get_scroll());
const double scale = get_scale();
p->fillRect(cache_rect, Qt::red);
QVector<int64_t> quads(4 * 64);
int range_count;
while ((range_count = oakengine_playback_cache_valid_ranges(
static_cast<OakEnginePlaybackCache *>(
playback_cache_),
quads.data(), quads.size() / 4)) == quads.size() / 4) {
quads.resize(quads.size() * 2);
}
for (int i = 0; i < range_count; i++) {
const Rational in(static_cast<int>(quads.at(i * 4 + 0)),
static_cast<int>(quads.at(i * 4 + 1)));
const Rational out(static_cast<int>(quads.at(i * 4 + 2)),
static_cast<int>(quads.at(i * 4 + 3)));
int range_left =
cache_rect.left() + (in - start).to_double() * scale;
if (range_left >= cache_rect.right()) {
continue;
}
int range_right =
cache_rect.left() + (out - start).to_double() * scale;
if (range_right < cache_rect.left()) {
continue;
}
int adjusted_left = std::max(range_left, cache_rect.left());
int adjusted_right =
std::min(range_right, cache_rect.right());
p->fillRect(adjusted_left, cache_rect.top(),
adjusted_right - adjusted_left,
cache_rect.height(), Qt::green);
}
}
}
// Draw the playhead if it's on screen at the moment
int playhead_pos = time_to_scene(viewer_output_playhead(get_viewer_node()));
p->setPen(Qt::NoPen);
p->setBrush(PLAYHEAD_COLOR);
draw_playhead(p, playhead_pos, line_bottom);
}
void TimeRuler::TimebaseChangedEvent(const Rational &tb)
{
super::TimebaseChangedEvent(tb);
timebase_flipped_dbl_ = tb.flipped().to_double();
update();
}
int TimeRuler::cache_status_height() const
{
return fontMetrics().height() / 4;
}
bool TimeRuler::show_context_menu(const QPoint &p)
{
if (super::show_context_menu(p)) {
return true;
} else {
Menu m(this);
MenuShared::instance()->add_items_for_time_ruler_menu(&m);
MenuShared::instance()->about_to_show_time_ruler_actions(timebase());
m.exec(mapToGlobal(p));
return true;
}
}
void TimeRuler::update_height()
{
int height = text_height();
// Add text height
if (text_visible_) {
height += text_height();
}
// Add cache status height
if (show_cache_status_) {
height += oakengine_playback_cache_indicator_height();
}
// Add marker height
height += MarkerPainting::height(fontMetrics());
setFixedHeight(height);
}
}
-80
View File
@@ -1,80 +0,0 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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/>.
***/
#ifndef OAK_TIMERULER_H
#define OAK_TIMERULER_H
#include <QTimer>
#include <QWidget>
#include "engineeventbridge.h"
#include "seekablewidget.h"
namespace olive
{
class TimeRuler : public SeekableWidget {
Q_OBJECT
public:
TimeRuler(bool text_visible = true, bool cache_status_visible = false,
QWidget *parent = nullptr);
void set_centered_text(bool c);
/**
* @brief Opaque playback cache handle (engine PlaybackCache, accessed
* through the oakengine_playback_cache_* C ABI).
*/
void set_playback_cache(void *cache);
protected:
virtual void drawForeground(QPainter *painter, const QRectF &rect) override;
virtual void TimebaseChangedEvent(const Rational &tb) override;
protected slots:
virtual bool show_context_menu(const QPoint &p) override;
private:
void update_height();
int cache_status_height() const;
int minimum_gap_between_lines_;
bool text_visible_;
bool centered_text_;
double timebase_flipped_dbl_;
bool show_cache_status_;
void *playback_cache_;
EngineEventBridge *bridge_;
int64_t cache_sub_invalidated_ = 0;
int64_t cache_sub_validated_ = 0;
};
}
#endif // OAK_TIMERULER_H