Merge branch 'master' into gizmos

This commit is contained in:
itsmattkc
2020-05-02 17:06:49 +10:00
130 changed files with 2310 additions and 981 deletions
+6 -3
View File
@@ -34,10 +34,13 @@ REM Add Qt and FFmpeg directory to path
set PATH=%PATH%;C:\Qt\5.13.2\msvc2017_64\bin;%APPVEYOR_BUILD_FOLDER%\%FFMPEG_VER%-dev
REM Run cmake
cmake -G "NMake Makefiles" . -DCMAKE_TOOLCHAIN_FILE=c:/Tools/vcpkg/scripts/buildsystems/vcpkg.cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo
cmake -G "Ninja" . -DCMAKE_TOOLCHAIN_FILE=c:/Tools/vcpkg/scripts/buildsystems/vcpkg.cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo
REM Build with JOM
C:\Qt\Tools\QtCreator\bin\jom.exe || exit /B 1
REM Build with Ninja
ninja.exe || exit /B 1
REM If this is a pull request, no further packaging/deploying needs to be done
if NOT "%APPVEYOR_PULL_REQUEST_NUMBER%" == "" goto end
REM Start building package
mkdir olive-editor
+13 -2
View File
@@ -1,8 +1,19 @@
# Contributing to Olive
Thank you for your interest in contributing to Olive! In order to keep the code as readable and maintainable as possible, code submitted should abide by the following standards:
### Standards
When contributing to Olive, it's recommended to use the following rules:
* [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html)
* 120 column limit
* The code style generally follows the [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html) including, but not limited to:
* Indentation is 2 spaces wide, spaces only (no tabs)
* `lowercase_underscored_variable_names`
* `lowercase_underscored_functions()` or `SentenceCaseFunctions()`
* `class SentenceCaseClassesAndStructs {}`
* `kSentenceCaseConstants` prepended with a lowercase `k`
* `UPPERCASE_UNDERSCORED_MACROS` for variables or same style as functions for macro functions
* `class_member_variables_` end with a `_`
* 100 column limit (where it doesn't impair readability)
* Unix line endings (only LF no CRLF)
* Javadoc documentation where appropriate
+10
View File
@@ -75,6 +75,8 @@ if(APPLE)
MACOSX_BUNDLE_ICON_FILE olive.icns
RESOURCE "${OLIVE_ICON}"
)
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.9")
endif()
# Set compiler definitions
@@ -107,6 +109,14 @@ else()
)
endif()
if(UNIX AND NOT APPLE)
target_compile_options(
${OLIVE_TARGET}
PRIVATE
-rdynamic
)
endif()
# Set include directories
target_include_directories(
${OLIVE_TARGET}
+15 -4
View File
@@ -40,6 +40,7 @@ EncodingParams::EncodingParams() :
video_bit_rate_(0),
video_max_bit_rate_(0),
video_buffer_size_(0),
video_threads_(0),
audio_enabled_(false)
{
}
@@ -63,26 +64,31 @@ void EncodingParams::EnableAudio(const AudioRenderingParams &audio_params, const
audio_codec_ = acodec;
}
void EncodingParams::SetVideoOption(const QString &key, const QString &value)
void EncodingParams::set_video_option(const QString &key, const QString &value)
{
video_opts_.insert(key, value);
}
void EncodingParams::SetVideoBitRate(const int64_t &rate)
void EncodingParams::set_video_bit_rate(const int64_t &rate)
{
video_bit_rate_ = rate;
}
void EncodingParams::SetVideoMaxBitRate(const int64_t &rate)
void EncodingParams::set_video_max_bit_rate(const int64_t &rate)
{
video_max_bit_rate_ = rate;
}
void EncodingParams::SetVideoBufferSize(const int64_t &sz)
void EncodingParams::set_video_buffer_size(const int64_t &sz)
{
video_buffer_size_ = sz;
}
void EncodingParams::set_video_threads(const int &threads)
{
video_threads_ = threads;
}
const QString &EncodingParams::filename() const
{
return filename_;
@@ -123,6 +129,11 @@ const int64_t &EncodingParams::video_buffer_size() const
return video_buffer_size_;
}
const int &EncodingParams::video_threads() const
{
return video_threads_;
}
bool EncodingParams::audio_enabled() const
{
return audio_enabled_;
+7 -4
View File
@@ -43,10 +43,11 @@ public:
void EnableVideo(const VideoRenderingParams& video_params, const QString& vcodec);
void EnableAudio(const AudioRenderingParams& audio_params, const QString& acodec);
void SetVideoOption(const QString& key, const QString& value);
void SetVideoBitRate(const int64_t& rate);
void SetVideoMaxBitRate(const int64_t& rate);
void SetVideoBufferSize(const int64_t& sz);
void set_video_option(const QString& key, const QString& value);
void set_video_bit_rate(const int64_t& rate);
void set_video_max_bit_rate(const int64_t& rate);
void set_video_buffer_size(const int64_t& sz);
void set_video_threads(const int& threads);
const QString& filename() const;
@@ -57,6 +58,7 @@ public:
const int64_t& video_bit_rate() const;
const int64_t& video_max_bit_rate() const;
const int64_t& video_buffer_size() const;
const int& video_threads() const;
bool audio_enabled() const;
const QString& audio_codec() const;
@@ -75,6 +77,7 @@ private:
int64_t video_bit_rate_;
int64_t video_max_bit_rate_;
int64_t video_buffer_size_;
int video_threads_;
bool audio_enabled_;
QString audio_codec_;
+8 -1
View File
@@ -468,7 +468,14 @@ bool FFmpegEncoder::SetupCodecContext(AVStream* stream, AVCodecContext* codec_ct
}
AVDictionary* codec_opts = nullptr;
av_dict_set(&codec_opts, "threads", "auto", 0);
// Set thread count
if (params().video_threads() == 0) {
av_dict_set(&codec_opts, "threads", "auto", 0);
} else {
QString thread_val = QString::number(params().video_threads());
av_dict_set(&codec_opts, "threads", thread_val.toUtf8(), 0);
}
// Try to open encoder
error_code = avcodec_open2(codec_ctx, codec, &codec_opts);
+30 -4
View File
@@ -192,8 +192,7 @@ FramePtr OIIODecoder::RetrieveVideo(const rational &timecode, const int& divider
if (divider == 1) {
// Just a simple copy
buffer_->get_pixels(OIIO::ROI(), buffer_->spec().format, frame->data(), OIIO::AutoStride, frame->linesize_bytes());
BufferToFrame(buffer_, frame);
} else {
@@ -204,8 +203,7 @@ FramePtr OIIODecoder::RetrieveVideo(const rational &timecode, const int& divider
qWarning() << "OIIO resize failed";
}
// Just a simple copy
dst.get_pixels(OIIO::ROI(), dst.spec().format, frame->data(), OIIO::AutoStride, frame->linesize_bytes());
BufferToFrame(&dst, frame);
}
@@ -233,6 +231,34 @@ QString OIIODecoder::GetIndexFilename()
return QString();
}
void OIIODecoder::BufferToFrame(OIIO::ImageBuf *buf, FramePtr frame)
{
#if OIIO_VERSION < 20112
//
// Workaround for OIIO bug that ignores destination stride in versions OLDER than 2.1.12
//
// See more: https://github.com/OpenImageIO/oiio/pull/2487
//
for (int i=0;i<buf->spec().height;i++) {
int width_in_bytes = frame->width() * PixelFormat::BytesPerPixel(frame->format());
memcpy(frame->data() + i * frame->linesize_bytes(),
#if OIIO_VERSION < 10903
reinterpret_cast<const char*>(buf->localpixels()) + i * width_in_bytes,
#else
reinterpret_cast<const char*>(buf->localpixels()) + i * buf->scanline_stride(),
#endif
width_in_bytes);
}
#else
buf->get_pixels(OIIO::ROI(),
buf->spec().format,
frame->data(),
OIIO::AutoStride,
frame->linesize_bytes());
#endif
}
bool OIIODecoder::FileTypeIsSupported(const QString& fn)
{
// We prioritize OIIO over FFmpeg to pick up still images more effectively, but some OIIO decoders (notably OpenJPEG)
+3
View File
@@ -48,12 +48,15 @@ public:
virtual QString GetIndexFilename() override;
static void BufferToFrame(OIIO::ImageBuf* buf, FramePtr frame);
private:
#if OIIO_VERSION < 10903
OIIO::ImageInput* image_;
#else
std::unique_ptr<OIIO::ImageInput> image_;
#endif
static bool FileTypeIsSupported(const QString& fn);
static int GetImageSequenceDigitCount(const QString& filename);
+3 -2
View File
@@ -41,14 +41,15 @@
OLIVE_NAMESPACE_ENTER
void crash_handler(int sig) {
void crash_handler(int sig)
{
QString log_path = QDir(FileFunctions::GetTempFilePath()).filePath(QStringLiteral("olive_crash"));
QFile output(log_path);
output.open(QFile::WriteOnly);
QTextStream ostream(&output);
ostream << "Signal: " << sig << "\n\n";
ostream << "Version: " << GITHASH << "\nSignal: " << sig << "\n\n";
#if defined(Q_OS_WINDOWS)
// Use Windows stackwalk API
+16 -1
View File
@@ -26,15 +26,28 @@
OLIVE_NAMESPACE_ENTER
Node* XMLLoadNode(QXmlStreamReader* reader) {
Node* XMLLoadNode(QXmlStreamReader* reader)
{
QString node_id;
quintptr node_ptr = 0;
QPointF node_pos;
QString node_label;
XMLAttributeLoop(reader, attr) {
if (attr.name() == QStringLiteral("id")) {
node_id = attr.value().toString();
} else if (attr.name() == QStringLiteral("ptr")) {
node_ptr = attr.value().toULongLong();
} else if (attr.name() == QStringLiteral("pos")) {
QStringList pos = attr.value().toString().split(':');
// Protection in case this file has been messed with
if (pos.size() == 2) {
node_pos.setX(pos.at(0).toDouble());
node_pos.setY(pos.at(1).toDouble());
}
} else if (attr.name() == QStringLiteral("label")) {
node_label = attr.value().toString();
}
}
@@ -47,6 +60,8 @@ Node* XMLLoadNode(QXmlStreamReader* reader) {
if (node) {
node->setProperty("xml_ptr", node_ptr);
node->SetPosition(node_pos);
node->SetLabel(node_label);
} else {
qWarning() << "Failed to load" << node_id << "- no node with that ID is installed";
}
+2 -2
View File
@@ -536,9 +536,9 @@ void Core::DeclareTypesForQt()
qRegisterMetaType<AudioRenderingParams>();
qRegisterMetaType<NodeKeyframe::Type>();
qRegisterMetaType<Decoder::RetrieveState>();
qRegisterMetaType<TimeRange>();
qRegisterMetaType<OLIVE_NAMESPACE::TimeRange>();
qRegisterMetaType<Color>();
qRegisterMetaType<ProjectPtr>();
qRegisterMetaType<OLIVE_NAMESPACE::ProjectPtr>();
}
void Core::StartGUI(bool full_screen)
+2 -2
View File
@@ -477,12 +477,12 @@ private:
private slots:
void SaveAutorecovery();
void ProjectSaveSucceeded(ProjectPtr p);
void ProjectSaveSucceeded(OLIVE_NAMESPACE::ProjectPtr p);
/**
* @brief Adds a project to the "open projects" list
*/
void AddOpenProject(ProjectPtr p);
void AddOpenProject(OLIVE_NAMESPACE::ProjectPtr p);
void ImportTaskComplete(QUndoCommand* command);
+2
View File
@@ -20,6 +20,8 @@ set(OLIVE_SOURCES
${OLIVE_SOURCES}
dialog/export/export.h
dialog/export/export.cpp
dialog/export/exportadvancedvideodialog.h
dialog/export/exportadvancedvideodialog.cpp
dialog/export/exportaudiotab.h
dialog/export/exportaudiotab.cpp
dialog/export/exportcodec.h
+5 -5
View File
@@ -78,7 +78,7 @@ void H264Section::AddOpts(EncodingParams *params)
if (method == kConstantRateFactor) {
// Simply set CRF value
params->SetVideoOption(QStringLiteral("crf"), QString::number(crf_section_->GetValue()));
params->set_video_option(QStringLiteral("crf"), QString::number(crf_section_->GetValue()));
} else {
@@ -95,11 +95,11 @@ void H264Section::AddOpts(EncodingParams *params)
}
// Disable CRF encoding
params->SetVideoOption(QStringLiteral("crf"), QStringLiteral("-1"));
params->set_video_option(QStringLiteral("crf"), QStringLiteral("-1"));
params->SetVideoBitRate(target_rate);
params->SetVideoMaxBitRate(max_rate);
params->SetVideoBufferSize(2000000);
params->set_video_bit_rate(target_rate);
params->set_video_max_bit_rate(max_rate);
params->set_video_buffer_size(2000000);
}
}
+104 -34
View File
@@ -238,10 +238,13 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) :
void ExportDialog::accept()
{
if (!video_enabled_->isChecked() && !audio_enabled_->isChecked()) {
QMessageBox::critical(this,
tr("Invalid parameters"),
tr("Both video and audio are disabled. There's nothing to export."),
QMessageBox::Ok);
QMessageBox b(this);
b.setIcon(QMessageBox::Critical);
b.setWindowModality(Qt::WindowModal);
b.setWindowTitle(tr("Invalid parameters"));
b.setText(tr("Both video and audio are disabled. There's nothing to export."));
b.addButton(QMessageBox::Ok);
b.exec();
return;
}
@@ -251,10 +254,16 @@ void ExportDialog::accept()
// If it doesn't, see if the user wants to append it automatically. If not, we don't abort the export.
if (!filename_edit_->text().endsWith(necessary_ext, Qt::CaseInsensitive)) {
if (QMessageBox::warning(this,
tr("Invalid filename"),
tr("The filename must contain the extension \".%1\". Would you like to append it automatically?"),
QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
QMessageBox b(this);
b.setIcon(QMessageBox::Warning);
b.setWindowModality(Qt::WindowModal);
b.setWindowTitle(tr("Invalid filename"));
b.setText(tr("The filename must contain the extension \".%1\". Would you like to append it "
"automatically?"));
b.addButton(QMessageBox::Yes);
b.addButton(QMessageBox::No);
if (b.exec() == QMessageBox::Yes) {
filename_edit_->setText(filename_edit_->text().append(necessary_ext));
} else {
return;
@@ -267,20 +276,45 @@ void ExportDialog::accept()
// If the directory does not exist, try to create it
if (!QDir(file_info.path()).mkpath(QStringLiteral("."))) {
QMessageBox::critical(this,
tr("Failed to create output directory"),
tr("The intended output directory doesn't exist and Olive couldn't create it. Please choose a different filename."),
QMessageBox::Ok);
QMessageBox b(this);
b.setIcon(QMessageBox::Critical);
b.setWindowModality(Qt::WindowModal);
b.setWindowTitle(tr("Failed to create output directory"));
b.setText(tr("The intended output directory doesn't exist and Olive couldn't create it. "
"Please choose a different filename."));
b.addButton(QMessageBox::Ok);
b.exec();
return;
}
// Validate if the file exists and whether the user wishes to overwrite it
if (file_info.exists()
&& QMessageBox::warning(this,
tr("Confirm Overwrite"),
tr("The file \"%1\" already exists. Do you want to overwrite it?").arg(filename_edit_->text()),
QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) {
return;
if (file_info.exists()) {
QMessageBox b(this);
b.setIcon(QMessageBox::Warning);
b.setWindowModality(Qt::WindowModal);
b.setWindowTitle(tr("Confirm Overwrite"));
b.setText(tr("The file \"%1\" already exists. Do you want to overwrite it?")
.arg(filename_edit_->text()));
b.addButton(QMessageBox::Yes);
b.addButton(QMessageBox::No);
if (b.exec() == QMessageBox::No) {
return;
}
}
// Validate video resolution
if (video_enabled_->isChecked()) {
if (video_tab_->width_slider()->GetValue() % 2 != 0
|| video_tab_->height_slider()->GetValue() % 2 != 0) {
QMessageBox b(this);
b.setIcon(QMessageBox::Critical);
b.setWindowModality(Qt::WindowModal);
b.setWindowTitle(tr("Invalid parameters"));
b.setText(tr("Width and height must be multiples of 2."));
b.exec();
return;
}
}
// Set up export parameters
@@ -307,10 +341,15 @@ void ExportDialog::accept()
void ExportDialog::closeEvent(QCloseEvent *e)
{
if (exporter_) {
if (QMessageBox::question(this,
tr("Still Exporting"),
tr("This sequence is still being exported. Do you wish to cancel it?"),
QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
QMessageBox b(this);
b.setIcon(QMessageBox::Question);
b.setWindowModality(Qt::WindowModal);
b.setWindowTitle(tr("Still Exporting"));
b.setText(tr("This sequence is still being exported. Do you wish to cancel it?"));
b.addButton(QMessageBox::Yes);
b.addButton(QMessageBox::No);
if (b.exec() == QMessageBox::Yes) {
CancelExport();
} else {
e->ignore();
@@ -378,10 +417,27 @@ void ExportDialog::ResolutionChanged()
if (video_tab_->maintain_aspect_checkbox()->isChecked()) {
// Keep aspect ratio maintained
if (sender() == video_tab_->height_slider()) {
video_tab_->width_slider()->SetValue(qRound(static_cast<double>(video_tab_->height_slider()->GetValue()) * video_aspect_ratio_));
// Convert height to float
double new_width = video_tab_->height_slider()->GetValue();
// Generate width from aspect ratio
new_width *= video_aspect_ratio_;
// Align to even number and set
video_tab_->width_slider()->SetValue(AlignEvenNumber(new_width));
} else {
// This catches both the width slider changing and the maintain aspect ratio checkbox changing
video_tab_->height_slider()->SetValue(qRound(static_cast<double>(video_tab_->width_slider()->GetValue()) / video_aspect_ratio_));
// Convert width to float
double new_height = video_tab_->width_slider()->GetValue();
// Generate height from aspect ratio
new_height /= video_aspect_ratio_;
// Align to even number and set
video_tab_->height_slider()->SetValue(AlignEvenNumber(new_height));
}
}
@@ -500,8 +556,15 @@ void ExportDialog::SetUIElementsEnabled(bool enabled)
preferences_area_->setEnabled(enabled);
buttons_->setEnabled(enabled);
progress_bar_->setEnabled(!enabled);
export_cancel_btn_->setEnabled(!enabled);
elapsed_label_->setEnabled(!enabled);
remaining_label_->setEnabled(!enabled);
}
int ExportDialog::AlignEvenNumber(double d)
{
return qCeil(d * 0.5) * 2;
}
ExportParams ExportDialog::GenerateParams() const
@@ -531,6 +594,8 @@ ExportParams ExportDialog::GenerateParams() const
params.EnableVideo(video_render_params,
video_codec.id());
params.set_video_threads(video_tab_->threads());
video_tab_->GetCodecSection()->AddOpts(&params);
params.set_color_transform(video_tab_->CurrentOCIOColorSpace());
@@ -609,10 +674,13 @@ void ExportDialog::ExporterIsDone()
progress_timer_.stop();
if (exporter_->GetExportStatus()) {
QMessageBox::information(this,
tr("Export Status"),
tr("Export completed successfully."),
QMessageBox::Ok);
QMessageBox b(this);
b.setIcon(QMessageBox::Information);
b.setWindowModality(Qt::WindowModal);
b.setWindowTitle(tr("Export Status"));
b.setText(tr("Export completed successfully."));
b.addButton(QMessageBox::Ok);
b.exec();
QDialog::accept();
} else {
@@ -621,16 +689,18 @@ void ExportDialog::ExporterIsDone()
Core::instance()->main_window()->SetTaskbarButtonState(TBPF_ERROR);
#endif
QMessageBox::critical(this,
tr("Export Status"),
tr("Export failed: %1").arg(exporter_->GetExportError()),
QMessageBox::Ok);
QMessageBox b(this);
b.setIcon(QMessageBox::Critical);
b.setWindowModality(Qt::WindowModal);
b.setWindowTitle(tr("Export Status"));
b.setText(tr("Export failed: %1").arg(exporter_->GetExportError()));
b.addButton(QMessageBox::Ok);
b.exec();
}
SetUIElementsEnabled(true);
}
exporter_->deleteLater();
exporter_ = nullptr;
cancelled_ = false;
+2
View File
@@ -55,6 +55,8 @@ private:
void SetUIElementsEnabled(bool enabled);
static int AlignEvenNumber(double d);
ExportParams GenerateParams() const;
static QString TimeToString(int64_t ms);
@@ -0,0 +1,42 @@
#include "exportadvancedvideodialog.h"
#include <QDialogButtonBox>
#include <QGridLayout>
#include <QLabel>
OLIVE_NAMESPACE_ENTER
ExportAdvancedVideoDialog::ExportAdvancedVideoDialog(QWidget *parent) :
QDialog(parent)
{
setWindowTitle(tr("Advanced"));
QGridLayout* layout = new QGridLayout(this);
int row = 0;
layout->addWidget(new QLabel(tr("Threads:")), row, 0);
thread_slider_ = new IntegerSlider();
thread_slider_->SetMinimum(0);
layout->addWidget(thread_slider_, row, 1);
row++;
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
connect(buttons, &QDialogButtonBox::accepted, this, &ExportAdvancedVideoDialog::accept);
connect(buttons, &QDialogButtonBox::rejected, this, &ExportAdvancedVideoDialog::reject);
layout->addWidget(buttons, row, 0, 1, 2);
}
int ExportAdvancedVideoDialog::threads() const
{
return static_cast<int>(thread_slider_->GetValue());
}
void ExportAdvancedVideoDialog::set_threads(int t)
{
thread_slider_->SetValue(t);
}
OLIVE_NAMESPACE_EXIT
@@ -0,0 +1,27 @@
#ifndef EXPORTADVANCEDVIDEODIALOG_H
#define EXPORTADVANCEDVIDEODIALOG_H
#include <QDialog>
#include "render/backend/exportparams.h"
#include "widget/slider/integerslider.h"
OLIVE_NAMESPACE_ENTER
class ExportAdvancedVideoDialog : public QDialog
{
Q_OBJECT
public:
ExportAdvancedVideoDialog(QWidget* parent = nullptr);
int threads() const;
void set_threads(int t);
private:
IntegerSlider* thread_slider_;
};
OLIVE_NAMESPACE_EXIT
#endif // EXPORTADVANCEDVIDEODIALOG_H
+28 -1
View File
@@ -24,8 +24,10 @@
#include <QGridLayout>
#include <QGroupBox>
#include <QLabel>
#include <QPushButton>
#include "core.h"
#include "exportadvancedvideodialog.h"
#include "render/backend/exportparams.h"
#include "render/colormanager.h"
@@ -33,7 +35,8 @@ OLIVE_NAMESPACE_ENTER
ExportVideoTab::ExportVideoTab(ColorManager* color_manager, QWidget *parent) :
QWidget(parent),
color_manager_(color_manager)
color_manager_(color_manager),
threads_(0)
{
QVBoxLayout* outer_layout = new QVBoxLayout(this);
@@ -106,6 +109,11 @@ H264Section *ExportVideoTab::h264_section() const
return h264_section_;
}
const int &ExportVideoTab::threads() const
{
return threads_;
}
QWidget* ExportVideoTab::SetupResolutionSection()
{
int row = 0;
@@ -118,6 +126,7 @@ QWidget* ExportVideoTab::SetupResolutionSection()
layout->addWidget(new QLabel(tr("Width:")), row, 0);
width_slider_ = new IntegerSlider();
width_slider_->SetMinimum(1);
layout->addWidget(width_slider_, row, 1);
row++;
@@ -125,6 +134,7 @@ QWidget* ExportVideoTab::SetupResolutionSection()
layout->addWidget(new QLabel(tr("Height:")), row, 0);
height_slider_ = new IntegerSlider();
height_slider_->SetMinimum(1);
layout->addWidget(height_slider_, row, 1);
row++;
@@ -196,6 +206,12 @@ QWidget *ExportVideoTab::SetupCodecSection()
h264_section_ = new H264Section();
codec_stack_->addWidget(h264_section_);
row++;
QPushButton* advanced_btn = new QPushButton(tr("Advanced"));
connect(advanced_btn, &QPushButton::clicked, this, &ExportVideoTab::OpenAdvancedDialog);
codec_layout->addWidget(advanced_btn, row, 1);
return codec_group;
}
@@ -204,4 +220,15 @@ void ExportVideoTab::MaintainAspectRatioChanged(bool val)
scaling_method_combobox_->setEnabled(!val);
}
void ExportVideoTab::OpenAdvancedDialog()
{
ExportAdvancedVideoDialog d(this);
d.set_threads(threads_);
if (d.exec() == QDialog::Accepted) {
threads_ = d.threads();
}
}
OLIVE_NAMESPACE_EXIT
+6
View File
@@ -57,6 +57,8 @@ public:
ImageSection* image_section() const;
H264Section* h264_section() const;
const int& threads() const;
signals:
void ColorSpaceChanged(const QString& colorspace);
@@ -83,9 +85,13 @@ private:
ColorManager* color_manager_;
int threads_;
private slots:
void MaintainAspectRatioChanged(bool val);
void OpenAdvancedDialog();
};
OLIVE_NAMESPACE_EXIT
@@ -22,7 +22,6 @@
#include <QGridLayout>
#include <QLabel>
#include <QPushButton>
#include "audio/audiomanager.h"
#include "config/config.h"
@@ -80,14 +79,14 @@ PreferencesAudioTab::PreferencesAudioTab()
row++;
QPushButton* refresh_devices = new QPushButton(tr("Refresh Devices"));
audio_tab_layout->addWidget(refresh_devices, row, 1);
refresh_devices_btn_ = new QPushButton(tr("Refresh Devices"));
audio_tab_layout->addWidget(refresh_devices_btn_, row, 1);
row++;
RetrieveDeviceLists();
connect(refresh_devices, &QPushButton::clicked, this, &PreferencesAudioTab::RefreshDevices);
connect(refresh_devices_btn_, &QPushButton::clicked, this, &PreferencesAudioTab::RefreshDevices);
connect(AudioManager::instance(), &AudioManager::OutputListReady, this, &PreferencesAudioTab::RetrieveOutputList);
connect(AudioManager::instance(), &AudioManager::InputListReady, this, &PreferencesAudioTab::RetrieveInputList);
}
@@ -151,6 +150,8 @@ void PreferencesAudioTab::RetrieveOutputList()
AudioManager::instance()->IsRefreshingOutputs(),
AudioManager::instance()->ListOutputDevices(),
Config::Current()["AudioOutput"].toString());
UpdateRefreshButtonEnabled();
}
void PreferencesAudioTab::RetrieveInputList()
@@ -159,6 +160,8 @@ void PreferencesAudioTab::RetrieveInputList()
AudioManager::instance()->IsRefreshingInputs(),
AudioManager::instance()->ListInputDevices(),
Config::Current()["AudioInput"].toString());
UpdateRefreshButtonEnabled();
}
void PreferencesAudioTab::RetrieveDeviceLists()
@@ -167,17 +170,22 @@ void PreferencesAudioTab::RetrieveDeviceLists()
RetrieveInputList();
}
void PreferencesAudioTab::UpdateRefreshButtonEnabled()
{
refresh_devices_btn_->setEnabled(audio_output_devices_->isEnabled()
&& audio_input_devices_->isEnabled());
}
void PreferencesAudioTab::PopulateComboBox(QComboBox *cb, bool still_refreshing, const QList<QAudioDeviceInfo> &list, const QString& preferred)
{
cb->clear();
cb->setEnabled(still_refreshing);
cb->setEnabled(!still_refreshing);
if (still_refreshing) {
cb->addItem(tr("Please wait..."));
} else {
bool found_preferred_device = false;
cb->setEnabled(true);
// Add null default item
cb->addItem(tr("Default"), QVariant());
@@ -23,6 +23,7 @@
#include <QAudioDeviceInfo>
#include <QComboBox>
#include <QPushButton>
#include "preferencestab.h"
@@ -57,6 +58,11 @@ private:
*/
QComboBox* recording_combobox_;
/**
* @brief Button that triggers a refresh of the available audio devices
*/
QPushButton* refresh_devices_btn_;
private slots:
void RefreshDevices();
@@ -67,6 +73,8 @@ private slots:
private:
void RetrieveDeviceLists();
void UpdateRefreshButtonEnabled();
static void PopulateComboBox(QComboBox* cb, bool still_refreshing, const QList<QAudioDeviceInfo>& list, const QString &preferred);
};
@@ -26,7 +26,6 @@
#include "audio/sampleformat.h"
#include "render/colormanager.h"
#include "render/pixelformat.h"
OLIVE_NAMESPACE_ENTER
@@ -49,12 +48,12 @@ PreferencesQualityTab::PreferencesQualityTab()
quality_stack_ = new QStackedWidget();
offline_group_ = new PreferencesQualityGroup(tr("Offline Quality"));
offline_group_->bit_depth_combobox()->setCurrentIndex(PixelFormat::instance()->GetConfiguredFormatForMode(RenderMode::kOffline));
offline_group_->SetBitDepth(PixelFormat::instance()->GetConfiguredFormatForMode(RenderMode::kOffline));
offline_group_->ocio_method()->setCurrentIndex(ColorManager::GetOCIOMethodForMode(RenderMode::kOffline));
quality_stack_->addWidget(offline_group_);
online_group_ = new PreferencesQualityGroup(tr("Online Quality"));
online_group_->bit_depth_combobox()->setCurrentIndex(PixelFormat::instance()->GetConfiguredFormatForMode(RenderMode::kOnline));
online_group_->SetBitDepth(PixelFormat::instance()->GetConfiguredFormatForMode(RenderMode::kOnline));
online_group_->ocio_method()->setCurrentIndex(ColorManager::GetOCIOMethodForMode(RenderMode::kOnline));
quality_stack_->addWidget(online_group_);
@@ -92,7 +91,8 @@ PreferencesQualityGroup::PreferencesQualityGroup(const QString &title, QWidget *
PixelFormat::Format pix_fmt = static_cast<PixelFormat::Format>(i);
// We always render with an alpha channel internally
if (PixelFormat::FormatHasAlphaChannel(pix_fmt)) {
if (PixelFormat::FormatHasAlphaChannel(pix_fmt)
&& PixelFormat::FormatIsFloat(pix_fmt)) {
bit_depth_combobox_->addItem(PixelFormat::GetName(pix_fmt),
i);
}
@@ -112,6 +112,16 @@ PreferencesQualityGroup::PreferencesQualityGroup(const QString &title, QWidget *
quality_outer_layout->addStretch();
}
void PreferencesQualityGroup::SetBitDepth(PixelFormat::Format f)
{
for (int i=0;i<bit_depth_combobox_->count();i++) {
if (bit_depth_combobox_->itemData(i) == f) {
bit_depth_combobox_->setCurrentIndex(i);
break;
}
}
}
QComboBox *PreferencesQualityGroup::bit_depth_combobox()
{
return bit_depth_combobox_;
@@ -26,6 +26,7 @@
#include <QGroupBox>
#include <QStackedWidget>
#include "render/pixelformat.h"
#include "preferencestab.h"
OLIVE_NAMESPACE_ENTER
@@ -36,6 +37,8 @@ class PreferencesQualityGroup : public QGroupBox
public:
PreferencesQualityGroup(const QString& title, QWidget* parent = nullptr);
void SetBitDepth(PixelFormat::Format f);
QComboBox* bit_depth_combobox();
QComboBox* ocio_method();
+2 -2
View File
@@ -50,8 +50,8 @@ int main(int argc, char *argv[]) {
format.setProfile(QSurfaceFormat::CoreProfile);
QSurfaceFormat::setDefaultFormat(format);
// Try to share OpenGL contexts
QApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
// Create application instance
QApplication a(argc, argv);
+12
View File
@@ -234,6 +234,18 @@ void Block::SaveInternal(QXmlStreamWriter *writer) const
}
}
QList<NodeInput *> Block::GetInputsToHash() const
{
QList<NodeInput*> inputs = Node::GetInputsToHash();
// Ignore these inputs
inputs.removeOne(media_in_input_);
inputs.removeOne(speed_input_);
inputs.removeOne(length_input_);
return inputs;
}
void Block::LengthInputChanged()
{
emit LengthChanged(length());
+2
View File
@@ -126,6 +126,8 @@ protected:
virtual void SaveInternal(QXmlStreamWriter* writer) const override;
virtual QList<NodeInput*> GetInputsToHash() const override;
Block* previous_;
Block* next_;
@@ -40,6 +40,11 @@ QString ExternalTransition::Name() const
return meta_.Name();
}
QString ExternalTransition::ShortName() const
{
return meta_.ShortName();
}
QString ExternalTransition::id() const
{
return meta_.id();
@@ -35,6 +35,7 @@ public:
virtual Node* copy() const override;
virtual QString Name() const override;
virtual QString ShortName() const override;
virtual QString id() const override;
virtual QString Category() const override;
virtual QString Description() const override;
+13
View File
@@ -129,6 +129,19 @@ double TransitionBlock::GetInProgress(const rational &time) const
return clamp((GetInternalTransitionTime(time) - out_offset().toDouble()) / in_offset().toDouble(), 0.0, 1.0);
}
void TransitionBlock::Hash(QCryptographicHash &hash, const rational &time) const
{
Block::Hash(hash, time);
double all_prog = GetTotalProgress(time);
double in_prog = GetInProgress(time);
double out_prog = GetOutProgress(time);
hash.addData(reinterpret_cast<const char*>(&all_prog), sizeof(double));
hash.addData(reinterpret_cast<const char*>(&in_prog), sizeof(double));
hash.addData(reinterpret_cast<const char*>(&out_prog), sizeof(double));
}
double TransitionBlock::GetInternalTransitionTime(const rational &time) const
{
return time.toDouble() - in().toDouble();
+2
View File
@@ -47,6 +47,8 @@ public:
double GetOutProgress(const rational& time) const;
double GetInProgress(const rational& time) const;
virtual void Hash(QCryptographicHash& hash, const rational &time) const override;
private:
double GetInternalTransitionTime(const rational& time) const;
+5
View File
@@ -42,6 +42,11 @@ QString ExternalNode::Name() const
return meta_.Name();
}
QString ExternalNode::ShortName() const
{
return meta_.ShortName();
}
QString ExternalNode::id() const
{
return meta_.id();
+1
View File
@@ -39,6 +39,7 @@ public:
virtual Node* copy() const override;
virtual QString Name() const override;
virtual QString ShortName() const override;
virtual QString id() const override;
virtual QString Category() const override;
virtual QString Description() const override;
+5
View File
@@ -59,6 +59,11 @@ QString MatrixGenerator::Name() const
return tr("Orthographic Matrix");
}
QString MatrixGenerator::ShortName() const
{
return tr("Ortho");
}
QString MatrixGenerator::id() const
{
return QStringLiteral("org.olivevideoeditor.Olive.transform");
+1
View File
@@ -34,6 +34,7 @@ public:
virtual Node* copy() const override;
virtual QString Name() const override;
virtual QString ShortName() const override;
virtual QString id() const override;
virtual QString Category() const override;
virtual QString Description() const override;
+13
View File
@@ -737,6 +737,19 @@ void NodeInput::remove_keyframe(NodeKeyframePtr key)
emit_time_range(time_affected);
}
NodeKeyframePtr NodeInput::get_keyframe_shared_ptr_from_raw(NodeKeyframe* raw) const
{
foreach (const KeyframeTrack& track, keyframe_tracks_) {
foreach (NodeKeyframePtr key, track) {
if (key.get() == raw) {
return key;
}
}
}
return nullptr;
}
void NodeInput::KeyframeTimeChanged()
{
NodeKeyframe* key = static_cast<NodeKeyframe*>(sender());
+6 -1
View File
@@ -172,6 +172,11 @@ public:
*/
void remove_keyframe(NodeKeyframePtr key);
/**
* @brief Hacky convenience function to turn a raw pointer into a shared pointer
*/
NodeKeyframePtr get_keyframe_shared_ptr_from_raw(NodeKeyframe *raw) const;
/**
* @brief Return whether a keyframe exists at this time
*
@@ -279,7 +284,7 @@ public:
QList<Node*> GetImmediateDependencies() const;
signals:
void ValueChanged(const TimeRange& range);
void ValueChanged(const OLIVE_NAMESPACE::TimeRange& range);
void KeyframeEnableChanged(bool);
+8
View File
@@ -62,4 +62,12 @@ NodeValueTable TimeInput::Value(NodeValueDatabase &value) const
return table;
}
void TimeInput::Hash(QCryptographicHash &hash, const rational &time) const
{
Node::Hash(hash, time);
// Make sure time is hashed
hash.addData(NodeParam::ValueToBytes(NodeParam::kRational, QVariant::fromValue(time)));
}
OLIVE_NAMESPACE_EXIT
+2
View File
@@ -40,6 +40,8 @@ public:
virtual NodeValueTable Value(NodeValueDatabase& value) const override;
virtual void Hash(QCryptographicHash& hash, const rational& time) const override;
};
OLIVE_NAMESPACE_EXIT
+12
View File
@@ -57,6 +57,15 @@ QString NodeMetaReader::Name() const
return GetStringForCurrentLanguage(&names_);
}
QString NodeMetaReader::ShortName() const
{
if (short_names_.isEmpty()) {
return Name();
} else {
return GetStringForCurrentLanguage(&short_names_);
}
}
const QString &NodeMetaReader::id() const
{
return id_;
@@ -173,6 +182,9 @@ void NodeMetaReader::XMLReadEffect(QXmlStreamReader* reader)
if (reader->name() == QStringLiteral("name")) {
// Pick up name
XMLReadLanguageString(reader, &names_);
} else if (reader->name() == QStringLiteral("shortnames")) {
// Pick up short name
XMLReadLanguageString(reader, &short_names_);
} else if (reader->name() == QStringLiteral("category")) {
// Pick up category
XMLReadLanguageString(reader, &categories_);
+2
View File
@@ -35,6 +35,7 @@ public:
NodeMetaReader(const QString& xml_meta_filename);
QString Name() const;
QString ShortName() const;
const QString& id() const;
QString Category() const;
QString Description() const;
@@ -67,6 +68,7 @@ private:
QString xml_filename_;
LanguageMap names_;
LanguageMap short_names_;
LanguageMap descriptions_;
LanguageMap categories_;
QMap<QString, LanguageMap > param_names_;
+103 -1
View File
@@ -25,6 +25,9 @@
#include <QFile>
#include "common/xmlutils.h"
#include "project/project.h"
#include "project/item/footage/footage.h"
#include "project/item/footage/imagestream.h"
OLIVE_NAMESPACE_ENTER
@@ -103,6 +106,13 @@ void Node::Save(QXmlStreamWriter *writer, const QString &custom_name) const
writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast<quintptr>(this)));
writer->writeAttribute(QStringLiteral("pos"),
QStringLiteral("%1:%2").arg(QString::number(GetPosition().x()),
QString::number(GetPosition().y())));
writer->writeAttribute(QStringLiteral("label"),
GetLabel());
foreach (NodeParam* param, parameters()) {
param->Save(writer);
}
@@ -112,6 +122,11 @@ void Node::Save(QXmlStreamWriter *writer, const QString &custom_name) const
writer->writeEndElement(); // node
}
QString Node::ShortName() const
{
return Name();
}
QString Node::Category() const
{
// Return an empty category for any nodes that don't use one
@@ -220,6 +235,11 @@ void Node::SaveInternal(QXmlStreamWriter *) const
{
}
QList<NodeInput *> Node::GetInputsToHash() const
{
return GetInputsIncludingArrays();
}
QString Node::ReadFileAsString(const QString &filename)
{
QFile f(filename);
@@ -277,6 +297,83 @@ void Node::DrawGizmos(QPainter *, const QRect &) const
{
}
const QString &Node::GetLabel() const
{
return label_;
}
void Node::SetLabel(const QString &s)
{
if (label_ != s) {
label_ = s;
emit LabelChanged(label_);
}
}
void Node::Hash(QCryptographicHash &hash, const rational& time) const
{
// Add this Node's ID
hash.addData(id().toUtf8());
QList<NodeInput*> inputs = GetInputsToHash();
foreach (NodeInput* input, inputs) {
// For each input, try to hash its value
// Get time adjustment
// For a single frame, we only care about one of the times
rational input_time = InputTimeAdjustment(input, TimeRange(time, time)).in();
if (input->IsConnected()) {
// Traverse down this edge
input->get_connected_node()->Hash(hash, input_time);
} else {
// Grab the value at this time
QVariant value = input->get_value_at_time(input_time);
hash.addData(NodeParam::ValueToBytes(input->data_type(), value));
}
// We have one exception for FOOTAGE types, since we resolve the footage into a frame in the renderer
if (input->data_type() == NodeParam::kFootage) {
StreamPtr stream = input->get_standard_value().value<StreamPtr>();
if (stream) {
// Add footage details to hash
// Footage filename
hash.addData(stream->footage()->filename().toUtf8());
// Footage last modified date
hash.addData(stream->footage()->timestamp().toString().toUtf8());
// Footage stream
hash.addData(QString::number(stream->index()).toUtf8());
if (stream->type() == Stream::kImage || stream->type() == Stream::kVideo) {
ImageStreamPtr image_stream = std::static_pointer_cast<ImageStream>(stream);
// Current color config and space
hash.addData(image_stream->footage()->project()->color_manager()->GetConfigFilename().toUtf8());
hash.addData(image_stream->colorspace().toUtf8());
// Alpha associated setting
hash.addData(QString::number(image_stream->premultiplied_alpha()).toUtf8());
}
// Footage timestamp
if (stream->type() == Stream::kVideo) {
hash.addData(QStringLiteral("%1/%2").arg(QString::number(input_time.numerator()),
QString::number(input_time.denominator())).toUtf8());
hash.addData(QString::number(static_cast<VideoStream*>(stream.get())->start_time()).toUtf8());
}
}
}
}
}
void Node::CopyInputs(Node *source, Node *destination, bool include_connections)
{
Q_ASSERT(source->id() == destination->id());
@@ -295,6 +392,9 @@ void Node::CopyInputs(Node *source, Node *destination, bool include_connections)
NodeInput::CopyValues(src, dst, include_connections);
}
}
destination->SetPosition(source->GetPosition());
destination->SetLabel(source->GetLabel());
}
bool Node::CanBeDeleted() const
@@ -559,7 +659,7 @@ NodeValue Node::InputValueFromTable(NodeInput *input, NodeValueDatabase &db, boo
}
}
const QPointF &Node::GetPosition()
const QPointF &Node::GetPosition() const
{
return position_;
}
@@ -567,6 +667,8 @@ const QPointF &Node::GetPosition()
void Node::SetPosition(const QPointF &pos)
{
position_ = pos;
emit PositionChanged(position_);
}
void Node::AddInput(NodeInput *input)
+31 -2
View File
@@ -92,6 +92,13 @@ public:
*/
virtual QString Name() const = 0;
/**
* @brief Returns a shortened name of this node if applicable
*
* Defaults to returning Name() but can be overridden.
*/
virtual QString ShortName() const;
/**
* @brief Return the unique identifier of the node
*
@@ -343,7 +350,7 @@ public:
virtual NodeValue InputValueFromTable(NodeInput* input, NodeValueDatabase &db, bool take) const;
const QPointF& GetPosition();
const QPointF& GetPosition() const;
void SetPosition(const QPointF& pos);
@@ -357,6 +364,11 @@ public:
virtual void DrawGizmos(QPainter* p, const QRect &viewport) const;
const QString& GetLabel() const;
void SetLabel(const QString& s);
virtual void Hash(QCryptographicHash& hash, const rational &time) const;
protected:
void AddInput(NodeInput* input);
@@ -368,6 +380,8 @@ protected:
virtual void SaveInternal(QXmlStreamWriter* writer) const;
virtual QList<NodeInput*> GetInputsToHash() const;
public slots:
signals:
@@ -389,6 +403,16 @@ signals:
*/
void EdgeRemoved(NodeEdgePtr edge);
/**
* @brief Signal emitted whenever the position is set through SetPosition()
*/
void PositionChanged(const QPointF& pos);
/**
* @brief Signal emitted when SetLabel() is called
*/
void LabelChanged(const QString& s);
private:
/**
* @brief Add a parameter to this node
@@ -424,8 +448,13 @@ private:
*/
QPointF position_;
/**
* @brief Custom user label for node
*/
QString label_;
private slots:
void InputChanged(const TimeRange &range);
void InputChanged(const OLIVE_NAMESPACE::TimeRange &range);
void InputConnectionChanged(NodeEdgePtr edge);
+11 -3
View File
@@ -418,6 +418,16 @@ NodeInputArray *TrackOutput::block_input() const
return block_input_;
}
void TrackOutput::Hash(QCryptographicHash &hash, const rational &time) const
{
// Resolve block list
Block* b = BlockAtTime(time);
if (b) {
return b->Hash(hash, time);
}
}
void TrackOutput::SetTrackName(const QString &name)
{
track_name_ = name;
@@ -556,9 +566,7 @@ void TrackOutput::BlockDisconnected(NodeEdgePtr edge)
block_cache_.removeAt(index_of_block);
// If there were blocks following this one, update their ins/outs
if (index_of_block < block_cache_.size()) {
UpdateInOutFrom(index_of_block);
}
UpdateInOutFrom(index_of_block);
// Join the previous and next blocks together
if (connected_block->previous()) {
+2
View File
@@ -152,6 +152,8 @@ public:
NodeInputArray* block_input() const;
virtual void Hash(QCryptographicHash& hash, const rational &time) const override;
public slots:
void SetTrackName(const QString& name);
+5
View File
@@ -37,6 +37,11 @@ NodeInput *CurvePanel::GetInput() const
return static_cast<CurveWidget*>(GetTimeBasedWidget())->GetInput();
}
void CurvePanel::DeleteSelected()
{
static_cast<CurveWidget*>(GetTimeBasedWidget())->DeleteSelected();
}
void CurvePanel::SetInput(NodeInput *input)
{
static_cast<CurveWidget*>(GetTimeBasedWidget())->SetInput(input);
+2
View File
@@ -34,6 +34,8 @@ public:
NodeInput* GetInput() const;
virtual void DeleteSelected() override;
public slots:
void SetInput(NodeInput* input);
+5
View File
@@ -73,6 +73,11 @@ void NodePanel::Paste()
node_view_->Paste();
}
void NodePanel::Duplicate()
{
node_view_->Duplicate();
}
void NodePanel::Select(const QList<Node *> &nodes)
{
node_view_->Select(nodes);
+2
View File
@@ -47,6 +47,8 @@ public:
virtual void Paste() override;
virtual void Duplicate() override;
public slots:
void Select(const QList<Node*>& nodes);
void SelectWithDependencies(const QList<Node*>& nodes);
+3 -3
View File
@@ -35,10 +35,10 @@ PanelManager::PanelManager(QObject *parent) :
void PanelManager::DeleteAllPanels()
{
foreach (PanelWidget* panel, focus_history_) {
delete panel;
}
// Prevent any confusion regarding focus history by clearing it first
QList<PanelWidget*> copy = focus_history_;
focus_history_.clear();
qDeleteAll(copy);
}
const QList<PanelWidget *> &PanelManager::panels()
+17 -5
View File
@@ -175,14 +175,26 @@ T *PanelManager::CreatePanel(QWidget *parent)
{
T* panel = new T(parent);
panel->SetMovementLocked(locked_);
// Connect destroy signal so we can remove it from focus history
connect(panel, &PanelWidget::destroyed, this, &PanelManager::PanelDestroyed);
// Add panel to the bottom of the focus history
focus_history_.append(panel);
panel->SetMovementLocked(locked_);
// Sane default for panel size
panel->resize(parent->size() / 3);
// We're about to center the panel relative to the parent (usually the main window), but for some
// reason this requires the panel to be shown first.
panel->show();
// Center the panel relative to the parent
QPoint parent_center = panel->mapFromGlobal(parent->mapToGlobal(parent->rect().center()));
QPoint panel_center = panel->rect().center();
panel->move(parent_center - panel_center);
// Connect destroy signal so we can remove it from focus history
connect(panel, &PanelWidget::destroyed, this, &PanelManager::PanelDestroyed, Qt::DirectConnection);
return panel;
}
+7
View File
@@ -60,6 +60,11 @@ void ParamPanel::SetTimestamp(const int64_t &timestamp)
}
}
void ParamPanel::DeleteSelected()
{
static_cast<NodeParamView*>(GetTimeBasedWidget())->DeleteSelected();
}
void ParamPanel::Retranslate()
{
SetTitle(tr("Parameter Editor"));
@@ -95,9 +100,11 @@ void ParamPanel::CreateCurvePanel(NodeInput *input)
panel->SetInput(input);
panel->SetTimebase(view->timebase());
panel->SetTimestamp(view->GetTimestamp());
panel->SetTimeTarget(view->GetTimeTarget());
connect(view, &NodeParamView::TimebaseChanged, panel, &CurvePanel::SetTimebase);
connect(view, &NodeParamView::TimeChanged, panel, &CurvePanel::SetTimestamp);
connect(view, &NodeParamView::TimeTargetChanged, panel, &CurvePanel::SetTimeTarget);
connect(panel, &CurvePanel::TimeChanged, view, &NodeParamView::SetTimestamp);
connect(panel, &CurvePanel::TimeChanged, view, &NodeParamView::TimeChanged);
connect(panel, &CurvePanel::CloseRequested, this, &ParamPanel::ClosingCurvePanel);
+2
View File
@@ -38,6 +38,8 @@ public slots:
virtual void SetTimestamp(const int64_t& timestamp) override;
virtual void DeleteSelected() override;
signals:
void TimeTargetChanged(Node* node);
+6 -2
View File
@@ -49,12 +49,16 @@ ProjectPanel::ProjectPanel(QWidget *parent) :
layout->addWidget(toolbar);
// Make toolbar connections
connect(toolbar, SIGNAL(NewClicked()), this, SLOT(ShowNewMenu()));
connect(toolbar, &ProjectToolbar::NewClicked, this, &ProjectPanel::ShowNewMenu);
connect(toolbar, &ProjectToolbar::OpenClicked, Core::instance(), &Core::OpenProject);
connect(toolbar, &ProjectToolbar::SaveClicked, Core::instance(), &Core::SaveActiveProject);
connect(toolbar, &ProjectToolbar::UndoClicked, Core::instance()->undo_stack(), &QUndoStack::undo);
connect(toolbar, &ProjectToolbar::RedoClicked, Core::instance()->undo_stack(), &QUndoStack::redo);
// Set up main explorer object
explorer_ = new ProjectExplorer(this);
layout->addWidget(explorer_);
connect(explorer_, SIGNAL(DoubleClickedItem(Item*)), this, SLOT(ItemDoubleClickSlot(Item*)));
connect(explorer_, &ProjectExplorer::DoubleClickedItem, this, &ProjectPanel::ItemDoubleClickSlot);
// Set toolbar's view to the explorer's view
toolbar->SetView(explorer_->view_type());
+1 -10
View File
@@ -84,19 +84,10 @@ QString ScopePanel::TypeToName(ScopePanel::Type t)
return QString();
}
void ScopePanel::SetDisplayReferredTexture(OpenGLTexture *texture)
{
Q_UNUSED(texture)
}
void ScopePanel::SetReferenceBuffer(Frame *frame)
{
histogram_->SetBuffer(frame);
}
void ScopePanel::SetReferenceTexture(OpenGLTexture *texture)
{
waveform_view_->SetTexture(texture);
waveform_view_->SetBuffer(frame);
}
void ScopePanel::SetColorManager(ColorManager *manager)
-4
View File
@@ -50,12 +50,8 @@ public:
static QString TypeToName(Type t);
public slots:
void SetDisplayReferredTexture(OpenGLTexture* texture);
void SetReferenceBuffer(Frame* frame);
void SetReferenceTexture(OpenGLTexture* texture);
void SetColorManager(ColorManager* manager);
protected:
+1 -22
View File
@@ -25,8 +25,7 @@
OLIVE_NAMESPACE_ENTER
ViewerPanelBase::ViewerPanelBase(const QString& object_name, QWidget *parent) :
TimeBasedPanel(object_name, parent),
scope_panel_count_(0)
TimeBasedPanel(object_name, parent)
{
}
@@ -103,33 +102,13 @@ void ViewerPanelBase::CreateScopePanel(ScopePanel::Type type)
p->SetType(type);
// If the scope closes, reduce the count (we do this because if no scopes are open, we can optimize the viewer slightly)
connect(p, &ScopePanel::CloseRequested, this, &ViewerPanelBase::ScopePanelClosed);
// Connect viewer widget texture drawing to scope panel
connect(vw, &ViewerWidget::DrewManagedTexture, p, &ScopePanel::SetDisplayReferredTexture);
connect(vw, &ViewerWidget::LoadedBuffer, p, &ScopePanel::SetReferenceBuffer);
connect(vw, &ViewerWidget::LoadedTexture, p, &ScopePanel::SetReferenceTexture);
connect(vw, &ViewerWidget::ColorManagerChanged, p, &ScopePanel::SetColorManager);
p->SetColorManager(vw->color_manager());
if (!scope_panel_count_) {
vw->SetEmitDrewManagedTextureEnabled(true);
}
scope_panel_count_++;
vw->ForceUpdate();
}
void ViewerPanelBase::ScopePanelClosed()
{
scope_panel_count_--;
if (!scope_panel_count_) {
static_cast<ViewerWidget*>(GetTimeBasedWidget())->SetEmitDrewManagedTextureEnabled(false);
}
}
OLIVE_NAMESPACE_EXIT
-6
View File
@@ -62,12 +62,6 @@ public slots:
protected:
void CreateScopePanel(ScopePanel::Type type);
private:
int scope_panel_count_;
private slots:
void ScopePanelClosed();
};
OLIVE_NAMESPACE_EXIT
+1 -1
View File
@@ -36,7 +36,7 @@ protected:
virtual void Action() override;
signals:
void ProjectLoaded(ProjectPtr project);
void ProjectLoaded(OLIVE_NAMESPACE::ProjectPtr project);
private:
QString filename_;
+1 -1
View File
@@ -33,7 +33,7 @@ public:
ProjectSaveManager(ProjectPtr project);
signals:
void ProjectSaveSucceeded(ProjectPtr p);
void ProjectSaveSucceeded(OLIVE_NAMESPACE::ProjectPtr p);
protected:
virtual void Action() override;
+35 -31
View File
@@ -65,43 +65,47 @@ void AudioBackend::ThreadCompletedCache(NodeDependency dep, NodeValueTable data,
if (job_time == render_job_info_.value(dep.range())) {
render_job_info_.remove(dep.range());
QByteArray cached_samples = data.Get(NodeParam::kSamples).value<SampleBufferPtr>()->toPackedData();
SampleBufferPtr cached_sample_ptr = data.Get(NodeParam::kSamples).value<SampleBufferPtr>();
int offset = params().time_to_bytes(dep.in());
int length = params().time_to_bytes(dep.range().length());
int out_point = qMin(offset + length, params().time_to_bytes(GetSequenceLength()));
if (cached_sample_ptr) {
QByteArray cached_samples = cached_sample_ptr->toPackedData();
if (offset < out_point) {
if (offset + length > out_point) {
length = out_point - offset;
}
int offset = params().time_to_bytes(dep.in());
int length = params().time_to_bytes(dep.range().length());
int out_point = qMin(offset + length, params().time_to_bytes(GetSequenceLength()));
QFile f(CachePathName());
if (f.open(QFile::ReadWrite)) {
if (f.size() < out_point && !f.resize(out_point)) {
qCritical() << "Failed to resize file" << CachePathName();
if (offset < out_point) {
if (offset + length > out_point) {
length = out_point - offset;
}
if (!f.seek(offset)) {
qCritical() << "Failed to seek file" << CachePathName();
QFile f(CachePathName());
if (f.open(QFile::ReadWrite)) {
if (f.size() < out_point && !f.resize(out_point)) {
qCritical() << "Failed to resize file" << CachePathName();
}
if (!f.seek(offset)) {
qCritical() << "Failed to seek file" << CachePathName();
}
// Replace data with this data
int copy_length = qMin(length, cached_samples.size());
f.write(cached_samples.data(), copy_length);
if (copy_length < length) {
// Fill in remainder with silence
QByteArray empty_space(length - copy_length, 0);
f.write(empty_space);
}
f.close();
} else {
qWarning() << "Failed to write to cached PCM file";
}
// Replace data with this data
int copy_length = qMin(length, cached_samples.size());
f.write(cached_samples.data(), copy_length);
if (copy_length < length) {
// Fill in remainder with silence
QByteArray empty_space(length - copy_length, 0);
f.write(empty_space);
}
f.close();
} else {
qWarning() << "Failed to write to cached PCM file";
}
}
}
+62 -28
View File
@@ -20,6 +20,8 @@
#include "exporter.h"
#include <QtConcurrent/QtConcurrent>
#include "render/backend/audio/audiobackend.h"
#include "render/backend/opengl/openglbackend.h"
#include "render/colormanager.h"
@@ -177,17 +179,6 @@ void Exporter::EncodeFrame()
while (cached_frames_.contains(waiting_for_frame_)) {
FramePtr frame = cached_frames_.take(waiting_for_frame_);
// OCIO conversion requires a frame in 32F format
if (frame->format() != PixelFormat::PIX_FMT_RGBA32F) {
frame = PixelFormat::ConvertPixelFormat(frame, PixelFormat::PIX_FMT_RGBA32F);
}
// Color conversion must be done with unassociated alpha, and the pipeline is always associated
ColorManager::DisassociateAlpha(frame);
// Convert color space
color_processor_->ConvertFrame(frame);
// Encode (may require re-associating alpha?)
QMetaObject::invokeMethod(encoder_,
"WriteFrame",
@@ -233,29 +224,39 @@ QMatrix4x4 Exporter::GenerateMatrix(ExportParams::VideoScalingMethod method, int
return preview_matrix;
}
void Exporter::FrameRendered(const rational &time, FramePtr value)
FramePtr FrameColorConvert(ColorProcessorPtr processor, FramePtr frame)
{
debug_timer_.stop();
qDebug() << "Converting" << frame->timestamp();
const QMap<rational, QByteArray>& time_hash_map = video_backend_->frame_cache()->time_hash_map();
QByteArray this_hash = time_hash_map.value(time);
qDebug() << "Received" << this_hash.toHex();
QList<rational> matching_times = time_hash_map.keys(this_hash);
foreach (const rational& t, matching_times) {
qDebug() << " Matches" << t.toDouble();
cached_frames_.insert(t, value);
// OCIO conversion requires a frame in 32F format
if (frame->format() != PixelFormat::PIX_FMT_RGBA32F) {
frame = PixelFormat::ConvertPixelFormat(frame, PixelFormat::PIX_FMT_RGBA32F);
}
qDebug() << " Waiting for" << waiting_for_frame_.toDouble();
// Color conversion must be done with unassociated alpha, and the pipeline is always associated
ColorManager::DisassociateAlpha(frame);
debug_timer_.start();
// Convert color space
processor->ConvertFrame(frame);
EncodeFrame();
// Re-associate alpha
ColorManager::ReassociateAlpha(frame);
return frame;
}
void Exporter::FrameRendered(FramePtr frame)
{
// Start color space conversion in another thread
QFutureWatcher<FramePtr>* watcher = new QFutureWatcher<FramePtr>();
connect(watcher, &QFutureWatcher<FramePtr>::finished, this, &Exporter::FrameColorFinished);
QFuture<FramePtr> future = QtConcurrent::run(FrameColorConvert,
color_processor_,
frame);
watcher->setFuture(future);
}
void Exporter::AudioRendered()
@@ -353,4 +354,37 @@ void Exporter::DebugTimerMessage()
qDebug() << "Still waiting for" << waiting_for_frame_.toDouble();
}
void Exporter::FrameColorFinished()
{
if (!video_backend_ && !audio_backend_) {
return;
}
QFutureWatcher<FramePtr>* watcher = static_cast< QFutureWatcher<FramePtr>* >(sender());
FramePtr frame = watcher->result();
watcher->deleteLater();
debug_timer_.stop();
const QMap<rational, QByteArray>& time_hash_map = video_backend_->frame_cache()->time_hash_map();
QByteArray this_hash = time_hash_map.value(frame->timestamp());
qDebug() << "Received" << this_hash.toHex();
QList<rational> matching_times = time_hash_map.keys(this_hash);
foreach (const rational& t, matching_times) {
qDebug() << " Matches" << t.toDouble();
cached_frames_.insert(t, frame);
}
qDebug() << " Waiting for" << waiting_for_frame_.toDouble();
debug_timer_.start();
EncodeFrame();
}
OLIVE_NAMESPACE_EXIT
+3 -1
View File
@@ -101,7 +101,7 @@ private:
QTimer debug_timer_;
private slots:
void FrameRendered(const rational &time, FramePtr value);
void FrameRendered(FramePtr frame);
void AudioRendered();
@@ -117,6 +117,8 @@ private slots:
void DebugTimerMessage();
void FrameColorFinished();
};
OLIVE_NAMESPACE_EXIT
+1 -1
View File
@@ -132,7 +132,7 @@ void OpenGLProxy::FrameToValue(FramePtr frame, StreamPtr stream, NodeValueTable*
VideoRenderingParams footage_params(frame->width(), frame->height(), frame->format());
footage_tex_ref = texture_cache_.Get(ctx_, footage_params, frame->data(), frame->linesize_pixels());
footage_tex_ref = texture_cache_.Get(ctx_, footage_params, frame);
if (ocio_method == ColorManager::kOCIOFast) {
if (!color_processor->IsEnabled()) {
@@ -74,6 +74,11 @@ void OpenGLTexture::Create(QOpenGLContext *ctx, int width, int height, const Pix
}
void OpenGLTexture::Create(QOpenGLContext *ctx, FramePtr frame)
{
Create(ctx, frame.get());
}
void OpenGLTexture::Create(QOpenGLContext *ctx, Frame *frame)
{
Create(ctx, frame->width(), frame->height(), frame->format(), frame->data(), frame->linesize_pixels());
}
@@ -120,6 +125,16 @@ const GLuint &OpenGLTexture::texture() const
return texture_;
}
void OpenGLTexture::Upload(FramePtr frame)
{
Upload(frame.get());
}
void OpenGLTexture::Upload(Frame *frame)
{
Upload(frame->data(), frame->linesize_pixels());
}
void OpenGLTexture::Upload(const void *data, int linesize)
{
if (!IsCreated()) {
@@ -44,6 +44,7 @@ public:
void Create(QOpenGLContext* ctx, int width, int height, const PixelFormat::Format &format, const void *data, int linesize);
void Create(QOpenGLContext* ctx, int width, int height, const PixelFormat::Format &format);
void Create(QOpenGLContext* ctx, FramePtr frame);
void Create(QOpenGLContext* ctx, Frame* frame);
bool IsCreated() const;
@@ -59,6 +60,8 @@ public:
const GLuint& texture() const;
void Upload(FramePtr frame);
void Upload(Frame* frame);
void Upload(const void *data, int linesize);
public slots:
@@ -29,6 +29,16 @@ OpenGLTextureCache::~OpenGLTextureCache()
}
}
OpenGLTextureCache::ReferencePtr OpenGLTextureCache::Get(QOpenGLContext *ctx, const VideoRenderingParams &params, FramePtr frame)
{
return Get(ctx, params, frame.get());
}
OpenGLTextureCache::ReferencePtr OpenGLTextureCache::Get(QOpenGLContext *ctx, const VideoRenderingParams &params, Frame *frame)
{
return Get(ctx, params, frame->data(), frame->linesize_pixels());
}
OpenGLTextureCache::ReferencePtr OpenGLTextureCache::Get(QOpenGLContext* ctx, const VideoRenderingParams &params, const void *data, int linesize)
{
OpenGLTexturePtr texture = nullptr;
@@ -57,6 +57,8 @@ public:
DISABLE_COPY_MOVE(OpenGLTextureCache)
ReferencePtr Get(QOpenGLContext *ctx, const VideoRenderingParams& params, FramePtr frame);
ReferencePtr Get(QOpenGLContext *ctx, const VideoRenderingParams& params, Frame* frame);
ReferencePtr Get(QOpenGLContext *ctx, const VideoRenderingParams& params, const void *data, int linesize);
ReferencePtr Get(QOpenGLContext *ctx, const VideoRenderingParams& params);
+1 -1
View File
@@ -78,7 +78,7 @@ void RenderWorker::RunNodeAccelerated(const Node *node, const TimeRange &range,
StreamPtr RenderWorker::ResolveStreamFromInput(NodeInput *input)
{
return input->get_value_at_time(0).value<StreamPtr>();
return input->get_standard_value().value<StreamPtr>();
}
DecoderPtr RenderWorker::ResolveDecoderFromInput(StreamPtr stream)
+1 -1
View File
@@ -110,7 +110,7 @@ signals:
void RangeInvalidated(const TimeRange& range);
void GeneratedFrame(const rational &time, FramePtr frame);
void GeneratedFrame(FramePtr frame);
private:
bool TimeIsQueued(const TimeRange &time) const;
+4 -113
View File
@@ -75,7 +75,7 @@ NodeValueTable VideoRenderWorker::RenderInternal(const NodeDependency& path, con
hasher.addData(reinterpret_cast<const char*>(&vfmt), sizeof(PixelFormat::Format));
hasher.addData(reinterpret_cast<const char*>(&vmode), sizeof(RenderMode::Mode));
HashNodeRecursively(&hasher, path.node(), path.in());
path.node()->Hash(hasher, path.in());
hash = hasher.result();
}
@@ -121,117 +121,6 @@ NodeValueTable VideoRenderWorker::RenderInternal(const NodeDependency& path, con
return value;
}
void VideoRenderWorker::HashNodeRecursively(QCryptographicHash *hash, const Node* n, const rational& time)
{
// Resolve BlockList
if (n->IsTrack()) {
n = static_cast<const TrackOutput*>(n)->BlockAtTime(time);
if (!n) {
return;
}
}
// Add this Node's ID
hash->addData(n->id().toUtf8());
if (n->IsBlock() && static_cast<const Block*>(n)->type() == Block::kTransition) {
const TransitionBlock* transition = static_cast<const TransitionBlock*>(n);
double all_prog = transition->GetTotalProgress(time);
double in_prog = transition->GetInProgress(time);
double out_prog = transition->GetOutProgress(time);
hash->addData(reinterpret_cast<const char*>(&all_prog), sizeof(double));
hash->addData(reinterpret_cast<const char*>(&in_prog), sizeof(double));
hash->addData(reinterpret_cast<const char*>(&out_prog), sizeof(double));
}
foreach (NodeParam* param, n->parameters()) {
// For each input, try to hash its value
if (param->type() == NodeParam::kInput) {
NodeInput* input = static_cast<NodeInput*>(param);
if (n->IsBlock()) {
const Block* b = static_cast<const Block*>(n);
// Ignore some Block attributes when hashing
if (input == b->media_in_input()
|| input == b->speed_input()
|| input == b->length_input()) {
continue;
}
}
// Get time adjustment
// For a single frame, we only care about one of the times
rational input_time = n->InputTimeAdjustment(input, TimeRange(time, time)).in();
if (input->IsConnected()) {
// Traverse down this edge
HashNodeRecursively(hash, input->get_connected_node(), input_time);
} else {
// Grab the value at this time
QVariant value = input->get_value_at_time(input_time);
hash->addData(NodeParam::ValueToBytes(input->data_type(), value));
}
// We have one exception for FOOTAGE types, since we resolve the footage into a frame in the renderer
if (input->data_type() == NodeParam::kFootage) {
StreamPtr stream = ResolveStreamFromInput(input);
if (stream) {
DecoderPtr decoder = ResolveDecoderFromInput(stream);
if (decoder) {
// Add footage details to hash
// Footage filename
hash->addData(stream->footage()->filename().toUtf8());
// Footage last modified date
hash->addData(stream->footage()->timestamp().toString().toUtf8());
// Footage stream
hash->addData(QString::number(stream->index()).toUtf8());
if (stream->type() == Stream::kImage || stream->type() == Stream::kVideo) {
ImageStreamPtr image_stream = std::static_pointer_cast<ImageStream>(stream);
// Current color config and space
hash->addData(image_stream->footage()->project()->color_manager()->GetConfigFilename().toUtf8());
hash->addData(image_stream->colorspace().toUtf8());
// Alpha associated setting
hash->addData(QString::number(image_stream->premultiplied_alpha()).toUtf8());
}
// Footage timestamp
if (stream->type() == Stream::kVideo) {
hash->addData(QStringLiteral("%1/%2").arg(QString::number(input_time.numerator()),
QString::number(input_time.denominator())).toUtf8());
hash->addData(QString::number(static_cast<VideoStream*>(stream.get())->start_time()).toUtf8());
/*Decoder::RetrieveState state = decoder->GetRetrieveState(input_time);
if (state == Decoder::kReady) {
VideoStreamPtr video_stream = std::static_pointer_cast<VideoStream>(stream);
int64_t timestamp_here = video_stream->get_closest_timestamp_in_frame_index(input_time);
hash->addData(QString::number(timestamp_here).toUtf8());
} else {
ReportUnavailableFootage(stream, state, input_time);
}*/
}
}
}
}
}
}
}
void VideoRenderWorker::SetParameters(const VideoRenderingParams &video_params)
{
video_params_ = video_params;
@@ -380,7 +269,9 @@ void VideoRenderWorker::Download(const rational& time, QVariant texture, QString
TextureToBuffer(texture, frame->width(), frame->height(), frame_gen_mat_, frame->data(), frame->linesize_pixels());
}
emit GeneratedFrame(time, frame);
frame->set_timestamp(time);
emit GeneratedFrame(frame);
}
}
+1 -3
View File
@@ -79,7 +79,7 @@ signals:
void HashAlreadyExists(NodeDependency path, qint64 job_time, QByteArray hash);
void GeneratedFrame(const rational &time, FramePtr frame);
void GeneratedFrame(FramePtr frame);
void Aborted();
@@ -105,8 +105,6 @@ protected:
ColorProcessorCache* color_cache();
private:
void HashNodeRecursively(QCryptographicHash* hash, const Node *n, const rational &time);
void Download(const rational &time, QVariant texture, QString filename);
void ResizeDownloadBuffer();
+10
View File
@@ -61,6 +61,16 @@ OCIO::ConstConfigRcPtr ColorManager::GetDefaultConfig()
void ColorManager::SetUpDefaultConfig()
{
if (!qgetenv("OCIO").isEmpty()) {
try {
default_config_ = OCIO::Config::CreateFromEnv();
return;
} catch (OCIO::Exception& e) {
qWarning() << "Failed to load config from OCIO environment variable config:" << e.what();
}
}
// Kind of hacky, but it'll work
QString dir = QDir(FileFunctions::GetTempFilePath()).filePath(QStringLiteral("ocioconf"));
+25 -4
View File
@@ -25,6 +25,7 @@
#include <QDebug>
#include <QFloat16>
#include "codec/oiio/oiiodecoder.h"
#include "common/define.h"
#include "core.h"
@@ -213,19 +214,39 @@ FramePtr PixelFormat::ConvertPixelFormat(FramePtr frame, const PixelFormat::Form
return frame;
}
// Create a destination frame with the same parameters
FramePtr converted = Frame::Create();
// Copy parameters
converted->set_video_params(VideoRenderingParams(frame->video_params().width(),
frame->video_params().height(),
dest_format));
converted->set_timestamp(frame->timestamp());
converted->allocate();
OIIO::ImageBuf src(OIIO::ImageSpec(frame->width(), frame->height(), ChannelCount(frame->format()), GetOIIOTypeDesc(frame->format())), frame->data());
OIIO::ImageBuf dst(OIIO::ImageSpec(converted->width(), converted->height(), ChannelCount(converted->format()), GetOIIOTypeDesc(converted->format())), converted->data());
// Do the conversion through OIIO - create a buffer for the source image
OIIO::ImageBuf src(OIIO::ImageSpec(frame->width(),
frame->height(),
ChannelCount(frame->format()),
GetOIIOTypeDesc(frame->format())));
// Set the pixels (this is necessary as opposed to an OIIO buffer wrapper since Frame has
// linesizes)
src.set_pixels(OIIO::ROI(),
GetOIIOTypeDesc(frame->format()),
frame->const_data(),
OIIO::AutoStride,
frame->linesize_bytes());
// Create a destination OIIO buffer with our destination format
OIIO::ImageBuf dst(OIIO::ImageSpec(converted->width(),
converted->height(),
ChannelCount(converted->format()),
GetOIIOTypeDesc(converted->format())));
if (dst.copy_pixels(src)) {
// Convert our buffer back to a frame
OIIODecoder::BufferToFrame(&dst, converted);
return converted;
} else {
return nullptr;
+1 -1
View File
@@ -29,7 +29,7 @@ out vec4 fragColor;
// Double gaussian formula, actually used in the code below
// Should be faster than the single gaussian above since it doesn't need sqrt()
float gaussian2(float x, float y, float sigma) {
return (1.0/(pow(sigma, 2.0)*2.0*M_PI))*exp(-0.5*((pow(x, 2.0) + pow(y, 2.0))/pow(sigma, 2.0)));
return (1.0/((sigma*sigma)*2.0*M_PI))*exp(-0.5*(((x*x) + (y*y))/(sigma*sigma)));
}
void main(void) {
+50 -10
View File
@@ -5,8 +5,13 @@
uniform sampler2D ove_maintex;
uniform vec2 ove_resolution;
uniform vec2 ove_viewport;
uniform vec3 luma_coeffs;
uniform float threshold;
uniform float waveform_scale;
uniform vec2 waveform_dims;
uniform vec4 waveform_region;
uniform vec4 waveform_uv;
in vec2 ove_texcoord;
@@ -14,18 +19,53 @@ out vec4 fragColor;
void main(void) {
vec3 col = vec3(0.0);
float s = ove_texcoord.y * 1.8 - 0.15;
float maxb = s + threshold;
float minb = s - threshold;
// Set an increment default to 10 bit encodings. This would likely be
// better served as a UI control, as waveforms will change their combing
// based on how granular the increment is set. For example, it can be
// challenging to spot 8 bit combing with an increment of 1. / 2.^8 - 1.
float increment = 1.0 / (pow(2, 10) - 1.0);
float maxb = waveform_dims.y + increment;
float minb = waveform_dims.y - increment;
int y_lim = int(ove_resolution.y);
// Intensity would make sense to also expose via the UI, as a density
// slider allows you to peek past certain values or reveal very low
// values. Hard coding it for now, as there isn't a clear way to have
// the various bit depth / code values always display at a consistent
// emission output strength.
float intensity = 0.10;
for (int i = 0; i < y_lim; i++) {
vec3 x = texture(ove_maintex, vec2(ove_texcoord.x, float(i) / float(ove_resolution.y))).rgb;
col += step(x, vec3(maxb)) * step(vec3(minb), x) / (ove_resolution.y * 0.125);
int y_lim = int(waveform_dims.y);
float l = dot(x, x);
col += step(l, maxb * maxb) * step(minb * minb, l) / (ove_resolution.y * 0.125);
vec3 cur_col = vec3(0.0);
vec3 cur_lum = vec3(0.0);
if (
(gl_FragCoord.x >= waveform_region.x) &&
(gl_FragCoord.y >= waveform_region.y) &&
(gl_FragCoord.x < waveform_region.z) &&
(gl_FragCoord.y < waveform_region.w)
) {
// col = vec3(0.5, 0.5, 0.0);
// int start = int(waveform_region.y);
int stop = int(waveform_dims.y);
float ratio = 0.0;
float waveform_x = (ove_texcoord.x - waveform_uv.x) / waveform_scale;
float waveform_y = (ove_texcoord.y - waveform_uv.y) / waveform_scale;
for (int i = 0; i < waveform_dims.y; i++) {
ratio = float(i) / float(waveform_dims.y);
cur_col = texture(
ove_maintex,
vec2(waveform_x, ratio)
).rgb;
col += step(vec3(waveform_y - increment), cur_col) *
step(cur_col, vec3(waveform_y + increment)) * intensity;
cur_lum = vec3(dot(cur_col, luma_coeffs));
col += step(vec3(waveform_y - increment), cur_lum) *
step(cur_lum, vec3(waveform_y + increment)) * intensity;
}
}
fragColor = vec4(col, 1.0);
+1 -1
View File
@@ -4,7 +4,6 @@ Base=#191919
BrightText=#FF0000
Button=#353535
ButtonText=#FFFFFF
Disabled-ButtonText=#808080
Highlight=#2A82DA
HighlightedText=#FFFFFF
Link=#2A82DA
@@ -16,3 +15,4 @@ WindowText=#FFFFFF
[Disabled]
ButtonText=#808080
Text=#A0A0A0
+13
View File
@@ -168,6 +168,11 @@ void CurveWidget::SetVerticalScale(const double &vscale)
view_->SetYScale(vscale);
}
void CurveWidget::DeleteSelected()
{
view_->DeleteSelected();
}
void CurveWidget::changeEvent(QEvent *e)
{
if (e->type() == QEvent::LanguageChange) {
@@ -200,6 +205,8 @@ void CurveWidget::ScaleChangedEvent(const double &scale)
void CurveWidget::TimeTargetChangedEvent(Node *target)
{
ConnectViewerNode(nullptr);
key_control_->SetTimeTarget(target);
view_->SetTimeTarget(target);
@@ -207,6 +214,12 @@ void CurveWidget::TimeTargetChangedEvent(Node *target)
if (bridge_) {
bridge_->SetTimeTarget(target);
}
// FIXME: If a non-viewer node is ever set here, it will fail to update the length
ViewerOutput* viewer = dynamic_cast<ViewerOutput*>(target);
if (viewer) {
ConnectViewerNode(viewer);
}
}
void CurveWidget::UpdateInputLabel()
+2
View File
@@ -47,6 +47,8 @@ public:
const double& GetVerticalScale();
void SetVerticalScale(const double& vscale);
void DeleteSelected();
protected:
virtual void changeEvent(QEvent *) override;
+20 -1
View File
@@ -73,6 +73,25 @@ void KeyframeViewBase::SetYScale(const double &y_scale)
}
}
void KeyframeViewBase::DeleteSelected()
{
QUndoCommand* command = new QUndoCommand();
QMap<NodeKeyframe*, KeyframeViewItem*>::const_iterator i;
for (i=item_map_.constBegin(); i!=item_map_.constEnd(); i++) {
if (i.value()->isSelected()) {
NodeInput* input_parent = i.key()->parent();
new NodeParamRemoveKeyframeCommand(input_parent,
input_parent->get_keyframe_shared_ptr_from_raw(i.key()),
command);
}
}
Core::instance()->undo_stack()->pushIfHasChildren(command);
}
void KeyframeViewBase::RemoveKeyframe(NodeKeyframePtr key)
{
KeyframeAboutToBeRemoved(key.get());
@@ -391,7 +410,7 @@ void KeyframeViewBase::ShowContextMenu()
{
Menu m;
MenuShared::instance()->AddItemsForEditMenu(&m);
MenuShared::instance()->AddItemsForEditMenu(&m, false);
QAction* linear_key_action = nullptr;
QAction* bezier_key_action = nullptr;
@@ -40,6 +40,8 @@ public:
const double& GetYScale() const;
void SetYScale(const double& y_scale);
void DeleteSelected();
public slots:
void RemoveKeyframe(NodeKeyframePtr key);
+9 -2
View File
@@ -32,6 +32,11 @@ ManagedDisplayWidget::ManagedDisplayWidget(QWidget *parent) :
setContextMenuPolicy(Qt::CustomContextMenu);
}
ManagedDisplayWidget::~ManagedDisplayWidget()
{
ContextCleanup();
}
void ManagedDisplayWidget::ConnectColorManager(ColorManager *color_manager)
{
if (color_manager_ == color_manager) {
@@ -142,9 +147,13 @@ void ManagedDisplayWidget::MenuLookSelect(QAction *action)
void ManagedDisplayWidget::SetColorTransform(const ColorTransform &transform)
{
makeCurrent();
color_transform_ = transform;
SetupColorProcessor();
ColorProcessorChangedEvent();
doneCurrent();
}
void ManagedDisplayWidget::initializeGL()
@@ -248,9 +257,7 @@ void ManagedDisplayWidget::SetupColorProcessor()
color_manager_->GetReferenceColorSpace(),
color_transform_);
makeCurrent();
color_service_->Enable(context(), true);
doneCurrent();
} catch (OCIO::Exception& e) {
@@ -35,6 +35,8 @@ class ManagedDisplayWidget : public QOpenGLWidget
public:
ManagedDisplayWidget(QWidget* parent = nullptr);
virtual ~ManagedDisplayWidget() override;
/**
* @brief Disconnect a ColorManager (equivalent to ConnectColorManager(nullptr))
*/
+11
View File
@@ -50,6 +50,17 @@ Menu::Menu(const QString &s, QWidget *parent) :
Init();
}
QAction *Menu::AddActionWithData(const QString &text, const QVariant &data, const QVariant &compare)
{
QAction* a = addAction(text);
a->setData(data);
a->setCheckable(true);
a->setChecked(data == compare);
return a;
}
QAction* Menu::InsertAlphabetically(const QString &s)
{
QAction* action = new QAction(s, this);
+4
View File
@@ -131,6 +131,10 @@ public:
return a;
}
QAction* AddActionWithData(const QString& text,
const QVariant& data,
const QVariant& compare);
QAction *InsertAlphabetically(const QString& s);
void InsertAlphabetically(QAction* entry);
void InsertAlphabetically(Menu* menu);
+7 -4
View File
@@ -79,7 +79,7 @@ void MenuShared::AddItemsForNewMenu(Menu *m)
m->addAction(new_folder_item_);
}
void MenuShared::AddItemsForEditMenu(Menu *m)
void MenuShared::AddItemsForEditMenu(Menu *m, bool for_clips)
{
m->addAction(edit_cut_item_);
m->addAction(edit_copy_item_);
@@ -87,8 +87,11 @@ void MenuShared::AddItemsForEditMenu(Menu *m)
m->addAction(edit_paste_insert_item_);
m->addAction(edit_duplicate_item_);
m->addAction(edit_delete_item_);
m->addAction(edit_ripple_delete_item_);
m->addAction(edit_split_item_);
if (for_clips) {
m->addAction(edit_ripple_delete_item_);
m->addAction(edit_split_item_);
}
}
void MenuShared::AddItemsForInOutMenu(Menu *m)
@@ -185,7 +188,7 @@ void MenuShared::PasteInsertTriggered()
void MenuShared::DuplicateTriggered()
{
qDebug() << "FIXME: Stub";
PanelManager::instance()->CurrentlyFocused()->Duplicate();
}
void MenuShared::EnableDisableTriggered()
+1 -1
View File
@@ -39,7 +39,7 @@ public:
void Retranslate();
void AddItemsForNewMenu(Menu* m);
void AddItemsForEditMenu(Menu* m);
void AddItemsForEditMenu(Menu* m, bool for_clips);
void AddItemsForInOutMenu(Menu* m);
void AddItemsForClipEditMenu(Menu* m);
+13 -1
View File
@@ -158,7 +158,9 @@ void NodeParamView::SetNodes(QList<Node *> nodes)
items_.append(item);
QMetaObject::invokeMethod(item, "SignalAllKeyframes", Qt::QueuedConnection);
QMetaObject::invokeMethod(item,
"SignalAllKeyframes",
Qt::QueuedConnection);
emit OpenedNode(node);
@@ -225,6 +227,16 @@ const QList<Node *> &NodeParamView::nodes()
return nodes_;
}
Node *NodeParamView::GetTimeTarget() const
{
return keyframe_view_->GetTimeTarget();
}
void NodeParamView::DeleteSelected()
{
keyframe_view_->DeleteSelected();
}
void NodeParamView::UpdateItemTime(const int64_t &timestamp)
{
rational time = Timecode::timestamp_to_time(timestamp, keyframe_view_->timebase());
+4
View File
@@ -40,6 +40,10 @@ public:
void SetNodes(QList<Node*> nodes);
const QList<Node*>& nodes();
Node* GetTimeTarget() const;
void DeleteSelected();
signals:
void InputDoubleClicked(NodeInput* input);
@@ -73,6 +73,8 @@ NodeParamViewItem::NodeParamViewItem(Node *node, QWidget *parent) :
connect(title_bar_collapse_btn_, &QPushButton::toggled, body_, &NodeParamViewItemBody::setVisible);
main_layout->addWidget(body_);
connect(node_, &Node::LabelChanged, this, &NodeParamViewItem::Retranslate);
Retranslate();
}
@@ -111,7 +113,11 @@ void NodeParamViewItem::Retranslate()
{
node_->Retranslate();
title_bar_lbl_->setText(node_->Name());
if (node_->GetLabel().isEmpty()) {
title_bar_lbl_->setText(node_->Name());
} else {
title_bar_lbl_->setText(tr("%1 (%2)").arg(node_->GetLabel(), node_->Name()));
}
body_->Retranslate();
}
+3 -2
View File
@@ -129,8 +129,6 @@ protected:
virtual void changeEvent(QEvent *e) override;
private:
void Retranslate();
NodeParamViewItemTitleBar* title_bar_;
QLabel* title_bar_lbl_;
@@ -143,6 +141,9 @@ private:
rational time_;
private slots:
void Retranslate();
};
OLIVE_NAMESPACE_EXIT
+1
View File
@@ -18,6 +18,7 @@ set(OLIVE_SOURCES
${OLIVE_SOURCES}
widget/nodeview/nodeview.h
widget/nodeview/nodeview.cpp
widget/nodeview/nodeviewcommon.h
widget/nodeview/nodeviewedge.h
widget/nodeview/nodeviewedge.cpp
widget/nodeview/nodeviewitem.h
+286 -78
View File
@@ -20,30 +20,34 @@
#include "nodeview.h"
#include <QInputDialog>
#include <QMouseEvent>
#include "core.h"
#include "nodeviewundo.h"
#include "node/factory.h"
#include "widget/menu/menushared.h"
#define super HandMovableView
OLIVE_NAMESPACE_ENTER
NodeView::NodeView(QWidget *parent) :
QGraphicsView(parent),
HandMovableView(parent),
graph_(nullptr),
attached_item_(nullptr),
drop_edge_(nullptr)
{
setScene(&scene_);
setDragMode(RubberBandDrag);
setContextMenuPolicy(Qt::CustomContextMenu);
setMouseTracking(true);
setRenderHint(QPainter::Antialiasing);
connect(&scene_, &QGraphicsScene::changed, this, &NodeView::ItemsChanged);
connect(&scene_, &QGraphicsScene::selectionChanged, this, &NodeView::SceneSelectionChangedSlot);
connect(this, &NodeView::customContextMenuRequested, this, &NodeView::ShowContextMenu);
setMouseTracking(true);
setRenderHint(QPainter::Antialiasing);
SetFlowDirection(NodeViewCommon::kTopToBottom);
}
NodeView::~NodeView()
@@ -93,21 +97,33 @@ void NodeView::DeleteSelected()
return;
}
QList<Node*> selected_nodes = scene_.GetSelectedNodes();
QUndoCommand* command = new QUndoCommand();
// Ensure no nodes are "undeletable"
for (int i=0;i<selected_nodes.size();i++) {
if (!selected_nodes.at(i)->CanBeDeleted()) {
selected_nodes.removeAt(i);
i--;
{
QList<NodeEdge*> selected_edges = scene_.GetSelectedEdges();
foreach (NodeEdge* edge, selected_edges) {
new NodeEdgeRemoveCommand(edge->output(), edge->input(), command);
}
}
if (selected_nodes.isEmpty()) {
return;
{
QList<Node*> selected_nodes = scene_.GetSelectedNodes();
// Ensure no nodes are "undeletable"
for (int i=0;i<selected_nodes.size();i++) {
if (!selected_nodes.at(i)->CanBeDeleted()) {
selected_nodes.removeAt(i);
i--;
}
}
if (!selected_nodes.isEmpty()) {
new NodeRemoveCommand(graph_, selected_nodes, command);
}
}
Core::instance()->undo_stack()->push(new NodeRemoveCommand(graph_, selected_nodes));
Core::instance()->undo_stack()->pushIfHasChildren(command);
}
void NodeView::SelectAll()
@@ -181,7 +197,60 @@ void NodeView::Paste()
Core::instance()->undo_stack()->pushIfHasChildren(command);
if (!pasted_nodes.isEmpty()) {
// FIXME: Attach to cursor so user can drop in place
AttachNodesToCursor(pasted_nodes);
}
}
void NodeView::Duplicate()
{
if (!graph_) {
return;
}
QList<Node*> selected = scene_.GetSelectedNodes();
if (selected.isEmpty()) {
return;
}
QUndoCommand* command = new QUndoCommand();
QList<Node*> duplicated_nodes;
foreach (Node* n, selected) {
Node* copy = n->copy();
Node::CopyInputs(n, copy, false);
duplicated_nodes.append(copy);
new NodeAddCommand(graph_, copy, command);
}
for (int i=0;i<selected.size();i++) {
Node* src = selected.at(i);
for (int j=0;j<selected.size();j++) {
if (i == j) {
continue;
}
Node* dst = selected.at(j);
foreach (NodeEdgePtr edge, src->output()->edges()) {
if (edge->input()->parentNode() == dst) {
new NodeEdgeAddCommand(duplicated_nodes.at(i)->output(),
duplicated_nodes.at(j)->GetInputWithID(edge->input()->id()),
command);
}
}
}
}
Core::instance()->undo_stack()->pushIfHasChildren(command);
if (!duplicated_nodes.isEmpty()) {
AttachNodesToCursor(duplicated_nodes);
}
}
@@ -196,10 +265,10 @@ void NodeView::ItemsChanged()
void NodeView::keyPressEvent(QKeyEvent *event)
{
QGraphicsView::keyPressEvent(event);
super::keyPressEvent(event);
if (event->key() == Qt::Key_Escape && attached_item_) {
DetachItemFromCursor();
if (event->key() == Qt::Key_Escape && !attached_items_.isEmpty()) {
DetachItemsFromCursor();
// We undo the last action which SHOULD be adding the node
// FIXME: Possible danger of this not being the case?
@@ -209,93 +278,111 @@ void NodeView::keyPressEvent(QKeyEvent *event)
void NodeView::mousePressEvent(QMouseEvent *event)
{
if (attached_item_) {
Node* dropping_node = attached_item_->GetNode();
if (HandPress(event)) return;
DetachItemFromCursor();
if (!attached_items_.isEmpty()) {
if (attached_items_.size() == 1) {
Node* dropping_node = attached_items_.first().item->GetNode();
if (drop_edge_) {
NodeEdgePtr old_edge = drop_edge_->edge();
if (drop_edge_) {
NodeEdgePtr old_edge = drop_edge_->edge();
// We have everything we need to place the node in between
QUndoCommand* command = new QUndoCommand();
// We have everything we need to place the node in between
QUndoCommand* command = new QUndoCommand();
// Remove old edge
new NodeEdgeRemoveCommand(old_edge, command);
// Remove old edge
new NodeEdgeRemoveCommand(old_edge, command);
// Place new edges
new NodeEdgeAddCommand(old_edge->output(), drop_compatible_input_, command);
new NodeEdgeAddCommand(dropping_node->output(), old_edge->input(), command);
// Place new edges
new NodeEdgeAddCommand(old_edge->output(), drop_input_, command);
new NodeEdgeAddCommand(dropping_node->output(), old_edge->input(), command);
Core::instance()->undo_stack()->push(command);
Core::instance()->undo_stack()->push(command);
}
drop_edge_ = nullptr;
}
drop_edge_ = nullptr;
DetachItemsFromCursor();
}
QGraphicsView::mousePressEvent(event);
super::mousePressEvent(event);
}
void NodeView::mouseMoveEvent(QMouseEvent *event)
{
QGraphicsView::mouseMoveEvent(event);
if (HandMove(event)) return;
if (attached_item_) {
attached_item_->setPos(mapToScene(event->pos()));
super::mouseMoveEvent(event);
// See if the user clicked on an edge
QRect edge_detect_rect(event->pos(), event->pos());
if (!attached_items_.isEmpty()) {
MoveAttachedNodesToCursor(event->pos());
// FIXME: Hardcoded numbers
edge_detect_rect.adjust(-20, -20, 20, 20);
// See if the user clicked on an edge (only when dropping single nodes)
if (attached_items_.size() == 1) {
Node* attached_node = attached_items_.first().item->GetNode();
QList<QGraphicsItem*> items = this->items(edge_detect_rect);
QRect edge_detect_rect(event->pos(), event->pos());
NodeViewEdge* new_drop_edge = nullptr;
// FIXME: Hardcoded numbers
edge_detect_rect.adjust(-20, -20, 20, 20);
foreach (QGraphicsItem* item, items) {
NodeViewEdge* edge = dynamic_cast<NodeViewEdge*>(item);
QList<QGraphicsItem*> items = this->items(edge_detect_rect);
if (edge) {
// Try to place this node inside this edge
NodeViewEdge* new_drop_edge = nullptr;
// See if the node we're dropping has an input of a compatible data type
NodeInput* edges_input = edge->edge()->input();
NodeParam::DataType input_type = edges_input->data_type();
// See if there is an edge here
foreach (QGraphicsItem* item, items) {
new_drop_edge = dynamic_cast<NodeViewEdge*>(item);
NodeInput* compatible_input = nullptr;
if (new_drop_edge) {
drop_input_ = nullptr;
foreach (NodeParam* drop_node_param, attached_item_->GetNode()->parameters()) {
if (drop_node_param->type() == NodeParam::kInput
&& static_cast<NodeInput*>(drop_node_param)->data_type() & input_type) {
compatible_input = static_cast<NodeInput*>(drop_node_param);
foreach (NodeParam* param, attached_node->parameters()) {
if (param->type() == NodeParam::kInput) {
NodeInput* input = static_cast<NodeInput*>(param);
if (input->IsConnectable()) {
if (input->data_type() & new_drop_edge->edge()->input()->data_type()) {
drop_input_ = input;
break;
} else if (!drop_input_) {
drop_input_ = input;
}
}
}
}
if (drop_input_) {
break;
} else {
new_drop_edge = nullptr;
}
}
}
if (compatible_input) {
new_drop_edge = edge;
drop_compatible_input_ = compatible_input;
break;
if (drop_edge_ != new_drop_edge) {
if (drop_edge_) {
drop_edge_->SetHighlighted(false);
}
}
}
if (drop_edge_ != new_drop_edge) {
if (drop_edge_) {
drop_edge_->SetHighlighted(false);
}
drop_edge_ = new_drop_edge;
drop_edge_ = new_drop_edge;
if (drop_edge_) {
drop_edge_->SetHighlighted(true);
if (drop_edge_) {
drop_edge_->SetHighlighted(true);
}
}
}
}
}
void NodeView::mouseReleaseEvent(QMouseEvent *event)
{
if (HandRelease(event)) return;
super::mouseReleaseEvent(event);
}
void NodeView::wheelEvent(QWheelEvent *event)
{
if (event->modifiers() & Qt::ControlModifier) {
@@ -321,10 +408,59 @@ void NodeView::ShowContextMenu(const QPoint &pos)
Menu m;
Menu* add_menu = NodeFactory::CreateMenu(&m);
add_menu->setTitle(tr("Add"));
connect(add_menu, &Menu::triggered, this, &NodeView::CreateNodeSlot);
m.addMenu(add_menu);
MenuShared::instance()->AddItemsForEditMenu(&m, false);
m.addSeparator();
QList<NodeViewItem*> selected = scene_.GetSelectedItems();
if (itemAt(pos) && !selected.isEmpty()) {
if (selected.size() == 1) {
// Label node action
QAction* label_action = m.addAction(tr("Label"));
connect(label_action, &QAction::triggered, this, &NodeView::ContextMenuLabelNode);
m.addSeparator();
}
// Auto-position action
QAction* autopos = m.addAction(tr("Auto-Position"));
connect(autopos, &QAction::triggered, this, &NodeView::AutoPositionDescendents);
} else {
Menu* direction_menu = new Menu(tr("Direction"), &m);
m.addMenu(direction_menu);
direction_menu->AddActionWithData(tr("Top to Bottom"),
NodeViewCommon::kTopToBottom,
scene_.GetFlowDirection());
direction_menu->AddActionWithData(tr("Bottom to Top"),
NodeViewCommon::kBottomToTop,
scene_.GetFlowDirection());
direction_menu->AddActionWithData(tr("Left to Right"),
NodeViewCommon::kLeftToRight,
scene_.GetFlowDirection());
direction_menu->AddActionWithData(tr("Right to Left"),
NodeViewCommon::kRightToLeft,
scene_.GetFlowDirection());
connect(direction_menu, &Menu::triggered, this, &NodeView::ContextMenuSetDirection);
m.addSeparator();
Menu* add_menu = NodeFactory::CreateMenu(&m);
add_menu->setTitle(tr("Add"));
connect(add_menu, &Menu::triggered, this, &NodeView::CreateNodeSlot);
m.addMenu(add_menu);
}
m.exec(mapToGlobal(pos));
}
@@ -337,7 +473,45 @@ void NodeView::CreateNodeSlot(QAction *action)
Core::instance()->undo_stack()->push(new NodeAddCommand(graph_, new_node));
NodeViewItem* item = scene_.NodeToUIObject(new_node);
AttachItemToCursor(item);
AttachItemsToCursor({item});
}
}
void NodeView::ContextMenuSetDirection(QAction *action)
{
SetFlowDirection(static_cast<NodeViewCommon::FlowDirection>(action->data().toInt()));
}
void NodeView::AutoPositionDescendents()
{
QList<Node*> selected = scene_.GetSelectedNodes();
foreach (Node* n, selected) {
scene_.ReorganizeFrom(n);
}
}
void NodeView::ContextMenuLabelNode()
{
QList<Node*> nodes = scene_.GetSelectedNodes();
if (nodes.isEmpty()) {
return;
}
Node* n = nodes.first();
bool ok;
QString s = QInputDialog::getText(this,
tr("Label Node"),
tr("Set node label"),
QLineEdit::Normal,
n->GetLabel(),
&ok);
if (ok) {
n->SetLabel(s);
}
}
@@ -475,16 +649,50 @@ void NodeView::PlaceNode(NodeViewItem *n, const QPointF &pos)
}
}
void NodeView::AttachItemToCursor(NodeViewItem *item)
void NodeView::AttachNodesToCursor(const QList<Node *> &nodes)
{
attached_item_ = item;
QList<NodeViewItem*> items;
setMouseTracking(attached_item_);
foreach (Node* p, nodes) {
items.append(scene_.NodeToUIObject(p));
}
AttachItemsToCursor(items);
}
void NodeView::DetachItemFromCursor()
void NodeView::AttachItemsToCursor(const QList<NodeViewItem*>& items)
{
AttachItemToCursor(nullptr);
DetachItemsFromCursor();
if (!items.isEmpty()) {
foreach (NodeViewItem* i, items) {
attached_items_.append({i, i->pos() - items.first()->pos()});
}
setMouseTracking(true);
MoveAttachedNodesToCursor(mapFromGlobal(QCursor::pos()));
}
}
void NodeView::DetachItemsFromCursor()
{
attached_items_.clear();
setMouseTracking(false);
}
void NodeView::SetFlowDirection(NodeViewCommon::FlowDirection dir)
{
scene_.SetFlowDirection(dir);
}
void NodeView::MoveAttachedNodesToCursor(const QPoint& p)
{
QPointF item_pos = mapToScene(p);
foreach (const AttachedItem& i, attached_items_) {
i.item->setPos(item_pos + i.original_pos);
}
}
OLIVE_NAMESPACE_EXIT
+35 -6
View File
@@ -26,6 +26,7 @@
#include "node/graph.h"
#include "nodeviewscene.h"
#include "widget/timelinewidget/view/handmovableview.h"
#include "widget/nodecopypaste/nodecopypaste.h"
OLIVE_NAMESPACE_ENTER
@@ -36,7 +37,7 @@ OLIVE_NAMESPACE_ENTER
* This widget takes a NodeGraph object and constructs a QGraphicsScene representing its data, viewing and allowing
* the user to make modifications to it.
*/
class NodeView : public QGraphicsView, public NodeCopyPasteWidget
class NodeView : public HandMovableView, public NodeCopyPasteWidget
{
Q_OBJECT
public:
@@ -63,6 +64,8 @@ public:
void CopySelected(bool cut);
void Paste();
void Duplicate();
signals:
/**
* @brief Signal emitted when the selected nodes have changed
@@ -73,24 +76,35 @@ protected:
virtual void keyPressEvent(QKeyEvent *event) override;
virtual void mousePressEvent(QMouseEvent *event) override;
virtual void mouseMoveEvent(QMouseEvent *event) override;
virtual void mouseReleaseEvent(QMouseEvent* event) override;
virtual void wheelEvent(QWheelEvent* event) override;
private:
void PlaceNode(NodeViewItem* n, const QPointF& pos);
void AttachItemToCursor(NodeViewItem* item);
void AttachNodesToCursor(const QList<Node*>& nodes);
void DetachItemFromCursor();
void AttachItemsToCursor(const QList<NodeViewItem*>& items);
void DetachItemsFromCursor();
void SetFlowDirection(NodeViewCommon::FlowDirection dir);
void MoveAttachedNodesToCursor(const QPoint &p);
NodeGraph* graph_;
NodeViewItem* attached_item_;
struct AttachedItem {
NodeViewItem* item;
QPointF original_pos;
};
QList<AttachedItem> attached_items_;
NodeViewEdge* drop_edge_;
NodeInput* drop_compatible_input_;
NodeInput* drop_input_;
NodeViewScene scene_;
@@ -117,6 +131,21 @@ private slots:
*/
void CreateNodeSlot(QAction* action);
/**
* @brief Receiver for setting the direction from the context menu
*/
void ContextMenuSetDirection(QAction* action);
/**
* @brief Receiver for auto-position descendents menu action
*/
void AutoPositionDescendents();
/**
* @brief Receiver for labelling a node from the context menu
*/
void ContextMenuLabelNode();
};
OLIVE_NAMESPACE_EXIT
+58
View File
@@ -0,0 +1,58 @@
/***
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 NODEVIEWCOMMON_H
#define NODEVIEWCOMMON_H
#include <QtGlobal>
#include "common/define.h"
OLIVE_NAMESPACE_ENTER
class NodeViewCommon {
public:
enum FlowDirection {
kTopToBottom,
kBottomToTop,
kLeftToRight,
kRightToLeft
};
static Qt::Orientation GetFlowOrientation(FlowDirection dir) {
if (dir == kTopToBottom || dir == kBottomToTop) {
return Qt::Vertical;
} else {
return Qt::Horizontal;
}
}
static bool DirectionsAreOpposing(FlowDirection a, FlowDirection b) {
return ((a == NodeViewCommon::kLeftToRight && b == NodeViewCommon::kRightToLeft)
|| (a == NodeViewCommon::kRightToLeft && b == NodeViewCommon::kLeftToRight)
|| (a == NodeViewCommon::kTopToBottom && b == NodeViewCommon::kBottomToTop)
|| (a == NodeViewCommon::kBottomToTop && b == NodeViewCommon::kTopToBottom));
}
};
OLIVE_NAMESPACE_EXIT
#endif // NODEVIEWCOMMON_H
+59 -21
View File
@@ -23,6 +23,7 @@
#include <QApplication>
#include <QDebug>
#include <QGraphicsSceneMouseEvent>
#include <QStyleOptionGraphicsItem>
#include "common/clamp.h"
#include "common/lerp.h"
@@ -34,9 +35,12 @@ OLIVE_NAMESPACE_ENTER
NodeViewEdge::NodeViewEdge(QGraphicsItem *parent) :
QGraphicsPathItem(parent),
edge_(nullptr),
color_group_(QPalette::Active),
color_role_(QPalette::Text)
connected_(false),
highlighted_(false),
flow_dir_(NodeViewCommon::kLeftToRight)
{
setFlag(QGraphicsItem::ItemIsSelectable);
// Ensures this UI object is drawn behind other objects
setZValue(-1);
@@ -75,45 +79,79 @@ void NodeViewEdge::Adjust()
}
// Draw a line between the two
SetPoints(output->GetParamPoint(edge_->output()), input->GetParamPoint(edge_->input()));
SetPoints(output->GetParamPoint(edge_->output(), output->pos()),
input->GetParamPoint(edge_->input(), output->pos()),
input->IsExpanded());
}
void NodeViewEdge::SetConnected(bool c)
{
if (c) {
color_group_ = QPalette::Active;
} else {
color_group_ = QPalette::Disabled;
}
connected_ = c;
UpdatePen();
update();
}
void NodeViewEdge::SetHighlighted(bool e)
{
if (e) {
color_role_ = QPalette::Highlight;
} else {
color_role_ = QPalette::Text;
}
highlighted_ = e;
UpdatePen();
update();
}
void NodeViewEdge::SetPoints(const QPointF &start, const QPointF &end)
void NodeViewEdge::SetPoints(const QPointF &start, const QPointF &end, bool input_is_expanded)
{
QPainterPath path;
double half_x = lerp(start.x(), end.x(), 0.5);
path.moveTo(start);
path.cubicTo(QPointF(half_x, start.y()), QPointF(half_x, end.y()), end);
double half_x = lerp(start.x(), end.x(), 0.5);
double half_y = lerp(start.y(), end.y(), 0.5);
QPointF cp1, cp2;
if (NodeViewCommon::GetFlowOrientation(flow_dir_) == Qt::Horizontal) {
cp1 = QPointF(half_x, start.y());
} else {
cp1 = QPointF(start.x(), half_y);
}
if (NodeViewCommon::GetFlowOrientation(flow_dir_) == Qt::Horizontal || input_is_expanded) {
cp2 = QPointF(half_x, end.y());
} else {
cp2 = QPointF(end.x(), half_y);
}
path.cubicTo(cp1, cp2, end);
setPath(path);
}
void NodeViewEdge::UpdatePen()
void NodeViewEdge::SetFlowDirection(NodeViewCommon::FlowDirection dir)
{
setPen(QPen(qApp->palette().color(color_group_, color_role_), edge_width_));
flow_dir_ = dir;
//update();
Adjust();
}
void NodeViewEdge::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *)
{
QPalette::ColorGroup group;
QPalette::ColorRole role;
if (connected_) {
group = QPalette::Active;
} else {
group = QPalette::Disabled;
}
if (highlighted_ != bool(option->state & QStyle::State_Selected)) {
role = QPalette::Highlight;
} else {
role = QPalette::Text;
}
painter->setPen(QPen(qApp->palette().color(group, role), edge_width_));
painter->setBrush(Qt::NoBrush);
painter->drawPath(path());
}
OLIVE_NAMESPACE_EXIT
+14 -5
View File
@@ -25,6 +25,7 @@
#include <QPalette>
#include "node/edge.h"
#include "nodeviewcommon.h"
OLIVE_NAMESPACE_ENTER
@@ -81,18 +82,26 @@ public:
/**
* @brief Set points to create curve from
*/
void SetPoints(const QPointF& start, const QPointF& end);
void SetPoints(const QPointF& start, const QPointF& end, bool input_is_expanded);
/**
* @brief Sets the direction nodes are flowing
*/
void SetFlowDirection(NodeViewCommon::FlowDirection dir);
protected:
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override;
private:
void UpdatePen();
NodeEdgePtr edge_;
int edge_width_;
QPalette::ColorGroup color_group_;
bool connected_;
QPalette::ColorRole color_role_;
bool highlighted_;
NodeViewCommon::FlowDirection flow_dir_;
};
+244 -59
View File
@@ -44,9 +44,11 @@ NodeViewItem::NodeViewItem(QGraphicsItem *parent) :
cached_drop_item_(nullptr),
cached_drop_item_expanded_(false),
expanded_(false),
hide_titlebar_(false),
standard_click_(false),
highlighted_index_(-1),
node_edge_change_command_(nullptr)
node_edge_change_command_(nullptr),
flow_dir_(NodeViewCommon::kLeftToRight)
{
// Set flags for this widget
setFlag(QGraphicsItem::ItemIsMovable);
@@ -57,26 +59,110 @@ NodeViewItem::NodeViewItem(QGraphicsItem *parent) :
// We use font metrics to set all the UI measurements for DPI-awareness
//
QFont default_font;
QFontMetrics font_metrics(default_font);
// Set border width
node_border_width_ = font_metrics.height() / 12;
node_border_width_ = DefaultItemBorder();
// Set text and icon padding
int node_text_padding = font_metrics.height() / 4;
// Not particularly great way of using text scaling to set the width (DPI-awareness, etc.)
int widget_width = QFontMetricsWidth(font_metrics, "HHHHHHHHHHHHHH");
// Use the current default font height to size this widget
// Set default "collapsed" size
int widget_height = font_metrics.height() + node_text_padding * 2;
int widget_width = DefaultItemWidth();
int widget_height = DefaultItemHeight();
title_bar_rect_ = QRectF(-widget_width/2, -widget_height/2, widget_width, widget_height);
setRect(title_bar_rect_);
}
QPointF NodeViewItem::GetNodePosition() const
{
QPointF node_pos;
qreal adjusted_x = pos().x() / DefaultItemHorizontalPadding();
qreal adjusted_y = pos().y() / DefaultItemVerticalPadding();
switch (flow_dir_) {
case NodeViewCommon::kLeftToRight:
node_pos.setX(adjusted_x);
node_pos.setY(adjusted_y);
break;
case NodeViewCommon::kRightToLeft:
node_pos.setX(-adjusted_x);
node_pos.setY(adjusted_y);
break;
case NodeViewCommon::kTopToBottom:
node_pos.setX(adjusted_y);
node_pos.setY(adjusted_x);
break;
case NodeViewCommon::kBottomToTop:
node_pos.setX(-adjusted_y);
node_pos.setY(adjusted_x);
break;
}
return node_pos;
}
void NodeViewItem::SetNodePosition(const QPointF &pos)
{
switch (flow_dir_) {
case NodeViewCommon::kLeftToRight:
setPos(pos.x() * DefaultItemHorizontalPadding(),
pos.y() * DefaultItemVerticalPadding());
break;
case NodeViewCommon::kRightToLeft:
setPos(-pos.x() * DefaultItemHorizontalPadding(),
pos.y() * DefaultItemVerticalPadding());
break;
case NodeViewCommon::kTopToBottom:
setPos(pos.y() * DefaultItemHorizontalPadding(),
pos.x() * DefaultItemVerticalPadding());
break;
case NodeViewCommon::kBottomToTop:
setPos(pos.y() * DefaultItemHorizontalPadding(),
-pos.x() * DefaultItemVerticalPadding());
break;
}
}
int NodeViewItem::DefaultTextPadding()
{
return QFontMetrics(QFont()).height() / 4;
}
int NodeViewItem::DefaultItemHeight()
{
return QFontMetrics(QFont()).height() + DefaultTextPadding() * 2;
}
int NodeViewItem::DefaultItemWidth()
{
return QFontMetricsWidth(QFontMetrics(QFont()), "HHHHHHHHHH");;
}
int NodeViewItem::DefaultMaximumTextWidth()
{
return QFontMetricsWidth(QFontMetrics(QFont()), "HHHHHHHH");;
}
int NodeViewItem::DefaultItemBorder()
{
return QFontMetrics(QFont()).height() / 12;
}
qreal NodeViewItem::DefaultItemHorizontalPadding() const
{
if (NodeViewCommon::GetFlowOrientation(flow_dir_) == Qt::Horizontal) {
return DefaultItemWidth() * 1.5;
} else {
return DefaultItemWidth() * 1.25;
}
}
qreal NodeViewItem::DefaultItemVerticalPadding() const
{
if (NodeViewCommon::GetFlowOrientation(flow_dir_) == Qt::Horizontal) {
return DefaultItemHeight() * 1.5;
} else {
return DefaultItemHeight() * 2.0;
}
}
void NodeViewItem::SetNode(Node *n)
{
node_ = n;
@@ -96,7 +182,7 @@ void NodeViewItem::SetNode(Node *n)
}
}
setPos(node_->GetPosition());
SetNodePosition(node_->GetPosition());
}
update();
@@ -112,18 +198,26 @@ bool NodeViewItem::IsExpanded() const
return expanded_;
}
void NodeViewItem::SetExpanded(bool e)
void NodeViewItem::SetExpanded(bool e, bool hide_titlebar)
{
if (expanded_ == e) {
if (node_inputs_.isEmpty()
|| (expanded_ == e && hide_titlebar_ == hide_titlebar)) {
return;
}
expanded_ = e;
hide_titlebar_ = hide_titlebar;
if (expanded_ && !node_inputs_.isEmpty()) {
// Create new rect
QRectF new_rect = title_bar_rect_;
new_rect.setHeight(new_rect.height() * node_inputs_.size());
if (hide_titlebar_) {
new_rect.setHeight(new_rect.height() * node_inputs_.size());
} else {
new_rect.setHeight(new_rect.height() * (node_inputs_.size() + 1));
}
setRect(new_rect);
} else {
setRect(title_bar_rect_);
@@ -143,33 +237,14 @@ void NodeViewItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti
// don't want here)
QPalette app_pal = Core::instance()->main_window()->palette();
{
QPen border_pen;
border_pen.setWidth(node_border_width_);
QBrush bkg_color;
if (option->state & QStyle::State_Selected) {
border_pen.setColor(app_pal.color(QPalette::Highlight));
} else {
border_pen.setColor(css_proxy_.BorderColor());
}
if (IsExpanded()) {
bkg_color = app_pal.color(QPalette::Window);
} else {
bkg_color = css_proxy_.TitleBarColor();
}
painter->setPen(border_pen);
painter->setBrush(bkg_color);
// Draw background rect if expanded
if (IsExpanded()) {
painter->setPen(Qt::NoPen);
painter->setBrush(app_pal.color(QPalette::Window));
painter->drawRect(rect());
}
painter->setPen(app_pal.color(QPalette::Text));
if (IsExpanded()) {
painter->setPen(app_pal.color(QPalette::Text));
for (int i=0;i<node_inputs_.size();i++) {
QRectF input_rect = GetInputRect(i);
@@ -180,13 +255,65 @@ void NodeViewItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti
painter->drawText(input_rect, Qt::AlignCenter, node_inputs_.at(i)->name());
}
}
} else if (node_) {
// Draw the titlebar
if (!hide_titlebar_ && node_) {
painter->setPen(Qt::black);
painter->setBrush(css_proxy_.TitleBarColor());
painter->drawRect(title_bar_rect_);
painter->setPen(app_pal.color(QPalette::Text));
QString node_label;
if (node_->GetLabel().isEmpty()) {
node_label = node_->ShortName();
} else {
node_label = node_->GetLabel();
}
{
QFont f;
QFontMetrics fm(f);
int max_text_width = DefaultMaximumTextWidth();
if (QFontMetricsWidth(fm, node_label) > max_text_width) {
QString concatenated;
do {
node_label.chop(1);
concatenated = QCoreApplication::translate("NodeViewItem", "%1...").arg(node_label);
} while (QFontMetricsWidth(fm, concatenated) > max_text_width);
node_label = concatenated;
}
}
// Draw the text in a rect (the rect is sized around text already in the constructor)
painter->drawText(title_bar_rect_, Qt::AlignCenter, node_->Name());
painter->drawText(title_bar_rect_,
Qt::AlignCenter,
node_label);
}
// Draw final border
QPen border_pen;
border_pen.setWidth(node_border_width_);
if (option->state & QStyle::State_Selected) {
border_pen.setColor(app_pal.color(QPalette::Highlight));
} else {
border_pen.setColor(css_proxy_.BorderColor());
}
painter->setPen(border_pen);
painter->setBrush(Qt::NoBrush);
painter->drawRect(rect());
}
void NodeViewItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
@@ -203,6 +330,7 @@ void NodeViewItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
// Create draggable object
dragging_edge_ = new NodeViewEdge();
dragging_edge_->SetFlowDirection(flow_dir_);
// Set up a QUndoCommand to make this action undoable
node_edge_change_command_ = new QUndoCommand();
@@ -213,7 +341,7 @@ void NodeViewItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
drag_src_param_ = param;
// Set the starting position to the current param's connector
dragging_edge_start_ = GetParamPoint(param);
dragging_edge_start_ = GetParamPoint(param, QPointF());
} else if (param->type() == NodeParam::kInput) {
// For an input param, we default to moving an existing edge
@@ -229,8 +357,8 @@ void NodeViewItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
drag_source_ = static_cast<NodeViewScene*>(scene())->NodeToUIObject(drag_src_param_->parentNode());
// Get the opposing parameter's rect center using the line's current coordinates
// (we use the current coordinates because a complex formula is used for the line's coords if the opposing
// node is collapsed, therefore it's easier to just retrieve it from line itself)
// (we use the current coordinates because a complex formula is used for the line's coords if
// the opposing node is collapsed, therefore it's easier to just retrieve it from line itself)
NodeViewEdge* existing_edge_ui = static_cast<NodeViewScene*>(scene())->EdgeToUIObject(edge);
QPainterPath existing_edge_line = existing_edge_ui->path();
QPointF edge_start = existing_edge_line.pointAtPercent(0);
@@ -288,6 +416,8 @@ void NodeViewItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
cached_drop_item_->SetExpanded(false);
}
cached_drop_item_->SetHighlightedIndex(-1);
cached_drop_item_->setZValue(0);
cached_drop_item_ = nullptr;
}
@@ -303,7 +433,7 @@ void NodeViewItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
cached_drop_item_expanded_ = !cached_drop_item_->IsExpanded();
if (cached_drop_item_expanded_) {
cached_drop_item_->SetExpanded(true);
cached_drop_item_->SetExpanded(true, true);
}
cached_drop_item_->setZValue(1);
@@ -330,7 +460,9 @@ void NodeViewItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
if (!cached_drop_item_->GetNode()->OutputsTo(node_)) {
drag_dest_param_ = comp_param;
highlight_their_index = i;
end_point = cached_drop_item_->mapToScene(cached_drop_item_->GetInputPoint(i));
QPointF end_point_local = cached_drop_item_->GetInputPoint(i, pos());
end_point = cached_drop_item_->mapToScene(end_point_local);
}
break;
@@ -340,9 +472,11 @@ void NodeViewItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
cached_drop_item_->SetHighlightedIndex(highlight_their_index);
}
dragging_edge_->SetConnected(drag_dest_param_ != nullptr);
dragging_edge_->SetConnected(drag_dest_param_);
dragging_edge_->SetPoints(dragging_edge_start_, end_point);
dragging_edge_->SetPoints(dragging_edge_start_,
end_point,
cached_drop_item_ ? cached_drop_item_->IsExpanded() : false);
return;
}
@@ -363,8 +497,14 @@ void NodeViewItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
scene()->removeItem(dragging_edge_);
// If we expanded an item in the drag, re-collapse it now
if (cached_drop_item_ != nullptr) {
cached_drop_item_->SetExpanded(false);
if (cached_drop_item_) {
if (cached_drop_item_expanded_) {
cached_drop_item_->SetExpanded(false);
}
cached_drop_item_->SetHighlightedIndex(-1);
cached_drop_item_->setZValue(0);
cached_drop_item_ = nullptr;
}
@@ -405,10 +545,19 @@ void NodeViewItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
}
}
void NodeViewItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
{
QGraphicsRectItem::mouseDoubleClickEvent(event);
SetExpanded(!IsExpanded());
}
QVariant NodeViewItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value)
{
if (change == ItemPositionHasChanged && node_) {
node_->SetPosition(value.toPointF());
node_->blockSignals(true);
node_->SetPosition(GetNodePosition());
node_->blockSignals(false);
}
return QGraphicsItem::itemChange(change, value);
@@ -429,6 +578,10 @@ QRectF NodeViewItem::GetInputRect(int index) const
{
QRectF r = title_bar_rect_;
if (!hide_titlebar_) {
index++;
}
if (IsExpanded()) {
r.translate(0, r.height() * index);
}
@@ -436,10 +589,22 @@ QRectF NodeViewItem::GetInputRect(int index) const
return r;
}
QPointF NodeViewItem::GetParamPoint(NodeParam *param) const
QPointF NodeViewItem::GetParamPoint(NodeParam *param, const QPointF& source_pos) const
{
if (param->type() == NodeParam::kOutput) {
return pos() + QPointF(rect().right(), rect().center().y());
switch (flow_dir_) {
case NodeViewCommon::kLeftToRight:
default:
return pos() + QPointF(rect().right(), rect().center().y());
case NodeViewCommon::kRightToLeft:
return pos() + QPointF(rect().left(), rect().center().y());
case NodeViewCommon::kTopToBottom:
return pos() + QPointF(rect().center().x(), rect().bottom());
case NodeViewCommon::kBottomToTop:
return pos() + QPointF(rect().center().x(), rect().top());
}
} else {
NodeInput* input = static_cast<NodeInput*>(param);
@@ -448,15 +613,35 @@ QPointF NodeViewItem::GetParamPoint(NodeParam *param) const
input = static_cast<NodeInput*>(input->parent());
}
return pos() + GetInputPoint(node_inputs_.indexOf(input));
return pos() + GetInputPoint(node_inputs_.indexOf(input), source_pos);
}
}
QPointF NodeViewItem::GetInputPoint(int index) const
void NodeViewItem::SetFlowDirection(NodeViewCommon::FlowDirection dir)
{
flow_dir_ = dir;
}
QPointF NodeViewItem::GetInputPoint(int index, const QPointF& source_pos) const
{
QRectF input_rect = GetInputRect(index);
return QPointF(input_rect.left(), input_rect.center().y());
Qt::Orientation flow_orientation = NodeViewCommon::GetFlowOrientation(flow_dir_);
if (flow_orientation == Qt::Horizontal || IsExpanded()) {
if (flow_dir_ == NodeViewCommon::kLeftToRight
|| (flow_orientation == Qt::Vertical && source_pos.x() < pos().x())) {
return QPointF(input_rect.left(), input_rect.center().y());
} else {
return QPointF(input_rect.right(), input_rect.center().y());
}
} else {
if (flow_dir_ == NodeViewCommon::kTopToBottom) {
return QPointF(input_rect.center().x(), input_rect.top());
} else {
return QPointF(input_rect.center().x(), input_rect.bottom());
}
}
}
OLIVE_NAMESPACE_EXIT
+31 -3
View File
@@ -28,6 +28,7 @@
#include <QWidget>
#include "node/node.h"
#include "nodeviewcommon.h"
#include "nodeviewedge.h"
#include "nodeviewitemwidgetproxy.h"
@@ -45,6 +46,9 @@ class NodeViewItem : public QGraphicsRectItem
public:
NodeViewItem(QGraphicsItem* parent = nullptr);
QPointF GetNodePosition() const;
void SetNodePosition(const QPointF& pos);
/**
* @brief Set the Node to correspond to this widget
*/
@@ -63,13 +67,32 @@ public:
/**
* @brief Set expanded state
*/
void SetExpanded(bool e);
void SetExpanded(bool e, bool hide_titlebar = false);
void ToggleExpanded();
/**
* @brief Returns GLOBAL point that edges should connect to for any NodeParam member of this object
*/
QPointF GetParamPoint(NodeParam* param) const;
QPointF GetParamPoint(NodeParam* param, const QPointF &source_pos) const;
/**
* @brief Sets the direction nodes are flowing
*/
void SetFlowDirection(NodeViewCommon::FlowDirection dir);
static int DefaultTextPadding();
static int DefaultItemHeight();
static int DefaultItemWidth();
static int DefaultMaximumTextWidth();
static int DefaultItemBorder();
qreal DefaultItemHorizontalPadding() const;
qreal DefaultItemVerticalPadding() const;
protected:
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override;
@@ -77,6 +100,7 @@ protected:
virtual void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override;
virtual QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override;
@@ -94,7 +118,7 @@ private:
/**
* @brief Returns local point that edges should connect to for a NodeInput in array node_inputs_[index]
*/
QPointF GetInputPoint(int index) const;
QPointF GetInputPoint(int index, const QPointF &source_pos) const;
/**
* @brief Reference to attached Node
@@ -136,6 +160,8 @@ private:
*/
bool expanded_;
bool hide_titlebar_;
/**
* @brief Current click mode
*
@@ -153,6 +179,8 @@ private:
*/
QUndoCommand* node_edge_change_command_;
NodeViewCommon::FlowDirection flow_dir_;
};
OLIVE_NAMESPACE_EXIT
+74 -128
View File
@@ -20,14 +20,40 @@
#include "nodeviewscene.h"
#include "nodeviewedge.h"
#include "nodeviewitem.h"
OLIVE_NAMESPACE_ENTER
NodeViewScene::NodeViewScene(QObject *parent) :
QGraphicsScene(parent),
graph_(nullptr)
graph_(nullptr),
direction_(NodeViewCommon::kLeftToRight)
{
connect(&reorganize_timer_, &QTimer::timeout, &reorganize_timer_, &QTimer::stop);
connect(&reorganize_timer_, &QTimer::timeout, this, &NodeViewScene::Reorganize);
}
void NodeViewScene::SetFlowDirection(NodeViewCommon::FlowDirection direction)
{
direction_ = direction;
{
// Iterate over node items setting direction
QHash<Node*, NodeViewItem*>::const_iterator i;
for (i=item_map_.constBegin(); i!=item_map_.constEnd(); i++) {
i.value()->SetFlowDirection(direction_);
// Update position too
i.value()->SetNodePosition(i.key()->GetPosition());
}
}
{
// Iterate over edge items setting direction
QHash<NodeEdge*, NodeViewEdge*>::const_iterator i;
for (i=edge_map_.constBegin(); i!=edge_map_.constEnd(); i++) {
i.value()->SetFlowDirection(direction_);
}
}
}
void NodeViewScene::clear()
@@ -35,7 +61,7 @@ void NodeViewScene::clear()
// Deselect everything (prevents signals that a selection has changed after deleting an object)
DeselectAll();
// HACK: QGraphicsScene contains some sort of internal hashing of the selected items which doesn't update unless
// HACK: QGraphicsScene contains some sort of internal caching of the selected items which doesn't update unless
// we call a function like this. That means even though we deselect all items above, QGraphicsScene will
// continue to incorrectly signal selectionChanged() when items that were selected (but are now not) get
// deleted. Calling this function appears to update the internal cache and prevent this.
@@ -119,6 +145,21 @@ QList<NodeViewItem *> NodeViewScene::GetSelectedItems() const
return selected;
}
QList<NodeEdge*> NodeViewScene::GetSelectedEdges() const
{
QList<NodeEdge*> edges;
QHash<NodeEdge*, NodeViewEdge*>::const_iterator i;
for (i=edge_map_.constBegin(); i!=edge_map_.constEnd(); i++) {
if (i.value()->isSelected()) {
edges.append(i.key());
}
}
return edges;
}
const QHash<Node *, NodeViewItem *> &NodeViewScene::item_map() const
{
return item_map_;
@@ -133,6 +174,7 @@ void NodeViewScene::AddNode(Node* node)
{
NodeViewItem* item = new NodeViewItem();
item->SetFlowDirection(direction_);
item->SetNode(node);
addItem(item);
@@ -152,11 +194,15 @@ void NodeViewScene::AddNode(Node* node)
}
}
QueueReorganize();
connect(node, &Node::PositionChanged, this, &NodeViewScene::NodePositionChanged);
connect(node, &Node::LabelChanged, this, &NodeViewScene::NodeLabelChanged);
}
void NodeViewScene::RemoveNode(Node *node)
{
disconnect(node, &Node::LabelChanged, this, &NodeViewScene::NodeLabelChanged);
disconnect(node, &Node::PositionChanged, this, &NodeViewScene::NodePositionChanged);
delete item_map_.take(node);
}
@@ -165,11 +211,10 @@ void NodeViewScene::AddEdge(NodeEdgePtr edge)
NodeViewEdge* edge_ui = new NodeViewEdge();
edge_ui->SetEdge(edge);
edge_ui->SetFlowDirection(direction_);
addItem(edge_ui);
edge_map_.insert(edge.get(), edge_ui);
QueueReorganize();
}
void NodeViewScene::RemoveEdge(NodeEdgePtr edge)
@@ -177,146 +222,47 @@ void NodeViewScene::RemoveEdge(NodeEdgePtr edge)
delete edge_map_.take(edge.get());
}
void NodeViewScene::QueueReorganize()
Qt::Orientation NodeViewScene::GetFlowOrientation() const
{
// Avoids the fairly complex Reorganize() function every single time a connection or node is added
reorganize_timer_.stop();
reorganize_timer_.start(20);
return NodeViewCommon::GetFlowOrientation(direction_);
}
QList<Node *> NodeViewScene::GetNodeDirectDescendants(Node* n, const QList<Node*> connected_nodes, QList<Node*>& processed_nodes)
NodeViewCommon::FlowDirection NodeViewScene::GetFlowDirection() const
{
QList<Node*> direct_descendants = connected_nodes;
processed_nodes.append(n);
// Remove any nodes that aren't necessarily attached directly
for (int i=0;i<direct_descendants.size();i++) {
Node* connected = direct_descendants.at(i);
for (int j=1;j<connected->output()->edges().size();j++) {
Node* this_output_connection = connected->output()->edges().at(j)->input()->parentNode();
if (!processed_nodes.contains(this_output_connection)) {
direct_descendants.removeAt(i);
i--;
break;
}
}
}
return direct_descendants;
return direction_;
}
int NodeViewScene::FindWeightsInternal(Node *node, QHash<Node *, int> &weights, QList<Node*>& weighted_nodes)
void NodeViewScene::ReorganizeFrom(Node* n)
{
QList<Node*> connected_nodes = node->GetImmediateDependencies();
QList<Node*> immediates = n->GetImmediateDependencies();
int weight = 0;
if (!connected_nodes.isEmpty()) {
QList<Node*> direct_descendants = GetNodeDirectDescendants(node, connected_nodes, weighted_nodes);
foreach (Node* dep, direct_descendants) {
weight += FindWeightsInternal(dep, weights, weighted_nodes);
}
}
weight = qMax(weight, 1);
weights.insert(node, weight);
return weight;
}
void NodeViewScene::ReorganizeInternal(NodeViewItem* src_item, QHash<Node*, int>& weights, QList<Node*>& positioned_nodes)
{
if (!src_item) {
if (immediates.isEmpty()) {
// Nothing to do
return;
}
Node* n = src_item->GetNode();
QPointF parent_pos = n->GetPosition();
QList<Node*> connected_nodes = n->GetImmediateDependencies();
qreal child_x = parent_pos.x() - 1.0;
qreal children_height = immediates.size()-1;
qreal children_y = parent_pos.y() - children_height * 0.5;
if (connected_nodes.isEmpty()) {
return;
}
for (int i=0;i<immediates.size();i++) {
immediates.at(i)->SetPosition(QPointF(child_x,
children_y + i));
QList<Node*> direct_descendants = GetNodeDirectDescendants(n, connected_nodes, positioned_nodes);
int descendant_weight = 0;
foreach (Node* dep, direct_descendants) {
descendant_weight += weights.value(dep);
}
qreal center_y = src_item->y();
qreal total_height = descendant_weight * src_item->rect().height() + (direct_descendants.size()-1) * src_item->rect().height()/2;
double item_top = center_y - (total_height/2) + src_item->rect().height()/2;
// Set each node's position
int weight_index = 0;
for (int i=0;i<direct_descendants.size();i++) {
Node* connected = direct_descendants.at(i);
NodeViewItem* item = NodeToUIObject(connected);
if (!item) {
continue;
}
double item_y = item_top;
// Multiply the index by the item height (with 1.5 for padding)
item_y += weight_index * src_item->rect().height() * 1.5;
QPointF item_pos(src_item->pos().x() - item->rect().width() * 3 / 2,
item_y);
item->setPos(item_pos);
weight_index += weights.value(connected);
}
// Recursively work on each node
foreach (Node* connected, connected_nodes) {
NodeViewItem* item = NodeToUIObject(connected);
if (!item) {
continue;
}
ReorganizeInternal(item, weights, positioned_nodes);
ReorganizeFrom(immediates.at(i));
}
}
void NodeViewScene::Reorganize()
void NodeViewScene::NodePositionChanged(const QPointF &pos)
{
if (!graph_) {
return;
}
item_map_.value(static_cast<Node*>(sender()))->SetNodePosition(pos);
}
QList<Node*> end_nodes;
// Calculate the nodes that don't output to anything, they'll be our anchors
foreach (Node* node, graph_->nodes()) {
if (!node->HasConnectedOutputs()) {
end_nodes.append(node);
}
}
QList<Node*> processed_nodes;
QHash<Node*, int> node_weights;
foreach (Node* end_node, end_nodes) {
FindWeightsInternal(end_node, node_weights, processed_nodes);
}
processed_nodes.clear();
foreach (Node* end_node, end_nodes) {
ReorganizeInternal(NodeToUIObject(end_node), node_weights, processed_nodes);
}
void NodeViewScene::NodeLabelChanged()
{
item_map_.value(static_cast<Node*>(sender()))->update();
}
OLIVE_NAMESPACE_EXIT

Some files were not shown because too many files have changed in this diff Show More