diff --git a/olive.pro b/olive.pro index c9ffc8345..f104d337e 100644 --- a/olive.pro +++ b/olive.pro @@ -1,240 +1,242 @@ -#------------------------------------------------- -# -# Project created by QtCreator 2018-05-11T10:31:59 -# -#------------------------------------------------- - -QT += core gui multimedia opengl - -greaterThan(QT_MAJOR_VERSION, 4): QT += widgets - -mac { - TARGET = Olive -} -!mac { - TARGET = olive-editor -} -TEMPLATE = app - -# The following define makes your compiler emit warnings if you use -# any feature of Qt which has been marked as deprecated (the exact warnings -# depend on your compiler). Please consult the documentation of the -# deprecated API in order to know how to port your code away from it. -DEFINES += QT_DEPRECATED_WARNINGS - -# You can also make your code fail to compile if you use deprecated APIs. -# In order to do so, uncomment the following line. -# You can also select to disable deprecated APIs only up to a certain version of Qt. -#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 - - -SOURCES += \ - main.cpp \ - mainwindow.cpp \ - panels/project.cpp \ - panels/effectcontrols.cpp \ - panels/viewer.cpp \ - panels/timeline.cpp \ - ui/sourcetable.cpp \ - dialogs/aboutdialog.cpp \ - ui/timelinewidget.cpp \ - project/media.cpp \ - project/footage.cpp \ - project/sequence.cpp \ - project/clip.cpp \ - playback/playback.cpp \ - playback/audio.cpp \ - io/config.cpp \ - dialogs/newsequencedialog.cpp \ - ui/viewerwidget.cpp \ - ui/viewercontainer.cpp \ - dialogs/exportdialog.cpp \ - ui/collapsiblewidget.cpp \ - panels/panels.cpp \ - playback/cacher.cpp \ - io/exportthread.cpp \ - ui/timelineheader.cpp \ - io/previewgenerator.cpp \ - ui/labelslider.cpp \ - dialogs/preferencesdialog.cpp \ - ui/audiomonitor.cpp \ - project/undo.cpp \ - ui/scrollarea.cpp \ - ui/comboboxex.cpp \ - ui/colorbutton.cpp \ - dialogs/replaceclipmediadialog.cpp \ - ui/fontcombobox.cpp \ - ui/checkboxex.cpp \ - ui/keyframeview.cpp \ - ui/texteditex.cpp \ - dialogs/demonotice.cpp \ - project/marker.cpp \ - dialogs/speeddialog.cpp \ - dialogs/mediapropertiesdialog.cpp \ - io/crc32.cpp \ - project/projectmodel.cpp \ - io/loadthread.cpp \ - dialogs/loaddialog.cpp \ - debug.cpp \ - io/path.cpp \ - effects/internal/linearfadetransition.cpp \ - effects/internal/transformeffect.cpp \ - effects/internal/solideffect.cpp \ - effects/internal/texteffect.cpp \ - effects/internal/timecodeeffect.cpp \ - effects/internal/audionoiseeffect.cpp \ - effects/internal/paneffect.cpp \ - effects/internal/toneeffect.cpp \ - effects/internal/volumeeffect.cpp \ - effects/internal/crossdissolvetransition.cpp \ - effects/internal/shakeeffect.cpp \ - effects/internal/exponentialfadetransition.cpp \ - effects/internal/logarithmicfadetransition.cpp \ - effects/internal/cornerpineffect.cpp \ - io/math.cpp \ - io/qpainterwrapper.cpp \ - project/effect.cpp \ - project/transition.cpp \ - project/effectrow.cpp \ - project/effectfield.cpp \ - effects/internal/cubetransition.cpp \ - project/effectgizmo.cpp \ - io/clipboard.cpp \ - dialogs/stabilizerdialog.cpp \ - io/avtogl.cpp \ - ui/sourceiconview.cpp \ - project/sourcescommon.cpp - -HEADERS += \ - mainwindow.h \ - panels/project.h \ - panels/effectcontrols.h \ - panels/viewer.h \ - panels/timeline.h \ - ui/sourcetable.h \ - dialogs/aboutdialog.h \ - ui/timelinewidget.h \ - project/media.h \ - project/footage.h \ - project/sequence.h \ - project/clip.h \ - playback/playback.h \ - playback/audio.h \ - io/config.h \ - dialogs/newsequencedialog.h \ - ui/viewerwidget.h \ - ui/viewercontainer.h \ - dialogs/exportdialog.h \ - ui/collapsiblewidget.h \ - panels/panels.h \ - playback/cacher.h \ - io/exportthread.h \ - ui/timelinetools.h \ - ui/timelineheader.h \ - io/previewgenerator.h \ - ui/labelslider.h \ - dialogs/preferencesdialog.h \ - ui/audiomonitor.h \ - project/undo.h \ - ui/scrollarea.h \ - ui/comboboxex.h \ - ui/colorbutton.h \ - dialogs/replaceclipmediadialog.h \ - ui/fontcombobox.h \ - ui/checkboxex.h \ - ui/keyframeview.h \ - ui/texteditex.h \ - dialogs/demonotice.h \ - project/marker.h \ - project/selection.h \ - dialogs/speeddialog.h \ - dialogs/mediapropertiesdialog.h \ - io/crc32.h \ - project/projectmodel.h \ - io/loadthread.h \ - dialogs/loaddialog.h \ - debug.h \ - io/path.h \ - effects/internal/transformeffect.h \ - effects/internal/solideffect.h \ - effects/internal/texteffect.h \ - effects/internal/timecodeeffect.h \ - effects/internal/audionoiseeffect.h \ - effects/internal/paneffect.h \ - effects/internal/toneeffect.h \ - effects/internal/volumeeffect.h \ - effects/internal/shakeeffect.h \ - effects/internal/linearfadetransition.h \ - effects/internal/crossdissolvetransition.h \ - effects/internal/exponentialfadetransition.h \ - effects/internal/logarithmicfadetransition.h \ - effects/internal/cornerpineffect.h \ - io/math.h \ - io/qpainterwrapper.h \ - project/effect.h \ - project/transition.h \ - project/effectrow.h \ - project/effectfield.h \ - effects/internal/cubetransition.h \ - project/effectgizmo.h \ - io/clipboard.h \ - dialogs/stabilizerdialog.h \ - io/avtogl.h \ - ui/sourceiconview.h \ - project/sourcescommon.h - -FORMS += \ - mainwindow.ui \ - panels/effectcontrols.ui \ - panels/viewer.ui \ - panels/timeline.ui \ - dialogs/aboutdialog.ui \ - dialogs/newsequencedialog.ui \ - dialogs/exportdialog.ui \ - dialogs/preferencesdialog.ui \ - dialogs/demonotice.ui - -win32 { - RC_FILE = icons/resources.rc - LIBS += -lavutil -lavformat -lavcodec -lavfilter -lswscale -lswresample -lopengl32 -} - -mac { - LIBS += -L/usr/local/lib -lavutil -lavformat -lavcodec -lavfilter -lswscale -lswresample - ICON = icons/olive.icns - INCLUDEPATH = /usr/local/include -} - -unix:!mac { - CONFIG += link_pkgconfig - PKGCONFIG += libavutil libavformat libavcodec libavfilter libswscale libswresample -} - -RESOURCES += \ - icons/icons.qrc - -unix:!mac { - metainfo.files = $$PWD/packaging/linux/org.olivevideoeditor.Olive.appdata.xml - metainfo.path = $$PREFIX/share/metainfo - desktop.files = $$PWD/packaging/linux/org.olivevideoeditor.Olive.desktop - desktop.path = $$PREFIX/share/applications - mime.files = $$PWD/packaging/linux/org.olivevideoeditor.Olive.xml - mime.path = $$PREFIX/share/mime/packages - icon16.files = $$PWD/packaging/linux/icons/16x16/org.olivevideoeditor.Olive.png - icon16.path = $$PREFIX/share/icons/hicolor/16x16/apps - icon32.files = $$PWD/packaging/linux/icons/32x32/org.olivevideoeditor.Olive.png - icon32.path = $$PREFIX/share/icons/hicolor/32x32/apps - icon48.files = $$PWD/packaging/linux/icons/48x48/org.olivevideoeditor.Olive.png - icon48.path = $$PREFIX/share/icons/hicolor/48x48/apps - icon64.files = $$PWD/packaging/linux/icons/64x64/org.olivevideoeditor.Olive.png - icon64.path = $$PREFIX/share/icons/hicolor/64x64/apps - icon128.files = $$PWD/packaging/linux/icons/128x128/org.olivevideoeditor.Olive.png - icon128.path = $$PREFIX/share/icons/hicolor/128x128/apps - icon256.files = $$PWD/packaging/linux/icons/256x256/org.olivevideoeditor.Olive.png - icon256.path = $$PREFIX/share/icons/hicolor/256x256/apps - icon512.files = $$PWD/packaging/linux/icons/512x512/org.olivevideoeditor.Olive.png - icon512.path = $$PREFIX/share/icons/hicolor/512x512/apps - icon1024.files = $$PWD/packaging/linux/icons/1024x1024/org.olivevideoeditor.Olive.png - icon1024.path = $$PREFIX/share/icons/hicolor/1024x1024/apps - INSTALLS += metainfo desktop mime icon16 icon32 icon48 icon64 icon128 icon256 icon512 icon1024 -} +#------------------------------------------------- +# +# Project created by QtCreator 2018-05-11T10:31:59 +# +#------------------------------------------------- + +QT += core gui multimedia opengl + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +mac { + TARGET = Olive +} +!mac { + TARGET = olive-editor +} +TEMPLATE = app + +# The following define makes your compiler emit warnings if you use +# any feature of Qt which has been marked as deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if you use deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + + +SOURCES += \ + main.cpp \ + mainwindow.cpp \ + panels/project.cpp \ + panels/effectcontrols.cpp \ + panels/viewer.cpp \ + panels/timeline.cpp \ + ui/sourcetable.cpp \ + dialogs/aboutdialog.cpp \ + ui/timelinewidget.cpp \ + project/media.cpp \ + project/footage.cpp \ + project/sequence.cpp \ + project/clip.cpp \ + playback/playback.cpp \ + playback/audio.cpp \ + io/config.cpp \ + dialogs/newsequencedialog.cpp \ + ui/viewerwidget.cpp \ + ui/viewercontainer.cpp \ + dialogs/exportdialog.cpp \ + ui/collapsiblewidget.cpp \ + panels/panels.cpp \ + playback/cacher.cpp \ + io/exportthread.cpp \ + ui/timelineheader.cpp \ + io/previewgenerator.cpp \ + ui/labelslider.cpp \ + dialogs/preferencesdialog.cpp \ + ui/audiomonitor.cpp \ + project/undo.cpp \ + ui/scrollarea.cpp \ + ui/comboboxex.cpp \ + ui/colorbutton.cpp \ + dialogs/replaceclipmediadialog.cpp \ + ui/fontcombobox.cpp \ + ui/checkboxex.cpp \ + ui/keyframeview.cpp \ + ui/texteditex.cpp \ + dialogs/demonotice.cpp \ + project/marker.cpp \ + dialogs/speeddialog.cpp \ + dialogs/mediapropertiesdialog.cpp \ + io/crc32.cpp \ + project/projectmodel.cpp \ + io/loadthread.cpp \ + dialogs/loaddialog.cpp \ + debug.cpp \ + io/path.cpp \ + effects/internal/linearfadetransition.cpp \ + effects/internal/transformeffect.cpp \ + effects/internal/solideffect.cpp \ + effects/internal/texteffect.cpp \ + effects/internal/timecodeeffect.cpp \ + effects/internal/audionoiseeffect.cpp \ + effects/internal/paneffect.cpp \ + effects/internal/toneeffect.cpp \ + effects/internal/volumeeffect.cpp \ + effects/internal/crossdissolvetransition.cpp \ + effects/internal/shakeeffect.cpp \ + effects/internal/exponentialfadetransition.cpp \ + effects/internal/logarithmicfadetransition.cpp \ + effects/internal/cornerpineffect.cpp \ + io/math.cpp \ + io/qpainterwrapper.cpp \ + project/effect.cpp \ + project/transition.cpp \ + project/effectrow.cpp \ + project/effectfield.cpp \ + effects/internal/cubetransition.cpp \ + project/effectgizmo.cpp \ + io/clipboard.cpp \ + dialogs/stabilizerdialog.cpp \ + io/avtogl.cpp \ + ui/resizablescrollbar.cpp \ + ui/sourceiconview.cpp \ + project/sourcescommon.cpp + +HEADERS += \ + mainwindow.h \ + panels/project.h \ + panels/effectcontrols.h \ + panels/viewer.h \ + panels/timeline.h \ + ui/sourcetable.h \ + dialogs/aboutdialog.h \ + ui/timelinewidget.h \ + project/media.h \ + project/footage.h \ + project/sequence.h \ + project/clip.h \ + playback/playback.h \ + playback/audio.h \ + io/config.h \ + dialogs/newsequencedialog.h \ + ui/viewerwidget.h \ + ui/viewercontainer.h \ + dialogs/exportdialog.h \ + ui/collapsiblewidget.h \ + panels/panels.h \ + playback/cacher.h \ + io/exportthread.h \ + ui/timelinetools.h \ + ui/timelineheader.h \ + io/previewgenerator.h \ + ui/labelslider.h \ + dialogs/preferencesdialog.h \ + ui/audiomonitor.h \ + project/undo.h \ + ui/scrollarea.h \ + ui/comboboxex.h \ + ui/colorbutton.h \ + dialogs/replaceclipmediadialog.h \ + ui/fontcombobox.h \ + ui/checkboxex.h \ + ui/keyframeview.h \ + ui/texteditex.h \ + dialogs/demonotice.h \ + project/marker.h \ + project/selection.h \ + dialogs/speeddialog.h \ + dialogs/mediapropertiesdialog.h \ + io/crc32.h \ + project/projectmodel.h \ + io/loadthread.h \ + dialogs/loaddialog.h \ + debug.h \ + io/path.h \ + effects/internal/transformeffect.h \ + effects/internal/solideffect.h \ + effects/internal/texteffect.h \ + effects/internal/timecodeeffect.h \ + effects/internal/audionoiseeffect.h \ + effects/internal/paneffect.h \ + effects/internal/toneeffect.h \ + effects/internal/volumeeffect.h \ + effects/internal/shakeeffect.h \ + effects/internal/linearfadetransition.h \ + effects/internal/crossdissolvetransition.h \ + effects/internal/exponentialfadetransition.h \ + effects/internal/logarithmicfadetransition.h \ + effects/internal/cornerpineffect.h \ + io/math.h \ + io/qpainterwrapper.h \ + project/effect.h \ + project/transition.h \ + project/effectrow.h \ + project/effectfield.h \ + effects/internal/cubetransition.h \ + project/effectgizmo.h \ + io/clipboard.h \ + dialogs/stabilizerdialog.h \ + io/avtogl.h \ + ui/resizablescrollbar.h \ + ui/sourceiconview.h \ + project/sourcescommon.h + +FORMS += \ + mainwindow.ui \ + panels/effectcontrols.ui \ + panels/viewer.ui \ + panels/timeline.ui \ + dialogs/aboutdialog.ui \ + dialogs/newsequencedialog.ui \ + dialogs/exportdialog.ui \ + dialogs/preferencesdialog.ui \ + dialogs/demonotice.ui + +win32 { + RC_FILE = icons/resources.rc + LIBS += -lavutil -lavformat -lavcodec -lavfilter -lswscale -lswresample -lopengl32 +} + +mac { + LIBS += -L/usr/local/lib -lavutil -lavformat -lavcodec -lavfilter -lswscale -lswresample + ICON = icons/olive.icns + INCLUDEPATH = /usr/local/include +} + +unix:!mac { + CONFIG += link_pkgconfig + PKGCONFIG += libavutil libavformat libavcodec libavfilter libswscale libswresample +} + +RESOURCES += \ + icons/icons.qrc + +unix:!mac { + metainfo.files = $$PWD/packaging/linux/org.olivevideoeditor.Olive.appdata.xml + metainfo.path = $$PREFIX/share/metainfo + desktop.files = $$PWD/packaging/linux/org.olivevideoeditor.Olive.desktop + desktop.path = $$PREFIX/share/applications + mime.files = $$PWD/packaging/linux/org.olivevideoeditor.Olive.xml + mime.path = $$PREFIX/share/mime/packages + icon16.files = $$PWD/packaging/linux/icons/16x16/org.olivevideoeditor.Olive.png + icon16.path = $$PREFIX/share/icons/hicolor/16x16/apps + icon32.files = $$PWD/packaging/linux/icons/32x32/org.olivevideoeditor.Olive.png + icon32.path = $$PREFIX/share/icons/hicolor/32x32/apps + icon48.files = $$PWD/packaging/linux/icons/48x48/org.olivevideoeditor.Olive.png + icon48.path = $$PREFIX/share/icons/hicolor/48x48/apps + icon64.files = $$PWD/packaging/linux/icons/64x64/org.olivevideoeditor.Olive.png + icon64.path = $$PREFIX/share/icons/hicolor/64x64/apps + icon128.files = $$PWD/packaging/linux/icons/128x128/org.olivevideoeditor.Olive.png + icon128.path = $$PREFIX/share/icons/hicolor/128x128/apps + icon256.files = $$PWD/packaging/linux/icons/256x256/org.olivevideoeditor.Olive.png + icon256.path = $$PREFIX/share/icons/hicolor/256x256/apps + icon512.files = $$PWD/packaging/linux/icons/512x512/org.olivevideoeditor.Olive.png + icon512.path = $$PREFIX/share/icons/hicolor/512x512/apps + icon1024.files = $$PWD/packaging/linux/icons/1024x1024/org.olivevideoeditor.Olive.png + icon1024.path = $$PREFIX/share/icons/hicolor/1024x1024/apps + INSTALLS += metainfo desktop mime icon16 icon32 icon48 icon64 icon128 icon256 icon512 icon1024 +} diff --git a/panels/effectcontrols.cpp b/panels/effectcontrols.cpp index 3fa48359c..4809aa482 100644 --- a/panels/effectcontrols.cpp +++ b/panels/effectcontrols.cpp @@ -43,6 +43,7 @@ EffectControls::EffectControls(QWidget *parent) : ui->label_2->setVisible(false); connect(ui->horizontalScrollBar, SIGNAL(valueChanged(int)), ui->headers, SLOT(set_scroll(int))); + connect(ui->horizontalScrollBar, SIGNAL(resize_move(double)), ui->keyframeView, SLOT(resize_move(double))); connect(ui->horizontalScrollBar, SIGNAL(valueChanged(int)), ui->keyframeView, SLOT(set_x_scroll(int))); connect(ui->verticalScrollBar, SIGNAL(valueChanged(int)), ui->keyframeView, SLOT(set_y_scroll(int))); connect(ui->verticalScrollBar, SIGNAL(valueChanged(int)), ui->scrollArea->verticalScrollBar(), SLOT(setValue(int))); diff --git a/panels/effectcontrols.ui b/panels/effectcontrols.ui index 8c450c815..587946d5a 100644 --- a/panels/effectcontrols.ui +++ b/panels/effectcontrols.ui @@ -71,8 +71,8 @@ 0 0 - 225 - 477 + 189 + 483 @@ -502,7 +502,7 @@ - + 0 @@ -543,6 +543,11 @@
panels/effectcontrols.h
1 + + ResizableScrollBar + QScrollBar +
ui/resizablescrollbar.h
+
diff --git a/panels/project.cpp b/panels/project.cpp index cad3eaefe..ce3264a6c 100644 --- a/panels/project.cpp +++ b/panels/project.cpp @@ -1,1187 +1,1187 @@ -#include "project.h" -#include "project/footage.h" - -#include "panels/panels.h" -#include "panels/timeline.h" -#include "panels/viewer.h" -#include "playback/playback.h" -#include "project/effect.h" -#include "project/transition.h" -#include "panels/timeline.h" -#include "project/sequence.h" -#include "project/clip.h" -#include "io/previewgenerator.h" -#include "project/undo.h" -#include "mainwindow.h" -#include "io/config.h" -#include "playback/cacher.h" -#include "dialogs/replaceclipmediadialog.h" -#include "panels/effectcontrols.h" -#include "dialogs/newsequencedialog.h" -#include "dialogs/mediapropertiesdialog.h" -#include "dialogs/loaddialog.h" -#include "io/clipboard.h" -#include "project/media.h" -#include "ui/sourcetable.h" -#include "ui/sourceiconview.h" -#include "project/sourcescommon.h" -#include "debug.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -extern "C" { - #include - #include -} - -#define MAXIMUM_RECENT_PROJECTS 10 - -ProjectModel project_model; - -QString autorecovery_filename; -QString project_url = ""; -QStringList recent_projects; -QString recent_proj_file; - -Project::Project(QWidget *parent) : - QDockWidget(parent) -{ - setObjectName("Project"); - resize(504, 371); - QSizePolicy sizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); - sizePolicy.setHorizontalStretch(0); - sizePolicy.setVerticalStretch(0); - sizePolicy.setHeightForWidth(this->sizePolicy().hasHeightForWidth()); - setSizePolicy(sizePolicy); - - QWidget* dockWidgetContents = new QWidget(); - QVBoxLayout* verticalLayout = new QVBoxLayout(dockWidgetContents); - verticalLayout->setContentsMargins(0, 0, 0, 0); - verticalLayout->setSpacing(0); - - setWidget(dockWidgetContents); - - sources_common = new SourcesCommon(this); - - sorter = new QSortFilterProxyModel(this); - sorter->setSourceModel(&project_model); - - tree_view = new SourceTable(dockWidgetContents); - tree_view->project_parent = this; - tree_view->setAcceptDrops(true); - tree_view->setEditTriggers(QAbstractItemView::NoEditTriggers); - tree_view->setDragDropMode(QAbstractItemView::DragDrop); - tree_view->setSelectionMode(QAbstractItemView::ExtendedSelection); - tree_view->setModel(sorter); - verticalLayout->addWidget(tree_view); - - icon_view_container = new QWidget(); - - QVBoxLayout* icon_view_container_layout = new QVBoxLayout(); - icon_view_container_layout->setMargin(0); - icon_view_container_layout->setSpacing(0); - icon_view_container->setLayout(icon_view_container_layout); - - QHBoxLayout* icon_view_controls = new QHBoxLayout(); - icon_view_controls->setMargin(0); - icon_view_controls->setSpacing(0); - - QIcon directory_up_button; - directory_up_button.addFile(":/icons/dirup.png", QSize(), QIcon::Normal); - directory_up_button.addFile(":/icons/dirup-disabled.png", QSize(), QIcon::Disabled); - - directory_up = new QPushButton(); - directory_up->setIcon(directory_up_button); - directory_up->setEnabled(false); - icon_view_controls->addWidget(directory_up); - - icon_view_controls->addStretch(); - - QSlider* icon_size_slider = new QSlider(Qt::Horizontal); - icon_size_slider->setMinimum(16); - icon_size_slider->setMaximum(120); - icon_view_controls->addWidget(icon_size_slider); - connect(icon_size_slider, SIGNAL(valueChanged(int)), this, SLOT(set_icon_view_size(int))); - - icon_view_container_layout->addLayout(icon_view_controls); - - icon_view = new SourceIconView(dockWidgetContents); - icon_view->project_parent = this; - icon_view->setModel(sorter); - icon_view->setIconSize(QSize(100, 100)); - icon_view->setViewMode(QListView::IconMode); - icon_view->setUniformItemSizes(true); - icon_view_container_layout->addWidget(icon_view); - - icon_size_slider->setValue(icon_view->iconSize().height()); - - verticalLayout->addWidget(icon_view_container); - - connect(directory_up, SIGNAL(clicked(bool)), this, SLOT(go_up_dir())); - connect(icon_view, SIGNAL(changed_root()), this, SLOT(set_up_dir_enabled())); - - //retranslateUi(Project); - setWindowTitle(QApplication::translate("Project", "Project", nullptr)); - - update_view_type(); -} - -Project::~Project() { - delete sorter; -} - -QString Project::get_next_sequence_name(QString start) { - if (start.isEmpty()) start = "Sequence"; - - int n = 1; - bool found = true; - QString name; - while (found) { - found = false; - name = start + " "; - if (n < 10) { - name += "0"; - } - name += QString::number(n); - for (int i=0;iget_name(), name, Qt::CaseInsensitive) == 0) { - found = true; - n++; - break; - } - } - } - return name; -} - -Sequence* create_sequence_from_media(QVector& media_list) { - Sequence* s = new Sequence(); - - s->name = panel_project->get_next_sequence_name(); - - // shitty hardcoded default values - s->width = 1920; - s->height = 1080; - s->frame_rate = 29.97; - s->audio_frequency = 48000; - s->audio_layout = 3; - - bool got_video_values = false; - bool got_audio_values = false; - for (int i=0;iget_type()) { - case MEDIA_TYPE_FOOTAGE: - { - Footage* m = media->to_footage(); - if (m->ready) { - if (!got_video_values) { - for (int j=0;jvideo_tracks.size();j++) { - FootageStream* ms = m->video_tracks.at(j); - s->width = ms->video_width; - s->height = ms->video_height; - if (ms->video_frame_rate != 0) { - s->frame_rate = ms->video_frame_rate; - - if (ms->video_interlacing != VIDEO_PROGRESSIVE) s->frame_rate *= 2; - - // only break with a decent frame rate, otherwise there may be a better candidate - got_video_values = true; - break; - } - } - } - if (!got_audio_values) { - for (int j=0;jaudio_tracks.size();j++) { - FootageStream* ms = m->audio_tracks.at(j); - s->audio_frequency = ms->audio_frequency; - got_audio_values = true; - break; - } - } - } - } - break; - case MEDIA_TYPE_SEQUENCE: - { - Sequence* seq = media->to_sequence(); - s->width = seq->width; - s->height = seq->height; - s->frame_rate = seq->frame_rate; - s->audio_frequency = seq->audio_frequency; - s->audio_layout = seq->audio_layout; - - got_video_values = true; - got_audio_values = true; - } - break; - } - if (got_video_values && got_audio_values) break; - } - - return s; -} - -void Project::duplicate_selected() { - QModelIndexList items = tree_view->selectionModel()->selectedRows(); - bool duped = false; - ComboAction* ca = new ComboAction(); - for (int j=0;jget_type() == MEDIA_TYPE_SEQUENCE) { - new_sequence(ca, i->to_sequence()->copy(), false, item_to_media(items.at(j).parent())); - duped = true; - } - } - if (duped) { - undo_stack.push(ca); - } else { - delete ca; - } -} - -void Project::replace_selected_file() { - QModelIndexList selected_items = tree_view->selectionModel()->selectedRows(); - if (selected_items.size() == 1) { - Media* item = item_to_media(selected_items.at(0)); - if (item->get_type() == MEDIA_TYPE_FOOTAGE) { - replace_media(item, 0); - } - } -} - -void Project::replace_media(Media* item, QString filename) { - if (filename.isEmpty()) { - filename = QFileDialog::getOpenFileName(this, "Replace '" + item->get_name() + "'", "", "All Files (*)"); - } - if (!filename.isEmpty()) { - ReplaceMediaCommand* rmc = new ReplaceMediaCommand(item, filename); - undo_stack.push(rmc); - } -} - -void Project::replace_clip_media() { - if (sequence == NULL) { - QMessageBox::critical(this, "No active sequence", "No sequence is active, please open the sequence you want to replace clips from.", QMessageBox::Ok); - } else { - QModelIndexList selected_items = tree_view->selectionModel()->selectedRows(); - if (selected_items.size() == 1) { - Media* item = item_to_media(selected_items.at(0)); - if (item->get_type() == MEDIA_TYPE_SEQUENCE && sequence == item->to_sequence()) { - QMessageBox::critical(this, "Active sequence selected", "You cannot insert a sequence into itself, so no clips of this media would be in this sequence.", QMessageBox::Ok); - } else { - ReplaceClipMediaDialog dialog(this, tree_view, item); - dialog.exec(); - } - } - } -} - -void Project::open_properties() { - QModelIndexList selected_items = tree_view->selectionModel()->selectedRows(); - if (selected_items.size() == 1) { - Media* item = item_to_media(selected_items.at(0)); - switch (item->get_type()) { - case MEDIA_TYPE_FOOTAGE: - { - MediaPropertiesDialog mpd(this, item); - mpd.exec(); - } - break; - case MEDIA_TYPE_SEQUENCE: - { - NewSequenceDialog nsd(this); - Sequence* s = item->to_sequence(); - nsd.existing_sequence = s; - nsd.existing_item = item; - nsd.exec(); - } - break; - default: - { - // fall back to renaming - QString new_name = QInputDialog::getText(this, "Rename '" + item->get_name() + "'", "Enter new name:", QLineEdit::Normal, item->get_name()); - if (!new_name.isEmpty()) { - MediaRename* mr = new MediaRename(item, new_name); - undo_stack.push(mr); - } - } - } - } -} - -Media* Project::new_sequence(ComboAction *ca, Sequence *s, bool open, Media* parent) { - if (parent == NULL) parent = project_model.get_root(); - Media* item = new Media(parent); - item->set_sequence(s); - - if (ca != NULL) { - ca->append(new NewSequenceCommand(item, parent)); - if (open) ca->append(new ChangeSequenceAction(s)); - } else { - project_model.appendChild(NULL, item); - if (open) set_sequence(s); - } - return item; -} - -QString Project::get_file_name_from_path(const QString& path) { - return path.mid(path.lastIndexOf('/')+1); -} - -/*Media* Project::new_item() { - Media* item = new Media(0); - //item->setFlags(item->flags() | Qt::ItemIsEditable); - return item; -}*/ - -bool Project::is_focused() { - return tree_view->hasFocus(); -} - -Media* Project::new_folder(QString name) { - Media* item = new Media(0); - item->set_folder(); - return item; -} - -Media *Project::item_to_media(const QModelIndex &index) { - return static_cast(sorter->mapToSource(index).internalPointer()); -// return static_cast(index.internalPointer()); -} - -void Project::get_all_media_from_table(QList items, QList& list, int search_type) { - for (int i=0;iget_type() == MEDIA_TYPE_FOLDER) { - QList children; - for (int j=0;jchildCount();j++) { - children.append(item->child(j)); - } - get_all_media_from_table(children, list, search_type); - } else if (search_type == item->get_type() || search_type == -1) { - list.append(item); - } - } -} - -bool delete_clips_in_clipboard_with_media(ComboAction* ca, Media* m) { - int delete_count = 0; - if (clipboard_type == CLIPBOARD_TYPE_CLIP) { - for (int i=0;i(clipboard.at(i)); - if (c->media == m) { - ca->append(new RemoveClipsFromClipboard(i-delete_count)); - delete_count++; - } - } - } - return (delete_count > 0); -} - -void Project::delete_selected_media() { - ComboAction* ca = new ComboAction(); - QModelIndexList selected_items = tree_view->selectionModel()->selectedRows(); - QList items; - for (int i=0;isetSortingEnabled(false); - tree_view->setSortingEnabled(true); - - // check if media is in use - QVector parents; - QList sequence_items; - QList all_top_level_items; - for (int i=0;i 0) { - QList media_items; - get_all_media_from_table(items, media_items, MEDIA_TYPE_FOOTAGE); - for (int i=0;ito_footage(); - bool confirm_delete = false; - for (int j=0;jto_sequence(); - for (int k=0;kclips.size();k++) { - Clip* c = s->clips.at(k); - if (c != NULL && c->media == item) { - if (!confirm_delete) { - // we found a reference, so we know we'll need to ask if the user wants to delete it - QMessageBox confirm(this); - confirm.setWindowTitle("Delete media in use?"); - confirm.setText("The media '" + media->name + "' is currently used in '" + s->name + "'. Deleting it will remove all instances in the sequence. Are you sure you want to do this?"); - QAbstractButton* yes_button = confirm.addButton(QMessageBox::Yes); - QAbstractButton* skip_button = NULL; - if (items.size() > 1) skip_button = confirm.addButton("Skip", QMessageBox::NoRole); - QAbstractButton* abort_button = confirm.addButton(QMessageBox::Cancel); - confirm.exec(); - if (confirm.clickedButton() == yes_button) { - // remove all clips referencing this media - confirm_delete = true; - redraw = true; - } else if (confirm.clickedButton() == skip_button) { - // remove media item and any folders containing it from the remove list - Media* parent = item; - while (parent != NULL) { - parents.append(parent); - - // re-add item's siblings - for (int m=0;mchildCount();m++) { - Media* child = parent->child(m); - bool found = false; - for (int n=0;nparentItem(); - } - - j = sequence_items.size(); - k = s->clips.size(); - } else if (confirm.clickedButton() == abort_button) { - // break out of loop - i = media_items.size(); - j = sequence_items.size(); - k = s->clips.size(); - - remove = false; - } - } - if (confirm_delete) { - ca->append(new DeleteClipAction(s, k)); - } - } - } - } - if (confirm_delete) { - delete_clips_in_clipboard_with_media(ca, item); - } - - } - } - - // remove - if (remove) { - // remove media and parents - for (int m=0;mappend(new DeleteMediaCommand(items.at(i))); - - if (items.at(i)->get_type() == MEDIA_TYPE_SEQUENCE) { - redraw = true; - - Sequence* s = items.at(i)->to_sequence(); - - if (s == sequence) { - ca->append(new ChangeSequenceAction(NULL)); - } - - if (s == panel_footage_viewer->seq) { - panel_footage_viewer->set_media(NULL); - } - } else if (items.at(i)->get_type() == MEDIA_TYPE_FOOTAGE) { - if (panel_footage_viewer->seq != NULL) { - for (int j=0;jseq->clips.size();j++) { - Clip* c = panel_footage_viewer->seq->clips.at(j); - if (c != NULL) { - if (c->media == items.at(i)->to_object()) { - panel_footage_viewer->set_media(NULL); - } - break; - } - } - } - } - } - undo_stack.push(ca); - - // redraw clips - if (redraw) { - update_ui(true); - } - } else { - delete ca; - } -} - -void Project::start_preview_generator(Media* item, bool replacing) { - // set up throbber animation - MediaThrobber* throbber = new MediaThrobber(item); - throbber->moveToThread(QApplication::instance()->thread()); - item->throbber = throbber; - QMetaObject::invokeMethod(throbber, "start", Qt::QueuedConnection); - - PreviewGenerator* pg = new PreviewGenerator(item, item->to_footage(), replacing); - item->to_footage()->preview_gen = pg; - connect(pg, SIGNAL(set_icon(int, bool)), throbber, SLOT(stop(int, bool))); - pg->start(QThread::LowPriority); -} - -void Project::process_file_list(QStringList& files, bool recursive, Media* replace, Media* parent) { - bool imported = false; - - QVector image_sequence_urls; - QVector image_sequence_importassequence; - QStringList image_sequence_formats = config.img_seq_formats.split("|"); - - if (!recursive) last_imported_media.clear(); - - bool create_undo_action = (!recursive && replace == NULL); - ComboAction* ca; - if (create_undo_action) ca = new ComboAction(); - - for (int i=0;iappend(new AddMediaCommand(folder, parent)); - } else { - project_model.appendChild(parent, folder); - } - - imported = true; - } else if (!files.at(i).isEmpty()) { - QString file(files.at(i)); - bool skip = false; - - /* Heuristic to determine whether file is part of an image sequence */ - - // check file extension (assume it's not a - - int lastcharindex = file.lastIndexOf("."); - bool found = true; - if (lastcharindex != -1 && lastcharindex > file.lastIndexOf('/')) { - // image_sequence_formats - found = false; - QString ext = file.mid(lastcharindex+1); - for (int j=0;jto_footage(); - m->reset(); - } else { - item = new Media(parent); - m = new Footage(); - } - - m->using_inout = false; - m->url = file; - m->name = get_file_name_from_path(files.at(i)); - - item->set_footage(m); - - // generate waveform/thumbnail in another thread - start_preview_generator(item, replace != NULL); - - last_imported_media.append(item); - - if (replace == NULL) { - if (create_undo_action) { - ca->append(new AddMediaCommand(item, parent)); - } else { - project_model.appendChild(parent, item); - } - } - - imported = true; - } - } - } - if (create_undo_action) { - if (imported) { - undo_stack.push(ca); - } else { - delete ca; - } - } -} - -Media* Project::get_selected_folder() { - // if one item is selected and it's a folder, return it - QModelIndexList selected_items = tree_view->selectionModel()->selectedRows(); - if (selected_items.size() == 1) { - Media* m = item_to_media(selected_items.at(0)); - if (m->get_type() == MEDIA_TYPE_FOLDER) return m; - } - return NULL; -} - -bool Project::reveal_media(Media *media, QModelIndex parent) { - for (int i=0;iget_type() == MEDIA_TYPE_FOLDER) { - if (reveal_media(media, item)) return true; - } else if (m == media) { - // expand all folders leading to this media - QModelIndex sorted_index = sorter->mapFromSource(item); - - QModelIndex hierarchy = sorted_index.parent(); - while (hierarchy.isValid()) { - tree_view->setExpanded(hierarchy, true); - hierarchy = hierarchy.parent(); - } - - // select item - tree_view->selectionModel()->select(sorted_index, QItemSelectionModel::Select); - - return true; - } - } - - return false; -} - -void Project::import_dialog() { - QFileDialog fd(this, "Import media...", "", "All Files (*)"); - fd.setFileMode(QFileDialog::ExistingFiles); - - if (fd.exec()) { - QStringList files = fd.selectedFiles(); - process_file_list(files, false, NULL, get_selected_folder()); - } -} - -void Project::delete_clips_using_selected_media() { - if (sequence == NULL) { - QMessageBox::critical(this, "No active sequence", "No sequence is active, please open the sequence you want to delete clips from.", QMessageBox::Ok); - } else { - ComboAction* ca = new ComboAction(); - bool deleted = false; - QModelIndexList items = tree_view->selectionModel()->selectedRows(); - for (int i=0;iclips.size();i++) { - Clip* c = sequence->clips.at(i); - if (c != NULL) { - for (int j=0;jmedia == m) { - ca->append(new DeleteClipAction(sequence, i)); - deleted = true; - } - } - } - } - for (int j=0;jclear_effects(true); - - // delete sequences first because it's important to close all the clips before deleting the media - QVector sequences = list_all_project_sequences(); - for (int i=0;ito_sequence(); - sequences.at(i)->set_sequence(NULL); - } - - // delete everything else - project_model.clear(); -} - -void Project::new_project() { - // clear existing project - set_sequence(NULL); - panel_footage_viewer->set_media(NULL); - clear(); - mainWindow->setWindowModified(false); -} - -void Project::load_project(bool autorecovery) { - new_project(); - - LoadDialog ld(this, autorecovery); - ld.exec(); -} - -void Project::save_folder(QXmlStreamWriter& stream, int type, bool set_ids_only, const QModelIndex& parent) { - bool root = (!parent.parent().isValid()); - for (int i=0;iget_type()) { - if (m->get_type() == MEDIA_TYPE_FOLDER) { - if (set_ids_only) { - m->temp_id = folder_id; // saves a temporary ID for matching in the project file - folder_id++; - } else { - // if we're saving folders, save the folder - stream.writeStartElement("folder"); - stream.writeAttribute("name", m->get_name()); - stream.writeAttribute("id", QString::number(m->temp_id)); - if (!item.parent().isValid()) { - stream.writeAttribute("parent", "0"); - } else { - stream.writeAttribute("parent", QString::number(project_model.getItem(item.parent())->temp_id)); - } - stream.writeEndElement(); - } - // save_folder(stream, item, type, set_ids_only); - } else { - int folder = root ? 0 : project_model.getItem(parent)->temp_id; - if (type == MEDIA_TYPE_FOOTAGE) { - Footage* f = m->to_footage(); - f->save_id = media_id; - stream.writeStartElement("footage"); - stream.writeAttribute("id", QString::number(media_id)); - stream.writeAttribute("folder", QString::number(folder)); - stream.writeAttribute("name", f->name); - stream.writeAttribute("url", proj_dir.relativeFilePath(f->url)); - stream.writeAttribute("duration", QString::number(f->length)); - stream.writeAttribute("using_inout", QString::number(f->using_inout)); - stream.writeAttribute("in", QString::number(f->in)); - stream.writeAttribute("out", QString::number(f->out)); - for (int j=0;jvideo_tracks.size();j++) { - FootageStream* ms = f->video_tracks.at(j); - stream.writeStartElement("video"); - stream.writeAttribute("id", QString::number(ms->file_index)); - stream.writeAttribute("width", QString::number(ms->video_width)); - stream.writeAttribute("height", QString::number(ms->video_height)); - stream.writeAttribute("framerate", QString::number(ms->video_frame_rate, 'f', 10)); - stream.writeAttribute("infinite", QString::number(ms->infinite_length)); - stream.writeEndElement(); - } - for (int j=0;jaudio_tracks.size();j++) { - FootageStream* ms = f->audio_tracks.at(j); - stream.writeStartElement("audio"); - stream.writeAttribute("id", QString::number(ms->file_index)); - stream.writeAttribute("channels", QString::number(ms->audio_channels)); - stream.writeAttribute("layout", QString::number(ms->audio_layout)); - stream.writeAttribute("frequency", QString::number(ms->audio_frequency)); - stream.writeEndElement(); - } - stream.writeEndElement(); - media_id++; - } else if (type == MEDIA_TYPE_SEQUENCE) { - Sequence* s = m->to_sequence(); - if (set_ids_only) { - s->save_id = sequence_id; - sequence_id++; - } else { - stream.writeStartElement("sequence"); - stream.writeAttribute("id", QString::number(s->save_id)); - stream.writeAttribute("folder", QString::number(folder)); - stream.writeAttribute("name", s->name); - stream.writeAttribute("width", QString::number(s->width)); - stream.writeAttribute("height", QString::number(s->height)); - stream.writeAttribute("framerate", QString::number(s->frame_rate, 'f', 10)); - stream.writeAttribute("afreq", QString::number(s->audio_frequency)); - stream.writeAttribute("alayout", QString::number(s->audio_layout)); - if (s == sequence) { - stream.writeAttribute("open", "1"); - } - stream.writeAttribute("workarea", QString::number(s->using_workarea)); - stream.writeAttribute("workareaIn", QString::number(s->workarea_in)); - stream.writeAttribute("workareaOut", QString::number(s->workarea_out)); - - for (int j=0;jtransitions.size();j++) { - Transition* t = s->transitions.at(j); - if (t != NULL) { - stream.writeStartElement("transition"); - stream.writeAttribute("id", QString::number(j)); - stream.writeAttribute("length", QString::number(t->get_true_length())); - t->save(stream); - stream.writeEndElement(); // transition - } - } - - for (int j=0;jclips.size();j++) { - Clip* c = s->clips.at(j); - if (c != NULL) { - stream.writeStartElement("clip"); // clip - stream.writeAttribute("id", QString::number(j)); - stream.writeAttribute("enabled", QString::number(c->enabled)); - stream.writeAttribute("name", c->name); - stream.writeAttribute("clipin", QString::number(c->clip_in)); - stream.writeAttribute("in", QString::number(c->timeline_in)); - stream.writeAttribute("out", QString::number(c->timeline_out)); - stream.writeAttribute("track", QString::number(c->track)); - stream.writeAttribute("opening", QString::number(c->opening_transition)); - stream.writeAttribute("closing", QString::number(c->closing_transition)); - - stream.writeAttribute("r", QString::number(c->color_r)); - stream.writeAttribute("g", QString::number(c->color_g)); - stream.writeAttribute("b", QString::number(c->color_b)); - - stream.writeAttribute("autoscale", QString::number(c->autoscale)); - stream.writeAttribute("speed", QString::number(c->speed, 'f', 10)); - stream.writeAttribute("maintainpitch", QString::number(c->maintain_audio_pitch)); - stream.writeAttribute("reverse", QString::number(c->reverse)); - - if (c->media != NULL) { - stream.writeAttribute("type", QString::number(c->media->get_type())); - switch (c->media->get_type()) { - case MEDIA_TYPE_FOOTAGE: - stream.writeAttribute("media", QString::number(c->media->to_footage()->save_id)); - stream.writeAttribute("stream", QString::number(c->media_stream)); - break; - case MEDIA_TYPE_SEQUENCE: - stream.writeAttribute("sequence", QString::number(c->media->to_sequence()->save_id)); - break; - } - } - - stream.writeStartElement("linked"); // linked - for (int k=0;klinked.size();k++) { - stream.writeStartElement("link"); // link - stream.writeAttribute("id", QString::number(c->linked.at(k))); - stream.writeEndElement(); // link - } - stream.writeEndElement(); // linked - - for (int k=0;keffects.size();k++) { - stream.writeStartElement("effect"); // effect - c->effects.at(k)->save(stream); - stream.writeEndElement(); // effect - } - - stream.writeEndElement(); // clip - } - } - for (int j=0;jmarkers.size();j++) { - stream.writeStartElement("marker"); - stream.writeAttribute("frame", QString::number(s->markers.at(j).frame)); - stream.writeAttribute("name", s->markers.at(j).name); - stream.writeEndElement(); - } - stream.writeEndElement(); - } - } - } - } - - if (m->get_type() == MEDIA_TYPE_FOLDER) { - save_folder(stream, type, set_ids_only, item); - } - } -} - -void Project::save_project(bool autorecovery) { - folder_id = 1; - media_id = 1; - sequence_id = 1; - - QFile file(autorecovery ? autorecovery_filename : project_url); - if (!file.open(QIODevice::WriteOnly/* | QIODevice::Text*/)) { - dout << "[ERROR] Could not open file"; - return; - } - - QXmlStreamWriter stream(&file); - stream.setAutoFormatting(true); - stream.writeStartDocument(); // doc - - stream.writeStartElement("project"); // project - - stream.writeTextElement("version", QString::number(SAVE_VERSION)); - - stream.writeTextElement("url", project_url); - proj_dir = QFileInfo(project_url).absoluteDir(); - - save_folder(stream, MEDIA_TYPE_FOLDER, true); - - stream.writeStartElement("folders"); // folders - save_folder(stream, MEDIA_TYPE_FOLDER, false); - stream.writeEndElement(); // folders - - stream.writeStartElement("media"); // media - save_folder(stream, MEDIA_TYPE_FOOTAGE, false); - stream.writeEndElement(); // media - - save_folder(stream, MEDIA_TYPE_SEQUENCE, true); - - stream.writeStartElement("sequences"); // sequences - save_folder(stream, MEDIA_TYPE_SEQUENCE, false); - stream.writeEndElement();// sequences - - stream.writeEndElement(); // project - - stream.writeEndDocument(); // doc - - file.close(); - - if (!autorecovery) { - add_recent_project(project_url); - mainWindow->setWindowModified(false); - } -} - -void Project::update_view_type() { - tree_view->setVisible(config.project_view_type == PROJECT_VIEW_TREE); - icon_view_container->setVisible(config.project_view_type == PROJECT_VIEW_ICON); - - switch (config.project_view_type) { - case PROJECT_VIEW_TREE: - sources_common->view = tree_view; - break; - case PROJECT_VIEW_ICON: - sources_common->view = icon_view; - break; - } -} - -void Project::set_icon_view() { - config.project_view_type = PROJECT_VIEW_ICON; - update_view_type(); -} - -void Project::set_tree_view() { - config.project_view_type = PROJECT_VIEW_TREE; - update_view_type(); -} - -void Project::save_recent_projects() { - // save to file - QFile f(recent_proj_file); - if (f.open(QFile::WriteOnly | QFile::Truncate | QFile::Text)) { - QTextStream out(&f); - for (int i=0;i 0) { - out << "\n"; - } - out << recent_projects.at(i); - } - f.close(); - } else { - dout << "[WARNING] Could not save recent projects"; - } -} - -void Project::clear_recent_projects() { - recent_projects.clear(); - save_recent_projects(); -} - -void Project::set_icon_view_size(int s) { - icon_view->setIconSize(QSize(s, s)); -} - -void Project::set_up_dir_enabled() { - directory_up->setEnabled(icon_view->rootIndex().isValid()); -} - -void Project::go_up_dir() { - icon_view->setRootIndex(icon_view->rootIndex().parent()); - set_up_dir_enabled(); -} - -void Project::add_recent_project(QString url) { - bool found = false; - for (int i=0;i MAXIMUM_RECENT_PROJECTS) { - recent_projects.removeLast(); - } - } - save_recent_projects(); -} - -void Project::list_all_sequences_worker(QVector* list, Media* parent) { - for (int i=0;iget_type()) { - case MEDIA_TYPE_SEQUENCE: - list->append(item); - break; - case MEDIA_TYPE_FOLDER: - list_all_sequences_worker(list, item); - break; - } - } -} - -QVector Project::list_all_project_sequences() { - QVector list; - list_all_sequences_worker(&list, NULL); - return list; -} - -#define THROBBER_LIMIT 20 -#define THROBBER_SIZE 50 - -MediaThrobber::MediaThrobber(Media *i) : pixmap(":/icons/throbber.png"), animation(0), item(i), animator(NULL) {} - -void MediaThrobber::start() { - // set up throbber - animation_update(); - animator = new QTimer(this); - animator->setInterval(20); - connect(animator, SIGNAL(timeout()), this, SLOT(animation_update())); - animator->start(); -} - -void MediaThrobber::animation_update() { - if (animation == THROBBER_LIMIT) { - animation = 0; - } - project_model.set_icon(item, QIcon(pixmap.copy(THROBBER_SIZE*animation, 0, THROBBER_SIZE, THROBBER_SIZE))); - animation++; -} - -void MediaThrobber::stop(int icon_type, bool replace) { - if (animator != NULL) { - animator->stop(); - delete animator; - } - - switch (icon_type) { - case ICON_TYPE_VIDEO: project_model.set_icon(item, QIcon(":/icons/videosource.png")); break; - case ICON_TYPE_AUDIO: project_model.set_icon(item, QIcon(":/icons/audiosource.png")); break; - case ICON_TYPE_IMAGE: project_model.set_icon(item, QIcon(":/icons/imagesource.png")); break; - case ICON_TYPE_ERROR: project_model.set_icon(item, QIcon(":/icons/error.png")); break; - } - - // refresh all clips - QVector sequences = panel_project->list_all_project_sequences(); - for (int i=0;ito_sequence(); - for (int j=0;jclips.size();j++) { - Clip* c = s->clips.at(j); - if (c != NULL) { - c->refresh(); - } - } - } - - // redraw clips - update_ui(replace); - - panel_project->tree_view->viewport()->update(); - item->throbber = NULL; - deleteLater(); -} +#include "project.h" +#include "project/footage.h" + +#include "panels/panels.h" +#include "panels/timeline.h" +#include "panels/viewer.h" +#include "playback/playback.h" +#include "project/effect.h" +#include "project/transition.h" +#include "panels/timeline.h" +#include "project/sequence.h" +#include "project/clip.h" +#include "io/previewgenerator.h" +#include "project/undo.h" +#include "mainwindow.h" +#include "io/config.h" +#include "playback/cacher.h" +#include "dialogs/replaceclipmediadialog.h" +#include "panels/effectcontrols.h" +#include "dialogs/newsequencedialog.h" +#include "dialogs/mediapropertiesdialog.h" +#include "dialogs/loaddialog.h" +#include "io/clipboard.h" +#include "project/media.h" +#include "ui/sourcetable.h" +#include "ui/sourceiconview.h" +#include "project/sourcescommon.h" +#include "debug.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" { + #include + #include +} + +#define MAXIMUM_RECENT_PROJECTS 10 + +ProjectModel project_model; + +QString autorecovery_filename; +QString project_url = ""; +QStringList recent_projects; +QString recent_proj_file; + +Project::Project(QWidget *parent) : + QDockWidget(parent) +{ + setObjectName("Project"); + resize(504, 371); + QSizePolicy sizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); + sizePolicy.setHorizontalStretch(0); + sizePolicy.setVerticalStretch(0); + sizePolicy.setHeightForWidth(this->sizePolicy().hasHeightForWidth()); + setSizePolicy(sizePolicy); + + QWidget* dockWidgetContents = new QWidget(); + QVBoxLayout* verticalLayout = new QVBoxLayout(dockWidgetContents); + verticalLayout->setContentsMargins(0, 0, 0, 0); + verticalLayout->setSpacing(0); + + setWidget(dockWidgetContents); + + sources_common = new SourcesCommon(this); + + sorter = new QSortFilterProxyModel(this); + sorter->setSourceModel(&project_model); + + tree_view = new SourceTable(dockWidgetContents); + tree_view->project_parent = this; + tree_view->setAcceptDrops(true); + tree_view->setEditTriggers(QAbstractItemView::NoEditTriggers); + tree_view->setDragDropMode(QAbstractItemView::DragDrop); + tree_view->setSelectionMode(QAbstractItemView::ExtendedSelection); + tree_view->setModel(sorter); + verticalLayout->addWidget(tree_view); + + icon_view_container = new QWidget(); + + QVBoxLayout* icon_view_container_layout = new QVBoxLayout(); + icon_view_container_layout->setMargin(0); + icon_view_container_layout->setSpacing(0); + icon_view_container->setLayout(icon_view_container_layout); + + QHBoxLayout* icon_view_controls = new QHBoxLayout(); + icon_view_controls->setMargin(0); + icon_view_controls->setSpacing(0); + + QIcon directory_up_button; + directory_up_button.addFile(":/icons/dirup.png", QSize(), QIcon::Normal); + directory_up_button.addFile(":/icons/dirup-disabled.png", QSize(), QIcon::Disabled); + + directory_up = new QPushButton(); + directory_up->setIcon(directory_up_button); + directory_up->setEnabled(false); + icon_view_controls->addWidget(directory_up); + + icon_view_controls->addStretch(); + + QSlider* icon_size_slider = new QSlider(Qt::Horizontal); + icon_size_slider->setMinimum(16); + icon_size_slider->setMaximum(120); + icon_view_controls->addWidget(icon_size_slider); + connect(icon_size_slider, SIGNAL(valueChanged(int)), this, SLOT(set_icon_view_size(int))); + + icon_view_container_layout->addLayout(icon_view_controls); + + icon_view = new SourceIconView(dockWidgetContents); + icon_view->project_parent = this; + icon_view->setModel(sorter); + icon_view->setIconSize(QSize(100, 100)); + icon_view->setViewMode(QListView::IconMode); + icon_view->setUniformItemSizes(true); + icon_view_container_layout->addWidget(icon_view); + + icon_size_slider->setValue(icon_view->iconSize().height()); + + verticalLayout->addWidget(icon_view_container); + + connect(directory_up, SIGNAL(clicked(bool)), this, SLOT(go_up_dir())); + connect(icon_view, SIGNAL(changed_root()), this, SLOT(set_up_dir_enabled())); + + //retranslateUi(Project); + setWindowTitle(QApplication::translate("Project", "Project", nullptr)); + + update_view_type(); +} + +Project::~Project() { + delete sorter; +} + +QString Project::get_next_sequence_name(QString start) { + if (start.isEmpty()) start = "Sequence"; + + int n = 1; + bool found = true; + QString name; + while (found) { + found = false; + name = start + " "; + if (n < 10) { + name += "0"; + } + name += QString::number(n); + for (int i=0;iget_name(), name, Qt::CaseInsensitive) == 0) { + found = true; + n++; + break; + } + } + } + return name; +} + +Sequence* create_sequence_from_media(QVector& media_list) { + Sequence* s = new Sequence(); + + s->name = panel_project->get_next_sequence_name(); + + // shitty hardcoded default values + s->width = 1920; + s->height = 1080; + s->frame_rate = 29.97; + s->audio_frequency = 48000; + s->audio_layout = 3; + + bool got_video_values = false; + bool got_audio_values = false; + for (int i=0;iget_type()) { + case MEDIA_TYPE_FOOTAGE: + { + Footage* m = media->to_footage(); + if (m->ready) { + if (!got_video_values) { + for (int j=0;jvideo_tracks.size();j++) { + FootageStream* ms = m->video_tracks.at(j); + s->width = ms->video_width; + s->height = ms->video_height; + if (ms->video_frame_rate != 0) { + s->frame_rate = ms->video_frame_rate; + + if (ms->video_interlacing != VIDEO_PROGRESSIVE) s->frame_rate *= 2; + + // only break with a decent frame rate, otherwise there may be a better candidate + got_video_values = true; + break; + } + } + } + if (!got_audio_values) { + for (int j=0;jaudio_tracks.size();j++) { + FootageStream* ms = m->audio_tracks.at(j); + s->audio_frequency = ms->audio_frequency; + got_audio_values = true; + break; + } + } + } + } + break; + case MEDIA_TYPE_SEQUENCE: + { + Sequence* seq = media->to_sequence(); + s->width = seq->width; + s->height = seq->height; + s->frame_rate = seq->frame_rate; + s->audio_frequency = seq->audio_frequency; + s->audio_layout = seq->audio_layout; + + got_video_values = true; + got_audio_values = true; + } + break; + } + if (got_video_values && got_audio_values) break; + } + + return s; +} + +void Project::duplicate_selected() { + QModelIndexList items = tree_view->selectionModel()->selectedRows(); + bool duped = false; + ComboAction* ca = new ComboAction(); + for (int j=0;jget_type() == MEDIA_TYPE_SEQUENCE) { + new_sequence(ca, i->to_sequence()->copy(), false, item_to_media(items.at(j).parent())); + duped = true; + } + } + if (duped) { + undo_stack.push(ca); + } else { + delete ca; + } +} + +void Project::replace_selected_file() { + QModelIndexList selected_items = tree_view->selectionModel()->selectedRows(); + if (selected_items.size() == 1) { + Media* item = item_to_media(selected_items.at(0)); + if (item->get_type() == MEDIA_TYPE_FOOTAGE) { + replace_media(item, 0); + } + } +} + +void Project::replace_media(Media* item, QString filename) { + if (filename.isEmpty()) { + filename = QFileDialog::getOpenFileName(this, "Replace '" + item->get_name() + "'", "", "All Files (*)"); + } + if (!filename.isEmpty()) { + ReplaceMediaCommand* rmc = new ReplaceMediaCommand(item, filename); + undo_stack.push(rmc); + } +} + +void Project::replace_clip_media() { + if (sequence == NULL) { + QMessageBox::critical(this, "No active sequence", "No sequence is active, please open the sequence you want to replace clips from.", QMessageBox::Ok); + } else { + QModelIndexList selected_items = tree_view->selectionModel()->selectedRows(); + if (selected_items.size() == 1) { + Media* item = item_to_media(selected_items.at(0)); + if (item->get_type() == MEDIA_TYPE_SEQUENCE && sequence == item->to_sequence()) { + QMessageBox::critical(this, "Active sequence selected", "You cannot insert a sequence into itself, so no clips of this media would be in this sequence.", QMessageBox::Ok); + } else { + ReplaceClipMediaDialog dialog(this, tree_view, item); + dialog.exec(); + } + } + } +} + +void Project::open_properties() { + QModelIndexList selected_items = tree_view->selectionModel()->selectedRows(); + if (selected_items.size() == 1) { + Media* item = item_to_media(selected_items.at(0)); + switch (item->get_type()) { + case MEDIA_TYPE_FOOTAGE: + { + MediaPropertiesDialog mpd(this, item); + mpd.exec(); + } + break; + case MEDIA_TYPE_SEQUENCE: + { + NewSequenceDialog nsd(this); + Sequence* s = item->to_sequence(); + nsd.existing_sequence = s; + nsd.existing_item = item; + nsd.exec(); + } + break; + default: + { + // fall back to renaming + QString new_name = QInputDialog::getText(this, "Rename '" + item->get_name() + "'", "Enter new name:", QLineEdit::Normal, item->get_name()); + if (!new_name.isEmpty()) { + MediaRename* mr = new MediaRename(item, new_name); + undo_stack.push(mr); + } + } + } + } +} + +Media* Project::new_sequence(ComboAction *ca, Sequence *s, bool open, Media* parent) { + if (parent == NULL) parent = project_model.get_root(); + Media* item = new Media(parent); + item->set_sequence(s); + + if (ca != NULL) { + ca->append(new NewSequenceCommand(item, parent)); + if (open) ca->append(new ChangeSequenceAction(s)); + } else { + project_model.appendChild(NULL, item); + if (open) set_sequence(s); + } + return item; +} + +QString Project::get_file_name_from_path(const QString& path) { + return path.mid(path.lastIndexOf('/')+1); +} + +/*Media* Project::new_item() { + Media* item = new Media(0); + //item->setFlags(item->flags() | Qt::ItemIsEditable); + return item; +}*/ + +bool Project::is_focused() { + return tree_view->hasFocus(); +} + +Media* Project::new_folder(QString name) { + Media* item = new Media(0); + item->set_folder(); + return item; +} + +Media *Project::item_to_media(const QModelIndex &index) { + return static_cast(sorter->mapToSource(index).internalPointer()); +// return static_cast(index.internalPointer()); +} + +void Project::get_all_media_from_table(QList items, QList& list, int search_type) { + for (int i=0;iget_type() == MEDIA_TYPE_FOLDER) { + QList children; + for (int j=0;jchildCount();j++) { + children.append(item->child(j)); + } + get_all_media_from_table(children, list, search_type); + } else if (search_type == item->get_type() || search_type == -1) { + list.append(item); + } + } +} + +bool delete_clips_in_clipboard_with_media(ComboAction* ca, Media* m) { + int delete_count = 0; + if (clipboard_type == CLIPBOARD_TYPE_CLIP) { + for (int i=0;i(clipboard.at(i)); + if (c->media == m) { + ca->append(new RemoveClipsFromClipboard(i-delete_count)); + delete_count++; + } + } + } + return (delete_count > 0); +} + +void Project::delete_selected_media() { + ComboAction* ca = new ComboAction(); + QModelIndexList selected_items = tree_view->selectionModel()->selectedRows(); + QList items; + for (int i=0;isetSortingEnabled(false); + tree_view->setSortingEnabled(true); + + // check if media is in use + QVector parents; + QList sequence_items; + QList all_top_level_items; + for (int i=0;i 0) { + QList media_items; + get_all_media_from_table(items, media_items, MEDIA_TYPE_FOOTAGE); + for (int i=0;ito_footage(); + bool confirm_delete = false; + for (int j=0;jto_sequence(); + for (int k=0;kclips.size();k++) { + Clip* c = s->clips.at(k); + if (c != NULL && c->media == item) { + if (!confirm_delete) { + // we found a reference, so we know we'll need to ask if the user wants to delete it + QMessageBox confirm(this); + confirm.setWindowTitle("Delete media in use?"); + confirm.setText("The media '" + media->name + "' is currently used in '" + s->name + "'. Deleting it will remove all instances in the sequence. Are you sure you want to do this?"); + QAbstractButton* yes_button = confirm.addButton(QMessageBox::Yes); + QAbstractButton* skip_button = NULL; + if (items.size() > 1) skip_button = confirm.addButton("Skip", QMessageBox::NoRole); + QAbstractButton* abort_button = confirm.addButton(QMessageBox::Cancel); + confirm.exec(); + if (confirm.clickedButton() == yes_button) { + // remove all clips referencing this media + confirm_delete = true; + redraw = true; + } else if (confirm.clickedButton() == skip_button) { + // remove media item and any folders containing it from the remove list + Media* parent = item; + while (parent != NULL) { + parents.append(parent); + + // re-add item's siblings + for (int m=0;mchildCount();m++) { + Media* child = parent->child(m); + bool found = false; + for (int n=0;nparentItem(); + } + + j = sequence_items.size(); + k = s->clips.size(); + } else if (confirm.clickedButton() == abort_button) { + // break out of loop + i = media_items.size(); + j = sequence_items.size(); + k = s->clips.size(); + + remove = false; + } + } + if (confirm_delete) { + ca->append(new DeleteClipAction(s, k)); + } + } + } + } + if (confirm_delete) { + delete_clips_in_clipboard_with_media(ca, item); + } + + } + } + + // remove + if (remove) { + // remove media and parents + for (int m=0;mappend(new DeleteMediaCommand(items.at(i))); + + if (items.at(i)->get_type() == MEDIA_TYPE_SEQUENCE) { + redraw = true; + + Sequence* s = items.at(i)->to_sequence(); + + if (s == sequence) { + ca->append(new ChangeSequenceAction(NULL)); + } + + if (s == panel_footage_viewer->seq) { + panel_footage_viewer->set_media(NULL); + } + } else if (items.at(i)->get_type() == MEDIA_TYPE_FOOTAGE) { + if (panel_footage_viewer->seq != NULL) { + for (int j=0;jseq->clips.size();j++) { + Clip* c = panel_footage_viewer->seq->clips.at(j); + if (c != NULL) { + if (c->media == items.at(i)->to_object()) { + panel_footage_viewer->set_media(NULL); + } + break; + } + } + } + } + } + undo_stack.push(ca); + + // redraw clips + if (redraw) { + update_ui(true); + } + } else { + delete ca; + } +} + +void Project::start_preview_generator(Media* item, bool replacing) { + // set up throbber animation + MediaThrobber* throbber = new MediaThrobber(item); + throbber->moveToThread(QApplication::instance()->thread()); + item->throbber = throbber; + QMetaObject::invokeMethod(throbber, "start", Qt::QueuedConnection); + + PreviewGenerator* pg = new PreviewGenerator(item, item->to_footage(), replacing); + item->to_footage()->preview_gen = pg; + connect(pg, SIGNAL(set_icon(int, bool)), throbber, SLOT(stop(int, bool))); + pg->start(QThread::LowPriority); +} + +void Project::process_file_list(QStringList& files, bool recursive, Media* replace, Media* parent) { + bool imported = false; + + QVector image_sequence_urls; + QVector image_sequence_importassequence; + QStringList image_sequence_formats = config.img_seq_formats.split("|"); + + if (!recursive) last_imported_media.clear(); + + bool create_undo_action = (!recursive && replace == NULL); + ComboAction* ca; + if (create_undo_action) ca = new ComboAction(); + + for (int i=0;iappend(new AddMediaCommand(folder, parent)); + } else { + project_model.appendChild(parent, folder); + } + + imported = true; + } else if (!files.at(i).isEmpty()) { + QString file(files.at(i)); + bool skip = false; + + /* Heuristic to determine whether file is part of an image sequence */ + + // check file extension (assume it's not a + + int lastcharindex = file.lastIndexOf("."); + bool found = true; + if (lastcharindex != -1 && lastcharindex > file.lastIndexOf('/')) { + // image_sequence_formats + found = false; + QString ext = file.mid(lastcharindex+1); + for (int j=0;jto_footage(); + m->reset(); + } else { + item = new Media(parent); + m = new Footage(); + } + + m->using_inout = false; + m->url = file; + m->name = get_file_name_from_path(files.at(i)); + + item->set_footage(m); + + // generate waveform/thumbnail in another thread + start_preview_generator(item, replace != NULL); + + last_imported_media.append(item); + + if (replace == NULL) { + if (create_undo_action) { + ca->append(new AddMediaCommand(item, parent)); + } else { + project_model.appendChild(parent, item); + } + } + + imported = true; + } + } + } + if (create_undo_action) { + if (imported) { + undo_stack.push(ca); + } else { + delete ca; + } + } +} + +Media* Project::get_selected_folder() { + // if one item is selected and it's a folder, return it + QModelIndexList selected_items = tree_view->selectionModel()->selectedRows(); + if (selected_items.size() == 1) { + Media* m = item_to_media(selected_items.at(0)); + if (m->get_type() == MEDIA_TYPE_FOLDER) return m; + } + return NULL; +} + +bool Project::reveal_media(Media *media, QModelIndex parent) { + for (int i=0;iget_type() == MEDIA_TYPE_FOLDER) { + if (reveal_media(media, item)) return true; + } else if (m == media) { + // expand all folders leading to this media + QModelIndex sorted_index = sorter->mapFromSource(item); + + QModelIndex hierarchy = sorted_index.parent(); + while (hierarchy.isValid()) { + tree_view->setExpanded(hierarchy, true); + hierarchy = hierarchy.parent(); + } + + // select item + tree_view->selectionModel()->select(sorted_index, QItemSelectionModel::Select); + + return true; + } + } + + return false; +} + +void Project::import_dialog() { + QFileDialog fd(this, "Import media...", "", "All Files (*)"); + fd.setFileMode(QFileDialog::ExistingFiles); + + if (fd.exec()) { + QStringList files = fd.selectedFiles(); + process_file_list(files, false, NULL, get_selected_folder()); + } +} + +void Project::delete_clips_using_selected_media() { + if (sequence == NULL) { + QMessageBox::critical(this, "No active sequence", "No sequence is active, please open the sequence you want to delete clips from.", QMessageBox::Ok); + } else { + ComboAction* ca = new ComboAction(); + bool deleted = false; + QModelIndexList items = tree_view->selectionModel()->selectedRows(); + for (int i=0;iclips.size();i++) { + Clip* c = sequence->clips.at(i); + if (c != NULL) { + for (int j=0;jmedia == m) { + ca->append(new DeleteClipAction(sequence, i)); + deleted = true; + } + } + } + } + for (int j=0;jclear_effects(true); + + // delete sequences first because it's important to close all the clips before deleting the media + QVector sequences = list_all_project_sequences(); + for (int i=0;ito_sequence(); + sequences.at(i)->set_sequence(NULL); + } + + // delete everything else + project_model.clear(); +} + +void Project::new_project() { + // clear existing project + set_sequence(NULL); + panel_footage_viewer->set_media(NULL); + clear(); + mainWindow->setWindowModified(false); +} + +void Project::load_project(bool autorecovery) { + new_project(); + + LoadDialog ld(this, autorecovery); + ld.exec(); +} + +void Project::save_folder(QXmlStreamWriter& stream, int type, bool set_ids_only, const QModelIndex& parent) { + bool root = (!parent.parent().isValid()); + for (int i=0;iget_type()) { + if (m->get_type() == MEDIA_TYPE_FOLDER) { + if (set_ids_only) { + m->temp_id = folder_id; // saves a temporary ID for matching in the project file + folder_id++; + } else { + // if we're saving folders, save the folder + stream.writeStartElement("folder"); + stream.writeAttribute("name", m->get_name()); + stream.writeAttribute("id", QString::number(m->temp_id)); + if (!item.parent().isValid()) { + stream.writeAttribute("parent", "0"); + } else { + stream.writeAttribute("parent", QString::number(project_model.getItem(item.parent())->temp_id)); + } + stream.writeEndElement(); + } + // save_folder(stream, item, type, set_ids_only); + } else { + int folder = root ? 0 : project_model.getItem(parent)->temp_id; + if (type == MEDIA_TYPE_FOOTAGE) { + Footage* f = m->to_footage(); + f->save_id = media_id; + stream.writeStartElement("footage"); + stream.writeAttribute("id", QString::number(media_id)); + stream.writeAttribute("folder", QString::number(folder)); + stream.writeAttribute("name", f->name); + stream.writeAttribute("url", proj_dir.relativeFilePath(f->url)); + stream.writeAttribute("duration", QString::number(f->length)); + stream.writeAttribute("using_inout", QString::number(f->using_inout)); + stream.writeAttribute("in", QString::number(f->in)); + stream.writeAttribute("out", QString::number(f->out)); + for (int j=0;jvideo_tracks.size();j++) { + FootageStream* ms = f->video_tracks.at(j); + stream.writeStartElement("video"); + stream.writeAttribute("id", QString::number(ms->file_index)); + stream.writeAttribute("width", QString::number(ms->video_width)); + stream.writeAttribute("height", QString::number(ms->video_height)); + stream.writeAttribute("framerate", QString::number(ms->video_frame_rate, 'f', 10)); + stream.writeAttribute("infinite", QString::number(ms->infinite_length)); + stream.writeEndElement(); + } + for (int j=0;jaudio_tracks.size();j++) { + FootageStream* ms = f->audio_tracks.at(j); + stream.writeStartElement("audio"); + stream.writeAttribute("id", QString::number(ms->file_index)); + stream.writeAttribute("channels", QString::number(ms->audio_channels)); + stream.writeAttribute("layout", QString::number(ms->audio_layout)); + stream.writeAttribute("frequency", QString::number(ms->audio_frequency)); + stream.writeEndElement(); + } + stream.writeEndElement(); + media_id++; + } else if (type == MEDIA_TYPE_SEQUENCE) { + Sequence* s = m->to_sequence(); + if (set_ids_only) { + s->save_id = sequence_id; + sequence_id++; + } else { + stream.writeStartElement("sequence"); + stream.writeAttribute("id", QString::number(s->save_id)); + stream.writeAttribute("folder", QString::number(folder)); + stream.writeAttribute("name", s->name); + stream.writeAttribute("width", QString::number(s->width)); + stream.writeAttribute("height", QString::number(s->height)); + stream.writeAttribute("framerate", QString::number(s->frame_rate, 'f', 10)); + stream.writeAttribute("afreq", QString::number(s->audio_frequency)); + stream.writeAttribute("alayout", QString::number(s->audio_layout)); + if (s == sequence) { + stream.writeAttribute("open", "1"); + } + stream.writeAttribute("workarea", QString::number(s->using_workarea)); + stream.writeAttribute("workareaIn", QString::number(s->workarea_in)); + stream.writeAttribute("workareaOut", QString::number(s->workarea_out)); + + for (int j=0;jtransitions.size();j++) { + Transition* t = s->transitions.at(j); + if (t != NULL) { + stream.writeStartElement("transition"); + stream.writeAttribute("id", QString::number(j)); + stream.writeAttribute("length", QString::number(t->get_true_length())); + t->save(stream); + stream.writeEndElement(); // transition + } + } + + for (int j=0;jclips.size();j++) { + Clip* c = s->clips.at(j); + if (c != NULL) { + stream.writeStartElement("clip"); // clip + stream.writeAttribute("id", QString::number(j)); + stream.writeAttribute("enabled", QString::number(c->enabled)); + stream.writeAttribute("name", c->name); + stream.writeAttribute("clipin", QString::number(c->clip_in)); + stream.writeAttribute("in", QString::number(c->timeline_in)); + stream.writeAttribute("out", QString::number(c->timeline_out)); + stream.writeAttribute("track", QString::number(c->track)); + stream.writeAttribute("opening", QString::number(c->opening_transition)); + stream.writeAttribute("closing", QString::number(c->closing_transition)); + + stream.writeAttribute("r", QString::number(c->color_r)); + stream.writeAttribute("g", QString::number(c->color_g)); + stream.writeAttribute("b", QString::number(c->color_b)); + + stream.writeAttribute("autoscale", QString::number(c->autoscale)); + stream.writeAttribute("speed", QString::number(c->speed, 'f', 10)); + stream.writeAttribute("maintainpitch", QString::number(c->maintain_audio_pitch)); + stream.writeAttribute("reverse", QString::number(c->reverse)); + + if (c->media != NULL) { + stream.writeAttribute("type", QString::number(c->media->get_type())); + switch (c->media->get_type()) { + case MEDIA_TYPE_FOOTAGE: + stream.writeAttribute("media", QString::number(c->media->to_footage()->save_id)); + stream.writeAttribute("stream", QString::number(c->media_stream)); + break; + case MEDIA_TYPE_SEQUENCE: + stream.writeAttribute("sequence", QString::number(c->media->to_sequence()->save_id)); + break; + } + } + + stream.writeStartElement("linked"); // linked + for (int k=0;klinked.size();k++) { + stream.writeStartElement("link"); // link + stream.writeAttribute("id", QString::number(c->linked.at(k))); + stream.writeEndElement(); // link + } + stream.writeEndElement(); // linked + + for (int k=0;keffects.size();k++) { + stream.writeStartElement("effect"); // effect + c->effects.at(k)->save(stream); + stream.writeEndElement(); // effect + } + + stream.writeEndElement(); // clip + } + } + for (int j=0;jmarkers.size();j++) { + stream.writeStartElement("marker"); + stream.writeAttribute("frame", QString::number(s->markers.at(j).frame)); + stream.writeAttribute("name", s->markers.at(j).name); + stream.writeEndElement(); + } + stream.writeEndElement(); + } + } + } + } + + if (m->get_type() == MEDIA_TYPE_FOLDER) { + save_folder(stream, type, set_ids_only, item); + } + } +} + +void Project::save_project(bool autorecovery) { + folder_id = 1; + media_id = 1; + sequence_id = 1; + + QFile file(autorecovery ? autorecovery_filename : project_url); + if (!file.open(QIODevice::WriteOnly/* | QIODevice::Text*/)) { + dout << "[ERROR] Could not open file"; + return; + } + + QXmlStreamWriter stream(&file); + stream.setAutoFormatting(true); + stream.writeStartDocument(); // doc + + stream.writeStartElement("project"); // project + + stream.writeTextElement("version", QString::number(SAVE_VERSION)); + + stream.writeTextElement("url", project_url); + proj_dir = QFileInfo(project_url).absoluteDir(); + + save_folder(stream, MEDIA_TYPE_FOLDER, true); + + stream.writeStartElement("folders"); // folders + save_folder(stream, MEDIA_TYPE_FOLDER, false); + stream.writeEndElement(); // folders + + stream.writeStartElement("media"); // media + save_folder(stream, MEDIA_TYPE_FOOTAGE, false); + stream.writeEndElement(); // media + + save_folder(stream, MEDIA_TYPE_SEQUENCE, true); + + stream.writeStartElement("sequences"); // sequences + save_folder(stream, MEDIA_TYPE_SEQUENCE, false); + stream.writeEndElement();// sequences + + stream.writeEndElement(); // project + + stream.writeEndDocument(); // doc + + file.close(); + + if (!autorecovery) { + add_recent_project(project_url); + mainWindow->setWindowModified(false); + } +} + +void Project::update_view_type() { + tree_view->setVisible(config.project_view_type == PROJECT_VIEW_TREE); + icon_view_container->setVisible(config.project_view_type == PROJECT_VIEW_ICON); + + switch (config.project_view_type) { + case PROJECT_VIEW_TREE: + sources_common->view = tree_view; + break; + case PROJECT_VIEW_ICON: + sources_common->view = icon_view; + break; + } +} + +void Project::set_icon_view() { + config.project_view_type = PROJECT_VIEW_ICON; + update_view_type(); +} + +void Project::set_tree_view() { + config.project_view_type = PROJECT_VIEW_TREE; + update_view_type(); +} + +void Project::save_recent_projects() { + // save to file + QFile f(recent_proj_file); + if (f.open(QFile::WriteOnly | QFile::Truncate | QFile::Text)) { + QTextStream out(&f); + for (int i=0;i 0) { + out << "\n"; + } + out << recent_projects.at(i); + } + f.close(); + } else { + dout << "[WARNING] Could not save recent projects"; + } +} + +void Project::clear_recent_projects() { + recent_projects.clear(); + save_recent_projects(); +} + +void Project::set_icon_view_size(int s) { + icon_view->setIconSize(QSize(s, s)); +} + +void Project::set_up_dir_enabled() { + directory_up->setEnabled(icon_view->rootIndex().isValid()); +} + +void Project::go_up_dir() { + icon_view->setRootIndex(icon_view->rootIndex().parent()); + set_up_dir_enabled(); +} + +void Project::add_recent_project(QString url) { + bool found = false; + for (int i=0;i MAXIMUM_RECENT_PROJECTS) { + recent_projects.removeLast(); + } + } + save_recent_projects(); +} + +void Project::list_all_sequences_worker(QVector* list, Media* parent) { + for (int i=0;iget_type()) { + case MEDIA_TYPE_SEQUENCE: + list->append(item); + break; + case MEDIA_TYPE_FOLDER: + list_all_sequences_worker(list, item); + break; + } + } +} + +QVector Project::list_all_project_sequences() { + QVector list; + list_all_sequences_worker(&list, NULL); + return list; +} + +#define THROBBER_LIMIT 20 +#define THROBBER_SIZE 50 + +MediaThrobber::MediaThrobber(Media *i) : pixmap(":/icons/throbber.png"), animation(0), item(i), animator(NULL) {} + +void MediaThrobber::start() { + // set up throbber + animation_update(); + animator = new QTimer(this); + animator->setInterval(20); + connect(animator, SIGNAL(timeout()), this, SLOT(animation_update())); + animator->start(); +} + +void MediaThrobber::animation_update() { + if (animation == THROBBER_LIMIT) { + animation = 0; + } + project_model.set_icon(item, QIcon(pixmap.copy(THROBBER_SIZE*animation, 0, THROBBER_SIZE, THROBBER_SIZE))); + animation++; +} + +void MediaThrobber::stop(int icon_type, bool replace) { + if (animator != NULL) { + animator->stop(); + delete animator; + } + + switch (icon_type) { + case ICON_TYPE_VIDEO: project_model.set_icon(item, QIcon(":/icons/videosource.png")); break; + case ICON_TYPE_AUDIO: project_model.set_icon(item, QIcon(":/icons/audiosource.png")); break; + case ICON_TYPE_IMAGE: project_model.set_icon(item, QIcon(":/icons/imagesource.png")); break; + case ICON_TYPE_ERROR: project_model.set_icon(item, QIcon(":/icons/error.png")); break; + } + + // refresh all clips + QVector sequences = panel_project->list_all_project_sequences(); + for (int i=0;ito_sequence(); + for (int j=0;jclips.size();j++) { + Clip* c = s->clips.at(j); + if (c != NULL) { + c->refresh(); + } + } + } + + // redraw clips + update_ui(replace); + + panel_project->tree_view->viewport()->update(); + item->throbber = NULL; + deleteLater(); +} diff --git a/panels/timeline.cpp b/panels/timeline.cpp index 0cb5ac181..b4c1abf0c 100644 --- a/panels/timeline.cpp +++ b/panels/timeline.cpp @@ -108,6 +108,7 @@ Timeline::Timeline(QWidget *parent) : connect(ui->horizontalScrollBar, SIGNAL(valueChanged(int)), this, SLOT(setScroll(int))); connect(ui->videoScrollbar, SIGNAL(valueChanged(int)), ui->video_area, SLOT(setScroll(int))); connect(ui->audioScrollbar, SIGNAL(valueChanged(int)), ui->audio_area, SLOT(setScroll(int))); + connect(ui->horizontalScrollBar, SIGNAL(resize_move(double)), this, SLOT(resize_move(double))); update_sequence(); } @@ -423,6 +424,7 @@ void Timeline::repaint_timeline() { if (sequence != NULL && !ui->horizontalScrollBar->isSliderDown() + && !ui->horizontalScrollBar->is_resizing() && panel_sequence_viewer->playing && !zoom_just_changed) { // auto scroll @@ -449,7 +451,7 @@ void Timeline::repaint_timeline() { if (sequence != NULL) { long sequenceEndFrame = sequence->getEndFrame(); - ui->headers->set_scrollbar_max(ui->horizontalScrollBar, sequenceEndFrame, (ui->editAreas->width()/2)); + set_sb_max(); if (last_frame != sequence->playhead) { ui->audio_monitor->update(); @@ -473,7 +475,11 @@ void Timeline::select_all() { } } repaint_timeline(); - } + } +} + +void Timeline::resizeEvent(QResizeEvent *event) { + if (sequence != NULL) set_sb_max(); } void Timeline::delete_in_out(bool ripple) { @@ -570,7 +576,8 @@ void Timeline::set_zoom_value(double v) { repaint_timeline(); // TODO find a way to gradually move towards target_scroll instead of just centering it? - center_scroll_to_playhead(ui->horizontalScrollBar, zoom, sequence->playhead); + if (!ui->horizontalScrollBar->is_resizing()) + center_scroll_to_playhead(ui->horizontalScrollBar, zoom, sequence->playhead); } void Timeline::set_zoom(bool in) { @@ -1400,7 +1407,7 @@ void Timeline::deselect() { } long getFrameFromScreenPoint(double zoom, int x) { - long f = qCeil((float) x / zoom); + long f = qCeil((float) x / zoom); if (f < 0) { return 0; } @@ -1408,7 +1415,7 @@ long getFrameFromScreenPoint(double zoom, int x) { } int getScreenPointFromFrame(double zoom, long frame) { - return (int) qFloor(frame*zoom); + return (int) qFloor(frame*zoom); } long Timeline::getTimelineFrameFromScreenPoint(int x) { @@ -1573,6 +1580,14 @@ void Timeline::transition_menu_select(QAction* a) { ui->toolTransitionButton->setChecked(true); } +void Timeline::resize_move(double z) { + set_zoom_value(zoom * z); +} + +void Timeline::set_sb_max() { + ui->headers->set_scrollbar_max(ui->horizontalScrollBar, sequence->getEndFrame(), ui->editAreas->width() - getScreenPointFromFrame(zoom, 200)); +} + void move_clip(ComboAction* ca, Clip *c, long iin, long iout, long iclip_in, int itrack, bool verify_transitions) { ca->append(new MoveClipAction(c, iin, iout, iclip_in, itrack)); diff --git a/panels/timeline.h b/panels/timeline.h index 6d4bb4a5a..a9633e723 100644 --- a/panels/timeline.h +++ b/panels/timeline.h @@ -186,6 +186,8 @@ public: int transition_tool_side; Ui::Timeline *ui; + + void resizeEvent(QResizeEvent *event); public slots: void repaint_timeline(); @@ -223,13 +225,16 @@ private slots: void transition_menu_select(QAction*); + void resize_move(double d); + private: void set_zoom_value(double v); QVector tool_buttons; void decheck_tool_buttons(QObject* sender); void set_tool(int tool); long last_frame; - int scroll; + int scroll; + void set_sb_max(); int default_track_height; }; diff --git a/panels/timeline.ui b/panels/timeline.ui index d5fb2e486..aadfdb639 100644 --- a/panels/timeline.ui +++ b/panels/timeline.ui @@ -456,7 +456,7 @@
- + 0 @@ -506,6 +506,11 @@
ui/audiomonitor.h
1 + + ResizableScrollBar + QScrollBar +
ui/resizablescrollbar.h
+
diff --git a/panels/viewer.cpp b/panels/viewer.cpp index e17bca6de..a5e0671e4 100644 --- a/panels/viewer.cpp +++ b/panels/viewer.cpp @@ -1,622 +1,653 @@ -#include "viewer.h" -#include "ui_viewer.h" - -#include "playback/audio.h" -#include "timeline.h" -#include "panels/project.h" -#include "project/sequence.h" -#include "project/clip.h" -#include "panels/panels.h" -#include "io/config.h" -#include "project/footage.h" -#include "project/media.h" -#include "project/undo.h" -#include "ui/audiomonitor.h" -#include "ui_timeline.h" -#include "playback/playback.h" -#include "ui/viewerwidget.h" -#include "debug.h" - -#define FRAMES_IN_ONE_MINUTE 1798 // 1800 - 2 -#define FRAMES_IN_TEN_MINUTES 17978 // (FRAMES_IN_ONE_MINUTE * 10) - 2 - -extern "C" { - #include - #include -} - -#include -#include -#include -#include -#include - -Viewer::Viewer(QWidget *parent) : - QDockWidget(parent), - playing(false), - just_played(false), - media(NULL), - seq(NULL), - ui(new Ui::Viewer), - created_sequence(false), - cue_recording_internal(false), - panel_name("Viewer: "), - minimum_zoom(1.0) -{ - ui->setupUi(this); - ui->headers->viewer = this; - ui->headers->snapping = false; - ui->headers->show_text(false); - ui->glViewerPane->viewer = this; - viewer_widget = ui->glViewerPane->child; - viewer_widget->viewer = this; - set_media(NULL); - - ui->currentTimecode->setEnabled(false); - ui->currentTimecode->set_minimum_value(0); - ui->currentTimecode->set_default_value(qSNaN()); - ui->currentTimecode->set_value(0, false); - ui->currentTimecode->set_display_type(LABELSLIDER_FRAMENUMBER); - connect(ui->currentTimecode, SIGNAL(valueChanged()), this, SLOT(update_playhead())); - - recording_flasher.setInterval(500); - - connect(&playback_updater, SIGNAL(timeout()), this, SLOT(timer_update())); - connect(&recording_flasher, SIGNAL(timeout()), this, SLOT(recording_flasher_update())); - connect(ui->horizontalScrollBar, SIGNAL(valueChanged(int)), ui->headers, SLOT(set_scroll(int))); - connect(ui->horizontalScrollBar, SIGNAL(valueChanged(int)), viewer_widget, SLOT(set_waveform_scroll(int))); - - update_playhead_timecode(0); - update_end_timecode(); -} - -Viewer::~Viewer() { - delete ui; -} - -bool Viewer::is_focused() { - return ui->headers->hasFocus() - || viewer_widget->hasFocus() - || ui->pushButton->hasFocus() - || ui->pushButton_2->hasFocus() - || ui->pushButton_3->hasFocus() - || ui->pushButton_4->hasFocus() - || ui->pushButton_5->hasFocus(); -} - -void Viewer::set_main_sequence() { - clean_created_seq(); - set_sequence(true, sequence); -} - -void Viewer::reset_all_audio() { - // reset all clip audio - if (seq != NULL) { - audio_ibuffer_frame = seq->playhead; - audio_ibuffer_timecode = (double) audio_ibuffer_frame / seq->frame_rate; - - for (int i=0;iclips.size();i++) { - Clip* c = seq->clips.at(i); - if (c != NULL) c->reset_audio(); - } - } - clear_audio_ibuffer(); -} - -long timecode_to_frame(const QString& s, int view, double frame_rate) { - QList list = s.split(QRegExp("[:;]")); - - if (view == TIMECODE_FRAMES || (list.size() == 1 && view != TIMECODE_MILLISECONDS)) { - return s.toLong(); - } - - int frRound = qRound(frame_rate); - int hours, minutes, seconds, frames; - - if (view == TIMECODE_MILLISECONDS) { - long milliseconds = s.toLong(); - - hours = milliseconds/3600000; - milliseconds -= (hours*3600000); - minutes = milliseconds/60000; - milliseconds -= (minutes*60000); - seconds = milliseconds/1000; - milliseconds -= (seconds*1000); - frames = qRound64((milliseconds*0.001)*frame_rate); - - seconds = qRound64(seconds * frame_rate); - minutes = qRound64(minutes * frame_rate * 60); - hours = qRound64(hours * frame_rate * 3600); - } else { - hours = ((list.size() > 0) ? list.at(0).toInt() : 0) * frRound * 3600; - minutes = ((list.size() > 1) ? list.at(1).toInt() : 0) * frRound * 60; - seconds = ((list.size() > 2) ? list.at(2).toInt() : 0) * frRound; - frames = (list.size() > 3) ? list.at(3).toInt() : 0; - } - - int f = (frames + seconds + minutes + hours); - - if ((view == TIMECODE_DROP || view == TIMECODE_MILLISECONDS) && frame_rate_is_droppable(frame_rate)) { - // return drop - int d; - int m; - - int dropFrames = round(frame_rate * .066666); //Number of frames to drop on the minute marks is the nearest integer to 6% of the framerate - int framesPer10Minutes = round(frame_rate * 60 * 10); //Number of frames per ten minutes - int framesPerMinute = (round(frame_rate)*60)- dropFrames; //Number of frames per minute is the round of the framerate * 60 minus the number of dropped frames - - d = f / framesPer10Minutes; - f -= dropFrames*9*d; - - m = f % framesPer10Minutes; - - if (m > dropFrames) { - f -= (dropFrames * ((m - dropFrames) / framesPerMinute)); - } - } - - // return non-drop - return f; -} - -QString frame_to_timecode(long f, int view, double frame_rate) { - if (view == TIMECODE_FRAMES) { - return QString::number(f); - } - - // return timecode - int hours = 0; - int mins = 0; - int secs = 0; - int frames = 0; - QString token = ":"; - - if ((view == TIMECODE_DROP || view == TIMECODE_MILLISECONDS) && frame_rate_is_droppable(frame_rate)) { - //CONVERT A FRAME NUMBER TO DROP FRAME TIMECODE - //Code by David Heidelberger, adapted from Andrew Duncan, further adapted for Olive by Olive Team - //Given an int called framenumber and a double called framerate - //Framerate should be 29.97, 59.94, or 23.976, otherwise the calculations will be off. - - int d; - int m; - - int dropFrames = round(frame_rate * .066666); //Number of frames to drop on the minute marks is the nearest integer to 6% of the framerate - int framesPerHour = round(frame_rate*60*60); //Number of frqRound64ames in an hour - int framesPer24Hours = framesPerHour*24; //Number of frames in a day - timecode rolls over after 24 hours - int framesPer10Minutes = round(frame_rate * 60 * 10); //Number of frames per ten minutes - int framesPerMinute = (round(frame_rate)*60)- dropFrames; //Number of frames per minute is the round of the framerate * 60 minus the number of dropped frames - - //If framenumber is greater than 24 hrs, next operation will rollover clock - f = f % framesPer24Hours; //% is the modulus operator, which returns a remainder. a % b = the remainder of a/b - - d = f / framesPer10Minutes; // \ means integer division, which is a/b without a remainder. Some languages you could use floor(a/b) - m = f % framesPer10Minutes; - - //In the original post, the next line read m>1, which only worked for 29.97. Jean-Baptiste Mardelle correctly pointed out that m should be compared to dropFrames. - if (m > dropFrames) { - f = f + (dropFrames*9*d) + dropFrames * ((m - dropFrames) / framesPerMinute); - } else { - f = f + dropFrames*9*d; - } - - int frRound = round(frame_rate); - frames = f % frRound; - secs = (f / frRound) % 60; - mins = ((f / frRound) / 60) % 60; - hours = (((f / frRound) / 60) / 60); - - token = ";"; - } else { - // non-drop timecode - - int int_fps = qRound(frame_rate); - hours = f / (3600 * int_fps); - mins = f / (60*int_fps) % 60; - secs = f/int_fps % 60; - frames = f%int_fps; - } - if (view == TIMECODE_MILLISECONDS) { - return QString::number((hours*3600000)+(mins*60000)+(secs*1000)+qCeil(frames*1000/frame_rate)); - } - return QString(QString::number(hours).rightJustified(2, '0') + - ":" + QString::number(mins).rightJustified(2, '0') + - ":" + QString::number(secs).rightJustified(2, '0') + - token + QString::number(frames).rightJustified(2, '0') - ); -} - -bool frame_rate_is_droppable(float rate) { - return (qFuzzyCompare(rate, 23.976f) || qFuzzyCompare(rate, 29.97f) || qFuzzyCompare(rate, 59.94f)); -} - -void Viewer::seek(long p) { - pause(); - seq->playhead = p; - update_parents(); - reset_all_audio(); - audio_scrub = true; -} - -void Viewer::go_to_start() { - if (seq != NULL) { - if (seq->using_workarea && seq->playhead != seq->workarea_in) { - seek(seq->workarea_in); - } else { - seek(0); - } - } -} - -void Viewer::previous_frame() { - if (seq != NULL && seq->playhead > 0) seek(seq->playhead-1); -} - -void Viewer::next_frame() { - if (seq != NULL) seek(seq->playhead+1); -} - -void Viewer::go_to_end() { - if (seq != NULL) { - if (seq->using_workarea && seq->playhead != seq->workarea_out) { - seek(seq->workarea_out); - } else { - seek(seq->getEndFrame()); - } - } -} - -void Viewer::cue_recording(long start, long end, int track) { - recording_start = start; - recording_end = end; - recording_track = track; - panel_sequence_viewer->seek(recording_start); - cue_recording_internal = true; - recording_flasher.start(); -} - -void Viewer::uncue_recording() { - cue_recording_internal = false; - recording_flasher.stop(); - ui->pushButton_3->setStyleSheet(QString()); -} - -bool Viewer::is_recording_cued() { - return cue_recording_internal; -} - -void Viewer::toggle_play() { - if (playing) { - pause(); - } else { - play(); - } -} - -void Viewer::play() { - if (panel_sequence_viewer->playing) panel_sequence_viewer->pause(); - if (panel_footage_viewer->playing) panel_footage_viewer->pause(); - - if (seq != NULL) { - reset_all_audio(); - if (is_recording_cued() && !start_recording()) { - dout << "[ERROR] Failed to record audio"; - return; - } - playhead_start = seq->playhead; - playing = true; - just_played = true; - set_playpause_icon(false); - start_msecs = QDateTime::currentMSecsSinceEpoch(); - timer_update(); - } -} - -void Viewer::play_wake() { - if (just_played) { - start_msecs = QDateTime::currentMSecsSinceEpoch(); - playback_updater.start(); - if (audio_thread != NULL) audio_thread->notifyReceiver(); - just_played = false; - } -} - -void Viewer::pause() { - playing = false; - just_played = false; - set_playpause_icon(true); - playback_updater.stop(); - - if (is_recording_cued()) { - uncue_recording(); - - if (recording) { - stop_recording(); - - // import audio - QStringList file_list; - file_list.append(get_recorded_audio_filename()); - panel_project->process_file_list(file_list); - - // add it to the sequence - Clip* c = new Clip(seq); - Media* m = panel_project->last_imported_media.at(0); - Footage* f = m->to_footage(); - - f->ready_lock.lock(); - - c->media = m; // latest media - c->media_stream = 0; - c->timeline_in = recording_start; - c->timeline_out = f->get_length_in_frames(seq->frame_rate); - c->clip_in = 0; - c->track = recording_track; - c->color_r = 128; - c->color_g = 192; - c->color_b = 128; - c->name = m->get_name(); - - f->ready_lock.unlock(); - - QVector add_clips; - add_clips.append(c); - undo_stack.push(new AddClipCommand(seq, add_clips)); // add clip - } - } -} - -void Viewer::update_playhead_timecode(long p) { - ui->currentTimecode->set_value(p, false); -} - -void Viewer::update_end_timecode() { - ui->endTimecode->setText((seq == NULL) ? frame_to_timecode(0, config.timecode_view, 30) : frame_to_timecode(seq->getEndFrame(), config.timecode_view, seq->frame_rate)); -} - -void Viewer::update_header_zoom() { - if (seq != NULL) { - long sequenceEndFrame = seq->getEndFrame(); - if (cached_end_frame != sequenceEndFrame) { - minimum_zoom = (sequenceEndFrame > 0) ? ((double) ui->headers->width() / (double) sequenceEndFrame) : 1; - ui->headers->update_zoom(qMax(ui->headers->get_zoom(), minimum_zoom)); - ui->headers->set_scrollbar_max(ui->horizontalScrollBar, sequenceEndFrame, ui->headers->width()); - viewer_widget->waveform_zoom = ui->headers->get_zoom(); - } else { - ui->headers->update(); - } - } -} - -void Viewer::update_parents() { - if (main_sequence) { - update_ui(false); - } else { - update_viewer(); - } -} - -void Viewer::update_viewer() { - update_header_zoom(); - viewer_widget->update(); - if (seq != NULL) update_playhead_timecode(seq->playhead); - update_end_timecode(); -} - -void Viewer::clear_inout_point() { - if (seq->using_workarea) { - undo_stack.push(new SetTimelineInOutCommand(seq, false, 0, 0)); - update_parents(); - } -} - -void Viewer::set_in_point() { - ui->headers->set_in_point(seq->playhead); -} - -void Viewer::set_out_point() { - ui->headers->set_out_point(seq->playhead); -} - -void Viewer::set_zoom(bool in) { - if (seq != NULL) { - if (in) { - ui->headers->update_zoom(ui->headers->get_zoom()*2); - } else { - ui->headers->update_zoom(qMax(minimum_zoom, ui->headers->get_zoom()*0.5)); - } - if (viewer_widget->waveform) { - viewer_widget->waveform_zoom = ui->headers->get_zoom(); - viewer_widget->update(); - } - ui->headers->set_scrollbar_max(ui->horizontalScrollBar, seq->getEndFrame(), ui->headers->width()); - center_scroll_to_playhead(ui->horizontalScrollBar, ui->headers->get_zoom(), seq->playhead); - } -} - -void Viewer::set_media(Media* m) { - main_sequence = false; - media = m; - clean_created_seq(); - if (media != NULL) { - switch (media->get_type()) { - case MEDIA_TYPE_FOOTAGE: - { - Footage* footage = media->to_footage(); - - seq = new Sequence(); - created_sequence = true; - seq->wrapper_sequence = true; - seq->name = footage->name; - - seq->using_workarea = footage->using_inout; - if (footage->using_inout) { - seq->workarea_in = footage->in; - seq->workarea_out = footage->out; - } - - seq->frame_rate = 30; - - if (footage->video_tracks.size() > 0) { - FootageStream* video_stream = footage->video_tracks.at(0); - seq->width = video_stream->video_width; - seq->height = video_stream->video_height; - if (video_stream->video_frame_rate > 0 && !video_stream->infinite_length) seq->frame_rate = video_stream->video_frame_rate; - - Clip* c = new Clip(seq); - c->media = media; - c->media_stream = video_stream->file_index; - c->timeline_in = 0; - c->timeline_out = footage->get_length_in_frames(seq->frame_rate); - if (c->timeline_out <= 0) c->timeline_out = 150; - c->track = -1; - c->clip_in = 0; - c->recalculateMaxLength(); - seq->clips.append(c); - } else { - seq->width = 1920; - seq->height = 1080; - } - - if (footage->audio_tracks.size() > 0) { - FootageStream* audio_stream = footage->audio_tracks.at(0); - seq->audio_frequency = audio_stream->audio_frequency; - - Clip* c = new Clip(seq); - c->media = media; - c->media_stream = audio_stream->file_index; - c->timeline_in = 0; - c->timeline_out = footage->get_length_in_frames(seq->frame_rate); - c->track = 0; - c->clip_in = 0; - c->recalculateMaxLength(); - seq->clips.append(c); - - if (footage->video_tracks.size() == 0) { - viewer_widget->waveform = true; - viewer_widget->waveform_clip = c; - viewer_widget->waveform_ms = audio_stream; - viewer_widget->update(); - } - } else { - seq->audio_frequency = 48000; - } - - seq->audio_layout = AV_CH_LAYOUT_STEREO; - } - break; - case MEDIA_TYPE_SEQUENCE: - seq = media->to_sequence(); - break; - } - } - set_sequence(false, seq); -} - -void Viewer::on_pushButton_clicked() { - go_to_start(); -} - -void Viewer::on_pushButton_5_clicked() { - go_to_end(); -} - -void Viewer::on_pushButton_2_clicked() { - previous_frame(); -} - -void Viewer::on_pushButton_4_clicked() { - next_frame(); -} - -void Viewer::on_pushButton_3_clicked() { - toggle_play(); -} - -void Viewer::update_playhead() { - seek(ui->currentTimecode->value()); -} - -void Viewer::timer_update() { - long previous_playhead = seq->playhead; - - seq->playhead = qRound(playhead_start + ((QDateTime::currentMSecsSinceEpoch()-start_msecs) * 0.001 * seq->frame_rate)); - update_parents(); - - long end_frame = (seq->using_workarea && previous_playhead < seq->workarea_out) ? seq->workarea_out : seq->getEndFrame(); - if ((!recording - && playing - && seq->playhead >= end_frame - && previous_playhead < end_frame) - || (recording && recording_start != recording_end && seq->playhead >= recording_end)) { - pause(); - } -} - -void Viewer::recording_flasher_update() { - if (ui->pushButton_3->styleSheet().isEmpty()) { - ui->pushButton_3->setStyleSheet("background: red;"); - } else { - ui->pushButton_3->setStyleSheet(QString()); - } -} - -void Viewer::clean_created_seq() { - viewer_widget->waveform = false; - - if (created_sequence) { - // TODO delete undo commands referencing this sequence to avoid crashes - /*for (int i=0;iheaders->setEnabled(!null_sequence); - ui->currentTimecode->setEnabled(!null_sequence); - viewer_widget->setEnabled(!null_sequence); - viewer_widget->setVisible(!null_sequence); - ui->pushButton->setEnabled(!null_sequence); - ui->pushButton_2->setEnabled(!null_sequence); - ui->pushButton_3->setEnabled(!null_sequence); - ui->pushButton_4->setEnabled(!null_sequence); - ui->pushButton_5->setEnabled(!null_sequence); - - if (!null_sequence) { - ui->currentTimecode->set_frame_rate(seq->frame_rate); - - playback_updater.setInterval(qFloor(1000 / seq->frame_rate)); - - update_playhead_timecode(seq->playhead); - update_end_timecode(); - - ui->glViewerPane->adjust(); - - setWindowTitle(panel_name + seq->name); - } else { - update_playhead_timecode(0); - update_end_timecode(); - - setWindowTitle(panel_name + "(none)"); - } - - update_header_zoom(); - - viewer_widget->update(); - - update(); -} - -void Viewer::set_playpause_icon(bool play) { - ui->pushButton_3->setIcon(QIcon((play) ? ":/icons/play.png" : ":/icons/pause.png")); -} +#include "viewer.h" +#include "ui_viewer.h" + +#include "playback/audio.h" +#include "timeline.h" +#include "panels/project.h" +#include "project/sequence.h" +#include "project/clip.h" +#include "panels/panels.h" +#include "io/config.h" +#include "project/footage.h" +#include "project/media.h" +#include "project/undo.h" +#include "ui/audiomonitor.h" +#include "ui_timeline.h" +#include "playback/playback.h" +#include "ui/viewerwidget.h" +#include "debug.h" + +#define FRAMES_IN_ONE_MINUTE 1798 // 1800 - 2 +#define FRAMES_IN_TEN_MINUTES 17978 // (FRAMES_IN_ONE_MINUTE * 10) - 2 + +extern "C" { + #include + #include +} + +#include +#include +#include +#include +#include + +Viewer::Viewer(QWidget *parent) : + QDockWidget(parent), + playing(false), + just_played(false), + media(NULL), + seq(NULL), + ui(new Ui::Viewer), + created_sequence(false), + cue_recording_internal(false), + panel_name("Viewer: "), + minimum_zoom(1.0) +{ + ui->setupUi(this); + ui->headers->viewer = this; + ui->headers->snapping = false; + ui->headers->show_text(false); + ui->glViewerPane->viewer = this; + viewer_widget = ui->glViewerPane->child; + viewer_widget->viewer = this; + set_media(NULL); + + ui->currentTimecode->setEnabled(false); + ui->currentTimecode->set_minimum_value(0); + ui->currentTimecode->set_default_value(qSNaN()); + ui->currentTimecode->set_value(0, false); + ui->currentTimecode->set_display_type(LABELSLIDER_FRAMENUMBER); + connect(ui->currentTimecode, SIGNAL(valueChanged()), this, SLOT(update_playhead())); + + recording_flasher.setInterval(500); + + connect(&playback_updater, SIGNAL(timeout()), this, SLOT(timer_update())); + connect(&recording_flasher, SIGNAL(timeout()), this, SLOT(recording_flasher_update())); + connect(ui->horizontalScrollBar, SIGNAL(valueChanged(int)), ui->headers, SLOT(set_scroll(int))); + connect(ui->horizontalScrollBar, SIGNAL(valueChanged(int)), viewer_widget, SLOT(set_waveform_scroll(int))); + connect(ui->horizontalScrollBar, SIGNAL(resize_move(double)), this, SLOT(resize_move(double))); + connect(ui->zoomComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(zoom_update(int))); + + update_playhead_timecode(0); + update_end_timecode(); +} + +Viewer::~Viewer() { + delete ui; +} + +bool Viewer::is_focused() { + return ui->headers->hasFocus() + || viewer_widget->hasFocus() + || ui->pushButton->hasFocus() + || ui->pushButton_2->hasFocus() + || ui->pushButton_3->hasFocus() + || ui->pushButton_4->hasFocus() + || ui->pushButton_5->hasFocus(); +} + +void Viewer::set_main_sequence() { + clean_created_seq(); + set_sequence(true, sequence); +} + +void Viewer::reset_all_audio() { + // reset all clip audio + if (seq != NULL) { + audio_ibuffer_frame = seq->playhead; + audio_ibuffer_timecode = (double) audio_ibuffer_frame / seq->frame_rate; + + for (int i=0;iclips.size();i++) { + Clip* c = seq->clips.at(i); + if (c != NULL) c->reset_audio(); + } + } + clear_audio_ibuffer(); +} + +long timecode_to_frame(const QString& s, int view, double frame_rate) { + QList list = s.split(QRegExp("[:;]")); + + if (view == TIMECODE_FRAMES || (list.size() == 1 && view != TIMECODE_MILLISECONDS)) { + return s.toLong(); + } + + int frRound = qRound(frame_rate); + int hours, minutes, seconds, frames; + + if (view == TIMECODE_MILLISECONDS) { + long milliseconds = s.toLong(); + + hours = milliseconds/3600000; + milliseconds -= (hours*3600000); + minutes = milliseconds/60000; + milliseconds -= (minutes*60000); + seconds = milliseconds/1000; + milliseconds -= (seconds*1000); + frames = qRound64((milliseconds*0.001)*frame_rate); + + seconds = qRound64(seconds * frame_rate); + minutes = qRound64(minutes * frame_rate * 60); + hours = qRound64(hours * frame_rate * 3600); + } else { + hours = ((list.size() > 0) ? list.at(0).toInt() : 0) * frRound * 3600; + minutes = ((list.size() > 1) ? list.at(1).toInt() : 0) * frRound * 60; + seconds = ((list.size() > 2) ? list.at(2).toInt() : 0) * frRound; + frames = (list.size() > 3) ? list.at(3).toInt() : 0; + } + + int f = (frames + seconds + minutes + hours); + + if ((view == TIMECODE_DROP || view == TIMECODE_MILLISECONDS) && frame_rate_is_droppable(frame_rate)) { + // return drop + int d; + int m; + + int dropFrames = round(frame_rate * .066666); //Number of frames to drop on the minute marks is the nearest integer to 6% of the framerate + int framesPer10Minutes = round(frame_rate * 60 * 10); //Number of frames per ten minutes + int framesPerMinute = (round(frame_rate)*60)- dropFrames; //Number of frames per minute is the round of the framerate * 60 minus the number of dropped frames + + d = f / framesPer10Minutes; + f -= dropFrames*9*d; + + m = f % framesPer10Minutes; + + if (m > dropFrames) { + f -= (dropFrames * ((m - dropFrames) / framesPerMinute)); + } + } + + // return non-drop + return f; +} + +QString frame_to_timecode(long f, int view, double frame_rate) { + if (view == TIMECODE_FRAMES) { + return QString::number(f); + } + + // return timecode + int hours = 0; + int mins = 0; + int secs = 0; + int frames = 0; + QString token = ":"; + + if ((view == TIMECODE_DROP || view == TIMECODE_MILLISECONDS) && frame_rate_is_droppable(frame_rate)) { + //CONVERT A FRAME NUMBER TO DROP FRAME TIMECODE + //Code by David Heidelberger, adapted from Andrew Duncan, further adapted for Olive by Olive Team + //Given an int called framenumber and a double called framerate + //Framerate should be 29.97, 59.94, or 23.976, otherwise the calculations will be off. + + int d; + int m; + + int dropFrames = round(frame_rate * .066666); //Number of frames to drop on the minute marks is the nearest integer to 6% of the framerate + int framesPerHour = round(frame_rate*60*60); //Number of frqRound64ames in an hour + int framesPer24Hours = framesPerHour*24; //Number of frames in a day - timecode rolls over after 24 hours + int framesPer10Minutes = round(frame_rate * 60 * 10); //Number of frames per ten minutes + int framesPerMinute = (round(frame_rate)*60)- dropFrames; //Number of frames per minute is the round of the framerate * 60 minus the number of dropped frames + + //If framenumber is greater than 24 hrs, next operation will rollover clock + f = f % framesPer24Hours; //% is the modulus operator, which returns a remainder. a % b = the remainder of a/b + + d = f / framesPer10Minutes; // \ means integer division, which is a/b without a remainder. Some languages you could use floor(a/b) + m = f % framesPer10Minutes; + + //In the original post, the next line read m>1, which only worked for 29.97. Jean-Baptiste Mardelle correctly pointed out that m should be compared to dropFrames. + if (m > dropFrames) { + f = f + (dropFrames*9*d) + dropFrames * ((m - dropFrames) / framesPerMinute); + } else { + f = f + dropFrames*9*d; + } + + int frRound = round(frame_rate); + frames = f % frRound; + secs = (f / frRound) % 60; + mins = ((f / frRound) / 60) % 60; + hours = (((f / frRound) / 60) / 60); + + token = ";"; + } else { + // non-drop timecode + + int int_fps = qRound(frame_rate); + hours = f / (3600 * int_fps); + mins = f / (60*int_fps) % 60; + secs = f/int_fps % 60; + frames = f%int_fps; + } + if (view == TIMECODE_MILLISECONDS) { + return QString::number((hours*3600000)+(mins*60000)+(secs*1000)+qCeil(frames*1000/frame_rate)); + } + return QString(QString::number(hours).rightJustified(2, '0') + + ":" + QString::number(mins).rightJustified(2, '0') + + ":" + QString::number(secs).rightJustified(2, '0') + + token + QString::number(frames).rightJustified(2, '0') + ); +} + +bool frame_rate_is_droppable(float rate) { + return (qFuzzyCompare(rate, 23.976f) || qFuzzyCompare(rate, 29.97f) || qFuzzyCompare(rate, 59.94f)); +} + +void Viewer::seek(long p) { + pause(); + seq->playhead = p; + update_parents(); + reset_all_audio(); + audio_scrub = true; +} + +void Viewer::go_to_start() { + if (seq != NULL) { + if (seq->using_workarea && seq->playhead != seq->workarea_in) { + seek(seq->workarea_in); + } else { + seek(0); + } + } +} + +void Viewer::previous_frame() { + if (seq != NULL && seq->playhead > 0) seek(seq->playhead-1); +} + +void Viewer::next_frame() { + if (seq != NULL) seek(seq->playhead+1); +} + +void Viewer::go_to_end() { + if (seq != NULL) { + if (seq->using_workarea && seq->playhead != seq->workarea_out) { + seek(seq->workarea_out); + } else { + seek(seq->getEndFrame()); + } + } +} + +void Viewer::cue_recording(long start, long end, int track) { + recording_start = start; + recording_end = end; + recording_track = track; + panel_sequence_viewer->seek(recording_start); + cue_recording_internal = true; + recording_flasher.start(); +} + +void Viewer::uncue_recording() { + cue_recording_internal = false; + recording_flasher.stop(); + ui->pushButton_3->setStyleSheet(QString()); +} + +bool Viewer::is_recording_cued() { + return cue_recording_internal; +} + +void Viewer::toggle_play() { + if (playing) { + pause(); + } else { + play(); + } +} + +void Viewer::play() { + if (panel_sequence_viewer->playing) panel_sequence_viewer->pause(); + if (panel_footage_viewer->playing) panel_footage_viewer->pause(); + + if (seq != NULL) { + reset_all_audio(); + if (is_recording_cued() && !start_recording()) { + dout << "[ERROR] Failed to record audio"; + return; + } + playhead_start = seq->playhead; + playing = true; + just_played = true; + set_playpause_icon(false); + start_msecs = QDateTime::currentMSecsSinceEpoch(); + timer_update(); + } +} + +void Viewer::play_wake() { + if (just_played) { + start_msecs = QDateTime::currentMSecsSinceEpoch(); + playback_updater.start(); + if (audio_thread != NULL) audio_thread->notifyReceiver(); + just_played = false; + } +} + +void Viewer::pause() { + playing = false; + just_played = false; + set_playpause_icon(true); + playback_updater.stop(); + + if (is_recording_cued()) { + uncue_recording(); + + if (recording) { + stop_recording(); + + // import audio + QStringList file_list; + file_list.append(get_recorded_audio_filename()); + panel_project->process_file_list(file_list); + + // add it to the sequence + Clip* c = new Clip(seq); + Media* m = panel_project->last_imported_media.at(0); + Footage* f = m->to_footage(); + + f->ready_lock.lock(); + + c->media = m; // latest media + c->media_stream = 0; + c->timeline_in = recording_start; + c->timeline_out = f->get_length_in_frames(seq->frame_rate); + c->clip_in = 0; + c->track = recording_track; + c->color_r = 128; + c->color_g = 192; + c->color_b = 128; + c->name = m->get_name(); + + f->ready_lock.unlock(); + + QVector add_clips; + add_clips.append(c); + undo_stack.push(new AddClipCommand(seq, add_clips)); // add clip + } + } +} + +void Viewer::update_playhead_timecode(long p) { + ui->currentTimecode->set_value(p, false); +} + +void Viewer::update_end_timecode() { + ui->endTimecode->setText((seq == NULL) ? frame_to_timecode(0, config.timecode_view, 30) : frame_to_timecode(seq->getEndFrame(), config.timecode_view, seq->frame_rate)); +} + +void Viewer::update_header_zoom() { + if (seq != NULL) { + long sequenceEndFrame = seq->getEndFrame(); + if (cached_end_frame != sequenceEndFrame) { + minimum_zoom = (sequenceEndFrame > 0) ? ((double) ui->headers->width() / (double) sequenceEndFrame) : 1; + ui->headers->update_zoom(qMax(ui->headers->get_zoom(), minimum_zoom)); + set_sb_max(); + viewer_widget->waveform_zoom = ui->headers->get_zoom(); + } else { + ui->headers->update(); + } + } +} + +void Viewer::update_parents() { + if (main_sequence) { + update_ui(false); + } else { + update_viewer(); + } +} + +void Viewer::resizeEvent(QResizeEvent *event) { + if (seq != NULL) { + set_sb_max(); + } +} + +void Viewer::update_viewer() { + update_header_zoom(); + viewer_widget->update(); + if (seq != NULL) update_playhead_timecode(seq->playhead); + update_end_timecode(); +} + +void Viewer::clear_inout_point() { + if (seq->using_workarea) { + undo_stack.push(new SetTimelineInOutCommand(seq, false, 0, 0)); + update_parents(); + } +} + +void Viewer::set_in_point() { + ui->headers->set_in_point(seq->playhead); +} + +void Viewer::set_out_point() { + ui->headers->set_out_point(seq->playhead); +} + +void Viewer::set_zoom(bool in) { + if (seq != NULL) { + set_zoom_value(in ? ui->headers->get_zoom()*2 : qMax(minimum_zoom, ui->headers->get_zoom()*0.5)); + } +} + +void Viewer::set_zoom_value(double d) { + ui->headers->update_zoom(d); + if (viewer_widget->waveform) { + viewer_widget->waveform_zoom = d; + viewer_widget->update(); + } + if (seq != NULL) { + set_sb_max(); + if (!ui->horizontalScrollBar->is_resizing()) + center_scroll_to_playhead(ui->horizontalScrollBar, ui->headers->get_zoom(), seq->playhead); + } +} + +void Viewer::set_sb_max() { + ui->headers->set_scrollbar_max(ui->horizontalScrollBar, seq->getEndFrame(), ui->headers->width()); +} + +void Viewer::set_media(Media* m) { + main_sequence = false; + media = m; + clean_created_seq(); + if (media != NULL) { + switch (media->get_type()) { + case MEDIA_TYPE_FOOTAGE: + { + Footage* footage = media->to_footage(); + + seq = new Sequence(); + created_sequence = true; + seq->wrapper_sequence = true; + seq->name = footage->name; + + seq->using_workarea = footage->using_inout; + if (footage->using_inout) { + seq->workarea_in = footage->in; + seq->workarea_out = footage->out; + } + + seq->frame_rate = 30; + + if (footage->video_tracks.size() > 0) { + FootageStream* video_stream = footage->video_tracks.at(0); + seq->width = video_stream->video_width; + seq->height = video_stream->video_height; + if (video_stream->video_frame_rate > 0 && !video_stream->infinite_length) seq->frame_rate = video_stream->video_frame_rate; + + Clip* c = new Clip(seq); + c->media = media; + c->media_stream = video_stream->file_index; + c->timeline_in = 0; + c->timeline_out = footage->get_length_in_frames(seq->frame_rate); + if (c->timeline_out <= 0) c->timeline_out = 150; + c->track = -1; + c->clip_in = 0; + c->recalculateMaxLength(); + seq->clips.append(c); + } else { + seq->width = 1920; + seq->height = 1080; + } + + if (footage->audio_tracks.size() > 0) { + FootageStream* audio_stream = footage->audio_tracks.at(0); + seq->audio_frequency = audio_stream->audio_frequency; + + Clip* c = new Clip(seq); + c->media = media; + c->media_stream = audio_stream->file_index; + c->timeline_in = 0; + c->timeline_out = footage->get_length_in_frames(seq->frame_rate); + c->track = 0; + c->clip_in = 0; + c->recalculateMaxLength(); + seq->clips.append(c); + + if (footage->video_tracks.size() == 0) { + viewer_widget->waveform = true; + viewer_widget->waveform_clip = c; + viewer_widget->waveform_ms = audio_stream; + viewer_widget->update(); + } + } else { + seq->audio_frequency = 48000; + } + + seq->audio_layout = AV_CH_LAYOUT_STEREO; + } + break; + case MEDIA_TYPE_SEQUENCE: + seq = media->to_sequence(); + break; + } + } + set_sequence(false, seq); +} + +void Viewer::on_pushButton_clicked() { + go_to_start(); +} + +void Viewer::on_pushButton_5_clicked() { + go_to_end(); +} + +void Viewer::on_pushButton_2_clicked() { + previous_frame(); +} + +void Viewer::on_pushButton_4_clicked() { + next_frame(); +} + +void Viewer::on_pushButton_3_clicked() { + toggle_play(); +} + +void Viewer::update_playhead() { + seek(ui->currentTimecode->value()); +} + +void Viewer::timer_update() { + long previous_playhead = seq->playhead; + + seq->playhead = qRound(playhead_start + ((QDateTime::currentMSecsSinceEpoch()-start_msecs) * 0.001 * seq->frame_rate)); + update_parents(); + + long end_frame = (seq->using_workarea && previous_playhead < seq->workarea_out) ? seq->workarea_out : seq->getEndFrame(); + if ((!recording + && playing + && seq->playhead >= end_frame + && previous_playhead < end_frame) + || (recording && recording_start != recording_end && seq->playhead >= recording_end)) { + pause(); + } +} + +void Viewer::recording_flasher_update() { + if (ui->pushButton_3->styleSheet().isEmpty()) { + ui->pushButton_3->setStyleSheet("background: red;"); + } else { + ui->pushButton_3->setStyleSheet(QString()); + } +} + +void Viewer::zoom_update(int i) { + if (i == 0) { + ui->glViewerPane->fit = true; + } else { + ui->glViewerPane->fit = false; + QString pc = ui->zoomComboBox->itemText(i); + pc = pc.left(pc.length() - 1); + ui->glViewerPane->zoom = pc.toDouble()*0.01; + } + ui->glViewerPane->adjust(); +} + +void Viewer::resize_move(double d) { + set_zoom_value(ui->headers->get_zoom()*d); +} + +void Viewer::clean_created_seq() { + viewer_widget->waveform = false; + + if (created_sequence) { + // TODO delete undo commands referencing this sequence to avoid crashes + /*for (int i=0;iheaders->setEnabled(!null_sequence); + ui->currentTimecode->setEnabled(!null_sequence); + viewer_widget->setEnabled(!null_sequence); + viewer_widget->setVisible(!null_sequence); + ui->pushButton->setEnabled(!null_sequence); + ui->pushButton_2->setEnabled(!null_sequence); + ui->pushButton_3->setEnabled(!null_sequence); + ui->pushButton_4->setEnabled(!null_sequence); + ui->pushButton_5->setEnabled(!null_sequence); + + if (!null_sequence) { + ui->currentTimecode->set_frame_rate(seq->frame_rate); + + playback_updater.setInterval(qFloor(1000 / seq->frame_rate)); + + update_playhead_timecode(seq->playhead); + update_end_timecode(); + + ui->glViewerPane->adjust(); + + setWindowTitle(panel_name + seq->name); + } else { + update_playhead_timecode(0); + update_end_timecode(); + + setWindowTitle(panel_name + "(none)"); + } + + update_header_zoom(); + + viewer_widget->update(); + + update(); +} + +void Viewer::set_playpause_icon(bool play) { + ui->pushButton_3->setIcon(QIcon((play) ? ":/icons/play.png" : ":/icons/pause.png")); +} diff --git a/panels/viewer.h b/panels/viewer.h index c5c43778b..71d316fa1 100644 --- a/panels/viewer.h +++ b/panels/viewer.h @@ -1,99 +1,105 @@ -#ifndef VIEWER_H -#define VIEWER_H - -#include -#include - -class Timeline; -class ViewerWidget; -class Media; -struct Sequence; - -namespace Ui { -class Viewer; -} - -bool frame_rate_is_droppable(float rate); -long timecode_to_frame(const QString& s, int view, double frame_rate); -QString frame_to_timecode(long f, int view, double frame_rate); - -class Viewer : public QDockWidget -{ - Q_OBJECT - -public: - explicit Viewer(QWidget *parent = 0); - ~Viewer(); - - bool is_focused(); - void set_main_sequence(); - void set_media(Media *m); - void compose(); - void set_playpause_icon(bool play); - void update_playhead_timecode(long p); - void update_end_timecode(); - void update_header_zoom(); - void update_viewer(); - void clear_inout_point(); - void set_in_point(); - void set_out_point(); - void set_zoom(bool in); - - // playback functions - void go_to_start(); - void previous_frame(); - void next_frame(); - void seek(long p); - void toggle_play(); - void play(); - void pause(); - void go_to_end(); - bool playing; - long playhead_start; - qint64 start_msecs; - QTimer playback_updater; - bool just_played; - - void cue_recording(long start, long end, int track); - void uncue_recording(); - bool is_recording_cued(); - long recording_start; - long recording_end; - int recording_track; - - void reset_all_audio(); - void update_parents(); - - ViewerWidget* viewer_widget; - - Media* media; - Sequence* seq; - - Ui::Viewer *ui; - -public slots: - void play_wake(); - -private slots: - void on_pushButton_clicked(); - void on_pushButton_5_clicked(); - void on_pushButton_2_clicked(); - void on_pushButton_4_clicked(); - void on_pushButton_3_clicked(); - void update_playhead(); - void timer_update(); - void recording_flasher_update(); -private: - void clean_created_seq(); - void set_sequence(bool main, Sequence* s); - bool main_sequence; - bool created_sequence; - long cached_end_frame; - QString panel_name; - double minimum_zoom; - - bool cue_recording_internal; - QTimer recording_flasher; -}; - -#endif // VIEWER_H +#ifndef VIEWER_H +#define VIEWER_H + +#include +#include + +class Timeline; +class ViewerWidget; +class Media; +struct Sequence; + +namespace Ui { +class Viewer; +} + +bool frame_rate_is_droppable(float rate); +long timecode_to_frame(const QString& s, int view, double frame_rate); +QString frame_to_timecode(long f, int view, double frame_rate); + +class Viewer : public QDockWidget +{ + Q_OBJECT + +public: + explicit Viewer(QWidget *parent = 0); + ~Viewer(); + + bool is_focused(); + void set_main_sequence(); + void set_media(Media *m); + void compose(); + void set_playpause_icon(bool play); + void update_playhead_timecode(long p); + void update_end_timecode(); + void update_header_zoom(); + void update_viewer(); + void clear_inout_point(); + void set_in_point(); + void set_out_point(); + void set_zoom(bool in); + + // playback functions + void go_to_start(); + void previous_frame(); + void next_frame(); + void seek(long p); + void toggle_play(); + void play(); + void pause(); + void go_to_end(); + bool playing; + long playhead_start; + qint64 start_msecs; + QTimer playback_updater; + bool just_played; + + void cue_recording(long start, long end, int track); + void uncue_recording(); + bool is_recording_cued(); + long recording_start; + long recording_end; + int recording_track; + + void reset_all_audio(); + void update_parents(); + + ViewerWidget* viewer_widget; + + Media* media; + Sequence* seq; + + Ui::Viewer *ui; + + void resizeEvent(QResizeEvent *event); + +public slots: + void play_wake(); + +private slots: + void on_pushButton_clicked(); + void on_pushButton_5_clicked(); + void on_pushButton_2_clicked(); + void on_pushButton_4_clicked(); + void on_pushButton_3_clicked(); + void update_playhead(); + void timer_update(); + void recording_flasher_update(); + void zoom_update(int i); + void resize_move(double d); +private: + void clean_created_seq(); + void set_sequence(bool main, Sequence* s); + bool main_sequence; + bool created_sequence; + long cached_end_frame; + QString panel_name; + double minimum_zoom; + void set_zoom_value(double d); + void set_sb_max(); + + bool cue_recording_internal; + QTimer recording_flasher; +}; + +#endif // VIEWER_H diff --git a/panels/viewer.ui b/panels/viewer.ui index f3713654c..2246fb0f6 100644 --- a/panels/viewer.ui +++ b/panels/viewer.ui @@ -55,7 +55,7 @@
- + 0 @@ -229,6 +229,17 @@ + + TimelineHeader + QWidget +
ui/timelineheader.h
+ 1 +
+ + ResizableScrollBar + QScrollBar +
ui/resizablescrollbar.h
+
ViewerContainer QWidget @@ -240,12 +251,6 @@ QLabel
ui/labelslider.h
- - TimelineHeader - QWidget -
ui/timelineheader.h
- 1 -
diff --git a/ui/keyframeview.cpp b/ui/keyframeview.cpp index de5ff7cb3..8fd23f63c 100644 --- a/ui/keyframeview.cpp +++ b/ui/keyframeview.cpp @@ -189,6 +189,10 @@ void KeyframeView::draw_keyframe(QPainter &p, int type, int x, int y, bool darke } } +void KeyframeView::resize_move(double d) { + header->update_zoom(header->get_zoom()*d); +} + void KeyframeView::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton) { int row_index = -1; diff --git a/ui/keyframeview.h b/ui/keyframeview.h index cfaebc419..e565d6e61 100644 --- a/ui/keyframeview.h +++ b/ui/keyframeview.h @@ -23,6 +23,7 @@ public: public slots: void set_x_scroll(int); void set_y_scroll(int); + void resize_move(double d); private: long adjust_row_keyframe(EffectRow* row, long time); QVector selected_rows; @@ -38,7 +39,7 @@ private: bool mousedown; bool dragging; bool keys_selected; - bool select_rect; + bool select_rect; bool keyframeIsSelected(EffectRow* row, int keyframe); diff --git a/ui/resizablescrollbar.cpp b/ui/resizablescrollbar.cpp new file mode 100644 index 000000000..0bde2cfeb --- /dev/null +++ b/ui/resizablescrollbar.cpp @@ -0,0 +1,104 @@ +#include "resizablescrollbar.h" + +#include +#include +#include + +#include "debug.h" + +#define RESIZE_HANDLE_SIZE 10 + +ResizableScrollBar::ResizableScrollBar(QWidget *parent) : + QScrollBar(parent), + resize_init(false), + resize_proc(false) +{ + setMouseTracking(true); +} + +bool ResizableScrollBar::is_resizing() { + return resize_proc; +} + +void ResizableScrollBar::resizeEvent(QResizeEvent *event) { + setPageStep(event->size().width()); +} + +void ResizableScrollBar::mousePressEvent(QMouseEvent *e) { + if (resize_init) { + QStyleOptionSlider opt; + initStyleOption(&opt); + + QRect sr = style()->subControlRect(QStyle::CC_ScrollBar, &opt, + QStyle::SC_ScrollBarSlider, this); + + resize_proc = true; + resize_start = e->pos().x(); + + resize_start_max = maximum(); + resize_start_width = sr.width(); + } else { + QScrollBar::mousePressEvent(e); + } +} + +void ResizableScrollBar::mouseMoveEvent(QMouseEvent *e) { + QStyleOptionSlider opt; + initStyleOption(&opt); + + QRect sr = style()->subControlRect(QStyle::CC_ScrollBar, &opt, + QStyle::SC_ScrollBarSlider, this); + QRect gr = style()->subControlRect(QStyle::CC_ScrollBar, &opt, + QStyle::SC_ScrollBarGroove, this); + + if (resize_proc) { + int diff = (e->pos().x() - resize_start); + if (resize_top) diff = -diff; + double scale = double(sr.width())/double(sr.width()+diff); + if (!qIsInf(scale) && !qIsNull(scale)) { + emit resize_move(scale); + resize_start = e->pos().x(); + + if (resize_top) { + int slider_min = gr.x(); + int slider_max = gr.right() - (sr.width()+diff); + int val = QStyle::sliderValueFromPosition(minimum(), maximum(), e->pos().x() - slider_min, slider_max - slider_min, opt.upsideDown); + + setValue(val); + } else { + setValue(qRound(value() * scale)); + } + } + } else { + bool new_resize_init = false; + + if ((orientation() == Qt::Horizontal && e->pos().x() > sr.left()-RESIZE_HANDLE_SIZE && e->pos().x() < sr.left()+RESIZE_HANDLE_SIZE) + || (orientation() == Qt::Vertical && e->pos().y() > sr.top()-RESIZE_HANDLE_SIZE && e->pos().y() < sr.top()+RESIZE_HANDLE_SIZE)) { + new_resize_init = true; + resize_top = true; + } else if ((orientation() == Qt::Horizontal && e->pos().x() > sr.right()-RESIZE_HANDLE_SIZE && e->pos().x() < sr.right()+RESIZE_HANDLE_SIZE) + || (orientation() == Qt::Vertical && e->pos().y() > sr.bottom()-RESIZE_HANDLE_SIZE && e->pos().y() < sr.bottom()+RESIZE_HANDLE_SIZE)) { + new_resize_init = true; + resize_top = false; + } + + if (resize_init != new_resize_init) { + if (new_resize_init) { + setCursor(Qt::SizeHorCursor); + } else { + unsetCursor(); + } + resize_init = new_resize_init; + } + + QScrollBar::mouseMoveEvent(e); + } +} + +void ResizableScrollBar::mouseReleaseEvent(QMouseEvent *e) { + if (resize_proc) { + resize_proc = false; + } else { + QScrollBar::mouseReleaseEvent(e); + } +} diff --git a/ui/resizablescrollbar.h b/ui/resizablescrollbar.h new file mode 100644 index 000000000..70e4094d7 --- /dev/null +++ b/ui/resizablescrollbar.h @@ -0,0 +1,29 @@ +#ifndef RESIZABLESCROLLBAR_H +#define RESIZABLESCROLLBAR_H + +#include + +class ResizableScrollBar : public QScrollBar +{ + Q_OBJECT +public: + ResizableScrollBar(QWidget * parent = 0); + bool is_resizing(); +signals: + void resize_move(double i); +protected: + void resizeEvent(QResizeEvent *event) override; + void mousePressEvent(QMouseEvent *) override; + void mouseMoveEvent(QMouseEvent *) override; + void mouseReleaseEvent(QMouseEvent *) override; +private: + bool resize_init; + bool resize_proc; + int resize_start; + bool resize_top; + + int resize_start_max; + int resize_start_width; +}; + +#endif // RESIZABLESCROLLBAR_H diff --git a/ui/timelineheader.cpp b/ui/timelineheader.cpp index 53cddecab..5db385314 100644 --- a/ui/timelineheader.cpp +++ b/ui/timelineheader.cpp @@ -410,3 +410,7 @@ void TimelineHeader::show_context_menu(const QPoint &pos) { contextMenu.addAction(&clear_in_out); contextMenu.exec(mapToGlobal(pos)); } + +void TimelineHeader::resized_scroll_listener(double d) { + update_zoom(zoom * d); +} diff --git a/ui/timelineheader.h b/ui/timelineheader.h index 4bced42af..884a44adf 100644 --- a/ui/timelineheader.h +++ b/ui/timelineheader.h @@ -30,6 +30,7 @@ public slots: void set_scroll(int); void set_visible_in(long i); void show_context_menu(const QPoint &pos); + void resized_scroll_listener(double d); protected: void paintEvent(QPaintEvent*);