Merge branch 'master' into gizmos
This commit is contained in:
+6
-3
@@ -34,10 +34,13 @@ REM Add Qt and FFmpeg directory to path
|
||||
set PATH=%PATH%;C:\Qt\5.13.2\msvc2017_64\bin;%APPVEYOR_BUILD_FOLDER%\%FFMPEG_VER%-dev
|
||||
|
||||
REM Run cmake
|
||||
cmake -G "NMake Makefiles" . -DCMAKE_TOOLCHAIN_FILE=c:/Tools/vcpkg/scripts/buildsystems/vcpkg.cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo
|
||||
cmake -G "Ninja" . -DCMAKE_TOOLCHAIN_FILE=c:/Tools/vcpkg/scripts/buildsystems/vcpkg.cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo
|
||||
|
||||
REM Build with JOM
|
||||
C:\Qt\Tools\QtCreator\bin\jom.exe || exit /B 1
|
||||
REM Build with Ninja
|
||||
ninja.exe || exit /B 1
|
||||
|
||||
REM If this is a pull request, no further packaging/deploying needs to be done
|
||||
if NOT "%APPVEYOR_PULL_REQUEST_NUMBER%" == "" goto end
|
||||
|
||||
REM Start building package
|
||||
mkdir olive-editor
|
||||
|
||||
+13
-2
@@ -1,8 +1,19 @@
|
||||
# Contributing to Olive
|
||||
|
||||
Thank you for your interest in contributing to Olive! In order to keep the code as readable and maintainable as possible, code submitted should abide by the following standards:
|
||||
|
||||
### Standards
|
||||
|
||||
When contributing to Olive, it's recommended to use the following rules:
|
||||
|
||||
* [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html)
|
||||
* 120 column limit
|
||||
* The code style generally follows the [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html) including, but not limited to:
|
||||
* Indentation is 2 spaces wide, spaces only (no tabs)
|
||||
* `lowercase_underscored_variable_names`
|
||||
* `lowercase_underscored_functions()` or `SentenceCaseFunctions()`
|
||||
* `class SentenceCaseClassesAndStructs {}`
|
||||
* `kSentenceCaseConstants` prepended with a lowercase `k`
|
||||
* `UPPERCASE_UNDERSCORED_MACROS` for variables or same style as functions for macro functions
|
||||
* `class_member_variables_` end with a `_`
|
||||
* 100 column limit (where it doesn't impair readability)
|
||||
* Unix line endings (only LF no CRLF)
|
||||
* Javadoc documentation where appropriate
|
||||
|
||||
@@ -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
@@ -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
@@ -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_;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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(¶ms);
|
||||
|
||||
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;
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_);
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -34,6 +34,8 @@ public:
|
||||
|
||||
NodeInput* GetInput() const;
|
||||
|
||||
virtual void DeleteSelected() override;
|
||||
|
||||
public slots:
|
||||
void SetInput(NodeInput* input);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -60,6 +60,11 @@ void ParamPanel::SetTimestamp(const int64_t ×tamp)
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
@@ -38,6 +38,8 @@ public slots:
|
||||
|
||||
virtual void SetTimestamp(const int64_t& timestamp) override;
|
||||
|
||||
virtual void DeleteSelected() override;
|
||||
|
||||
signals:
|
||||
void TimeTargetChanged(Node* node);
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -62,12 +62,6 @@ public slots:
|
||||
protected:
|
||||
void CreateScopePanel(ScopePanel::Type type);
|
||||
|
||||
private:
|
||||
int scope_panel_count_;
|
||||
|
||||
private slots:
|
||||
void ScopePanelClosed();
|
||||
|
||||
};
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -36,7 +36,7 @@ protected:
|
||||
virtual void Action() override;
|
||||
|
||||
signals:
|
||||
void ProjectLoaded(ProjectPtr project);
|
||||
void ProjectLoaded(OLIVE_NAMESPACE::ProjectPtr project);
|
||||
|
||||
private:
|
||||
QString filename_;
|
||||
|
||||
@@ -33,7 +33,7 @@ public:
|
||||
ProjectSaveManager(ProjectPtr project);
|
||||
|
||||
signals:
|
||||
void ProjectSaveSucceeded(ProjectPtr p);
|
||||
void ProjectSaveSucceeded(OLIVE_NAMESPACE::ProjectPtr p);
|
||||
|
||||
protected:
|
||||
virtual void Action() override;
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ¶ms, FramePtr frame)
|
||||
{
|
||||
return Get(ctx, params, frame.get());
|
||||
}
|
||||
|
||||
OpenGLTextureCache::ReferencePtr OpenGLTextureCache::Get(QOpenGLContext *ctx, const VideoRenderingParams ¶ms, Frame *frame)
|
||||
{
|
||||
return Get(ctx, params, frame->data(), frame->linesize_pixels());
|
||||
}
|
||||
|
||||
OpenGLTextureCache::ReferencePtr OpenGLTextureCache::Get(QOpenGLContext* ctx, const VideoRenderingParams ¶ms, 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);
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -47,6 +47,8 @@ public:
|
||||
const double& GetVerticalScale();
|
||||
void SetVerticalScale(const double& vscale);
|
||||
|
||||
void DeleteSelected();
|
||||
|
||||
protected:
|
||||
virtual void changeEvent(QEvent *) override;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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))
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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 ×tamp)
|
||||
{
|
||||
rational time = Timecode::timestamp_to_time(timestamp, keyframe_view_->timebase());
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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_;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -20,14 +20,40 @@
|
||||
|
||||
#include "nodeviewscene.h"
|
||||
|
||||
#include "nodeviewedge.h"
|
||||
#include "nodeviewitem.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
NodeViewScene::NodeViewScene(QObject *parent) :
|
||||
QGraphicsScene(parent),
|
||||
graph_(nullptr)
|
||||
graph_(nullptr),
|
||||
direction_(NodeViewCommon::kLeftToRight)
|
||||
{
|
||||
connect(&reorganize_timer_, &QTimer::timeout, &reorganize_timer_, &QTimer::stop);
|
||||
connect(&reorganize_timer_, &QTimer::timeout, this, &NodeViewScene::Reorganize);
|
||||
}
|
||||
|
||||
void NodeViewScene::SetFlowDirection(NodeViewCommon::FlowDirection direction)
|
||||
{
|
||||
direction_ = direction;
|
||||
|
||||
{
|
||||
// Iterate over node items setting direction
|
||||
QHash<Node*, NodeViewItem*>::const_iterator i;
|
||||
for (i=item_map_.constBegin(); i!=item_map_.constEnd(); i++) {
|
||||
i.value()->SetFlowDirection(direction_);
|
||||
|
||||
// Update position too
|
||||
i.value()->SetNodePosition(i.key()->GetPosition());
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Iterate over edge items setting direction
|
||||
QHash<NodeEdge*, NodeViewEdge*>::const_iterator i;
|
||||
for (i=edge_map_.constBegin(); i!=edge_map_.constEnd(); i++) {
|
||||
i.value()->SetFlowDirection(direction_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NodeViewScene::clear()
|
||||
@@ -35,7 +61,7 @@ void NodeViewScene::clear()
|
||||
// Deselect everything (prevents signals that a selection has changed after deleting an object)
|
||||
DeselectAll();
|
||||
|
||||
// HACK: QGraphicsScene contains some sort of internal hashing of the selected items which doesn't update unless
|
||||
// HACK: QGraphicsScene contains some sort of internal caching of the selected items which doesn't update unless
|
||||
// we call a function like this. That means even though we deselect all items above, QGraphicsScene will
|
||||
// continue to incorrectly signal selectionChanged() when items that were selected (but are now not) get
|
||||
// deleted. Calling this function appears to update the internal cache and prevent this.
|
||||
@@ -119,6 +145,21 @@ QList<NodeViewItem *> NodeViewScene::GetSelectedItems() const
|
||||
return selected;
|
||||
}
|
||||
|
||||
QList<NodeEdge*> NodeViewScene::GetSelectedEdges() const
|
||||
{
|
||||
QList<NodeEdge*> edges;
|
||||
|
||||
QHash<NodeEdge*, NodeViewEdge*>::const_iterator i;
|
||||
|
||||
for (i=edge_map_.constBegin(); i!=edge_map_.constEnd(); i++) {
|
||||
if (i.value()->isSelected()) {
|
||||
edges.append(i.key());
|
||||
}
|
||||
}
|
||||
|
||||
return edges;
|
||||
}
|
||||
|
||||
const QHash<Node *, NodeViewItem *> &NodeViewScene::item_map() const
|
||||
{
|
||||
return item_map_;
|
||||
@@ -133,6 +174,7 @@ void NodeViewScene::AddNode(Node* node)
|
||||
{
|
||||
NodeViewItem* item = new NodeViewItem();
|
||||
|
||||
item->SetFlowDirection(direction_);
|
||||
item->SetNode(node);
|
||||
|
||||
addItem(item);
|
||||
@@ -152,11 +194,15 @@ void NodeViewScene::AddNode(Node* node)
|
||||
}
|
||||
}
|
||||
|
||||
QueueReorganize();
|
||||
connect(node, &Node::PositionChanged, this, &NodeViewScene::NodePositionChanged);
|
||||
connect(node, &Node::LabelChanged, this, &NodeViewScene::NodeLabelChanged);
|
||||
}
|
||||
|
||||
void NodeViewScene::RemoveNode(Node *node)
|
||||
{
|
||||
disconnect(node, &Node::LabelChanged, this, &NodeViewScene::NodeLabelChanged);
|
||||
disconnect(node, &Node::PositionChanged, this, &NodeViewScene::NodePositionChanged);
|
||||
|
||||
delete item_map_.take(node);
|
||||
}
|
||||
|
||||
@@ -165,11 +211,10 @@ void NodeViewScene::AddEdge(NodeEdgePtr edge)
|
||||
NodeViewEdge* edge_ui = new NodeViewEdge();
|
||||
|
||||
edge_ui->SetEdge(edge);
|
||||
edge_ui->SetFlowDirection(direction_);
|
||||
|
||||
addItem(edge_ui);
|
||||
edge_map_.insert(edge.get(), edge_ui);
|
||||
|
||||
QueueReorganize();
|
||||
}
|
||||
|
||||
void NodeViewScene::RemoveEdge(NodeEdgePtr edge)
|
||||
@@ -177,146 +222,47 @@ void NodeViewScene::RemoveEdge(NodeEdgePtr edge)
|
||||
delete edge_map_.take(edge.get());
|
||||
}
|
||||
|
||||
void NodeViewScene::QueueReorganize()
|
||||
Qt::Orientation NodeViewScene::GetFlowOrientation() const
|
||||
{
|
||||
// Avoids the fairly complex Reorganize() function every single time a connection or node is added
|
||||
|
||||
reorganize_timer_.stop();
|
||||
reorganize_timer_.start(20);
|
||||
return NodeViewCommon::GetFlowOrientation(direction_);
|
||||
}
|
||||
|
||||
QList<Node *> NodeViewScene::GetNodeDirectDescendants(Node* n, const QList<Node*> connected_nodes, QList<Node*>& processed_nodes)
|
||||
NodeViewCommon::FlowDirection NodeViewScene::GetFlowDirection() const
|
||||
{
|
||||
QList<Node*> direct_descendants = connected_nodes;
|
||||
|
||||
processed_nodes.append(n);
|
||||
|
||||
// Remove any nodes that aren't necessarily attached directly
|
||||
for (int i=0;i<direct_descendants.size();i++) {
|
||||
Node* connected = direct_descendants.at(i);
|
||||
|
||||
for (int j=1;j<connected->output()->edges().size();j++) {
|
||||
Node* this_output_connection = connected->output()->edges().at(j)->input()->parentNode();
|
||||
if (!processed_nodes.contains(this_output_connection)) {
|
||||
direct_descendants.removeAt(i);
|
||||
i--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return direct_descendants;
|
||||
return direction_;
|
||||
}
|
||||
|
||||
int NodeViewScene::FindWeightsInternal(Node *node, QHash<Node *, int> &weights, QList<Node*>& weighted_nodes)
|
||||
void NodeViewScene::ReorganizeFrom(Node* n)
|
||||
{
|
||||
QList<Node*> connected_nodes = node->GetImmediateDependencies();
|
||||
QList<Node*> immediates = n->GetImmediateDependencies();
|
||||
|
||||
int weight = 0;
|
||||
|
||||
if (!connected_nodes.isEmpty()) {
|
||||
QList<Node*> direct_descendants = GetNodeDirectDescendants(node, connected_nodes, weighted_nodes);
|
||||
|
||||
foreach (Node* dep, direct_descendants) {
|
||||
weight += FindWeightsInternal(dep, weights, weighted_nodes);
|
||||
}
|
||||
}
|
||||
|
||||
weight = qMax(weight, 1);
|
||||
|
||||
weights.insert(node, weight);
|
||||
|
||||
return weight;
|
||||
}
|
||||
|
||||
void NodeViewScene::ReorganizeInternal(NodeViewItem* src_item, QHash<Node*, int>& weights, QList<Node*>& positioned_nodes)
|
||||
{
|
||||
if (!src_item) {
|
||||
if (immediates.isEmpty()) {
|
||||
// Nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
Node* n = src_item->GetNode();
|
||||
QPointF parent_pos = n->GetPosition();
|
||||
|
||||
QList<Node*> connected_nodes = n->GetImmediateDependencies();
|
||||
qreal child_x = parent_pos.x() - 1.0;
|
||||
qreal children_height = immediates.size()-1;
|
||||
qreal children_y = parent_pos.y() - children_height * 0.5;
|
||||
|
||||
if (connected_nodes.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (int i=0;i<immediates.size();i++) {
|
||||
immediates.at(i)->SetPosition(QPointF(child_x,
|
||||
children_y + i));
|
||||
|
||||
QList<Node*> direct_descendants = GetNodeDirectDescendants(n, connected_nodes, positioned_nodes);
|
||||
|
||||
int descendant_weight = 0;
|
||||
foreach (Node* dep, direct_descendants) {
|
||||
descendant_weight += weights.value(dep);
|
||||
}
|
||||
|
||||
qreal center_y = src_item->y();
|
||||
qreal total_height = descendant_weight * src_item->rect().height() + (direct_descendants.size()-1) * src_item->rect().height()/2;
|
||||
double item_top = center_y - (total_height/2) + src_item->rect().height()/2;
|
||||
|
||||
// Set each node's position
|
||||
int weight_index = 0;
|
||||
for (int i=0;i<direct_descendants.size();i++) {
|
||||
Node* connected = direct_descendants.at(i);
|
||||
|
||||
NodeViewItem* item = NodeToUIObject(connected);
|
||||
|
||||
if (!item) {
|
||||
continue;
|
||||
}
|
||||
|
||||
double item_y = item_top;
|
||||
|
||||
// Multiply the index by the item height (with 1.5 for padding)
|
||||
item_y += weight_index * src_item->rect().height() * 1.5;
|
||||
|
||||
QPointF item_pos(src_item->pos().x() - item->rect().width() * 3 / 2,
|
||||
item_y);
|
||||
|
||||
item->setPos(item_pos);
|
||||
|
||||
weight_index += weights.value(connected);
|
||||
}
|
||||
|
||||
// Recursively work on each node
|
||||
foreach (Node* connected, connected_nodes) {
|
||||
NodeViewItem* item = NodeToUIObject(connected);
|
||||
|
||||
if (!item) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ReorganizeInternal(item, weights, positioned_nodes);
|
||||
ReorganizeFrom(immediates.at(i));
|
||||
}
|
||||
}
|
||||
|
||||
void NodeViewScene::Reorganize()
|
||||
void NodeViewScene::NodePositionChanged(const QPointF &pos)
|
||||
{
|
||||
if (!graph_) {
|
||||
return;
|
||||
}
|
||||
item_map_.value(static_cast<Node*>(sender()))->SetNodePosition(pos);
|
||||
}
|
||||
|
||||
QList<Node*> end_nodes;
|
||||
|
||||
// Calculate the nodes that don't output to anything, they'll be our anchors
|
||||
foreach (Node* node, graph_->nodes()) {
|
||||
if (!node->HasConnectedOutputs()) {
|
||||
end_nodes.append(node);
|
||||
}
|
||||
}
|
||||
|
||||
QList<Node*> processed_nodes;
|
||||
|
||||
QHash<Node*, int> node_weights;
|
||||
foreach (Node* end_node, end_nodes) {
|
||||
FindWeightsInternal(end_node, node_weights, processed_nodes);
|
||||
}
|
||||
|
||||
processed_nodes.clear();
|
||||
|
||||
foreach (Node* end_node, end_nodes) {
|
||||
ReorganizeInternal(NodeToUIObject(end_node), node_weights, processed_nodes);
|
||||
}
|
||||
void NodeViewScene::NodeLabelChanged()
|
||||
{
|
||||
item_map_.value(static_cast<Node*>(sender()))->update();
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user