From b5092777df95a0e8d58f9e7433f194db64f0fc17 Mon Sep 17 00:00:00 2001 From: Mike Solar Date: Sun, 14 Sep 2025 20:53:22 +0800 Subject: [PATCH 01/10] change: core submodule. --- ext/core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/core b/ext/core index 25e4152bb..08eeee5fa 160000 --- a/ext/core +++ b/ext/core @@ -1 +1 @@ -Subproject commit 25e4152bb70a0f4e30b9249e4f4b31e7936dc84a +Subproject commit 08eeee5fa90bed55442f2be9e324482f0be0a73c From e8238dac545feea7db8d165cc2b268daf251a53e Mon Sep 17 00:00:00 2001 From: Mike Solar Date: Sun, 14 Sep 2025 20:55:52 +0800 Subject: [PATCH 02/10] change: core submodule. --- .gitmodules | 2 +- ext/core | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index 46b51ee95..55ea8f7de 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "ext/core"] path = ext/core - url = https://github.com/olive-editor/core + url = https://github.com/OliveCommunity/core.git [submodule "ext/KDDockWidgets"] path = ext/KDDockWidgets url = https://github.com/olive-editor/KDDockWidgets.git diff --git a/ext/core b/ext/core index 08eeee5fa..25e4152bb 160000 --- a/ext/core +++ b/ext/core @@ -1 +1 @@ -Subproject commit 08eeee5fa90bed55442f2be9e324482f0be0a73c +Subproject commit 25e4152bb70a0f4e30b9249e4f4b31e7936dc84a From c1b86183916f04b7c27f324e97a602731e4f2fe3 Mon Sep 17 00:00:00 2001 From: Mike Solar Date: Sun, 14 Sep 2025 21:10:02 +0800 Subject: [PATCH 03/10] fix: crash when processing audio --- .gitmodules | 1 + app/codec/ffmpeg/ffmpegdecoder.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 55ea8f7de..f9f9206df 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,7 @@ [submodule "ext/core"] path = ext/core url = https://github.com/OliveCommunity/core.git + branch = dev [submodule "ext/KDDockWidgets"] path = ext/KDDockWidgets url = https://github.com/olive-editor/KDDockWidgets.git diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index e963e8e64..db5c4d588 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -629,7 +629,7 @@ bool FFmpegDecoder::ConformAudioInternal(const QVector &filenames, } // Create resampling context AVChannelLayout layout = params.channel_layout(); - SwrContext *resampler; + SwrContext *resampler=NULL; swr_alloc_set_opts2( &resampler, &layout, FFmpegUtils::GetFFmpegSampleFormat(params.format()), From 2ea53667c049d50ee72f866b63ba5d99fd334b3c Mon Sep 17 00:00:00 2001 From: Mike Solar Date: Mon, 10 Nov 2025 21:06:35 +0800 Subject: [PATCH 04/10] Refactor FFmpeg decoder for improved readability and efficiency Simplify the frame reading process and metadata extraction in `ffmpegdecoder.cpp` by reducing redundant code and improving variable initialization. Add a helper function in `ffmpegdecoder.h` to map FFmpeg field orders to Olive interlacing types, enhancing code clarity. Adjust the condition for interleaved write in `ffmpegencoder.cpp` for better handling. --- app/codec/ffmpeg/ffmpegdecoder.cpp | 160 +++++++++++------------------ app/codec/ffmpeg/ffmpegdecoder.h | 18 ++++ app/codec/ffmpeg/ffmpegencoder.cpp | 3 +- ext/KDDockWidgets | 2 +- 4 files changed, 82 insertions(+), 101 deletions(-) diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index db5c4d588..a412f7dd5 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -28,6 +28,7 @@ extern "C" { #include #include #include +#include } #include @@ -382,116 +383,77 @@ FootageDescription FFmpegDecoder::Probe(const QString &filename, VideoParams::kInterlaceNone; { - // Read at least two frames to get more information about this video stream - AVPacket *pkt = av_packet_alloc(); - AVFrame *frame = av_frame_alloc(); + // Read at least two frames to get more information about this video stream + AVPacket *pkt = av_packet_alloc(); + AVFrame *frame = av_frame_alloc(); - { - Instance instance; - instance.Open(filename_c, avstream->index); + VideoParams::Interlacing interlacing = VideoParams::kInterlaceNone; + AVRational pixel_aspect_ratio = {1, 1}; + AVRational frame_rate = avstream->avg_frame_rate; + AVPixelFormat compatible_pix_fmt = + FFmpegUtils::GetCompatiblePixelFormat( + static_cast(avstream->codecpar->format)); + bool image_is_still = false; - // Read first frame and retrieve some metadata - if (instance.GetFrame(pkt, frame) >= 0) { - // Check if video is interlaced and what field dominance it has if so - if (frame->interlaced_frame) { - if (frame->top_field_first) { - interlacing = - VideoParams::kInterlacedTopFirst; - } else { - interlacing = - VideoParams::kInterlacedBottomFirst; - } - } + { + Instance instance; + if (instance.Open(filename_c, avstream->index) != 0) + goto cleanup; - pixel_aspect_ratio = - av_guess_sample_aspect_ratio( - instance.fmt_ctx(), instance.avstream(), - frame); + AVCodecContext *avctx = instance.codec_ctx(); + interlacing = FFmpegFieldOrderToOlive(avctx->field_order); - frame_rate = av_guess_frame_rate( - instance.fmt_ctx(), instance.avstream(), - frame); + if (instance.GetFrame(pkt, frame) >= 0) { + pixel_aspect_ratio = + av_guess_sample_aspect_ratio(instance.fmt_ctx(), + instance.avstream(), frame); + frame_rate = + av_guess_frame_rate(instance.fmt_ctx(), + instance.avstream(), frame); + } - compatible_pix_fmt = - FFmpegUtils::GetCompatiblePixelFormat( - static_cast( - avstream->codecpar->format)); - } + int ret = instance.GetFrame(pkt, frame); + if (ret == AVERROR_EOF) { + image_is_still = true; + } else if (avstream->duration == AV_NOPTS_VALUE || + duration_guessed_from_bitrate) { + int64_t last_ts = frame->best_effort_timestamp; + while (instance.GetFrame(pkt, frame) >= 0 && + (!cancelled || !cancelled->IsCancelled())) + last_ts = frame->best_effort_timestamp; + avstream->duration = last_ts; + } - // Read second frame - int ret = instance.GetFrame(pkt, frame); + instance.Close(); + } - if (ret >= 0) { - // Check if we need a manual duration - if (avstream->duration == AV_NOPTS_VALUE || - duration_guessed_from_bitrate) { - if (footage_duration == AV_NOPTS_VALUE || - duration_guessed_from_bitrate) { - // Manually read through file for duration - int64_t new_dur; + cleanup: + av_frame_free(&frame); + av_packet_free(&pkt); - do { - new_dur = - frame->best_effort_timestamp; - } while (instance.GetFrame( - pkt, frame) >= 0 && - (!cancelled || - !cancelled->IsCancelled())); + VideoParams stream; + stream.set_stream_index(i); + stream.set_width(avstream->codecpar->width); + stream.set_height(avstream->codecpar->height); + stream.set_video_type(image_is_still ? VideoParams::kVideoTypeStill + : VideoParams::kVideoTypeVideo); + stream.set_format(GetNativePixelFormat(compatible_pix_fmt)); + stream.set_channel_count(GetNativeChannelCount(compatible_pix_fmt)); + stream.set_interlacing(interlacing); // <-- 已正确填充 + stream.set_pixel_aspect_ratio(pixel_aspect_ratio); + stream.set_frame_rate(frame_rate); + stream.set_start_time(avstream->start_time); + stream.set_time_base(avstream->time_base); + stream.set_duration(avstream->duration); + stream.set_color_range(avstream->codecpar->color_range == AVCOL_RANGE_JPEG + ? VideoParams::kColorRangeFull + : VideoParams::kColorRangeLimited); + stream.set_premultiplied_alpha(false); - avstream->duration = new_dur; - - } else { - // Fallback to footage duration - avstream->duration = - Timecode::rescale_timestamp_ceil( - footage_duration, - rational(1, AV_TIME_BASE), - avstream->time_base); - } - } - } else if (ret == AVERROR_EOF) { - // Video has only one frame in it, treat it like a still image - image_is_still = true; - } - - instance.Close(); - } - - av_frame_free(&frame); - av_packet_free(&pkt); + desc.AddVideoStream(stream); + image_is_still ? still_streams++ : video_streams++; } - VideoParams stream; - stream.set_stream_index(i); - stream.set_width(avstream->codecpar->width); - stream.set_height(avstream->codecpar->height); - stream.set_video_type((image_is_still) ? - VideoParams::kVideoTypeStill : - VideoParams::kVideoTypeVideo); - stream.set_format(GetNativePixelFormat(compatible_pix_fmt)); - stream.set_channel_count( - GetNativeChannelCount(compatible_pix_fmt)); - stream.set_interlacing(interlacing); - stream.set_pixel_aspect_ratio(pixel_aspect_ratio); - stream.set_frame_rate(frame_rate); - stream.set_start_time(avstream->start_time); - stream.set_time_base(avstream->time_base); - stream.set_duration(avstream->duration); - stream.set_color_range(avstream->codecpar->color_range == - AVCOL_RANGE_JPEG ? - VideoParams::kColorRangeFull : - VideoParams::kColorRangeLimited); - - // Defaults to false, requires user intervention if incorrect - stream.set_premultiplied_alpha(false); - - desc.AddVideoStream(stream); - - if (image_is_still) { - still_streams++; - } else { - video_streams++; - } } else if (avstream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { diff --git a/app/codec/ffmpeg/ffmpegdecoder.h b/app/codec/ffmpeg/ffmpegdecoder.h index e904ef2f9..f99f88b0b 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.h +++ b/app/codec/ffmpeg/ffmpegdecoder.h @@ -42,6 +42,20 @@ extern "C" { namespace olive { +// 头文件里先备好枚举映射 +static VideoParams::Interlacing FFmpegFieldOrderToOlive(AVFieldOrder fo) +{ + switch (fo) { + case AV_FIELD_TT: // 隔行,顶场在前 + return VideoParams::kInterlacedTopFirst; + case AV_FIELD_BB: // 隔行,底场在前 + return VideoParams::kInterlacedBottomFirst; + case AV_FIELD_PROGRESSIVE: + default: + return VideoParams::kInterlaceNone; + } +} + /** * @brief A Decoder derivative that wraps FFmpeg functions as on Olive decoder */ @@ -124,6 +138,10 @@ private: { return avstream_; } + AVCodecContext *codec_ctx() + { + return codec_ctx_; + } private: AVFormatContext *fmt_ctx_; diff --git a/app/codec/ffmpeg/ffmpegencoder.cpp b/app/codec/ffmpeg/ffmpegencoder.cpp index 5f7ea1d87..951392114 100644 --- a/app/codec/ffmpeg/ffmpegencoder.cpp +++ b/app/codec/ffmpeg/ffmpegencoder.cpp @@ -24,6 +24,7 @@ extern "C" { #include #include #include +#include } #include @@ -868,7 +869,7 @@ void FFmpegEncoder::FlushEncoders() } if (fmt_ctx_) { - if (fmt_ctx_->oformat->flags & AVFMT_ALLOW_FLUSH) { + if (fmt_ctx_->oformat->flags) { int r = av_interleaved_write_frame(fmt_ctx_, nullptr); if (r < 0) { FFmpegError(tr("Failed to write interleaved packet"), r); diff --git a/ext/KDDockWidgets b/ext/KDDockWidgets index 8d2d0a576..f24a04252 160000 --- a/ext/KDDockWidgets +++ b/ext/KDDockWidgets @@ -1 +1 @@ -Subproject commit 8d2d0a5764f8393cc148a2296d511276a8ffe559 +Subproject commit f24a04252ba319ef323769e766b3ba5252c15c4c From 1e33a31b73c7211671016fac55c69c69f217c2c7 Mon Sep 17 00:00:00 2001 From: Mike Solar Date: Sun, 23 Nov 2025 16:35:09 +0800 Subject: [PATCH 05/10] Update FFmpeg handling and focus management Refactor FFmpeg frame processing and improve focus management in panels. Add memory sanitization for debug builds and update KDDockWidgets integration. --- .gitmodules | 1 + CMakeLists.txt | 8 ++++++-- app/codec/ffmpeg/ffmpegdecoder.cpp | 21 ++++++++++++++------- app/common/ffmpegutils.h | 2 +- app/core.cpp | 1 + app/main.cpp | 1 + app/panel/panel.cpp | 18 +++++++++++++++--- app/panel/panel.h | 16 +++++++++++++--- app/panel/project/project.cpp | 2 +- app/render/texture.h | 9 ++++++++- app/widget/viewer/viewer.cpp | 7 +++++-- app/window/mainwindow/mainwindow.cpp | 10 ++++++---- app/window/mainwindow/mainwindow.h | 2 +- ext/KDDockWidgets | 2 +- ext/core | 2 +- 15 files changed, 75 insertions(+), 27 deletions(-) diff --git a/.gitmodules b/.gitmodules index f9f9206df..41b12b89f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -5,3 +5,4 @@ [submodule "ext/KDDockWidgets"] path = ext/KDDockWidgets url = https://github.com/olive-editor/KDDockWidgets.git + branch = main diff --git a/CMakeLists.txt b/CMakeLists.txt index 303939381..4115edd31 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,7 +17,11 @@ cmake_minimum_required(VERSION 3.13 FATAL_ERROR) project(olive-editor VERSION 0.2.0 LANGUAGES CXX) - +if(${CMAKE_BUILD_TYPE} EQUAL Debug) +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=memory -g") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=memory -g") +set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} -fsanitize=memory) +endif () option(BUILD_QT6 "Build with Qt 6 over 5 (experimental)" ON) option(BUILD_DOXYGEN "Build Doxygen documentation" OFF) option(BUILD_TESTS "Build unit tests" OFF) @@ -168,7 +172,7 @@ list(APPEND OLIVE_LIBRARIES ) # Link FFmpeg -find_package(FFMPEG 3.0 REQUIRED +find_package(FFMPEG REQUIRED COMPONENTS avutil avcodec diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index a412f7dd5..502587b3d 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -140,7 +140,7 @@ TexturePtr FFmpegDecoder::ProcessFrameIntoTexture(AVFramePtr f, break; } - AVFrame *hw_in = f.get(); + AVFramePtr hw_in = f; VideoParams plane_params = vp; plane_params.set_channel_count(1); @@ -148,7 +148,7 @@ TexturePtr FFmpegDecoder::ProcessFrameIntoTexture(AVFramePtr f, TexturePtr y_plane = p.renderer->CreateTexture( plane_params, hw_in->data[0], hw_in->linesize[0] / px_size); - + y_plane->handleFrame(hw_in); switch (f->format) { case AV_PIX_FMT_YUV420P: case AV_PIX_FMT_YUV422P: @@ -170,8 +170,11 @@ TexturePtr FFmpegDecoder::ProcessFrameIntoTexture(AVFramePtr f, TexturePtr u_plane = p.renderer->CreateTexture( plane_params, hw_in->data[1], hw_in->linesize[1] / px_size); + u_plane->handleFrame(hw_in); + TexturePtr v_plane = p.renderer->CreateTexture( plane_params, hw_in->data[2], hw_in->linesize[2] / px_size); + v_plane->handleFrame(hw_in); ShaderJob job; job.Insert(QStringLiteral("y_channel"), @@ -207,6 +210,7 @@ TexturePtr FFmpegDecoder::ProcessFrameIntoTexture(AVFramePtr f, case AV_PIX_FMT_RGBA: case AV_PIX_FMT_RGBA64LE: // RGBA can be uploaded directly to the texture + tex->handleFrame(f); tex->Upload(f->data[0], f->linesize[0] / vp.GetBytesPerPixel()); break; } @@ -282,14 +286,17 @@ TexturePtr FFmpegDecoder::RetrieveVideoInternal(const RetrieveVideoParams &p) AVCOL_RANGE_MPEG; // Perform any CPU processing required - f = PreProcessFrame(f, p); + AVFramePtr ptr = PreProcessFrame(f, p); + f=std::move(ptr); if (!f) { // Error occurred while software scaling return nullptr; } // Finally, perform any GPU processing required - return ProcessFrameIntoTexture(f, p, original); + TexturePtr texture = ProcessFrameIntoTexture(f, p, original); + + return texture; } return nullptr; @@ -799,7 +806,7 @@ AVFramePtr FFmpegDecoder::PreProcessFrame(AVFramePtr f, dest->format = f->format; dest->color_range = f->color_range; dest->colorspace = f->colorspace; - + dest->hw_frames_ctx = nullptr; if (p.divider > 1) { dest->width = VideoParams::GetScaledDimension(dest->width, p.divider); dest->height = VideoParams::GetScaledDimension(dest->height, p.divider); @@ -852,8 +859,8 @@ AVFramePtr FFmpegDecoder::PreProcessFrame(AVFramePtr f, dest->color_range == AVCOL_RANGE_JPEG ? 1 : 0, 0, 0x10000, 0x10000); } - r = sws_scale(sws_ctx_, f->data, f->linesize, 0, f->height, dest->data, - dest->linesize); + r = sws_scale_frame(sws_ctx_, dest.get(), f.get()); + if (r < 0) { FFmpegError(r); return nullptr; diff --git a/app/common/ffmpegutils.h b/app/common/ffmpegutils.h index 1d5e7da29..a667fb925 100644 --- a/app/common/ffmpegutils.h +++ b/app/common/ffmpegutils.h @@ -87,7 +87,7 @@ public: using AVFramePtr = std::shared_ptr; inline AVFramePtr CreateAVFramePtr(AVFrame *f) { - return std::shared_ptr(f, [](AVFrame *g) { av_frame_free(&g); }); + return std::shared_ptr(f, [](AVFrame *g) { av_frame_free(&g); }); } inline AVFramePtr CreateAVFramePtr() { diff --git a/app/core.cpp b/app/core.cpp index 412c9972e..b6d2dd5b0 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -776,6 +776,7 @@ void Core::StartGUI(bool full_screen) connect(qApp, &QApplication::focusChanged, PanelManager::instance(), &PanelManager::FocusChanged); + KDDockWidgets::initFrontend(KDDockWidgets::FrontendType::QtWidgets); // Set KDDockWidgets flags auto &config = KDDockWidgets::Config::self(); auto flags = config.flags(); diff --git a/app/main.cpp b/app/main.cpp index 6e351735c..2545f9a95 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -145,6 +145,7 @@ int decompress_project(const QString &project) int main(int argc, char *argv[]) { + // Set up debug handler qInstallMessageHandler(olive::DebugHandler); diff --git a/app/panel/panel.cpp b/app/panel/panel.cpp index b1262624f..601ccc7ca 100644 --- a/app/panel/panel.cpp +++ b/app/panel/panel.cpp @@ -27,23 +27,26 @@ #include #include #include +#include "Window_p.h" #include "panel/panelmanager.h" +#include + namespace olive { -#define super KDDockWidgets::DockWidget +#define super KDDockWidgets::QtWidgets::DockWidget PanelWidget::PanelWidget(const QString &object_name) : super(object_name) , border_visible_(false) , signal_instead_of_close_(false) { - setFocusPolicy(Qt::ClickFocus); + View::setFocusPolicy(Qt::ClickFocus); connect(this, &PanelWidget::shown, this, - static_cast(&PanelWidget::setFocus)); + reinterpret_cast(&PanelWidget::setFocus)); PanelManager::instance()->RegisterPanel(this); } @@ -125,6 +128,15 @@ void PanelWidget::changeEvent(QEvent *e) if (e->type() == QEvent::LanguageChange) { Retranslate(); } + + if (e->type() == QEvent::WindowStateChange) { + if (isVisible() && !isMinimized()) { + emit shown(Qt::OtherFocusReason); + } + else { + emit hidden(); + } + } super::changeEvent(e); } diff --git a/app/panel/panel.h b/app/panel/panel.h index 56777fd11..787a4981b 100644 --- a/app/panel/panel.h +++ b/app/panel/panel.h @@ -21,18 +21,23 @@ #ifndef PANEL_WIDGET_H #define PANEL_WIDGET_H +#include "KDDockWidgets/src/core/Window_p.h" +#include "KDDockWidgets/src/qtwidgets/views/TabBar.h" + #include #include #include "common/define.h" +#include + namespace olive { /** * @brief A widget that is always dockable within the MainWindow. */ -class PanelWidget : public KDDockWidgets::DockWidget { +class PanelWidget : public KDDockWidgets::QtWidgets::DockWidget { Q_OBJECT public: /** @@ -279,7 +284,8 @@ public: signals: void CloseRequested(); - + void shown(Qt::FocusReason reason); + void hidden(); protected: /** * @brief paintEvent @@ -323,7 +329,7 @@ protected slots: * String to set the subtitle to */ void SetSubtitle(const QString &t); - +protected slots: private: /** * @brief Internal function that sets the QDockWidget's window title whenever the title/subtitle change. @@ -339,6 +345,10 @@ private: bool border_visible_; bool signal_instead_of_close_; + + QMetaObject::Connection m_tabBarConnection; + QMetaObject::Connection m_windowConnection; + bool m_lastVisibleState = false; }; } diff --git a/app/panel/project/project.cpp b/app/panel/project/project.cpp index f2f011e10..b2525f3b7 100644 --- a/app/panel/project/project.cpp +++ b/app/panel/project/project.cpp @@ -180,7 +180,7 @@ void ProjectPanel::ItemDoubleClickSlot(Node *item) PanelManager::instance()->MostRecentlyFocused(); panel->ConnectViewerNode(static_cast(item)); panel->raise(); - panel->setFocus(); + panel->setFocus(Qt::FocusReason::MouseFocusReason); } else if (dynamic_cast(item)) { // Open this sequence in the Timeline Core::instance()->main_window()->OpenSequence( diff --git a/app/render/texture.h b/app/render/texture.h index 350ffe9e3..d167343fb 100644 --- a/app/render/texture.h +++ b/app/render/texture.h @@ -21,6 +21,8 @@ #ifndef RENDERTEXTURE_H #define RENDERTEXTURE_H +#include "common/ffmpegutils.h" + #include #include @@ -150,7 +152,10 @@ public: { return job_; } - + void handleFrame(AVFramePtr ptr) + { + frame_=ptr; + } private: Renderer *renderer_; @@ -159,6 +164,8 @@ private: QVariant id_; AcceleratedJob *job_; + + AVFramePtr frame_; }; } diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index cc17e6993..52f11887e 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -1203,8 +1203,11 @@ void ViewerWidget::UpdateMinimumScale() // Avoids divide by zero SetMinimumScale(0); } else { - SetMinimumScale(static_cast(ruler()->width()) / - GetConnectedNode()->GetLength().toDouble()); + double min_scale = static_cast(ruler()->width()) / + GetConnectedNode()->GetLength().toDouble(); + // Ensure min_scale doesn't exceed max_scale to prevent crash + min_scale = qMin(min_scale, GetMaximumScale()); + SetMinimumScale(min_scale); } } diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index d556ebf8b..b020f98e1 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -29,15 +29,17 @@ #include #endif +#include "KDDockWidgets/src/qtwidgets/Window_p.h" #include "dialog/about/about.h" #include "mainmenu.h" #include "mainstatusbar.h" +#include "KDDockWidgets/src/LayoutSaver.h" #include "timeline/timelineundoworkarea.h" namespace olive { -#define super KDDockWidgets::MainWindow +#define super KDDockWidgets::QtWidgets::MainWindow MainWindow::MainWindow(QWidget *parent) : super(QStringLiteral("OliveMain"), KDDockWidgets::MainWindowOption_None, @@ -95,7 +97,7 @@ MainWindow::MainWindow(QWidget *parent) // emit the "shown" signal before emitting the "hidden" signals, resulting // in Core thinking there are -1 pixel samplers open. To mitigate that, // we force "shown" to emit ourselves here. - emit pixel_sampler_panel_->shown(); + emit pixel_sampler_panel_->shown(Qt::OtherFocusReason); // Make node-related connections connect(node_panel_, &NodePanel::NodeSelectionChangedWithContexts, @@ -382,7 +384,7 @@ void MainWindow::ToggleMaximizedPanel() premaximized_state_.clear(); currently_focused_panel->raise(); - currently_focused_panel->setFocus(); + currently_focused_panel->setFocus(Qt::ActiveWindowFocusReason); PanelManager::instance()->SetSuppressChangedSignal(false); } @@ -430,7 +432,7 @@ void MainWindow::SetProject(Project *p) project_panel_->set_project(p); if (project_) { - project_panel_->setFocus(); + project_panel_->setFocus(Qt::OtherFocusReason); } } diff --git a/app/window/mainwindow/mainwindow.h b/app/window/mainwindow/mainwindow.h index ac925c5c5..c56b80f17 100644 --- a/app/window/mainwindow/mainwindow.h +++ b/app/window/mainwindow/mainwindow.h @@ -53,7 +53,7 @@ namespace olive /** * @brief Olive's main window responsible for docking widgets and the main menu bar. */ -class MainWindow : public KDDockWidgets::MainWindow { +class MainWindow : public KDDockWidgets::QtWidgets::MainWindow { Q_OBJECT public: MainWindow(QWidget *parent = nullptr); diff --git a/ext/KDDockWidgets b/ext/KDDockWidgets index f24a04252..d24ba267d 160000 --- a/ext/KDDockWidgets +++ b/ext/KDDockWidgets @@ -1 +1 @@ -Subproject commit f24a04252ba319ef323769e766b3ba5252c15c4c +Subproject commit d24ba267da1a8e7e1f34d69db5e07336d68f6886 diff --git a/ext/core b/ext/core index 25e4152bb..a3628c464 160000 --- a/ext/core +++ b/ext/core @@ -1 +1 @@ -Subproject commit 25e4152bb70a0f4e30b9249e4f4b31e7936dc84a +Subproject commit a3628c46417f373596455a2bb724bf20a8edf9bc From 2c4d235da6a782468e9c1b114c8919c9033547c2 Mon Sep 17 00:00:00 2001 From: Mike Solar Date: Sun, 23 Nov 2025 16:39:30 +0800 Subject: [PATCH 06/10] Update KDDockWidgets submodule URL --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 41b12b89f..7e19cdfab 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,5 +4,5 @@ branch = dev [submodule "ext/KDDockWidgets"] path = ext/KDDockWidgets - url = https://github.com/olive-editor/KDDockWidgets.git + url = https://github.com/OliveCommunity/KDDockWidgets.git branch = main From f317e0e173e8b69c393aba90c027f0a531f1da88 Mon Sep 17 00:00:00 2001 From: Mike Solar Date: Mon, 24 Nov 2025 10:33:38 +0800 Subject: [PATCH 07/10] Add null checks for timebase in TimeScaledObject and fix background color assignment in ViewerDisplayWidget --- CMakeLists.txt | 1 + app/widget/timebased/timescaledobject.cpp | 12 ++++++++++++ app/widget/viewer/viewerdisplay.cpp | 2 +- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4115edd31..296b10a91 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -107,6 +107,7 @@ list(APPEND OLIVE_INCLUDE_DIRS ${OPENEXR_INCLUDES}) list(APPEND OLIVE_LIBRARIES olivecore) list(APPEND OLIVE_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/ext/core/include) + # Link Qt set(QT_LIBRARIES Core diff --git a/app/widget/timebased/timescaledobject.cpp b/app/widget/timebased/timescaledobject.cpp index c28d2bc29..c27072738 100644 --- a/app/widget/timebased/timescaledobject.cpp +++ b/app/widget/timebased/timescaledobject.cpp @@ -58,6 +58,9 @@ const double &TimeScaledObject::timebase_dbl() const rational TimeScaledObject::SceneToTime(const double &x, const double &x_scale, const rational &timebase, bool round) { + if (timebase.isNull()) { + return rational(); + } double unscaled_time = x / x_scale / timebase.toDouble(); // Adjust screen point by scale and timebase @@ -87,16 +90,25 @@ rational TimeScaledObject::SceneToTimeNoGrid(const double &x, double TimeScaledObject::TimeToScene(const rational &time) const { + if (timebase_.isNull()) { + return 0.0; + } return time.toDouble() * scale_; } rational TimeScaledObject::SceneToTime(const double &x, bool round) const { + if (timebase_.isNull()) { + return rational(); + } return SceneToTime(x, scale_, timebase_, round); } rational TimeScaledObject::SceneToTimeNoGrid(const double &x) const { + if (timebase_.isNull()) { + return rational::fromDouble(x / scale_); + } return SceneToTimeNoGrid(x, scale_); } diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index 1020e4b4a..501c9bf51 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -374,7 +374,7 @@ bool ViewerDisplayWidget::eventFilter(QObject *o, QEvent *e) void ViewerDisplayWidget::OnPaint() { // Clear background to empty - QColor bg_color = show_widget_background_ ? palette().window().color() : + QColor bg_color = show_widget_background_ ? palette().window().color() : Qt::black; renderer()->ClearDestination(nullptr, bg_color.redF(), bg_color.greenF(), bg_color.blueF()); From 2dd8c4629a660dad8daa40fa34ecd06d59c1e633 Mon Sep 17 00:00:00 2001 From: Mike Solar Date: Mon, 24 Nov 2025 20:33:45 +0800 Subject: [PATCH 08/10] Update include path for Window_p.h Changed the include path of "Window_p.h" to use a relative path instead of an absolute one, ensuring better portability and build consistency. --- ext/KDDockWidgets | 2 +- ext/core | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/KDDockWidgets b/ext/KDDockWidgets index d24ba267d..35d131b91 160000 --- a/ext/KDDockWidgets +++ b/ext/KDDockWidgets @@ -1 +1 @@ -Subproject commit d24ba267da1a8e7e1f34d69db5e07336d68f6886 +Subproject commit 35d131b91a7e58fe5962613ce824dd53649ba613 diff --git a/ext/core b/ext/core index a3628c464..50caae473 160000 --- a/ext/core +++ b/ext/core @@ -1 +1 @@ -Subproject commit a3628c46417f373596455a2bb724bf20a8edf9bc +Subproject commit 50caae473d6de11d129f1fb6a14d2c73dac83f51 From d81140a312571bfe315e0b4ea63b4b165b1de7ee Mon Sep 17 00:00:00 2001 From: Mike Solar Date: Mon, 24 Nov 2025 20:41:22 +0800 Subject: [PATCH 09/10] Update KDDockWidgets library --- ext/KDDockWidgets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/KDDockWidgets b/ext/KDDockWidgets index 35d131b91..0e9723b96 160000 --- a/ext/KDDockWidgets +++ b/ext/KDDockWidgets @@ -1 +1 @@ -Subproject commit 35d131b91a7e58fe5962613ce824dd53649ba613 +Subproject commit 0e9723b96b100deeb03977db0f587ad207d0bef4 From 6dec37eef419f2b293de1f12f745e0fac2e71b5e Mon Sep 17 00:00:00 2001 From: Mike Solar Date: Mon, 24 Nov 2025 21:39:01 +0800 Subject: [PATCH 10/10] Add a patch to fix include path for Window_p.h in qtcommon directory --- ...-for-Window_p.h-in-qtcommon-director.patch | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 0001-Fix-include-path-for-Window_p.h-in-qtcommon-director.patch diff --git a/0001-Fix-include-path-for-Window_p.h-in-qtcommon-director.patch b/0001-Fix-include-path-for-Window_p.h-in-qtcommon-director.patch new file mode 100644 index 000000000..e51c8ee8a --- /dev/null +++ b/0001-Fix-include-path-for-Window_p.h-in-qtcommon-director.patch @@ -0,0 +1,25 @@ +From 6b0ef44eb189411d36c739ccde8a081a3e62034a Mon Sep 17 00:00:00 2001 +From: Mike Solar +Date: Mon, 24 Nov 2025 20:57:12 +0800 +Subject: [PATCH] Fix include path for Window_p.h in qtcommon directory + +--- + src/qtcommon/Window_p.h | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/qtcommon/Window_p.h b/src/qtcommon/Window_p.h +index f29b209a5..d1d6b47ad 100644 +--- a/src/qtcommon/Window_p.h ++++ b/src/qtcommon/Window_p.h +@@ -11,7 +11,7 @@ + + #pragma once + +-#include "core/Window_p.h" ++#include "../core/Window_p.h" + #include "Screen_p.h" + + #include +-- +2.52.0 +