diff --git a/app/codec/CMakeLists.txt b/app/codec/CMakeLists.txt index 94d6b4062..42f456059 100644 --- a/app/codec/CMakeLists.txt +++ b/app/codec/CMakeLists.txt @@ -33,9 +33,5 @@ set(OLIVE_SOURCES codec/frame.h codec/samplebuffer.cpp codec/samplebuffer.h - codec/waveinput.cpp - codec/waveinput.h - codec/waveoutput.cpp - codec/waveoutput.h PARENT_SCOPE ) diff --git a/app/codec/conformmanager.cpp b/app/codec/conformmanager.cpp index 38b5874a5..340f1e763 100644 --- a/app/codec/conformmanager.cpp +++ b/app/codec/conformmanager.cpp @@ -57,19 +57,13 @@ ConformManager::Conform ConformManager::GetConformState(const QString &decoder_i QString ConformManager::GetConformedFilename(const QString &cache_path, const Decoder::CodecStream &stream, const AudioParams ¶ms) { - QString index_fn = QStringLiteral("%1.%2:%3").arg(FileFunctions::GetUniqueFileIdentifier(stream.filename()), - QString::number(stream.stream())); + QString index_fn = QStringLiteral("%1-%2.%3.%4.%5.pcm").arg(FileFunctions::GetUniqueFileIdentifier(stream.filename()), + QString::number(stream.stream()), + QString::number(params.sample_rate()), + QString::number(params.format()), + QString::number(params.channel_layout())); - index_fn = QDir(cache_path).filePath(index_fn); - - index_fn.append('.'); - index_fn.append(QString::number(params.sample_rate())); - index_fn.append('.'); - index_fn.append(QString::number(params.format())); - index_fn.append('.'); - index_fn.append(QString::number(params.channel_layout())); - - return index_fn; + return QDir(cache_path).filePath(index_fn); } void ConformManager::ConformTaskFinished(Task *task, bool succeeded) diff --git a/app/codec/decoder.cpp b/app/codec/decoder.cpp index 5647d1519..cac7d553f 100644 --- a/app/codec/decoder.cpp +++ b/app/codec/decoder.cpp @@ -25,8 +25,6 @@ #include "codec/ffmpeg/ffmpegdecoder.h" #include "codec/oiio/oiiodecoder.h" -#include "codec/waveinput.h" -#include "codec/waveoutput.h" #include "common/ffmpegutils.h" #include "common/filefunctions.h" #include "common/timecodefunctions.h" @@ -129,7 +127,7 @@ Decoder::RetrieveAudioData Decoder::RetrieveAudio(const TimeRange &range, const } // See if we got the conform - SampleBufferPtr out_buffer = RetrieveAudioFromConform(conform.filename, range, loop_mode); + SampleBufferPtr out_buffer = RetrieveAudioFromConform(conform.filename, range, loop_mode, params); return {kOK, out_buffer, nullptr}; } @@ -265,13 +263,11 @@ bool Decoder::ConformAudioInternal(const QString& filename, const AudioParams &p return false; } -SampleBufferPtr Decoder::RetrieveAudioFromConform(const QString &conform_filename, const TimeRange& range, Footage::LoopMode loop_mode) +SampleBufferPtr Decoder::RetrieveAudioFromConform(const QString &conform_filename, const TimeRange& range, Footage::LoopMode loop_mode, const AudioParams &input_params) { - WaveInput input(conform_filename); - - if (input.open()) { - const AudioParams& input_params = input.params(); + QFile input(conform_filename); + if (input.open(QFile::ReadOnly)) { QByteArray packed_data(input_params.time_to_bytes(range.length()), Qt::Uninitialized); qint64 read_index = input_params.time_to_bytes(range.in()); @@ -279,12 +275,12 @@ SampleBufferPtr Decoder::RetrieveAudioFromConform(const QString &conform_filenam while (write_index < packed_data.size()) { if (loop_mode == Footage::kLoopModeLoop) { - while (read_index >= input.data_length()) { - read_index -= input.data_length(); + while (read_index >= input.size()) { + read_index -= input.size(); } while (read_index < 0) { - read_index += input.data_length(); + read_index += input.size(); } } @@ -294,13 +290,14 @@ SampleBufferPtr Decoder::RetrieveAudioFromConform(const QString &conform_filenam // Reading before 0, write silence here until audio data would actually start write_count = qMin(-read_index, qint64(packed_data.size())); memset(packed_data.data() + write_index, 0, write_count); - } else if (read_index >= input.data_length()) { + } else if (read_index >= input.size()) { // Reading after data length, write silence until the end of the buffer write_count = packed_data.size() - write_index; memset(packed_data.data() + write_index, 0, write_count); } else { - write_count = qMin(input.data_length() - read_index, packed_data.size() - write_index); - input.read(read_index, packed_data.data() + write_index, write_count); + write_count = qMin(input.size() - read_index, packed_data.size() - write_index); + input.seek(read_index); + input.read(packed_data.data() + write_index, write_count); } read_index += write_count; diff --git a/app/codec/decoder.h b/app/codec/decoder.h index 5a7382444..3b9a64ac9 100644 --- a/app/codec/decoder.h +++ b/app/codec/decoder.h @@ -33,7 +33,6 @@ extern "C" { #include "codec/frame.h" #include "codec/samplebuffer.h" -#include "codec/waveoutput.h" #include "common/rational.h" #include "node/project/footage/footage.h" #include "node/project/footage/footagedescription.h" @@ -313,7 +312,7 @@ signals: private: void UpdateLastAccessed(); - SampleBufferPtr RetrieveAudioFromConform(const QString& conform_filename, const TimeRange &range, Footage::LoopMode loop_mode); + SampleBufferPtr RetrieveAudioFromConform(const QString& conform_filename, const TimeRange &range, Footage::LoopMode loop_mode, const AudioParams ¶ms); CodecStream stream_; diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index 7b8659603..ccee600aa 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -38,7 +38,6 @@ extern "C" { #include #include -#include "codec/waveinput.h" #include "common/define.h" #include "common/ffmpegutils.h" #include "common/filefunctions.h" @@ -465,7 +464,7 @@ bool FFmpegDecoder::ConformAudioInternal(const QString &filename, const AudioPar swr_init(resampler); - WaveOutput wave_out(filename, params); + QFile wave_out(filename); AVPacket* pkt = av_packet_alloc(); AVFrame* frame = av_frame_alloc(); @@ -473,7 +472,7 @@ bool FFmpegDecoder::ConformAudioInternal(const QString &filename, const AudioPar bool success = false; - if (wave_out.open()) { + if (wave_out.open(QFile::WriteOnly)) { while (true) { // Check if we have a `cancelled` ptr and its value if (cancelled && *cancelled) { diff --git a/app/codec/ffmpeg/ffmpegdecoder.h b/app/codec/ffmpeg/ffmpegdecoder.h index 62572a904..1239cc421 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.h +++ b/app/codec/ffmpeg/ffmpegdecoder.h @@ -37,7 +37,6 @@ extern "C" { #include #include "codec/decoder.h" -#include "codec/waveoutput.h" #include "ffmpegframepool.h" namespace olive { diff --git a/app/codec/ffmpeg/ffmpegencoder.cpp b/app/codec/ffmpeg/ffmpegencoder.cpp index a2926f15d..2fab82435 100644 --- a/app/codec/ffmpeg/ffmpegencoder.cpp +++ b/app/codec/ffmpeg/ffmpegencoder.cpp @@ -322,7 +322,7 @@ bool FFmpegEncoder::WriteAudio(SampleBufferPtr audio) if (audio_frame_offset_ == audio_max_samples_ || (i == converted && !input_data)) { // Got all the samples we needed, write the frame - audio_frame_->pts = audio_write_count_; + audio_frame_->pts = av_rescale_q(audio_write_count_, {1, audio_codec_ctx_->sample_rate}, audio_codec_ctx_->time_base); WriteAVFrame(audio_frame_, audio_codec_ctx_, audio_stream_); audio_write_count_ += audio_frame_offset_; @@ -336,7 +336,7 @@ bool FFmpegEncoder::WriteAudio(SampleBufferPtr audio) if (!input_data && audio_frame_offset_ > 0) { audio_frame_->nb_samples = audio_frame_offset_; - audio_frame_->pts = audio_write_count_; + audio_frame_->pts = av_rescale_q(audio_write_count_, {1, audio_codec_ctx_->sample_rate}, audio_codec_ctx_->time_base); WriteAVFrame(audio_frame_, audio_codec_ctx_, audio_stream_); } @@ -396,7 +396,7 @@ bool FFmpegEncoder::WriteSubtitle(const SubtitleBlock *sub_block) subtitle.num_rects = 1; subtitle.rects = &rect_array; - subtitle.pts = Timecode::time_to_timestamp(sub_block->in(), av_get_time_base_q(), Timecode::kFloor); + subtitle.pts = Timecode::time_to_timestamp(sub_block->in(), subtitle_codec_ctx_->time_base, Timecode::kFloor); subtitle.end_display_time = qRound64(sub_block->length().toDouble() * 1000); QVector out_buf(1024 * 1024); @@ -412,9 +412,9 @@ bool FFmpegEncoder::WriteSubtitle(const SubtitleBlock *sub_block) pkt->data = out_buf.data(); pkt->size = sub_sz; pkt->pts = subtitle.pts; - pkt->duration = subtitle.end_display_time; + pkt->duration = av_rescale_q(subtitle.end_display_time, {1, 1000}, subtitle_codec_ctx_->time_base); pkt->dts = pkt->pts; - av_packet_rescale_ts(pkt, av_get_time_base_q(), subtitle_stream_->time_base); + av_packet_rescale_ts(pkt, subtitle_codec_ctx_->time_base, subtitle_stream_->time_base); int err = av_interleaved_write_frame(fmt_ctx_, pkt); bool ret = true; @@ -694,6 +694,7 @@ bool FFmpegEncoder::InitializeStream(AVMediaType type, AVStream** stream_ptr, AV codec_ctx->height = params().video_params().height(); codec_ctx->sample_aspect_ratio = params().video_params().pixel_aspect_ratio().toAVRational(); codec_ctx->time_base = params().video_params().frame_rate_as_time_base().toAVRational(); + codec_ctx->framerate = params().video_params().frame_rate().toAVRational(); codec_ctx->pix_fmt = av_get_pix_fmt(params().video_pix_fmt().toUtf8()); if (params().video_params().interlacing() != VideoParams::kInterlaceNone) { @@ -818,6 +819,10 @@ bool FFmpegEncoder::SetupCodecContext(AVStream* stream, AVCodecContext* codec_ct return false; } + if (codec->type == AVMEDIA_TYPE_VIDEO) { + stream->avg_frame_rate = codec_ctx->framerate; + } + return true; } diff --git a/app/codec/waveinput.cpp b/app/codec/waveinput.cpp deleted file mode 100644 index b7342a88c..000000000 --- a/app/codec/waveinput.cpp +++ /dev/null @@ -1,244 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2021 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 . - -***/ - -#include "waveinput.h" - -extern "C" { -#include -} - -#include -#include - -namespace olive { - -WaveInput::WaveInput(const QString &f) : - file_(f) -{ -} - -WaveInput::~WaveInput() -{ - close(); -} - -bool WaveInput::open() -{ - if (!file_.open(QFile::ReadOnly)) { - return false; - } - - if (file_.read(4) != "RIFF") { - close(); - qCritical() << "No RIFF found"; - return false; - } - - // Skip filesize bytes - file_.seek(file_.pos() + 4); - - if (file_.read(4) != "WAVE") { - close(); - qCritical() << "No WAVE found"; - return false; - } - - // Find fmt_ section - if (!find_str(&file_, "fmt ")) { - close(); - qCritical() << "No fmt found"; - return false; - } - - // Skip fmt_ section size - file_.seek(file_.pos()+4); - - // Create data stream for reading bytes into types - QDataStream data_stream(&file_); - data_stream.setByteOrder(QDataStream::LittleEndian); - - // Read data type - uint16_t data_type; - data_stream >> data_type; - - bool data_is_float; - switch (data_type) { - case 1: // PCM Integer - data_is_float = false; - break; - case 3: - data_is_float = true; - break; - default: - // If it's neither float nor int, we can't work with this file - close(); - qCritical() << "Invalid WAV type" << data_type; - return false; - } - - // Read number of channels - uint16_t channel_count; - data_stream >> channel_count; - - uint64_t channel_layout = static_cast(av_get_default_channel_layout(channel_count)); - - int32_t sample_rate; - data_stream >> sample_rate; - - // Skip bytes per second value and bytes per sample value - file_.seek(file_.pos() + 6); - - uint16_t bits_per_sample; - data_stream >> bits_per_sample; - - AudioParams::Format format; - - switch (bits_per_sample) { - case 8: - format = AudioParams::kFormatUnsigned8; - break; - case 16: - format = AudioParams::kFormatSigned16; - break; - case 32: - if (data_is_float) { - format = AudioParams::kFormatFloat32; - } else { - format = AudioParams::kFormatSigned32; - } - break; - case 64: - if (data_is_float) { - format = AudioParams::kFormatFloat64; - } else { - format = AudioParams::kFormatSigned64; - } - break; - default: - // We don't know this format... - close(); - qCritical() << "Invalid format found" << bits_per_sample; - return false; - } - - // We're good to go! - params_ = AudioParams(sample_rate, channel_layout, format); - - if (!find_str(&file_, "data")) { - close(); - qCritical() << "No data tag found"; - return false; - } - - data_stream >> data_size_; - data_position_ = file_.pos(); - - return true; -} - -bool WaveInput::is_open() const -{ - return file_.isOpen(); -} - -QByteArray WaveInput::read(qint64 length) -{ - if (!is_open()) { - return QByteArray(); - } - - return file_.read(qMin(calculate_max_read(), length)); -} - -QByteArray WaveInput::read(qint64 offset, qint64 length) -{ - if (!is_open()) { - return QByteArray(); - } - - seek(offset); - return file_.read(qMin(calculate_max_read(), length)); -} - -qint64 WaveInput::read(qint64 offset, char *buffer, qint64 length) -{ - if (!is_open()) { - return 0; - } - - Q_ASSERT(length > 0); - - seek(offset); - return file_.read(buffer, qMin(calculate_max_read(), length)); -} - -bool WaveInput::seek(qint64 pos) -{ - return file_.seek(data_position_ + qMin(pos, qint64(data_size_))); -} - -bool WaveInput::at_end() const -{ - return file_.pos() == (data_position_ + data_size_); -} - -const AudioParams &WaveInput::params() const -{ - return params_; -} - -void WaveInput::close() -{ - if (file_.isOpen()) { - file_.close(); - } -} - -const quint32 &WaveInput::data_length() const -{ - return data_size_; -} - -int WaveInput::sample_count() const -{ - return params_.bytes_to_samples(data_size_); -} - -bool WaveInput::find_str(QFile *f, const char *str) -{ - qint64 pos = f->pos(); - while (f->read(4) != str) { - if (f->atEnd()) { - return false; - } - - pos++; - f->seek(pos); - } - - return true; -} - -qint64 WaveInput::calculate_max_read() const -{ - return data_size_ - (file_.pos() - data_position_ ); -} - -} diff --git a/app/codec/waveoutput.cpp b/app/codec/waveoutput.cpp deleted file mode 100644 index d5fd431c5..000000000 --- a/app/codec/waveoutput.cpp +++ /dev/null @@ -1,180 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2021 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 . - -***/ - -#include "waveoutput.h" - -#include "render/audioparams.h" - -namespace olive { - -const int16_t kWAVIntegerFormat = 1; -const int16_t kWAVFloatFormat = 3; - -WaveOutput::WaveOutput(const QString &f, - const AudioParams& params) : - file_(f), - params_(params) -{ - Q_ASSERT(params_.is_valid()); -} - -WaveOutput::~WaveOutput() -{ - close(); -} - -bool WaveOutput::open() -{ - data_length_ = 0; - - if (file_.open(QFile::WriteOnly)) { - // RIFF header - file_.write("RIFF"); - - // Total file size minus RIFF and this integer (minus 8 bytes, filled in later) - write_int(&file_, 0); - - // File type header - file_.write("WAVE"); - - // Begin format descriptor chunk - file_.write("fmt "); - - // Format chunk size - write_int(&file_, 16); - - // Type of format - switch (params_.format()) { - case AudioParams::kFormatUnsigned8: - case AudioParams::kFormatSigned16: - case AudioParams::kFormatSigned32: - case AudioParams::kFormatSigned64: - write_int(&file_, kWAVIntegerFormat); - break; - case AudioParams::kFormatFloat32: - case AudioParams::kFormatFloat64: - write_int(&file_, kWAVFloatFormat); - break; - case AudioParams::kFormatInvalid: - case AudioParams::kFormatCount: - qWarning() << "Invalid sample format for WAVE audio"; - file_.close(); - return false; - } - - // Number of channels - write_int(&file_, static_cast(params_.channel_count())); - - // Sample rate - write_int(&file_, params_.sample_rate()); - - // Bytes per second - write_int(&file_, params_.samples_to_bytes(params_.sample_rate())); - - // Bytes per sample - write_int(&file_, static_cast(params_.samples_to_bytes(1))); - - // Bits per sample per channel - write_int(&file_, static_cast(params_.bits_per_sample())); - - // Data chunk header - file_.write("data"); - - // Size of data chunk (filled in later) - write_int(&file_, 0); - - return true; - } - - return false; -} - -void WaveOutput::write(const QByteArray &bytes) -{ - if (file_.isOpen()) { - file_.write(bytes); - - data_length_ += bytes.size(); - } -} - -void WaveOutput::write(const char *bytes, int length) -{ - if (file_.isOpen()) { - file_.write(bytes, length); - - data_length_ += length; - } -} - -void WaveOutput::close() -{ - if (file_.isOpen()) { - - // Write file sizes - file_.seek(4); - write_int(&file_, data_length_ + 36); - - file_.seek(40); - write_int(&file_, data_length_); - - file_.close(); - } -} - -const int& WaveOutput::data_length() const -{ - return data_length_; -} - -const AudioParams &WaveOutput::params() const -{ - return params_; -} - -void WaveOutput::switch_endianness(QByteArray& array) -{ - int half_sz = array.size()/2; - - for (int i=0;i -void WaveOutput::write_int(QFile *file, T integer) -{ - QByteArray bytes; - bytes.resize(sizeof(T)); - memcpy(bytes.data(), &integer, static_cast(bytes.size())); - - // WAV expects little-endian, so if the integer is big endian we need to switch - if (QSysInfo::ByteOrder == QSysInfo::BigEndian) { - switch_endianness(bytes); - } - - file->write(bytes); -} - -} diff --git a/app/core.cpp b/app/core.cpp index dc41884a8..290393867 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -833,6 +833,42 @@ void Core::SaveUnrecoveredList() } } +bool Core::RevertProjectInternal(Project *p, bool by_opening_existing) +{ + if (p->filename().isEmpty()) { + QMessageBox::critical(main_window_, tr("Revert"), + tr("This project has not yet been saved, therefore there is no last saved state to revert to.")); + } else { + QString msg; + + if (by_opening_existing) { + msg = tr("The project \"%1\" is already open. By re-opening it, the project will revert to " + "its last saved state. Any unsaved changes will be lost. Do you wish to continue?").arg(p->filename()); + } else { + msg = tr("This will revert the project \"%1\" back to its last saved state. " + "All unsaved changes will be lost. Do you wish to continue?").arg(p->name()); + } + + if (QMessageBox::question(main_window_, tr("Revert"), msg, QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) { + // Copy filename because CloseProject is going to delete `p` + QString filename = p->filename(); + + // Close project without prompting to save it + CloseProjectBehavior b = kCloseProjectDontSave; + CloseProject(p, false, b); + + // NOTE: `p` will be deleted now, so don't try accessing it + + // Re-open project at the filename + OpenProjectInternal(filename); + + return true; + } + } + + return false; +} + void Core::SaveAutorecovery() { if (Config::Current()[QStringLiteral("AutorecoveryEnabled")].toBool()) { @@ -1009,6 +1045,15 @@ bool Core::SaveAllProjects() return true; } +void Core::RevertActiveProject() +{ + Project *p = GetActiveProject(); + + if (p) { + RevertProjectInternal(p, false); + } +} + bool Core::CloseActiveProject() { return CloseProject(GetActiveProject(), true); @@ -1194,9 +1239,18 @@ void Core::OpenProjectInternal(const QString &filename, bool recovery_project) { // See if this project is open already foreach (Project* p, open_projects_) { - if (p->filename() == filename) { + // Comparing QFileInfos will handle case insensitivity and both slash directions on platforms + // where this is necessary (not naming any names *cough* Windows) + if (QFileInfo(p->filename()) == QFileInfo(filename)) { // This project is already open - AddOpenProject(p); + bool reverted = RevertProjectInternal(p, true); + + if (!reverted) { + // Calling this will focus attention to the project that the user just tried to re-open + AddOpenProject(p); + } + + // Don't do anything else return; } } diff --git a/app/core.h b/app/core.h index 67fb6a95d..a85a0d17e 100644 --- a/app/core.h +++ b/app/core.h @@ -329,6 +329,11 @@ public slots: */ bool SaveAllProjects(); + /** + * @brief Revert project to last saved state (basically close and open it) + */ + void RevertActiveProject(); + /** * @brief Closes the active project * @@ -506,6 +511,8 @@ private: void SaveUnrecoveredList(); + bool RevertProjectInternal(Project *p, bool by_opening_existing); + /** * @brief Internal main window object */ diff --git a/app/dialog/preferences/keysequenceeditor.cpp b/app/dialog/preferences/keysequenceeditor.cpp index ef57aaa3a..37ab138bb 100644 --- a/app/dialog/preferences/keysequenceeditor.cpp +++ b/app/dialog/preferences/keysequenceeditor.cpp @@ -45,9 +45,9 @@ QString KeySequenceEditor::action_name() { } QString KeySequenceEditor::export_shortcut() { - QString ks = keySequence().toString(); - if (ks != action->property("keydefault")) { - return action->property("id").toString() + "\t" + ks; + QKeySequence ks = keySequence(); + if (ks != action->property("keydefault").value()) { + return action->property("id").toString() + "\t" + ks.toString(); } return nullptr; } diff --git a/app/dialog/preferences/tabs/preferenceskeyboardtab.cpp b/app/dialog/preferences/tabs/preferenceskeyboardtab.cpp index ef7ada6ee..a7e906372 100644 --- a/app/dialog/preferences/tabs/preferenceskeyboardtab.cpp +++ b/app/dialog/preferences/tabs/preferenceskeyboardtab.cpp @@ -150,6 +150,9 @@ bool PreferencesKeyboardTab::refine_shortcut_list(const QString &s, QTreeWidgetI for (int i=0;itopLevelItemCount();i++) { refine_shortcut_list(s, keyboard_tree_->topLevelItem(i)); } + + // Return value is `all_children_are_hidden` which doesn't matter at the top level + return false; } else { parent->setExpanded(!s.isEmpty()); @@ -158,7 +161,9 @@ bool PreferencesKeyboardTab::refine_shortcut_list(const QString &s, QTreeWidgetI for (int i=0;ichildCount();i++) { QTreeWidgetItem* item = parent->child(i); if (item->childCount() > 0) { - all_children_are_hidden = refine_shortcut_list(s, item); + if (!refine_shortcut_list(s, item)) { + all_children_are_hidden = false; + } } else { item->setHidden(false); if (s.isEmpty()) { @@ -183,7 +188,6 @@ bool PreferencesKeyboardTab::refine_shortcut_list(const QString &s, QTreeWidgetI return all_children_are_hidden; } - return true; } void PreferencesKeyboardTab::load_shortcut_file() { diff --git a/app/node/factory.cpp b/app/node/factory.cpp index 9e18f0a99..9f6c7f335 100644 --- a/app/node/factory.cpp +++ b/app/node/factory.cpp @@ -33,6 +33,7 @@ #include "distort/transform/transformdistortnode.h" #include "generator/matrix/matrix.h" #include "generator/polygon/polygon.h" +#include "generator/shape/shapenode.h" #include "generator/solid/solid.h" #include "generator/text/text.h" #include "filter/blur/blur.h" @@ -239,13 +240,14 @@ Node *NodeFactory::CreateFromFactoryIndex(const NodeFactory::InternalID &id) return new TimeRemapNode(); case kSubtitleBlock: return new SubtitleBlock(); + case kShapeGenerator: + return new ShapeNode(); case kInternalNodeCount: break; } - // We should never get here - abort(); + return nullptr; } } diff --git a/app/node/factory.h b/app/node/factory.h index 8ba811cd3..a53a3d6ff 100644 --- a/app/node/factory.h +++ b/app/node/factory.h @@ -59,6 +59,7 @@ public: kValueNode, kTimeRemapNode, kSubtitleBlock, + kShapeGenerator, // Count value kInternalNodeCount diff --git a/app/node/generator/CMakeLists.txt b/app/node/generator/CMakeLists.txt index 8c5706e50..dfdabb071 100644 --- a/app/node/generator/CMakeLists.txt +++ b/app/node/generator/CMakeLists.txt @@ -16,6 +16,7 @@ add_subdirectory(matrix) add_subdirectory(polygon) +add_subdirectory(shape) add_subdirectory(solid) add_subdirectory(text) diff --git a/app/node/generator/shape/CMakeLists.txt b/app/node/generator/shape/CMakeLists.txt new file mode 100644 index 000000000..b3d0fed04 --- /dev/null +++ b/app/node/generator/shape/CMakeLists.txt @@ -0,0 +1,24 @@ +# Olive - Non-Linear Video Editor +# Copyright (C) 2021 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 . + +set(OLIVE_SOURCES + ${OLIVE_SOURCES} + node/generator/shape/shapenode.cpp + node/generator/shape/shapenode.h + node/generator/shape/shapenodebase.cpp + node/generator/shape/shapenodebase.h + PARENT_SCOPE +) diff --git a/app/node/generator/shape/shapenode.cpp b/app/node/generator/shape/shapenode.cpp new file mode 100644 index 000000000..0c8f258a4 --- /dev/null +++ b/app/node/generator/shape/shapenode.cpp @@ -0,0 +1,89 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 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 . + +***/ + +#include "shapenode.h" + +namespace olive { + +#define super ShapeNodeBase + +QString ShapeNode::kTypeInput = QStringLiteral("type_in"); + +ShapeNode::ShapeNode() +{ + PrependInput(kTypeInput, NodeValue::kCombo); +} + +QString ShapeNode::Name() const +{ + return tr("Shape"); +} + +QString ShapeNode::id() const +{ + return QStringLiteral("org.olivevideoeditor.Olive.shape"); +} + +QVector ShapeNode::Category() const +{ + return {kCategoryGenerator}; +} + +QString ShapeNode::Description() const +{ + return tr("Generate a 2D primitive shape."); +} + +void ShapeNode::Retranslate() +{ + super::Retranslate(); + + SetInputName(kTypeInput, tr("Type")); + + // Coordinate with Type enum + SetComboBoxStrings(kTypeInput, {tr("Rectangle"), tr("Ellipse")}); +} + +ShaderCode ShapeNode::GetShaderCode(const QString &shader_id) const +{ + Q_UNUSED(shader_id) + + return ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/shape.frag"))); +} + +NodeValueTable ShapeNode::Value(const QString &output, NodeValueDatabase &value) const +{ + Q_UNUSED(output) + + ShaderJob job; + + job.InsertValue(this, kTypeInput, value); + job.InsertValue(this, kPositionInput, value); + job.InsertValue(this, kSizeInput, value); + job.InsertValue(this, kColorInput, value); + job.InsertValue(QStringLiteral("resolution_in"), value[QStringLiteral("global")].GetWithMeta(NodeValue::kVec2, QStringLiteral("resolution"))); + job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn); + + NodeValueTable table = value.Merge(); + table.Push(NodeValue::kShaderJob, QVariant::fromValue(job), this); + return table; +} + +} diff --git a/app/codec/waveinput.h b/app/node/generator/shape/shapenode.h similarity index 51% rename from app/codec/waveinput.h rename to app/node/generator/shape/shapenode.h index d8b863da1..0381be00d 100644 --- a/app/codec/waveinput.h +++ b/app/node/generator/shape/shapenode.h @@ -18,58 +18,44 @@ ***/ -#ifndef WAVEINPUT_H -#define WAVEINPUT_H +#ifndef SHAPENODE_H +#define SHAPENODE_H -#include - -#include "render/audioparams.h" +#include "shapenodebase.h" namespace olive { -class WaveInput +class ShapeNode : public ShapeNodeBase { + Q_OBJECT public: - WaveInput(const QString& f); + ShapeNode(); - ~WaveInput(); + enum Type { + kRectangle, + kEllipse + }; - DISABLE_COPY_MOVE(WaveInput) + NODE_DEFAULT_DESTRUCTOR(ShapeNode) + NODE_COPY_FUNCTION(ShapeNode) - bool open(); + virtual QString Name() const override; + virtual QString id() const override; + virtual QVector Category() const override; + virtual QString Description() const override; - bool is_open() const; + virtual void Retranslate() override; - QByteArray read(qint64 length); - QByteArray read(qint64 offset, qint64 length); - qint64 read(qint64 offset, char *buffer, qint64 length); + virtual ShaderCode GetShaderCode(const QString& shader_id) const override; + virtual NodeValueTable Value(const QString& output, NodeValueDatabase& value) const override; - bool seek(qint64 pos); - - bool at_end() const; - - const AudioParams& params() const; - - void close(); - - const quint32& data_length() const; - - int sample_count() const; + static QString kTypeInput; private: - bool find_str(QFile* f, const char* str); - qint64 calculate_max_read() const; - AudioParams params_; - - QFile file_; - - qint64 data_position_; - - quint32 data_size_; }; } -#endif // WAVEINPUT_H +#endif // SHAPENODE_H diff --git a/app/node/generator/shape/shapenodebase.cpp b/app/node/generator/shape/shapenodebase.cpp new file mode 100644 index 000000000..e8e77db77 --- /dev/null +++ b/app/node/generator/shape/shapenodebase.cpp @@ -0,0 +1,49 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 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 . + +***/ + +#include "shapenodebase.h" + +#include + +namespace olive { + +#define super Node + +QString ShapeNodeBase::kPositionInput = QStringLiteral("pos_in"); +QString ShapeNodeBase::kSizeInput = QStringLiteral("size_in"); +QString ShapeNodeBase::kColorInput = QStringLiteral("color_in"); + +ShapeNodeBase::ShapeNodeBase() +{ + AddInput(kPositionInput, NodeValue::kVec2, QVector2D(10, 10)); + AddInput(kSizeInput, NodeValue::kVec2, QVector2D(100, 100)); + AddInput(kColorInput, NodeValue::kColor, QVariant::fromValue(Color(1.0, 0.0, 0.0, 1.0))); +} + +void ShapeNodeBase::Retranslate() +{ + super::Retranslate(); + + SetInputName(kPositionInput, tr("Position")); + SetInputName(kSizeInput, tr("Size")); + SetInputName(kColorInput, tr("Color")); +} + +} diff --git a/app/codec/waveoutput.h b/app/node/generator/shape/shapenodebase.h similarity index 53% rename from app/codec/waveoutput.h rename to app/node/generator/shape/shapenodebase.h index 1626a207c..368b3f20a 100644 --- a/app/codec/waveoutput.h +++ b/app/node/generator/shape/shapenodebase.h @@ -18,51 +18,29 @@ ***/ -#ifndef WAVEAUDIO_H -#define WAVEAUDIO_H +#ifndef SHAPENODEBASE_H +#define SHAPENODEBASE_H -#include -#include - -#include "render/audioparams.h" +#include "node/node.h" namespace olive { -class WaveOutput +class ShapeNodeBase : public Node { + Q_OBJECT public: - WaveOutput(const QString& f, - const AudioParams& params); + ShapeNodeBase(); - ~WaveOutput(); + NODE_DEFAULT_DESTRUCTOR(ShapeNodeBase) - DISABLE_COPY_MOVE(WaveOutput) + virtual void Retranslate() override; - bool open(); - - void write(const QByteArray& bytes); - void write(const char* bytes, int length); - - void close(); - - const int& data_length() const; - - const AudioParams& params() const; - -private: - template - void write_int(QFile* file, T integer); - - void switch_endianness(QByteArray &array); - - QFile file_; - - AudioParams params_; - - int data_length_; + static QString kPositionInput; + static QString kSizeInput; + static QString kColorInput; }; } -#endif // WAVEAUDIO_H +#endif // SHAPENODEBASE_H diff --git a/app/node/node.h b/app/node/node.h index 22c02b528..dde2e706f 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -46,7 +46,11 @@ namespace olive { -#define NODE_DEFAULT_DESTRUCTOR(x) virtual ~x() override {DisconnectAll();} +#define NODE_DEFAULT_DESTRUCTOR(x) \ + virtual ~x() override {DisconnectAll();} + +#define NODE_COPY_FUNCTION(x) \ + virtual Node *copy() const override {return new x();} class NodeGraph; class Folder; diff --git a/app/render/audioplaybackcache.cpp b/app/render/audioplaybackcache.cpp index ed507581b..93b3c592a 100644 --- a/app/render/audioplaybackcache.cpp +++ b/app/render/audioplaybackcache.cpp @@ -26,6 +26,7 @@ #include #include "common/filefunctions.h" +#include "node/output/viewer/viewer.h" namespace olive { @@ -390,7 +391,14 @@ void AudioPlaybackCache::UpdateOffsetsFrom(int index) AudioPlaybackCache::PlaybackDevice *AudioPlaybackCache::CreatePlaybackDevice(QObject* parent) const { - return new PlaybackDevice(playlist_, params_.bytes_per_sample_per_channel(), parent); + PlaybackDevice *d = new PlaybackDevice(playlist_, params_.bytes_per_sample_per_channel(), parent); + + // If we're child of a viewer, set the data limit so audio doesn't play beyond the length + if (ViewerOutput *viewer = viewer_parent()) { + d->SetDataLimit(params_.time_to_bytes_per_channel(viewer->GetAudioLength())); + } + + return d; } AudioPlaybackCache::Segment::Segment(qint64 size) @@ -403,7 +411,8 @@ AudioPlaybackCache::PlaybackDevice::PlaybackDevice(const AudioPlaybackCache::Pla playlist_(playlist), current_segment_(0), segment_read_index_(0), - sample_size_(sample_sz) + sample_size_(sample_sz), + limit_(INT64_MAX) { } @@ -437,10 +446,15 @@ qint64 AudioPlaybackCache::PlaybackDevice::readData(char *data, qint64 maxSize) while (read_size < maxSize && current_segment_ >= 0 - && current_segment_ < playlist_.size()) { + && current_segment_ < playlist_.size() + && playlist_.at(current_segment_).offset() + segment_read_index_ < limit_) { const Segment& cs = playlist_.at(current_segment_); qint64 current_segment_sz = cs.size(); + if (cs.offset() + current_segment_sz > limit_) { + current_segment_sz = limit_ - cs.offset(); + } + QVector segment_files(cs.channels()); segment_files.fill(nullptr); @@ -480,13 +494,13 @@ qint64 AudioPlaybackCache::PlaybackDevice::readData(char *data, qint64 maxSize) // Add to the read index segment_read_index_ += sample_size_; + } - // If we've reached the end of this segment, tick the counter over to the next segment - if (segment_read_index_ == current_segment_sz) { - // Jump to the next file - segment_read_index_ = 0; - current_segment_++; - } + // If we've reached the end of this segment, tick the counter over to the next segment + if (segment_read_index_ == current_segment_sz) { + // Jump to the next file + segment_read_index_ = 0; + current_segment_++; } } diff --git a/app/render/audioplaybackcache.h b/app/render/audioplaybackcache.h index 4e4bc76de..ee697808d 100644 --- a/app/render/audioplaybackcache.h +++ b/app/render/audioplaybackcache.h @@ -145,6 +145,11 @@ public: public: PlaybackDevice(const Playlist& playlist, int sample_sz, QObject* parent = nullptr); + void SetDataLimit(qint64 limit) + { + limit_ = limit; + } + virtual ~PlaybackDevice() override; virtual bool isSequential() const override @@ -178,6 +183,8 @@ public: int sample_size_; + qint64 limit_; + }; /** diff --git a/app/shaders/shape.frag b/app/shaders/shape.frag new file mode 100644 index 000000000..2c379b586 --- /dev/null +++ b/app/shaders/shape.frag @@ -0,0 +1,39 @@ +// Input texture coordinate +varying vec2 ove_texcoord; + +// Match with ShapeNode::Type +#define SHAPE_RECTANGLE 0 +#define SHAPE_ELLIPSE 1 + +uniform vec2 pos_in; +uniform vec2 size_in; +uniform int type_in; +uniform vec2 resolution_in; +uniform vec4 color_in; + +void main() { + vec2 real_position = pos_in/resolution_in; + vec2 real_size = size_in/resolution_in; + + vec4 col = vec4(0.0); + + if (type_in == SHAPE_RECTANGLE) { + if (ove_texcoord.x >= real_position.x && ove_texcoord.y >= real_position.y + && ove_texcoord.x < real_position.x+real_size.x && ove_texcoord.y < real_position.y+real_size.y) { + col = color_in; + } + } else if (type_in == SHAPE_ELLIPSE) { + vec2 center = pos_in+size_in*0.5; + float radius = size_in.y*0.5; + float aspect_ratio = size_in.x/size_in.y; + + vec2 offset = ove_texcoord*resolution_in - center; + offset.x /= aspect_ratio; + float d = length(offset)-radius; + float t = clamp(d, 0.0, 1.0); + + col = color_in * (1.0-t); + } + + gl_FragColor = col; +} diff --git a/app/ts/ru_RU.ts b/app/ts/ru_RU.ts index a6a2e6b54..2e6123ff7 100644 --- a/app/ts/ru_RU.ts +++ b/app/ts/ru_RU.ts @@ -4,37 +4,37 @@ AudioParams - + %1 Hz %1 Гц - + Mono Моно - + Stereo Стерео - + 2.1 2.1 - + 5.1 5.1 - + 7.1 7.1 - + Unknown (0x%1) Неизвестно (0x%1) @@ -42,24 +42,24 @@ Config - + Error loading settings Ошибка при загрузке настроек - + Failed to load application settings. This session will use defaults. %1 - + Error saving settings Ошибка при сохранении настроек - + Failed to save application settings. The application may lack write permissions for this location. @@ -95,12 +95,12 @@ NodeCopyPasteWidget - + Error pasting nodes Ошибка при вставке нод - + Failed to paste nodes: %1 Не удалось вставить ноды: %1 @@ -209,7 +209,7 @@ NodeViewItem - + %1... %1... @@ -420,19 +420,39 @@ olive::AboutDialog - + + Welcome to %1 + Приветствуем в %1 + + + About %1 О программе %1 - + Olive is a free open source non-linear video editor. This software is licensed under the GNU GPL Version 3. Olive — свободный нелинейный видеоредактор. Программа распространяется на условиях GNU GPL v3. - - <html>Olive wouldn't be possible without the support of gracious donations from <a href='https://www.patreon.com/olivevideoeditor'>Patreon</a></html>: - + + <b>Olive relies on support from the community to continue its development.</b> + <b>Разработка Olive возможна благодаря поддержке сообщества.</b> + + + + Olive wouldn't be possible without the support of gracious donations from the following people. + Проекту уже помогают: + + + + <html>%1 If you like this project, please consider making a <a href='https://olivevideoeditor.org/donate.php'>one-time donation</a> or <a href='https://www.patreon.com/olivevideoeditor'>pledging monthly</a> to support its development.</html> + <html>%1 Если вам нравится этот проект, будем признательны за <a href='https://olivevideoeditor.org/donate.php'>разовое</a> или <a href='https://www.patreon.com/olivevideoeditor'>ежемесячное</a> пожертвование для поддержки разработки.</html> + + + + Don't show this message again + Больше не показывать это сообщение @@ -480,27 +500,27 @@ olive::Block - + Length Длительность - + Media In Факт. начало - + Enabled Включено - + Speed Скорость - + Reverse Развернуть @@ -864,180 +884,182 @@ Не удалось создать последовательность - + Possible image sequence detected Обнаружена возможная последовательность изображений - + The file '%1' looks like it might be part of an image sequence. Would you like to import it as such? - + You must specify a project file to export Необходимо указать проектный файл, который экспортировать - + Specified project does not exist Указанный проект не существует - + Failed to open startup file Не удалось открыть стартовый файл - + The project "%1" doesn't exist. A new project will be started instead. Проект "%1" не существует. Вместо него будет создан новый проект. - - + + Missing OpenTimelineIO Libraries Отсутствуют библиотеки OpenTimelineIO - - + + This build was compiled without OpenTimelineIO and therefore cannot open OpenTimelineIO files. Это сборка подготовлена без OpenTimelineIO, поэтому вы не сможете открыть файлы OpenTimelineIO. - - + + Error Ошибка - + This Sequence is empty. There is nothing to export. Эта последовательность пустая. В ней нет данных для экспорта. - + No valid sequence detected. Make sure a sequence is loaded and it has a connected Viewer node. - - + + Auto-Recovery Error Ошибка при автовосстановлении - + Failed to save auto-recovery to "%1". Olive may not have permission to this directory. - + Olive Project Проект Olive - + OpenTimelineIO OpenTimelineIO - + The following projects had unsaved changes when Olive forcefully quit. Would you like to load them? - + В следующих проектах не были сохранены изменения, когда Olive упал. Хотите загрузить их? - + Found auto-recoveries but failed to load the auto-recovery index. Auto-recover projects will have to be opened manually. Your recoverable projects are still available at: %1 - + Найдены данные для автовосстановления, но не удалось загрузить индекс автовосстановления. Автоматически восстанавливаемые проекты придется открыть вручную. + +Восстанавливаемые проекты по-прежнему доступны здесь: %1 - + The following project versions have been auto-saved: Следующие версии проектов были автоматически сохранены: - + Save Project As Сохранить проект как - + Load Project Загрузка проекта - + Label Node Метка ноды - + Set node label Указать метку ноды - + Sequence %1 Последовательность %1 - + Cannot open recent project Не удалось открыть недавний проект - + The project "%1" doesn't exist. Would you like to remove this file from the recent list? Проект"%1" не существует. Хотите удалить этот файл из списка недавно открывавшихся? - + Unsaved Changes Несохраненные изменения - + The project '%1' has unsaved changes. Would you like to save them? В проекте '%1' остались несохраненные изменения. Сохранить их? - + Save Сохранить - + Save All Сохранить все - + Don't Save Не сохранять - + Don't Save All Не сохранять все - + Failed to cache sequence Не удалось закэшировать последовательность - + No active viewer found with this sequence. Для этой последовательности не найден активный монитор. - + Open Project Открыть проект @@ -1077,7 +1099,7 @@ Your recoverable projects are still available at: %1 Waiting for crash report to be generated... - + Создаётся отчёт о падении программы… @@ -1108,7 +1130,7 @@ Your recoverable projects are still available at: %1 Failed to find symbols necessary to send report. This is a packaging issue. Please notify the maintainers of this package. - + Не удалось найти отладочные символы, чтобы отправить отчёт. Это проблема сборки программы. Сообщите об этом автору сборки. @@ -1242,54 +1264,54 @@ Your recoverable projects are still available at: %1 olive::DiskCacheDialog - + Disk Cache: %1 Дисковый кэш: %1 - + Disk Cache Settings Параметры кэша на диске - + Maximum Disk Cache: Макс. кэш на диске: - + %1 GB %1 Гбайт - - - + + + Clear Disk Cache Очистить дисковый кэш - + Automatically clear disk cache on close Автоматически стирать при закрытии - + Are you sure you want to clear the disk cache in '%1'? Вы действительно хотите стереть дисковый кэш в '%1'? - + Disk Cache Cleared Дисковый кэш очищен - + Disk cache failed to fully clear. You may have to delete the cache files manually. - + Disk Cache Partially Cleared Дисковый кэш частично очищен @@ -1995,42 +2017,42 @@ Your recoverable projects are still available at: %1 olive::Footage - + Filename Имя файла - + Loop Mode - + None Нет - + Loop Петля - + Clamp - + %1: Image - %2x%3 - %1: Изображение - %2x%3 + %1: Изображение - %2x%3 - + %1: Video - %2x%3 - %1: Видео - %2x%3 + %1: Видео - %2x%3 - + %1: Audio - %n Channel(s), %2Hz %1: Звук - %n канал, %2Гц @@ -2039,32 +2061,32 @@ Your recoverable projects are still available at: %1 - + Video Видео - + Audio Звук - + Subtitle - Субтитры + Субтитры - + Unknown - Неизвестно + Неизвестно - + Filename: %1 Имя файла: %1 - + Invalid @@ -2076,7 +2098,7 @@ Your recoverable projects are still available at: %1 Import video, audio, or still image files into the composition. - + Вставить видео, звук или статическое изображение в проект. @@ -2120,7 +2142,7 @@ Your recoverable projects are still available at: %1 olive::FootageViewerPanel - + Footage Viewer Просмотр видеоматериала @@ -2243,7 +2265,7 @@ Your recoverable projects are still available at: %1 Frame to Export: - + Экспортируемый кадр: @@ -2272,27 +2294,27 @@ Your recoverable projects are still available at: %1 Свойства ключевого кадра - + In: Вход: - + Out: Выход: - + Linear Линейный - + Hold Константа - + Bezier Безье @@ -2323,17 +2345,17 @@ Your recoverable projects are still available at: %1 olive::LoadOTIOTask - + Failed to load OpenTimelineIO from file "%1" Не удалось загрузить OpenTimelineIO из файла "%1" - + Unknown OpenTimelineIO root element Неизвестный корневой элемент OpenTimelineIO - + Failed to load clip Не удалось загрузить клип @@ -2341,402 +2363,427 @@ Your recoverable projects are still available at: %1 olive::MainMenu - + &Save '%1' Со&хранить '%1' - + Save '%1' &As Сохранить '%1' к&ак - + Close '%1' Закрыть '%1' - + Close All Except '%1' Закрыть все кроме '%1' - + &Save Project &Сохранить проект - + Save Project &As Сохранить проект &как - + Close Project Закрыть проект - + Close All Except Current Project Закрыть все проекты кроме текущего - + (None) (нет) - + &File &Файл - + &New &Создать - + &Open Project &Открыть проект - + Open &Recent Открыть из &недавнего - + &Clear Recent List О&чистить список недавних - + Sa&ve All Projects Сохра&нить все проекты - + &Import... &Импортировать… - + &Export &Экспортировать - + &Media... &Медиаданные… - + Close All Projects Закрыть все проекты - + E&xit В&ыход - + &Edit &Правка - + Delete (alt) - + Удалить (alt) - + Insert Вставить - + Overwrite Переписать - + Select &All Выд&елить всё - + Deselect All Снять выделение - + Ripple to In Point Сдвиг до точки входа - + Ripple to Out Point Сдвиг до точки выхода - + Edit to In Point Правка до точки входа - + Edit to Out Point Правка до точки выхода - + + Nudge Left + Сдвинуть влево + + + + Nudge Right + Сдвинуть вправо + + + + Move In Point to Playhead + Точка входа к указателю воспроизведения + + + + Move Out Point to Playhead + Точка выхода к указателю воспроизведения + + + Delete In/Out Point Удалить точку входа/выхода - + Ripple Delete In/Out Point Удалить со сдвигом точку входа/выхода - + Set/Edit Marker Установить/Изменить маркер - + &View &Вид - + Zoom In Приблизить - + Zoom Out Отдалить - + Increase Track Height Увеличить высоту дорожки - + Decrease Track Height Уменьшить высоту дорожки - + Toggle Show All Показывать весь проект - + Full Screen Полноэкранный режим - + Full Screen Viewer Просмотр в полноэкранном режиме - + &Playback Вос&произведение - + Go to Start К началу - + Previous Frame К предыдущему кадру - + Play/Pause Воспроизведение/Пауза - + Play In to Out Проиграть от входа до выхода - + Next Frame К следующему кадру - + Go to End В конец - + Go to Previous Cut К предыдущему отрезу - + Go to Next Cut К следующему отрезу - + Go to In Point К точке входа - + Go to Out Point К точке выхода - + Shuttle Left Уменьшить скорость - + Shuttle Stop Пауза - + Shuttle Right Увеличить скорость - + Loop Петля - + &Sequence П&оследовательность - + Cache Entire Sequence Закэшировать всю последовательность - + Cache Sequence In/Out Закэшировать вход/выход последовательности - + + Clear Disk Cache + Очистить дисковый кэш + + + &Window &Окно - + Maximize Panel Развернуть панель - + Lock Panels Закрепить панели - + Reset to Default Layout Вернуть исходный вид панелей - + &Tools &Инструменты - + Pointer Tool Указатель - + Edit Tool Выделение - + Ripple Tool Монтаж со сдвигом - + Rolling Tool Монтаж с совмещением - + Razor Tool Подрезка - + Slip Tool Прокрутка с совмещением - + Slide Tool Прокрутка - + Hand Tool Навигация - + Zoom Tool Масштаб - + Transition Tool Переход - + Enable Snapping Включить прилипание - + Preferences Параметры - + &Help &Справка - + A&ction Search &Найти команду - + Send &Feedback... &Дать обратную связь… - + &About... &О программе… @@ -2761,12 +2808,12 @@ Your recoverable projects are still available at: %1 olive::MainWindow - + Driver Warning - Предупреждение драйвера + Предупреждение о драйвере - + Olive has detected your system is using the Nouveau graphics driver. This driver is known to have stability and performance issues with Olive. It is highly recommended you install the proprietary NVIDIA driver before continuing to use Olive. @@ -2924,127 +2971,132 @@ This driver is known to have stability and performance issues with Olive. It is olive::MenuShared - + &Project &Проект - + &Sequence П&оследовательность - + &Folder П&апка - + Cu&t В&ырезать - + Cop&y С&копировать - + &Paste &Вставить - + Paste Insert - + Duplicate Сделать копию - + Delete Удалить - + Ripple Delete Удалить со сдвигом - + Split Разделить - + + Speed/Duration + Скорость и длительность + + + Set In Point Установить точку входа - + Set Out Point Установить точку выхода - + Reset In Point Сбросить точку входа - + Reset Out Point Сбросить точку выхода - + Clear In/Out Point Очистить точки входа/выхода - + Add Default Transition Добавить переход по умолчанию - + Link/Unlink Связать/Убрать связь - + Enable/Disable Включить/Отключить - + Nest Вложить - + Frames Кадры - + Drop Frame С пропуском кадров - + Non-Drop Frame Без пропуска кадров - + Milliseconds Миллисекунды - + Seconds Секунды @@ -3103,67 +3155,67 @@ This driver is known to have stability and performance issues with Olive. It is olive::Node - + Input Вход - + Output Выход - + General Общие - + Distort Искажения - + Math Математика - + Color Цвет - + Filter Фильтр - + Timeline Монтажный стол - + Generator Генератор - + Channel Канал - + Transition Переход - + Project Проект - + Uncategorized Без категории @@ -3171,7 +3223,7 @@ This driver is known to have stability and performance issues with Olive. It is olive::NodePanel - + Node Editor Редактор нод @@ -3222,7 +3274,7 @@ This driver is known to have stability and performance issues with Olive. It is olive::NodeParamViewItem - + %1 (%2) %1 (%2) @@ -3230,8 +3282,13 @@ This driver is known to have stability and performance issues with Olive. It is olive::NodeParamViewItemBody - - + + ... + + + + + %1: %1: @@ -3249,6 +3306,19 @@ This driver is known to have stability and performance issues with Olive. It is + + olive::NodePropertiesDialog + + + Node Properties + + + + + Name: + Название: + + olive::NodeTablePanel @@ -3305,17 +3375,17 @@ This driver is known to have stability and performance issues with Olive. It is X - + X Y - + Y Z - + Z @@ -3326,71 +3396,84 @@ This driver is known to have stability and performance issues with Olive. It is olive::NodeView - + Label Метка - - Auto-Position - Автопозиционирование - - - + Open in Viewer - + Smooth Edges Плавные края - + Filter Фильтр - - Show All - Показывать все + + Show All Nodes + Показывать все ноды - - Show Selected Blocks Only - Показывать только выбранные блоки + + Show Selected + Показывать выбранное - + Direction Направление - + Top to Bottom Сверху вниз - + Bottom to Top Снизу вверх - + Left to Right Слева направо - + Right to Left Справа налево - + Add Добавить + + olive::NodeViewToolBar + + + Add Node + Добавить + + + + Mini-Map + Миникарта + + + + Toggle Mini-Map + Включить или выключить миникарту + + olive::PanNode @@ -3472,7 +3555,7 @@ This driver is known to have stability and performance issues with Olive. It is Pixel Sampler - + Пипетка @@ -3989,13 +4072,13 @@ This driver is known to have stability and performance issues with Olive. It is olive::Project - + Root - - + + (untitled) (без названия) @@ -4003,22 +4086,22 @@ This driver is known to have stability and performance issues with Olive. It is olive::ProjectExplorer - + &New &Создать - + &Import... &Импортировать… - + Confirm Item Deletion Подтвердите удаление объекта - + The item "%1" is currently connected to the following nodes: %2 @@ -4027,52 +4110,52 @@ Are you sure you wish to delete this footage? - + %1 (%2) %1 (%2) - + Open in New Tab Открыть в новой вкладке - + Open in New Window Открыть в новом окне - + Reveal in Explorer Открыть в Проводнике - + Reveal in Finder Открыть в Finder - + Reveal in File Manager Открыть в файловом менеджере - + Pre-Cache Предкэширование - + No sequences exist in project - + For "%1" - + P&roperties С&войства @@ -4150,17 +4233,17 @@ Are you sure you wish to delete this footage? olive::ProjectPanel - + Folder Папка - + Project Проект - + (none) (нет) @@ -4272,22 +4355,32 @@ Are you sure you wish to delete this footage? olive::ProjectViewModel - + Name Название - + Duration Длительность - + Rate Частота - + + Modified + Изменено + + + + Created + Создано + + + Move Items Переместить объекты @@ -4362,17 +4455,17 @@ Are you sure you wish to delete this footage? olive::Sequence - + Video Tracks Видеодорожки - + Audio Tracks Звуковые дорожки - + Subtitle Tracks Дорожки субтитров @@ -4420,12 +4513,12 @@ Are you sure you wish to delete this footage? Введите название этой последовательности - + Confirm Set As Default - + Are you sure you want to set the current parameters as defaults? @@ -4468,12 +4561,17 @@ Are you sure you wish to delete this footage? Качество: - + + Auto-Cache: + Автокэширование: + + + Save Preset Сохранить профиль - + (%1x%2) (%1x%2) @@ -4516,42 +4614,42 @@ Are you sure you wish to delete this footage? PAL - + %1 23.976 FPS %1 23,976 к/с - + %1 25 FPS %1 25 к/с - + %1 29.97 FPS %1 29,97 к/с - + %1 50 FPS %1 50 к/с - + %1 59.94 FPS %1 59,94 к/с - + %1 Standard - + %1 Widescreen - + Delete Preset Удалить профиль @@ -4569,12 +4667,12 @@ Are you sure you wish to delete this footage? --- - + --- Invalid Value - + Некорректное значение @@ -4601,7 +4699,7 @@ Are you sure you wish to delete this footage? Generate a solid color. - + Создать сплошную заливку. @@ -4609,6 +4707,39 @@ Are you sure you wish to delete this footage? Цвет + + olive::SpeedDurationDialog + + + Speed/Duration + Скорость и длительность + + + + Speed: + Скорость: + + + + Duration: + Длительность: + + + + Link Speed and Duration + Связать скорость и длительность + + + + Ripple Trailing Clips + + + + + Rippling is a stub and will not do anything. Do you wish to continue? + + + olive::StringSlider @@ -4731,7 +4862,7 @@ Are you sure you wish to delete this footage? Enable HTML - + Включить HTML @@ -4808,7 +4939,7 @@ Are you sure you wish to delete this footage? Time Remap - + Переназначить время @@ -4819,28 +4950,40 @@ Are you sure you wish to delete this footage? olive::TimelinePanel - + Timeline Монтажный стол + + olive::TimelineView + + + In: %1 +Out: %2 +Duration: %3 + Начало: %1 +Конец: %2 +Длительность: %3 + + olive::TimelineWidget - - + + Properties Свойства - + Use Audio Time Units - + Show Waveforms - + Показывать волновую форму @@ -4922,42 +5065,42 @@ Are you sure you wish to delete this footage? olive::Track - + Track Дорожка - + Node for representing and processing a single array of Blocks sorted by time. Also represents the end of a Sequence. - + Blocks Блоки - + Muted Приглушено - + Video %1 Видео %1 - + Audio %1 Звук %1 - + Subtitle %1 Субтитры %1 - + Track %1 Дорожка %1 @@ -5035,38 +5178,38 @@ Are you sure you wish to delete this footage? Transform an image in 2D space. Equivalent to multiplying by an orthographic matrix. - + Преобразует изображение в 2D-пространстве. Эквивалент умножения на прямоугольную матрицу. olive::TransitionBlock - + From От - + To До - + Curve Кривая - + Linear Линейный - + Exponential Экспоненциальный - + Logarithmic Логарифмический @@ -5220,12 +5363,12 @@ Are you sure you wish to delete this footage? Divider: - + Делитель: Stream Index: - + Индекс потока: @@ -5298,45 +5441,50 @@ Are you sure you wish to delete this footage? olive::ViewerOutput - + Viewer Монитор - + Interface between a Viewer panel and the node system. - + %1 FPS %1 к/с - + %1 Hz %1 Гц - + Video Parameters Параметры видео - + Audio Parameters Параметры звука - + Texture Текстура - + Samples Сэмплы + + + Auto-Cache + Автокэширование + olive::ViewerPanel @@ -5349,98 +5497,93 @@ Are you sure you wish to delete this footage? olive::ViewerWidget - + Error Ошибка - + No in or out points are set to cache. - - + + Safe Margins Безопасная область - + Zoom Масштаб - + Fit Уместить - + %1% %1% - + Full Screen Полноэкранный режим - + Screen %1: %2x%3 Экран %1: %2×%3 - + Deinterlace - + Scopes Анализаторы - + Cache Кэш - - Auto-Cache - Автокэширование - - - + Show FPS Показывать частоту кадров - + Cache Entire Sequence Закэшировать всю последовательность - + Cache Sequence In/Out Закэшировать вход/выход последовательности - + Off Выкл. - + On Вкл - + Custom Aspect Другое соотношение - + Show Audio Waveform Показывать волновую форму @@ -5456,7 +5599,7 @@ Are you sure you wish to delete this footage? Adjusts the volume of an audio source. - + Изменить громкость источника звука. diff --git a/app/ui/icons/icons.cpp b/app/ui/icons/icons.cpp index f5466bb01..322237b65 100644 --- a/app/ui/icons/icons.cpp +++ b/app/ui/icons/icons.cpp @@ -60,6 +60,7 @@ QIcon icon::Sequence; QIcon icon::Video; QIcon icon::Audio; QIcon icon::Image; +QIcon icon::MiniMap; QIcon icon::TriUp; QIcon icon::TriLeft; QIcon icon::TriDown; @@ -110,6 +111,8 @@ void icon::LoadAll(const QString& theme) Audio = Create(theme, "audiosource"); Image = Create(theme, "imagesource"); + MiniMap = Create(theme, "map"); + TriUp = Create(theme, "tri-up"); TriLeft = Create(theme, "tri-left"); TriDown = Create(theme, "tri-down"); diff --git a/app/ui/icons/icons.h b/app/ui/icons/icons.h index 88bbf207e..c8c1788a0 100644 --- a/app/ui/icons/icons.h +++ b/app/ui/icons/icons.h @@ -65,6 +65,9 @@ extern QIcon Video; extern QIcon Audio; extern QIcon Image; +// Node Icons +extern QIcon MiniMap; + // Triangle Arrows extern QIcon TriUp; extern QIcon TriLeft; diff --git a/app/ui/style/olive-dark/olive-dark.qrc b/app/ui/style/olive-dark/olive-dark.qrc index 551f40913..cf8b1381e 100644 --- a/app/ui/style/olive-dark/olive-dark.qrc +++ b/app/ui/style/olive-dark/olive-dark.qrc @@ -110,6 +110,10 @@ png/magnet.32.png png/magnet.64.png png/magnet.128.png + png/map.16.png + png/map.32.png + png/map.64.png + png/map.128.png png/minus.16.png png/minus.32.png png/minus.64.png diff --git a/app/ui/style/olive-dark/png/map.128.png b/app/ui/style/olive-dark/png/map.128.png new file mode 100644 index 000000000..d02a8be78 Binary files /dev/null and b/app/ui/style/olive-dark/png/map.128.png differ diff --git a/app/ui/style/olive-dark/png/map.16.png b/app/ui/style/olive-dark/png/map.16.png new file mode 100644 index 000000000..63f0a15b7 Binary files /dev/null and b/app/ui/style/olive-dark/png/map.16.png differ diff --git a/app/ui/style/olive-dark/png/map.32.png b/app/ui/style/olive-dark/png/map.32.png new file mode 100644 index 000000000..a55df93a3 Binary files /dev/null and b/app/ui/style/olive-dark/png/map.32.png differ diff --git a/app/ui/style/olive-dark/png/map.64.png b/app/ui/style/olive-dark/png/map.64.png new file mode 100644 index 000000000..0798b617e Binary files /dev/null and b/app/ui/style/olive-dark/png/map.64.png differ diff --git a/app/ui/style/olive-dark/svg/map.svg b/app/ui/style/olive-dark/svg/map.svg new file mode 100644 index 000000000..b65b63e58 --- /dev/null +++ b/app/ui/style/olive-dark/svg/map.svg @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + image/svg+xml + + + + Martin Ruskov + + + http://commons.wikimedia.org/wiki/Tango_icon + + + + + + + + + + + + + + + + + diff --git a/app/ui/style/olive-light/olive-light.qrc b/app/ui/style/olive-light/olive-light.qrc index 09583f156..d080d292e 100644 --- a/app/ui/style/olive-light/olive-light.qrc +++ b/app/ui/style/olive-light/olive-light.qrc @@ -110,6 +110,10 @@ png/magnet.32.png png/magnet.64.png png/magnet.128.png + png/map.16.png + png/map.32.png + png/map.64.png + png/map.128.png png/minus.16.png png/minus.32.png png/minus.64.png diff --git a/app/ui/style/olive-light/png/map.128.png b/app/ui/style/olive-light/png/map.128.png new file mode 100644 index 000000000..724b977a4 Binary files /dev/null and b/app/ui/style/olive-light/png/map.128.png differ diff --git a/app/ui/style/olive-light/png/map.16.png b/app/ui/style/olive-light/png/map.16.png new file mode 100644 index 000000000..c6283ee79 Binary files /dev/null and b/app/ui/style/olive-light/png/map.16.png differ diff --git a/app/ui/style/olive-light/png/map.32.png b/app/ui/style/olive-light/png/map.32.png new file mode 100644 index 000000000..04a464eed Binary files /dev/null and b/app/ui/style/olive-light/png/map.32.png differ diff --git a/app/ui/style/olive-light/png/map.64.png b/app/ui/style/olive-light/png/map.64.png new file mode 100644 index 000000000..4df5ffeef Binary files /dev/null and b/app/ui/style/olive-light/png/map.64.png differ diff --git a/app/ui/style/olive-light/svg/map.svg b/app/ui/style/olive-light/svg/map.svg new file mode 100644 index 000000000..fa176eb33 --- /dev/null +++ b/app/ui/style/olive-light/svg/map.svg @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + image/svg+xml + + + + Martin Ruskov + + + http://commons.wikimedia.org/wiki/Tango_icon + + + + + + + + + + + + + + + + + diff --git a/app/widget/menu/menu.cpp b/app/widget/menu/menu.cpp index f925877bb..6745940b8 100644 --- a/app/widget/menu/menu.cpp +++ b/app/widget/menu/menu.cpp @@ -87,7 +87,7 @@ void Menu::InsertAlphabetically(Menu *menu) InsertAlphabetically(menu->menuAction()); } -void Menu::ConformItem(QAction *a, const QString &id, const QString &key) +void Menu::ConformItem(QAction *a, const QString &id, const QKeySequence &key) { a->setProperty("id", id); diff --git a/app/widget/menu/menu.h b/app/widget/menu/menu.h index a11a0eafc..0bc4f4b7d 100644 --- a/app/widget/menu/menu.h +++ b/app/widget/menu/menu.h @@ -122,7 +122,7 @@ public: QAction* AddItem(const QString& id, const typename QtPrivate::FunctionPointer::Object *receiver, Func member, - const QString &key = QString()) + const QKeySequence &key = QKeySequence()) { QAction* a = CreateItem(this, id, receiver, member, key); @@ -171,7 +171,7 @@ public: const QString& id, const typename QtPrivate::FunctionPointer::Object *receiver, Func member, - const QString& key = QString()) + const QKeySequence &key = QKeySequence()) { QAction* a = new QAction(parent); @@ -215,7 +215,7 @@ public: const QString& id, const typename QtPrivate::FunctionPointer::Object *receiver, Func member, - const QString& key = QString()) + const QKeySequence &key = QKeySequence()) { ConformItem(a, id, key); @@ -224,7 +224,7 @@ public: static void ConformItem(QAction *a, const QString& id, - const QString& key = QString()); + const QKeySequence &key = QKeySequence()); private: void Init(); diff --git a/app/widget/menu/menushared.cpp b/app/widget/menu/menushared.cpp index 7b0506a45..1cabcf6de 100644 --- a/app/widget/menu/menushared.cpp +++ b/app/widget/menu/menushared.cpp @@ -33,32 +33,32 @@ MenuShared* MenuShared::instance_ = nullptr; MenuShared::MenuShared() { // "New" menu shared items - new_project_item_ = Menu::CreateItem(this, "newproj", Core::instance(), &Core::CreateNewProject, "Ctrl+N"); - new_sequence_item_ = Menu::CreateItem(this, "newseq", Core::instance(), &Core::CreateNewSequence, "Ctrl+Shift+N"); + new_project_item_ = Menu::CreateItem(this, "newproj", Core::instance(), &Core::CreateNewProject, tr("Ctrl+N")); + new_sequence_item_ = Menu::CreateItem(this, "newseq", Core::instance(), &Core::CreateNewSequence, tr("Ctrl+Shift+N")); new_folder_item_ = Menu::CreateItem(this, "newfolder", Core::instance(), &Core::CreateNewFolder); // "Edit" menu shared items - edit_cut_item_ = Menu::CreateItem(this, "cut", this, &MenuShared::CutTriggered, "Ctrl+X"); - edit_copy_item_ = Menu::CreateItem(this, "copy", this, &MenuShared::CopyTriggered, "Ctrl+C"); - edit_paste_item_ = Menu::CreateItem(this, "paste", this, &MenuShared::PasteTriggered, "Ctrl+V"); - edit_paste_insert_item_ = Menu::CreateItem(this, "pasteinsert", this, &MenuShared::PasteInsertTriggered, "Ctrl+Shift+V"); - edit_duplicate_item_ = Menu::CreateItem(this, "duplicate", this, &MenuShared::DuplicateTriggered, "Ctrl+D"); - edit_delete_item_ = Menu::CreateItem(this, "delete", this, &MenuShared::DeleteSelectedTriggered, "Del"); - edit_ripple_delete_item_ = Menu::CreateItem(this, "rippledelete", this, &MenuShared::RippleDeleteTriggered, "Shift+Del"); - edit_split_item_ = Menu::CreateItem(this, "split", this, &MenuShared::SplitAtPlayheadTriggered, "Ctrl+K"); - edit_speedduration_item_ = Menu::CreateItem(this, "speeddur", this, &MenuShared::SpeedDurationTriggered, "Ctrl+R"); + edit_cut_item_ = Menu::CreateItem(this, "cut", this, &MenuShared::CutTriggered, tr("Ctrl+X")); + edit_copy_item_ = Menu::CreateItem(this, "copy", this, &MenuShared::CopyTriggered, tr("Ctrl+C")); + edit_paste_item_ = Menu::CreateItem(this, "paste", this, &MenuShared::PasteTriggered, tr("Ctrl+V")); + edit_paste_insert_item_ = Menu::CreateItem(this, "pasteinsert", this, &MenuShared::PasteInsertTriggered, tr("Ctrl+Shift+V")); + edit_duplicate_item_ = Menu::CreateItem(this, "duplicate", this, &MenuShared::DuplicateTriggered, tr("Ctrl+D")); + edit_delete_item_ = Menu::CreateItem(this, "delete", this, &MenuShared::DeleteSelectedTriggered, tr("Del")); + edit_ripple_delete_item_ = Menu::CreateItem(this, "rippledelete", this, &MenuShared::RippleDeleteTriggered, tr("Shift+Del")); + edit_split_item_ = Menu::CreateItem(this, "split", this, &MenuShared::SplitAtPlayheadTriggered, tr("Ctrl+K")); + edit_speedduration_item_ = Menu::CreateItem(this, "speeddur", this, &MenuShared::SpeedDurationTriggered, tr("Ctrl+R")); // "In/Out" menu shared items - inout_set_in_item_ = Menu::CreateItem(this, "setinpoint", this, &MenuShared::SetInTriggered, "I"); - inout_set_out_item_ = Menu::CreateItem(this, "setoutpoint", this, &MenuShared::SetOutTriggered, "O"); + inout_set_in_item_ = Menu::CreateItem(this, "setinpoint", this, &MenuShared::SetInTriggered, tr("I")); + inout_set_out_item_ = Menu::CreateItem(this, "setoutpoint", this, &MenuShared::SetOutTriggered, tr("O")); inout_reset_in_item_ = Menu::CreateItem(this, "resetin", this, &MenuShared::ResetInTriggered); inout_reset_out_item_ = Menu::CreateItem(this, "resetout", this, &MenuShared::ResetOutTriggered); - inout_clear_inout_item_ = Menu::CreateItem(this, "clearinout", this, &MenuShared::ClearInOutTriggered, "G"); + inout_clear_inout_item_ = Menu::CreateItem(this, "clearinout", this, &MenuShared::ClearInOutTriggered, tr("G")); // "Clip Edit" menu shared items - clip_add_default_transition_item_ = Menu::CreateItem(this, "deftransition", this, &MenuShared::DefaultTransitionTriggered, "Ctrl+Shift+D"); - clip_link_unlink_item_ = Menu::CreateItem(this, "linkunlink", this, &MenuShared::ToggleLinksTriggered, "Ctrl+L"); - clip_enable_disable_item_ = Menu::CreateItem(this, "enabledisable", this, &MenuShared::EnableDisableTriggered, "Shift+E"); + clip_add_default_transition_item_ = Menu::CreateItem(this, "deftransition", this, &MenuShared::DefaultTransitionTriggered, tr("Ctrl+Shift+D")); + clip_link_unlink_item_ = Menu::CreateItem(this, "linkunlink", this, &MenuShared::ToggleLinksTriggered, tr("Ctrl+L")); + clip_enable_disable_item_ = Menu::CreateItem(this, "enabledisable", this, &MenuShared::EnableDisableTriggered, tr("Shift+E")); clip_nest_item_ = Menu::CreateItem(this, "nest", this, &MenuShared::NestTriggered); // TimeRuler menu shared items diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp index 78d54cc26..d6b939408 100644 --- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp +++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp @@ -514,7 +514,17 @@ void NodeParamViewWidgetBridge::UpdateWidgetValues() { QComboBox* cb = static_cast(widgets_.first()); cb->blockSignals(true); - cb->setCurrentIndex(input_.GetValueAtTime(node_time).toInt()); + int index = input_.GetValueAtTime(node_time).toInt(); + int real_row = -1; + for (int i=0; icount(); i++) { + if (!cb->itemText(i).isEmpty()) { + real_row++; + } + + if (real_row == index) { + cb->setCurrentIndex(i); + } + } cb->blockSignals(false); break; } diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index e804391e1..da7f65da5 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -363,9 +363,13 @@ void NodeView::Duplicate() void NodeView::SetColorLabel(int index) { + MultiUndoCommand *command = new MultiUndoCommand(); + for (Node* node : qAsConst(selected_nodes_)) { - node->SetOverrideColor(index); + command->add_child(new NodeOverrideColorCommand(node, index)); } + + Core::instance()->undo_stack()->push(command); } void NodeView::ZoomIn() diff --git a/app/widget/nodeview/nodeviewtoolbar.cpp b/app/widget/nodeview/nodeviewtoolbar.cpp index c63be73db..6ffaa6d35 100644 --- a/app/widget/nodeview/nodeviewtoolbar.cpp +++ b/app/widget/nodeview/nodeviewtoolbar.cpp @@ -43,13 +43,13 @@ void NodeViewToolBar::changeEvent(QEvent *e) void NodeViewToolBar::Retranslate() { add_node_btn_->setToolTip(tr("Add Node")); - minimap_btn_->setText(tr("Mini-Map")); minimap_btn_->setToolTip(tr("Toggle Mini-Map")); } void NodeViewToolBar::UpdateIcons() { add_node_btn_->setIcon(icon::Add); + minimap_btn_->setIcon(icon::MiniMap); } } diff --git a/app/widget/nodeview/nodeviewundo.cpp b/app/widget/nodeview/nodeviewundo.cpp index 0197dcf94..3b43a7b9a 100644 --- a/app/widget/nodeview/nodeviewundo.cpp +++ b/app/widget/nodeview/nodeviewundo.cpp @@ -171,4 +171,26 @@ Project *NodeRenameCommand::GetRelevantProject() const return nodes_.isEmpty() ? nullptr : nodes_.first()->project(); } +NodeOverrideColorCommand::NodeOverrideColorCommand(Node *node, int index) : + node_(node), + new_index_(index) +{ +} + +Project *NodeOverrideColorCommand::GetRelevantProject() const +{ + return node_->project(); +} + +void NodeOverrideColorCommand::redo() +{ + old_index_ = node_->GetOverrideColor(); + node_->SetOverrideColor(new_index_); +} + +void NodeOverrideColorCommand::undo() +{ + node_->SetOverrideColor(old_index_); +} + } diff --git a/app/widget/nodeview/nodeviewundo.h b/app/widget/nodeview/nodeviewundo.h index fcf23ae92..f678007b1 100644 --- a/app/widget/nodeview/nodeviewundo.h +++ b/app/widget/nodeview/nodeviewundo.h @@ -342,6 +342,27 @@ private: }; +class NodeOverrideColorCommand : public UndoCommand +{ +public: + NodeOverrideColorCommand(Node *node, int index); + + virtual Project * GetRelevantProject() const override; + +protected: + virtual void redo() override; + + virtual void undo() override; + +private: + Node *node_; + + int old_index_; + + int new_index_; + +}; + } #endif // NODEVIEWUNDO_H diff --git a/app/widget/timebased/timebasedview.cpp b/app/widget/timebased/timebasedview.cpp index 0156771ab..b9ae6b68f 100644 --- a/app/widget/timebased/timebasedview.cpp +++ b/app/widget/timebased/timebasedview.cpp @@ -217,6 +217,8 @@ bool TimeBasedView::PlayheadMove(QMouseEvent *event) rational movement; snap_service_->SnapPoint({mouse_time}, &movement, SnapService::kSnapAll & ~SnapService::kSnapToPlayhead); + + mouse_time += movement; } SetTime(mouse_time); diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index 9c4890a29..862178736 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -720,9 +720,13 @@ void TimelineWidget::ToggleSelectedEnabled() void TimelineWidget::SetColorLabel(int index) { + MultiUndoCommand *command = new MultiUndoCommand(); + foreach (Block* b, selected_blocks_) { - b->SetOverrideColor(index); + command->add_child(new NodeOverrideColorCommand(b, index)); } + + Core::instance()->undo_stack()->push(command); } void TimelineWidget::NudgeLeft() diff --git a/app/widget/timelinewidget/undo/timelineundopointer.cpp b/app/widget/timelinewidget/undo/timelineundopointer.cpp index 0954cd41c..4f5c448b6 100644 --- a/app/widget/timelinewidget/undo/timelineundopointer.cpp +++ b/app/widget/timelinewidget/undo/timelineundopointer.cpp @@ -426,9 +426,7 @@ void TrackPlaceBlockCommand::undo() } t->EndOperation(); - if (ripple_remove_command_) { - t->Node::InvalidateCache(insert_range, Track::kBlockInput); - } + t->Node::InvalidateCache(insert_range, Track::kBlockInput); // Remove tracks if we added them for (int i=add_track_commands_.size()-1; i>=0; i--) { diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index b410557d3..a5845c25c 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -151,6 +151,8 @@ void SeekableWidget::SeekToScreenPoint(int screen) snap_service_->SnapPoint({playhead_time}, &movement, SnapService::kSnapAll & ~SnapService::kSnapToPlayhead); + + playhead_time += movement; } if (playhead_time != GetTime()) { diff --git a/app/window/mainwindow/mainmenu.cpp b/app/window/mainwindow/mainmenu.cpp index d158904dd..8dfc0bcd4 100644 --- a/app/window/mainwindow/mainmenu.cpp +++ b/app/window/mainwindow/mainmenu.cpp @@ -50,24 +50,26 @@ MainMenu::MainMenu(MainWindow *parent) : file_menu_ = new Menu(this, this, &MainMenu::FileMenuAboutToShow); file_new_menu_ = new Menu(file_menu_); MenuShared::instance()->AddItemsForNewMenu(file_new_menu_); - file_open_item_ = file_menu_->AddItem("openproj", Core::instance(), &Core::OpenProject, "Ctrl+O"); + file_open_item_ = file_menu_->AddItem("openproj", Core::instance(), &Core::OpenProject, tr("Ctrl+O")); file_open_recent_menu_ = new Menu(file_menu_); file_open_recent_separator_ = file_open_recent_menu_->addSeparator(); file_open_recent_clear_item_ = file_open_recent_menu_->AddItem("clearopenrecent", Core::instance(), &Core::ClearOpenRecentList); - file_save_item_ = file_menu_->AddItem("saveproj", Core::instance(), &Core::SaveActiveProject, "Ctrl+S"); - file_save_as_item_ = file_menu_->AddItem("saveprojas", Core::instance(), &Core::SaveActiveProjectAs, "Ctrl+Shift+S"); + file_save_item_ = file_menu_->AddItem("saveproj", Core::instance(), &Core::SaveActiveProject, tr("Ctrl+S")); + file_save_as_item_ = file_menu_->AddItem("saveprojas", Core::instance(), &Core::SaveActiveProjectAs, tr("Ctrl+Shift+S")); file_save_all_item_ = file_menu_->AddItem("saveallproj", Core::instance(), &Core::SaveAllProjects); file_menu_->addSeparator(); - file_import_item_ = file_menu_->AddItem("import", Core::instance(), &Core::DialogImportShow, "Ctrl+I"); + file_revert_item_ = file_menu_->AddItem("revert", Core::instance(), &Core::RevertActiveProject, tr("F12")); + file_menu_->addSeparator(); + file_import_item_ = file_menu_->AddItem("import", Core::instance(), &Core::DialogImportShow, tr("Ctrl+I")); file_menu_->addSeparator(); file_export_menu_ = new Menu(file_menu_); - file_export_media_item_ = file_export_menu_->AddItem("export", Core::instance(), &Core::DialogExportShow, "Ctrl+M"); + file_export_media_item_ = file_export_menu_->AddItem("export", Core::instance(), &Core::DialogExportShow, tr("Ctrl+M")); file_menu_->addSeparator(); file_close_project_item_ = file_menu_->AddItem("closeproj", Core::instance(), &Core::CloseActiveProject); file_close_all_projects_item_ = file_menu_->AddItem("closeallproj", Core::instance(), static_cast(&Core::CloseAllProjects)); file_close_all_except_item_ = file_menu_->AddItem("closeallexcept", Core::instance(), &Core::CloseAllExceptActiveProject); file_menu_->addSeparator(); - file_exit_item_ = file_menu_->AddItem("exit", parent, &MainWindow::close, "Ctrl+Q"); + file_exit_item_ = file_menu_->AddItem("exit", parent, &MainWindow::close); // // EDIT MENU @@ -78,10 +80,10 @@ MainMenu::MainMenu(MainWindow *parent) : connect(edit_menu_, &Menu::aboutToHide, this, &MainMenu::EditMenuAboutToHide); edit_undo_item_ = Core::instance()->undo_stack()->GetUndoAction(); - Menu::ConformItem(edit_undo_item_, "undo", "Ctrl+Z"); + Menu::ConformItem(edit_undo_item_, "undo", tr("Ctrl+Z")); edit_menu_->addAction(edit_undo_item_); edit_redo_item_ = Core::instance()->undo_stack()->GetRedoAction(); - Menu::ConformItem(edit_redo_item_, "redo", "Ctrl+Shift+Z"); + Menu::ConformItem(edit_redo_item_, "redo", tr("Ctrl+Shift+Z")); edit_menu_->addAction(edit_redo_item_); edit_menu_->addSeparator(); @@ -90,49 +92,49 @@ MainMenu::MainMenu(MainWindow *parent) : // Create "alternate delete" action so we can pick up backspace as well as delete while still // keeping them configurable edit_delete2_item_ = new QAction(); - Menu::ConformItem(edit_delete2_item_, "delete2", MenuShared::instance(), &MenuShared::DeleteSelectedTriggered, "Backspace"); + Menu::ConformItem(edit_delete2_item_, "delete2", MenuShared::instance(), &MenuShared::DeleteSelectedTriggered, tr("Backspace")); auto actions = edit_menu_->actions(); edit_menu_->insertAction(actions.at(actions.indexOf(MenuShared::instance()->edit_delete_item()) + 1), edit_delete2_item_); } edit_menu_->addSeparator(); - edit_select_all_item_ = edit_menu_->AddItem("selectall", this, &MainMenu::SelectAllTriggered, "Ctrl+A"); - edit_deselect_all_item_ = edit_menu_->AddItem("deselectall", this, &MainMenu::DeselectAllTriggered, "Ctrl+Shift+A"); + edit_select_all_item_ = edit_menu_->AddItem("selectall", this, &MainMenu::SelectAllTriggered, tr("Ctrl+A")); + edit_deselect_all_item_ = edit_menu_->AddItem("deselectall", this, &MainMenu::DeselectAllTriggered, tr("Ctrl+Shift+A")); edit_menu_->addSeparator(); MenuShared::instance()->AddItemsForClipEditMenu(edit_menu_); edit_menu_->addSeparator(); - edit_insert_item_ = edit_menu_->AddItem("insert", this, &MainMenu::InsertTriggered, ","); - edit_overwrite_item_ = edit_menu_->AddItem("overwrite", this, &MainMenu::OverwriteTriggered, "."); + edit_insert_item_ = edit_menu_->AddItem("insert", this, &MainMenu::InsertTriggered, tr(",")); + edit_overwrite_item_ = edit_menu_->AddItem("overwrite", this, &MainMenu::OverwriteTriggered, tr(".")); edit_menu_->addSeparator(); - edit_ripple_to_in_item_ = edit_menu_->AddItem("rippletoin", this, &MainMenu::RippleToInTriggered, "Q"); - edit_ripple_to_out_item_ = edit_menu_->AddItem("rippletoout", this, &MainMenu::RippleToOutTriggered, "W"); - edit_edit_to_in_item_ = edit_menu_->AddItem("edittoin", this, &MainMenu::EditToInTriggered, "Ctrl+Alt+Q"); - edit_edit_to_out_item_ = edit_menu_->AddItem("edittoout", this, &MainMenu::EditToOutTriggered, "Ctrl+Alt+W"); + edit_ripple_to_in_item_ = edit_menu_->AddItem("rippletoin", this, &MainMenu::RippleToInTriggered, tr("Q")); + edit_ripple_to_out_item_ = edit_menu_->AddItem("rippletoout", this, &MainMenu::RippleToOutTriggered, tr("W")); + edit_edit_to_in_item_ = edit_menu_->AddItem("edittoin", this, &MainMenu::EditToInTriggered, tr("Ctrl+Alt+Q")); + edit_edit_to_out_item_ = edit_menu_->AddItem("edittoout", this, &MainMenu::EditToOutTriggered, tr("Ctrl+Alt+W")); edit_menu_->addSeparator(); - edit_nudge_left_item_ = edit_menu_->AddItem("nudgeleft", this, &MainMenu::NudgeLeftTriggered, "Alt+Left"); - edit_nudge_right_item_ = edit_menu_->AddItem("nudgeright", this, &MainMenu::NudgeRightTriggered, "Alt+Right"); - edit_move_in_to_playhead_item_ = edit_menu_->AddItem("moveintoplayhead", this, &MainMenu::MoveInToPlayheadTriggered, "["); - edit_move_out_to_playhead_item_ = edit_menu_->AddItem("moveouttoplayhead", this, &MainMenu::MoveOutToPlayheadTriggered, "]"); + edit_nudge_left_item_ = edit_menu_->AddItem("nudgeleft", this, &MainMenu::NudgeLeftTriggered, tr("Alt+Left")); + edit_nudge_right_item_ = edit_menu_->AddItem("nudgeright", this, &MainMenu::NudgeRightTriggered, tr("Alt+Right")); + edit_move_in_to_playhead_item_ = edit_menu_->AddItem("moveintoplayhead", this, &MainMenu::MoveInToPlayheadTriggered, tr("[")); + edit_move_out_to_playhead_item_ = edit_menu_->AddItem("moveouttoplayhead", this, &MainMenu::MoveOutToPlayheadTriggered, tr("]")); edit_menu_->addSeparator(); MenuShared::instance()->AddItemsForInOutMenu(edit_menu_); - edit_delete_inout_item_ = edit_menu_->AddItem("deleteinout", this, &MainMenu::DeleteInOutTriggered, ";"); - edit_ripple_delete_inout_item_ = edit_menu_->AddItem("rippledeleteinout", this, &MainMenu::RippleDeleteInOutTriggered, "'"); + edit_delete_inout_item_ = edit_menu_->AddItem("deleteinout", this, &MainMenu::DeleteInOutTriggered, tr(";")); + edit_ripple_delete_inout_item_ = edit_menu_->AddItem("rippledeleteinout", this, &MainMenu::RippleDeleteInOutTriggered, tr("'")); edit_menu_->addSeparator(); - edit_set_marker_item_ = edit_menu_->AddItem("marker", this, &MainMenu::SetMarkerTriggered, "M"); + edit_set_marker_item_ = edit_menu_->AddItem("marker", this, &MainMenu::SetMarkerTriggered, tr("M")); // // VIEW MENU // view_menu_ = new Menu(this, this, &MainMenu::ViewMenuAboutToShow); - view_zoom_in_item_ = view_menu_->AddItem("zoomin", this, &MainMenu::ZoomInTriggered, "="); - view_zoom_out_item_ = view_menu_->AddItem("zoomout", this, &MainMenu::ZoomOutTriggered, "-"); - view_increase_track_height_item_ = view_menu_->AddItem("vzoomin", this, &MainMenu::IncreaseTrackHeightTriggered, "Ctrl+="); - view_decrease_track_height_item_ = view_menu_->AddItem("vzoomout", this, &MainMenu::DecreaseTrackHeightTriggered, "Ctrl+-"); - view_show_all_item_ = view_menu_->AddItem("showall", this, &MainMenu::ToggleShowAllTriggered, "\\"); + view_zoom_in_item_ = view_menu_->AddItem("zoomin", this, &MainMenu::ZoomInTriggered, tr("=")); + view_zoom_out_item_ = view_menu_->AddItem("zoomout", this, &MainMenu::ZoomOutTriggered, tr("-")); + view_increase_track_height_item_ = view_menu_->AddItem("vzoomin", this, &MainMenu::IncreaseTrackHeightTriggered, tr("Ctrl+=")); + view_decrease_track_height_item_ = view_menu_->AddItem("vzoomout", this, &MainMenu::DecreaseTrackHeightTriggered, tr("Ctrl+-")); + view_show_all_item_ = view_menu_->AddItem("showall", this, &MainMenu::ToggleShowAllTriggered, tr("\\")); view_show_all_item_->setCheckable(true); view_menu_->addSeparator(); - view_full_screen_item_ = view_menu_->AddItem("fullscreen", parent, &MainWindow::SetFullscreen, "F11"); + view_full_screen_item_ = view_menu_->AddItem("fullscreen", parent, &MainWindow::SetFullscreen, tr("F11")); view_full_screen_item_->setCheckable(true); view_full_screen_viewer_item_ = view_menu_->AddItem("fullscreenviewer", this, &MainMenu::FullScreenViewerTriggered); @@ -141,28 +143,28 @@ MainMenu::MainMenu(MainWindow *parent) : // PLAYBACK MENU // playback_menu_ = new Menu(this, this, &MainMenu::PlaybackMenuAboutToShow); - playback_gotostart_item_ = playback_menu_->AddItem("gotostart", this, &MainMenu::GoToStartTriggered, "Home"); - playback_prevframe_item_ = playback_menu_->AddItem("prevframe", this, &MainMenu::PrevFrameTriggered, "Left"); - playback_playpause_item_ = playback_menu_->AddItem("playpause", this, &MainMenu::PlayPauseTriggered, "Space"); - playback_playinout_item_ = playback_menu_->AddItem("playintoout", this, &MainMenu::PlayInToOutTriggered, "Shift+Space"); - playback_nextframe_item_ = playback_menu_->AddItem("nextframe", this, &MainMenu::NextFrameTriggered, "Right"); - playback_gotoend_item_ = playback_menu_->AddItem("gotoend", this, &MainMenu::GoToEndTriggered, "End"); + playback_gotostart_item_ = playback_menu_->AddItem("gotostart", this, &MainMenu::GoToStartTriggered, tr("Home")); + playback_prevframe_item_ = playback_menu_->AddItem("prevframe", this, &MainMenu::PrevFrameTriggered, tr("Left")); + playback_playpause_item_ = playback_menu_->AddItem("playpause", this, &MainMenu::PlayPauseTriggered, tr("Space")); + playback_playinout_item_ = playback_menu_->AddItem("playintoout", this, &MainMenu::PlayInToOutTriggered, tr("Shift+Space")); + playback_nextframe_item_ = playback_menu_->AddItem("nextframe", this, &MainMenu::NextFrameTriggered, tr("Right")); + playback_gotoend_item_ = playback_menu_->AddItem("gotoend", this, &MainMenu::GoToEndTriggered, tr("End")); playback_menu_->addSeparator(); - playback_prevcut_item_ = playback_menu_->AddItem("prevcut", this, &MainMenu::GoToPrevCutTriggered, "Up"); - playback_nextcut_item_ = playback_menu_->AddItem("nextcut", this, &MainMenu::GoToNextCutTriggered, "Down"); + playback_prevcut_item_ = playback_menu_->AddItem("prevcut", this, &MainMenu::GoToPrevCutTriggered, tr("Up")); + playback_nextcut_item_ = playback_menu_->AddItem("nextcut", this, &MainMenu::GoToNextCutTriggered, tr("Down")); playback_menu_->addSeparator(); - playback_gotoin_item_ = playback_menu_->AddItem("gotoin", this, &MainMenu::GoToInTriggered, "Shift+I"); - playback_gotoout_item_ = playback_menu_->AddItem("gotoout", this, &MainMenu::GoToOutTriggered, "Shift+O"); + playback_gotoin_item_ = playback_menu_->AddItem("gotoin", this, &MainMenu::GoToInTriggered, tr("Shift+I")); + playback_gotoout_item_ = playback_menu_->AddItem("gotoout", this, &MainMenu::GoToOutTriggered, tr("Shift+O")); playback_menu_->addSeparator(); - playback_shuttleleft_item_ = playback_menu_->AddItem("decspeed", this, &MainMenu::ShuttleLeftTriggered, "J"); - playback_shuttlestop_item_ = playback_menu_->AddItem("pause", this, &MainMenu::ShuttleStopTriggered, "K"); - playback_shuttleright_item_ = playback_menu_->AddItem("incspeed", this, &MainMenu::ShuttleRightTriggered, "L"); + playback_shuttleleft_item_ = playback_menu_->AddItem("decspeed", this, &MainMenu::ShuttleLeftTriggered, tr("J")); + playback_shuttlestop_item_ = playback_menu_->AddItem("pause", this, &MainMenu::ShuttleStopTriggered, tr("K")); + playback_shuttleright_item_ = playback_menu_->AddItem("incspeed", this, &MainMenu::ShuttleRightTriggered, tr("L")); playback_menu_->addSeparator(); @@ -186,7 +188,7 @@ MainMenu::MainMenu(MainWindow *parent) : // window_menu_ = new Menu(this, this, &MainMenu::WindowMenuAboutToShow); window_menu_separator_ = window_menu_->addSeparator(); - window_maximize_panel_item_ = window_menu_->AddItem("maximizepanel", parent, &MainWindow::ToggleMaximizedPanel, "`"); + window_maximize_panel_item_ = window_menu_->AddItem("maximizepanel", parent, &MainWindow::ToggleMaximizedPanel, tr("`")); window_lock_layout_item_ = window_menu_->AddItem("lockpanels", PanelManager::instance(), &PanelManager::SetPanelsLocked); window_lock_layout_item_->setCheckable(true); window_menu_->addSeparator(); @@ -200,71 +202,81 @@ MainMenu::MainMenu(MainWindow *parent) : tools_group_ = new QActionGroup(this); - tools_pointer_item_ = tools_menu_->AddItem("pointertool", this, &MainMenu::ToolItemTriggered, "V"); + tools_pointer_item_ = tools_menu_->AddItem("pointertool", this, &MainMenu::ToolItemTriggered, tr("V")); tools_pointer_item_->setCheckable(true); tools_pointer_item_->setData(Tool::kPointer); tools_group_->addAction(tools_pointer_item_); - tools_edit_item_ = tools_menu_->AddItem("edittool", this, &MainMenu::ToolItemTriggered, "X"); + tools_edit_item_ = tools_menu_->AddItem("edittool", this, &MainMenu::ToolItemTriggered, tr("X")); tools_edit_item_->setCheckable(true); tools_edit_item_->setData(Tool::kEdit); tools_group_->addAction(tools_edit_item_); - tools_ripple_item_ = tools_menu_->AddItem("rippletool", this, &MainMenu::ToolItemTriggered, "B"); + tools_ripple_item_ = tools_menu_->AddItem("rippletool", this, &MainMenu::ToolItemTriggered, tr("B")); tools_ripple_item_->setCheckable(true); tools_ripple_item_->setData(Tool::kRipple); tools_group_->addAction(tools_ripple_item_); - tools_rolling_item_ = tools_menu_->AddItem("rollingtool", this, &MainMenu::ToolItemTriggered, "N"); + tools_rolling_item_ = tools_menu_->AddItem("rollingtool", this, &MainMenu::ToolItemTriggered, tr("N")); tools_rolling_item_->setCheckable(true); tools_rolling_item_->setData(Tool::kRolling); tools_group_->addAction(tools_rolling_item_); - tools_razor_item_ = tools_menu_->AddItem("razortool", this, &MainMenu::ToolItemTriggered, "C"); + tools_razor_item_ = tools_menu_->AddItem("razortool", this, &MainMenu::ToolItemTriggered, tr("C")); tools_razor_item_->setCheckable(true); tools_razor_item_->setData(Tool::kRazor); tools_group_->addAction(tools_razor_item_); - tools_slip_item_ = tools_menu_->AddItem("sliptool", this, &MainMenu::ToolItemTriggered, "Y"); + tools_slip_item_ = tools_menu_->AddItem("sliptool", this, &MainMenu::ToolItemTriggered, tr("Y")); tools_slip_item_->setCheckable(true); tools_slip_item_->setData(Tool::kSlip); tools_group_->addAction(tools_slip_item_); - tools_slide_item_ = tools_menu_->AddItem("slidetool", this, &MainMenu::ToolItemTriggered, "U"); + tools_slide_item_ = tools_menu_->AddItem("slidetool", this, &MainMenu::ToolItemTriggered, tr("U")); tools_slide_item_->setCheckable(true); tools_slide_item_->setData(Tool::kSlide); tools_group_->addAction(tools_slide_item_); - tools_hand_item_ = tools_menu_->AddItem("handtool", this, &MainMenu::ToolItemTriggered, "H"); + tools_hand_item_ = tools_menu_->AddItem("handtool", this, &MainMenu::ToolItemTriggered, tr("H")); tools_hand_item_->setCheckable(true); tools_hand_item_->setData(Tool::kHand); tools_group_->addAction(tools_hand_item_); - tools_zoom_item_ = tools_menu_->AddItem("zoomtool", this, &MainMenu::ToolItemTriggered, "Z"); + tools_zoom_item_ = tools_menu_->AddItem("zoomtool", this, &MainMenu::ToolItemTriggered, tr("Z")); tools_zoom_item_->setCheckable(true); tools_zoom_item_->setData(Tool::kZoom); tools_group_->addAction(tools_zoom_item_); - tools_transition_item_ = tools_menu_->AddItem("transitiontool", this, &MainMenu::ToolItemTriggered, "T"); + tools_transition_item_ = tools_menu_->AddItem("transitiontool", this, &MainMenu::ToolItemTriggered, tr("T")); tools_transition_item_->setCheckable(true); tools_transition_item_->setData(Tool::kTransition); tools_group_->addAction(tools_transition_item_); + tools_add_item_ = tools_menu_->AddItem("addtool", this, &MainMenu::ToolItemTriggered, tr("A")); + tools_add_item_->setCheckable(true); + tools_add_item_->setData(Tool::kAdd); + tools_group_->addAction(tools_add_item_); + + tools_record_item_ = tools_menu_->AddItem("recordtool", this, &MainMenu::ToolItemTriggered, tr("R")); + tools_record_item_->setCheckable(true); + tools_record_item_->setData(Tool::kRecord); + tools_group_->addAction(tools_record_item_); + tools_menu_->addSeparator(); - tools_snapping_item_ = tools_menu_->AddItem("snapping", Core::instance(), &Core::SetSnapping, "S"); + tools_snapping_item_ = tools_menu_->AddItem("snapping", Core::instance(), &Core::SetSnapping, tr("S")); tools_snapping_item_->setCheckable(true); tools_snapping_item_->setChecked(Core::instance()->snapping()); tools_menu_->addSeparator(); - tools_preferences_item_ = tools_menu_->AddItem("prefs", Core::instance(), &Core::DialogPreferencesShow, "Ctrl+,"); + tools_preferences_item_ = tools_menu_->AddItem("prefs", Core::instance(), &Core::DialogPreferencesShow, tr("Ctrl+,")); // // HELP MENU // help_menu_ = new Menu(this); - help_action_search_item_ = help_menu_->AddItem("actionsearch", this, &MainMenu::ActionSearchTriggered, "/"); + help_action_search_item_ = help_menu_->AddItem("actionsearch", this, &MainMenu::ActionSearchTriggered, tr("/")); help_menu_->addSeparator(); help_feedback_item_ = help_menu_->AddItem("feedback", this, &MainMenu::HelpFeedbackTriggered); help_menu_->addSeparator(); @@ -669,6 +681,7 @@ void MainMenu::Retranslate() file_open_recent_menu_->setTitle(tr("Open &Recent")); file_open_recent_clear_item_->setText(tr("&Clear Recent List")); file_save_all_item_->setText(tr("Sa&ve All Projects")); + file_revert_item_->setText(tr("Revert")); file_import_item_->setText(tr("&Import...")); file_export_menu_->setTitle(tr("&Export")); file_export_media_item_->setText(tr("&Media...")); @@ -748,6 +761,8 @@ void MainMenu::Retranslate() tools_hand_item_->setText(tr("Hand Tool")); tools_zoom_item_->setText(tr("Zoom Tool")); tools_transition_item_->setText(tr("Transition Tool")); + tools_add_item_->setText(tr("Add Tool")); + tools_record_item_->setText(tr("Record Tool")); tools_snapping_item_->setText(tr("Enable Snapping")); tools_preferences_item_->setText(tr("Preferences")); diff --git a/app/window/mainwindow/mainmenu.h b/app/window/mainwindow/mainmenu.h index 4cedd3522..ff038e64a 100644 --- a/app/window/mainwindow/mainmenu.h +++ b/app/window/mainwindow/mainmenu.h @@ -203,6 +203,7 @@ private: QAction* file_save_item_; QAction* file_save_as_item_; QAction* file_save_all_item_; + QAction* file_revert_item_; QAction* file_import_item_; Menu* file_export_menu_; QAction* file_export_media_item_; @@ -279,6 +280,8 @@ private: QAction* tools_hand_item_; QAction* tools_zoom_item_; QAction* tools_transition_item_; + QAction* tools_add_item_; + QAction* tools_record_item_; QAction* tools_snapping_item_; QAction* tools_preferences_item_; diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index fec8e5453..612cc68cd 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -677,7 +677,7 @@ void SaveCustomShortcutsInternal(QMenu* menu, QMap* shortcuts) if (a->menu()) { SaveCustomShortcutsInternal(a->menu(), shortcuts); } else if (!a->isSeparator()) { - QString default_shortcut = a->property("keydefault").toString(); + QString default_shortcut = a->property("keydefault").value().toString(); QString current_shortcut = a->shortcut().toString(); if (current_shortcut != default_shortcut) { QString action_id = a->property("id").toString();