Merge branch 'master' into ui-optimizations

This commit is contained in:
itsmattkc
2020-10-05 15:19:57 +11:00
24 changed files with 334 additions and 15 deletions
+5 -1
View File
@@ -168,6 +168,10 @@ elseif (APPLE)
)
endif()
if(UNIX AND NOT APPLE)
install(TARGETS ${OLIVE_TARGET} RUNTIME DESTINATION bin)
endif()
# Enable Crashpad if found
if (GoogleCrashpad_FOUND)
set(OLIVE_DEFINITIONS ${OLIVE_DEFINITIONS} USE_CRASHPAD)
@@ -225,7 +229,7 @@ if (GoogleCrashpad_FOUND)
set(MINIDUMP_STACKWALK "minidump_stackwalk${CMAKE_EXECUTABLE_SUFFIX}")
if(UNIX AND NOT APPLE)
install(TARGETS ${OLIVE_TARGET} ${OLIVE_CRASH_TARGET} RUNTIME DESTINATION bin)
install(TARGETS ${OLIVE_CRASH_TARGET} RUNTIME DESTINATION bin)
install(PROGRAMS ${CRASHPAD_LIBRARY_DIRS}/${CRASHPAD_HANDLER} DESTINATION bin)
install(PROGRAMS ${BREAKPAD_BIN_DIR}/${MINIDUMP_STACKWALK} DESTINATION bin)
endif()
+1
View File
@@ -68,6 +68,7 @@
OLIVE_NAMESPACE_ENTER
Core* Core::instance_ = nullptr;
const uint Core::kProjectVersion = 201003;
Core::Core(const CoreParams& params) :
main_window_(nullptr),
+2
View File
@@ -268,6 +268,8 @@ public:
*/
void CacheActiveSequence(bool in_out_only);
static const uint kProjectVersion;
public slots:
/**
* @brief Starts an open file dialog to load a project from file
+5
View File
@@ -27,6 +27,11 @@ Node *AudioInput::copy() const
return new AudioInput();
}
Stream::Type AudioInput::type() const
{
return Stream::kAudio;
}
QString AudioInput::Name() const
{
return tr("Audio Input");
+2
View File
@@ -32,6 +32,8 @@ public:
virtual Node* copy() const override;
virtual Stream::Type type() const override;
virtual QString Name() const override;
virtual QString ShortName() const override;
virtual QString id() const override;
+5
View File
@@ -50,6 +50,11 @@ void MediaInput::SetFootage(StreamPtr f)
footage_input_->set_standard_value(QVariant::fromValue(f));
}
bool MediaInput::IsMedia() const
{
return true;
}
void MediaInput::Retranslate()
{
footage_input_->set_name(tr("Footage"));
+6
View File
@@ -23,6 +23,7 @@
#include "codec/decoder.h"
#include "node/node.h"
#include "project/item/footage/stream.h"
OLIVE_NAMESPACE_ENTER
@@ -35,11 +36,16 @@ class MediaInput : public Node
public:
MediaInput();
virtual Stream::Type type() const = 0;
virtual QList<CategoryID> Category() const override;
StreamPtr footage();
void SetFootage(StreamPtr f);
virtual bool IsMedia() const override;
virtual void Retranslate() override;
virtual NodeValueTable Value(NodeValueDatabase& value) const override;
+5
View File
@@ -36,6 +36,11 @@ Node *VideoInput::copy() const
return new VideoInput();
}
Stream::Type VideoInput::type() const
{
return Stream::kVideo;
}
QString VideoInput::Name() const
{
return tr("Video Input");
+2
View File
@@ -35,6 +35,8 @@ public:
virtual Node* copy() const override;
virtual Stream::Type type() const override;
virtual QString Name() const override;
virtual QString ShortName() const override;
virtual QString id() const override;
+5
View File
@@ -440,6 +440,11 @@ bool Node::IsTrack() const
return false;
}
bool Node::IsMedia() const
{
return false;
}
const QList<NodeParam *>& Node::parameters() const
{
return params_;
+9
View File
@@ -363,6 +363,15 @@ public:
*/
virtual bool IsTrack() const;
/**
* @brief Returns whether this Node is a "Media" type or not
*
* You shouldn't ever need to override this since all derivatives of Media will automatically have this set to true.
* It's just a more convenient way of checking than dynamic_casting.
*/
virtual bool IsMedia() const;
/**
* @brief The main processing function
*
+1
View File
@@ -25,6 +25,7 @@
#include <QUrl>
#include "core.h"
#include "node/input/media/media.h"
OLIVE_NAMESPACE_ENTER
+1
View File
@@ -25,6 +25,7 @@
#include "project.h"
#include "undo/undocommand.h"
#include "node/block/block.h"
OLIVE_NAMESPACE_ENTER
+17 -1
View File
@@ -25,6 +25,7 @@
#include <QXmlStreamReader>
#include "common/xmlutils.h"
#include "core.h"
OLIVE_NAMESPACE_ENTER
@@ -45,7 +46,17 @@ bool ProjectLoadTask::Run()
if (reader.name() == QStringLiteral("olive")) {
while(XMLReadNextStartElement(&reader)) {
if (reader.name() == QStringLiteral("version")) {
qDebug() << "Project version:" << reader.readElementText();
uint project_version = reader.readElementText().toUInt();
if (project_version > Core::kProjectVersion) {
// Project is newer than we support
SetError(tr("This project is newer than this version of Olive and cannot be opened."));
return false;
} else if (project_version < 201003) { // Change this if we drop support for a project version
// Project is older than we support
SetError(tr("This project is from a version of Olive that is no longer supported in this version."));
return false;
}
} else if (reader.name() == QStringLiteral("project")) {
ProjectPtr project = std::make_shared<Project>();
@@ -66,6 +77,11 @@ bool ProjectLoadTask::Run()
reader.skipCurrentElement();
}
}
} else if (reader.name() == QStringLiteral("project")) {
// 0.1 projects use "project" as the root instead of Olive. We don't currently support
// these projects
SetError(tr("This project is from a version of Olive that is no longer supported in this version."));
return false;
} else {
reader.skipCurrentElement();
}
+4 -1
View File
@@ -25,6 +25,7 @@
#include <QXmlStreamWriter>
#include "common/filefunctions.h"
#include "core.h"
OLIVE_NAMESPACE_ENTER
@@ -49,7 +50,9 @@ bool ProjectSaveTask::Run()
writer.writeStartElement("olive");
writer.writeTextElement("version", "0.2.0");
// Version is stored in YYMMDD from whenever the project format was last changed
// Allows easy integer math for checking project versions.
writer.writeTextElement("version", QString::number(Core::kProjectVersion));
project_->Save(&writer);
+16
View File
@@ -154,6 +154,15 @@ void NodeRemoveCommand::redo_internal()
// Take nodes from graph (TakeNode() will automatically disconnect edges)
foreach (Node* n, nodes_) {
// If the node is a block, unlink any linked blocks before removing
if (n->IsBlock()) {
Block *b = static_cast<Block *>(n);
if (b->HasLinks()) {
BlockUnlinkAllCommand *unlink_command = new BlockUnlinkAllCommand(b);
unlink_command->redo();
block_unlink_commands_.append(unlink_command);
}
}
graph_->TakeNode(n, &memory_manager_);
}
}
@@ -165,12 +174,19 @@ void NodeRemoveCommand::undo_internal()
graph_->AddNode(n);
}
// Relink any blocks that were unlinked
foreach(BlockUnlinkAllCommand* command, block_unlink_commands_) {
command->undo();
delete command;
}
// Re-connect edges
foreach (NodeEdgePtr edge, edges_) {
NodeParam::ConnectEdge(edge->output(), edge->input());
}
edges_.clear();
block_unlink_commands_.clear();
}
Project *NodeRemoveCommand::GetRelevantProject() const
+2
View File
@@ -27,6 +27,7 @@
#include "node/node.h"
#include "nodeviewitem.h"
#include "undo/undocommand.h"
#include "widget/timelinewidget/undo/undo.h"
OLIVE_NAMESPACE_ENTER
@@ -112,6 +113,7 @@ private:
NodeGraph* graph_;
QList<Node*> nodes_;
QList<NodeEdgePtr> edges_;
QList<BlockUnlinkAllCommand*> block_unlink_commands_;
};
class NodeRemoveWithExclusiveDeps : public UndoCommand {
@@ -32,5 +32,7 @@ set(OLIVE_SOURCES
widget/projectexplorer/projectexplorericonviewitemdelegate.cpp
widget/projectexplorer/projectexplorernavigation.h
widget/projectexplorer/projectexplorernavigation.cpp
widget/projectexplorer/projectexplorerundo.h
widget/projectexplorer/projectexplorerundo.cpp
PARENT_SCOPE
)
+125 -6
View File
@@ -23,6 +23,7 @@
#include <QDebug>
#include <QDesktopServices>
#include <QDir>
#include <QMessageBox>
#include <QProcess>
#include <QUrl>
#include <QVBoxLayout>
@@ -31,11 +32,14 @@
#include "core.h"
#include "dialog/footageproperties/footageproperties.h"
#include "dialog/sequence/sequence.h"
#include "projectexplorerundo.h"
#include "task/precache/precachetask.h"
#include "task/taskmanager.h"
#include "widget/menu/menu.h"
#include "widget/menu/menushared.h"
#include "window/mainwindow/mainwindow.h"
#include "widget/timelinewidget/timelinewidget.h"
#include "widget/nodeview/nodeviewundo.h"
OLIVE_NAMESPACE_ENTER
@@ -536,6 +540,30 @@ void ProjectExplorer::DeselectAll()
CurrentView()->selectionModel()->clearSelection();
}
QList<MediaInput *> ProjectExplorer::GetMediaNodesUsingFootage(Footage *item)
{
QList<MediaInput *> list;
// Get all sequences.
QList<ItemPtr> sequences = model_.project()->get_items_of_type(Item::kSequence);
// Footage can contain multiple streams, all of which need to be dealt with
foreach (ItemPtr s, sequences) {
const QList<Node*>& nodes = static_cast<Sequence*>(s.get())->nodes();
foreach (Node* n, nodes) {
if (n->IsMedia()) {
MediaInput* media_node = static_cast<MediaInput*>(n);
if (media_node->footage()->footage() == item) {
list.append(media_node);
}
}
}
}
return list;
}
void ProjectExplorer::DeleteSelected()
{
QList<Item*> selected = SelectedItems();
@@ -547,18 +575,109 @@ void ProjectExplorer::DeleteSelected()
QUndoCommand* command = new QUndoCommand();
foreach (Item* item, selected) {
ItemPtr item_ptr = item->get_shared_ptr();
// If this is a sequence, close it
if (item_ptr->type() == Item::kSequence) {
Sequence* s = static_cast<Sequence*>(item_ptr.get());
// Verify whether this item is in use anywhere
switch (item->type()) {
case Item::kSequence:
{
// If this is a sequence, check if it's open and close it if necessary
Sequence* s = static_cast<Sequence*>(item);
if (Core::instance()->main_window()->IsSequenceOpen(s)) {
Core::instance()->main_window()->CloseSequence(s);
}
break;
}
case Item::kFootage:
{
// If this is footage, check if it's used anywhere in any sequence
Footage* footage = static_cast<Footage*>(item);
QList<MediaInput*> footage_nodes = GetMediaNodesUsingFootage(footage);
if (!footage_nodes.isEmpty()) {
// Footage is in use, show messagebox asking what to do about it
QList<Sequence*> used_in_sequences;
// Compile list of sequences to assist the user in making this decision
foreach (MediaInput* i, footage_nodes) {
Sequence* media_parent = static_cast<Sequence*>(i->parent());
if (!used_in_sequences.contains(media_parent)) {
used_in_sequences.append(media_parent);
}
}
QString sequence_list_str;
foreach (Sequence* s, used_in_sequences) {
sequence_list_str.append(QStringLiteral("%1\n").arg(s->name()));
}
QMessageBox msgbox(this);
msgbox.setWindowTitle(tr("Confirm Footage Deletion"));
msgbox.setText(tr("The footage \"%1\" is currently used in the following sequence(s):\n\n"
"%2\nWhat would you like to do with these clips?")
.arg(footage->filename(), sequence_list_str));
msgbox.setIcon(QMessageBox::Warning);
// Set up buttons
QPushButton* offline_btn = msgbox.addButton(tr("Offline Footage"), QMessageBox::YesRole);
QPushButton* delete_clip_btn = msgbox.addButton(tr("Delete Clips"), QMessageBox::NoRole);
msgbox.addButton(QMessageBox::Cancel);
// Run messagebox
msgbox.exec();
if (msgbox.clickedButton() == offline_btn || msgbox.clickedButton() == delete_clip_btn) {
// For safety, even if we're deleting clips, we'll offline the footage nodes too
new OfflineFootageCommand(footage_nodes, command);
}
if (msgbox.clickedButton() == delete_clip_btn) {
// Delete any blocks that use this footage
QList<Block*> blocks_to_remove;
foreach (Sequence* s, used_in_sequences) {
foreach (TrackOutput* track, s->viewer_output()->GetTracks()) {
foreach (Block* b, track->Blocks()) {
QList<Node*> deps = b->GetDependencies();
foreach (MediaInput* i, footage_nodes) {
if (deps.contains(i)) {
blocks_to_remove.append(b);
break;
}
}
}
}
}
TimelineWidget::ReplaceBlocksWithGaps(blocks_to_remove, true, command);
} else if (msgbox.clickedButton() != offline_btn) {
// Must have cancelled
delete command;
return;
}
}
// Close footage if currently open in footage panel
FootageViewerPanel* footage_panel = PanelManager::instance()->GetPanelsOfType<FootageViewerPanel>().first();
if (footage_panel->GetSelectedFootage().contains(footage)) {
footage_panel->SetFootage(nullptr);
}
break;
}
case Item::kFolder:
// Do nothing
break;
}
new ProjectViewModel::RemoveItemCommand(&model_, item_ptr, command);
new ProjectViewModel::RemoveItemCommand(&model_, item->get_shared_ptr(), command);
}
Core::instance()->undo_stack()->pushIfHasChildren(command);
@@ -25,6 +25,7 @@
#include <QTimer>
#include <QTreeView>
#include "node/input/media/media.h"
#include "project/project.h"
#include "project/projectviewmodel.h"
#include "widget/projectexplorer/projectexplorericonview.h"
@@ -101,6 +102,18 @@ signals:
void DoubleClickedItem(Item* item);
private:
/**
* @brief Check if an item is in use anywhere and return any relevant input nodes
*/
QList<MediaInput*> GetMediaNodesUsingFootage(Footage* item);
/**
* @brief Get all the blocks that solely rely on an input node
*
* Ignores blocks that depend on multiple inputs
*/
QList<Block*> GetFootageBlocks(QList<Node*> nodes);
/**
* @brief Simple convenience function for adding a view to this stacked widget
*
@@ -0,0 +1,54 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "projectexplorerundo.h"
OLIVE_NAMESPACE_ENTER
OfflineFootageCommand::OfflineFootageCommand(const QList<MediaInput *> &media, QUndoCommand* parent) :
UndoCommand(parent)
{
foreach (MediaInput* i, media) {
stream_data_.insert(i, i->footage());
}
project_ = static_cast<Sequence*>(media.first()->parent())->project();
}
Project *OfflineFootageCommand::GetRelevantProject() const
{
return project_;
}
void OfflineFootageCommand::redo_internal()
{
for (auto it=stream_data_.cbegin(); it!=stream_data_.cend(); it++) {
it.key()->SetFootage(nullptr);
}
}
void OfflineFootageCommand::undo_internal()
{
for (auto it=stream_data_.cbegin(); it!=stream_data_.cend(); it++) {
it.key()->SetFootage(it.value());
}
}
OLIVE_NAMESPACE_EXIT
@@ -0,0 +1,52 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef PROJECTEXPLORERUNDO_H
#define PROJECTEXPLORERUNDO_H
#include "node/input/media/media.h"
#include "undo/undocommand.h"
OLIVE_NAMESPACE_ENTER
/**
* @brief An undo command for offlining footage when it is deleted from the project explorer
*/
class OfflineFootageCommand : public UndoCommand {
public:
OfflineFootageCommand(const QList<MediaInput*>& media, QUndoCommand* parent = nullptr);
virtual Project* GetRelevantProject() const override;
protected:
virtual void redo_internal() override;
virtual void undo_internal() override;
private:
QMap<MediaInput*, StreamPtr> stream_data_;
Project* project_;
};
OLIVE_NAMESPACE_EXIT
#endif // PROJECTEXPLORERUNDO_H
@@ -487,8 +487,6 @@ void TimelineWidget::ReplaceBlocksWithGaps(const QList<Block *> &blocks,
new TrackReplaceBlockWithGapCommand(original_track, b, command);
if (remove_from_graph) {
new BlockUnlinkAllCommand(b, command);
new NodeRemoveWithExclusiveDeps(static_cast<NodeGraph*>(b->parent()), b, command);
}
}
-4
View File
@@ -244,10 +244,6 @@ void TrackRippleRemoveAreaCommand::redo_internal()
foreach (Block* remove_block, removed_blocks_) {
track_->RippleRemoveBlock(remove_block);
BlockUnlinkAllCommand* unlink_command = new BlockUnlinkAllCommand(remove_block);
unlink_command->redo();
remove_block_commands_.append(unlink_command);
NodeRemoveWithExclusiveDeps* remove_command = new NodeRemoveWithExclusiveDeps(static_cast<NodeGraph*>(remove_block->parent()), remove_block);
remove_command->redo();
remove_block_commands_.append(remove_command);