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
+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
+22 -14
View File
@@ -25,8 +25,8 @@
#include <QTimer>
#include "node/graph.h"
#include "widget/nodeview/nodeviewedge.h"
#include "widget/nodeview/nodeviewitem.h"
#include "nodeviewedge.h"
#include "nodeviewitem.h"
OLIVE_NAMESPACE_ENTER
@@ -65,10 +65,21 @@ public:
QList<Node*> GetSelectedNodes() const;
QList<NodeViewItem*> GetSelectedItems() const;
QList<NodeEdge *> GetSelectedEdges() const;
const QHash<Node*, NodeViewItem*>& item_map() const;
const QHash<NodeEdge*, NodeViewEdge*>& edge_map() const;
Qt::Orientation GetFlowOrientation() const;
NodeViewCommon::FlowDirection GetFlowDirection() const;
void SetFlowDirection(NodeViewCommon::FlowDirection direction);
/**
* @brief Automatically reposition the nodes based on their connections
*/
void ReorganizeFrom(Node* n);
public slots:
/**
* @brief Slot when a Node is added to a graph (SetGraph() connects this)
@@ -103,27 +114,24 @@ public slots:
void RemoveEdge(NodeEdgePtr edge);
private:
void QueueReorganize();
QList<Node*> GetNodeDirectDescendants(Node* n, const QList<Node*> connected_nodes, QList<Node*>& processed_nodes);
int FindWeightsInternal(Node* node, QHash<Node*, int>& weights, QList<Node*>& weighted_nodes);
void ReorganizeInternal(NodeViewItem *src_item, QHash<Node*, int>& weights, QList<Node *> &positioned_nodes);
QHash<Node*, NodeViewItem*> item_map_;
QHash<NodeEdge*, NodeViewEdge*> edge_map_;
QTimer reorganize_timer_;
NodeGraph* graph_;
NodeViewCommon::FlowDirection direction_;
private slots:
/**
* @brief Automatically reposition the nodes based on their connections
* @brief Receiver for whenever a node position changes
*/
void Reorganize();
void NodePositionChanged(const QPointF& pos);
/**
* @brief Receiver for when a node's label has changed
*/
void NodeLabelChanged();
};
+2
View File
@@ -165,6 +165,8 @@ public:
virtual void ToggleSelectedEnabled(){}
virtual void Duplicate(){}
signals:
void CloseRequested();

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