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 @@
1
+
+ ResizableScrollBar
+ QScrollBar
+
+
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 @@
1
+
+ ResizableScrollBar
+ QScrollBar
+
+
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
+
+ 1
+
+
+ ResizableScrollBar
+ QScrollBar
+
+
ViewerContainer
QWidget
@@ -240,12 +251,6 @@
QLabel
-
- TimelineHeader
- QWidget
-
- 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*);