diff --git a/.clang-format b/.clang-format index f24513acc97d596d6d78f3dbf9b8e62d579f41b4..57f14efdde6e9fa0a063289f44768397e65cce3f 100644 --- a/.clang-format +++ b/.clang-format @@ -63,6 +63,7 @@ ForEachMacros: - 'FOR_EVER_IF' - 'FOR_EVER_UNTIL' - 'FOR_EACH_FLAT_LIST_ITEM' + - 'parallel_for' IncludeCategories: - Regex: '.*' diff --git a/CMakeLists.txt b/CMakeLists.txt index bc73970b37abf6bfa8ceca76ddf5616be52aa301..161c798e44b9effe7934150ca97fb1dda939f146 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,49 +1,36 @@ cmake_minimum_required(VERSION 3.5) +# Alpho Vivy, CXX only project(Vivy VERSION 0.1 LANGUAGES CXX) - -set(CMAKE_INCLUDE_CURRENT_DIR ON) cmake_policy(SET CMP0100 NEW) # Let cmake use moc and uic for .hh files +cmake_policy(SET CMP0009 NEW) # Do not follow symlinks with GLOB_RECURSE +# For Qt set(CMAKE_AUTOUIC ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) +# C++20, at least we try set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) +# Pthread ftw set(THREADS_PREFER_PTHREAD_FLAG ON) -# Find all dependencies -find_package(QT NAMES Qt6 Qt5 COMPONENTS Widgets REQUIRED) +# Find Qt dependencies +find_package(QT NAMES Qt6 Qt5 COMPONENTS Widgets REQUIRED) find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets REQUIRED) -find_library(AVCODEC_LIBRARY avcodec 4.0 REQUIRED) -find_library(AVUTIL_LIBRARY avutil 4.0 REQUIRED) -find_library(SWRESAMPLE_LIBRARY swresample REQUIRED) -find_library(AVFORMAT_LIBRARY avformat REQUIRED) -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fopenmp") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fopenmp") +# Find others dependencies +find_library(AVCODEC_LIBRARY avcodec 4.0 REQUIRED) +find_library(AVUTIL_LIBRARY avutil 4.0 REQUIRED) +find_library(SWRESAMPLE_LIBRARY swresample REQUIRED) +find_library(AVFORMAT_LIBRARY avformat REQUIRED) # Grab all files -file(GLOB Vivy_SRC - "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/Document/*.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/Ass/*.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/Lib/*.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/UI/*.cc" -) -file(GLOB Vivy_INC - "${CMAKE_CURRENT_SOURCE_DIR}/src/*.hh" - "${CMAKE_CURRENT_SOURCE_DIR}/src/Document/*.hh" - "${CMAKE_CURRENT_SOURCE_DIR}/src/Ass/*.hh" - "${CMAKE_CURRENT_SOURCE_DIR}/src/Lib/*.hh" - "${CMAKE_CURRENT_SOURCE_DIR}/src/UI/*.hh" -) -set(PROJECT_SOURCES - ${Vivy_SRC} - ${Vivy_INC} -) +file(GLOB_RECURSE Vivy_SRC CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cc") +file(GLOB_RECURSE Vivy_INC CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/*.hh") +set(PROJECT_SOURCES ${Vivy_SRC} ${Vivy_INC}) # Add the Vivy executable if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) @@ -69,41 +56,36 @@ target_link_libraries(Vivy PRIVATE ${AVFORMAT_LIBRARY}) # Headers related things target_include_directories(Vivy PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/inc) target_precompile_headers(Vivy PRIVATE - # ASS headers - "${CMAKE_CURRENT_SOURCE_DIR}/src/Ass/Ass.hh" - "${CMAKE_CURRENT_SOURCE_DIR}/src/Ass/Line.hh" - "${CMAKE_CURRENT_SOURCE_DIR}/src/Ass/Syl.hh" - "${CMAKE_CURRENT_SOURCE_DIR}/src/Ass/Style.hh" - "${CMAKE_CURRENT_SOURCE_DIR}/src/Ass/AssFactory.hh" - "${CMAKE_CURRENT_SOURCE_DIR}/src/Ass/Char.hh" - - # Libs - "${CMAKE_CURRENT_SOURCE_DIR}/src/Lib/Utils.hh" - "${CMAKE_CURRENT_SOURCE_DIR}/src/Lib/Audio.hh" + # Private Vivy headers + ${Vivy_INC} - # Document headers - "${CMAKE_CURRENT_SOURCE_DIR}/src/Document/VivyDocument.hh" - "${CMAKE_CURRENT_SOURCE_DIR}/src/Document/VivyDocumentStore.hh" - "${CMAKE_CURRENT_SOURCE_DIR}/src/Document/CRTPSubDocument.hh" + # Qt headers + <QString> + <QList> + <QMap> + <QWidget> + <QIcon> ) # More options and warnings target_compile_options(Vivy PRIVATE -Wall -Wextra -Wshadow -pedantic - -Wcast-align -Wconversion -Wsign-conversion + -Wcast-align -Wconversion -Wsign-conversion -Wunused-variable -Wmisleading-indentation -Wnull-dereference -Wdouble-promotion -Wformat=2 - $<$<COMPILE_LANGUAGE:CXX>: - -Woverloaded-virtual - -Wnon-virtual-dtor - > + -Woverloaded-virtual -Wnon-virtual-dtor ) +# Some compiler specific warnings and options if (${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang") target_compile_options(Vivy PRIVATE - -Wno-unused-private-field # Skip the unused private fields for now + -Wno-unused-private-field # Skip the unused private fields for now + -fopenmp # We do OpenMP here ) + target_link_libraries(Vivy PRIVATE -fopenmp) elseif (${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU") + target_compile_options(Vivy PRIVATE -fopenmp) + target_link_libraries(Vivy PRIVATE -fopenmp) endif() set_target_properties(Vivy PROPERTIES diff --git a/src/Ass/Ass.hh b/src/Lib/Ass/Ass.hh similarity index 100% rename from src/Ass/Ass.hh rename to src/Lib/Ass/Ass.hh diff --git a/src/Ass/AssFactory.cc b/src/Lib/Ass/AssFactory.cc similarity index 100% rename from src/Ass/AssFactory.cc rename to src/Lib/Ass/AssFactory.cc diff --git a/src/Ass/AssFactory.hh b/src/Lib/Ass/AssFactory.hh similarity index 98% rename from src/Ass/AssFactory.hh rename to src/Lib/Ass/AssFactory.hh index e5343ec97deae93cbd864fa1844ca2fb72512b4a..48f849b01cc6ef60afbaa5b4d18627b59d24f439 100644 --- a/src/Ass/AssFactory.hh +++ b/src/Lib/Ass/AssFactory.hh @@ -1,7 +1,7 @@ #ifndef VIVY_ASS_FACTORY_H #define VIVY_ASS_FACTORY_H -#include "../Lib/Utils.hh" +#include "../Utils.hh" #include "Style.hh" #include "Line.hh" diff --git a/src/Ass/Char.cc b/src/Lib/Ass/Char.cc similarity index 100% rename from src/Ass/Char.cc rename to src/Lib/Ass/Char.cc diff --git a/src/Ass/Char.hh b/src/Lib/Ass/Char.hh similarity index 100% rename from src/Ass/Char.hh rename to src/Lib/Ass/Char.hh diff --git a/src/Ass/Line.cc b/src/Lib/Ass/Line.cc similarity index 100% rename from src/Ass/Line.cc rename to src/Lib/Ass/Line.cc diff --git a/src/Ass/Line.hh b/src/Lib/Ass/Line.hh similarity index 100% rename from src/Ass/Line.hh rename to src/Lib/Ass/Line.hh diff --git a/src/Ass/Style.cc b/src/Lib/Ass/Style.cc similarity index 100% rename from src/Ass/Style.cc rename to src/Lib/Ass/Style.cc diff --git a/src/Ass/Style.hh b/src/Lib/Ass/Style.hh similarity index 100% rename from src/Ass/Style.hh rename to src/Lib/Ass/Style.hh diff --git a/src/Ass/StyleProperties.hh b/src/Lib/Ass/StyleProperties.hh similarity index 100% rename from src/Ass/StyleProperties.hh rename to src/Lib/Ass/StyleProperties.hh diff --git a/src/Ass/Syl.cc b/src/Lib/Ass/Syl.cc similarity index 100% rename from src/Ass/Syl.cc rename to src/Lib/Ass/Syl.cc diff --git a/src/Ass/Syl.hh b/src/Lib/Ass/Syl.hh similarity index 100% rename from src/Ass/Syl.hh rename to src/Lib/Ass/Syl.hh diff --git a/src/Lib/Audio.cc b/src/Lib/Audio.cc index 65c1cdc856d01a6d6a8023dc87ea1925fd751c78..ec20ea67aa8af5be2de9650007cabfa7976cf1e9 100644 --- a/src/Lib/Audio.cc +++ b/src/Lib/Audio.cc @@ -97,12 +97,15 @@ AudioContext::getProperties() const noexcept QJsonObject self; QJsonArray streams; + QFileInfo file(filePath); + for (const auto &audioStreamPtr : audioStreams) { streams.append(audioStreamPtr->getProperties()); } - self.insert("File path", filePath); self.insert("Streams", streams); + self.insert("File path", filePath); + self.insert("Base name", file.baseName()); ret.setObject(self); return ret; diff --git a/src/Lib/Document/AbstractDocument.hh b/src/Lib/Document/AbstractDocument.hh new file mode 100644 index 0000000000000000000000000000000000000000..e6295263f040572bc680b5c3a97f6244da07e11d --- /dev/null +++ b/src/Lib/Document/AbstractDocument.hh @@ -0,0 +1,21 @@ +#ifndef VIVY_ABSTRACT_DOCUMENT_H +#define VIVY_ABSTRACT_DOCUMENT_H + +#ifndef __cplusplus +#error "This is a C++ header" +#endif + +namespace Vivy +{ +class AbstractDocument { +protected: + AbstractDocument() = default; + virtual ~AbstractDocument() noexcept = default; + +public: + virtual bool rename(const QString &) noexcept = 0; + virtual QString getName() const noexcept = 0; +}; +} + +#endif // VIVY_ABSTRACT_DOCUMENT_H diff --git a/src/Document/CRTPSubDocument.cc b/src/Lib/Document/CRTPSubDocument.cc similarity index 62% rename from src/Document/CRTPSubDocument.cc rename to src/Lib/Document/CRTPSubDocument.cc index 85983b5e5389a04d152ba1fe50efd10d09e387a7..d6ac414b607200268e9d6a2b7a9755fd6d59a6ce 100644 --- a/src/Document/CRTPSubDocument.cc +++ b/src/Lib/Document/CRTPSubDocument.cc @@ -1,5 +1,8 @@ #include "CRTPSubDocument.hh" +#include <QJsonObject> +#include <QJsonDocument> + using namespace Vivy; // AudioSubDocument implementation @@ -55,6 +58,23 @@ AudioSubDocument::initFromPath(const QString &path) qDebug() << "Audio OK for" << path; } +QString +AudioSubDocument::getElementName() const noexcept +{ + return "AudioSubDocument"; +} + +QJsonDocument +AudioSubDocument::getProperties() const noexcept +{ + QJsonDocument ret; + QJsonObject object; + const QJsonDocument contextDocument = contextPtr->getProperties(); + object.insert("Audio context", contextDocument.object()); + ret.setObject(object); + return ret; +} + // VideoSubDocument implementation // Init a video sub-document from a file @@ -63,7 +83,24 @@ VideoSubDocument::initFromPath(const QString &) { } +QString +VideoSubDocument::getElementName() const noexcept +{ + return "VideoSubDocument"; +} + +QJsonDocument +VideoSubDocument::getProperties() const noexcept +{ + QJsonDocument ret; + QJsonObject object; + ret.setObject(object); + return ret; +} + // AssSubDocument implementation + +// Init a ASS sub-document from a file void AssSubDocument::initFromPath(const QString &path) { @@ -71,3 +108,17 @@ AssSubDocument::initFromPath(const QString &path) factory.getStyles(styles); factory.getLines(lines); } +QString +AssSubDocument::getElementName() const noexcept +{ + return "AssSubDocument"; +} + +QJsonDocument +AssSubDocument::getProperties() const noexcept +{ + QJsonDocument ret; + QJsonObject object; + ret.setObject(object); + return ret; +} diff --git a/src/Document/CRTPSubDocument.hh b/src/Lib/Document/CRTPSubDocument.hh similarity index 88% rename from src/Document/CRTPSubDocument.hh rename to src/Lib/Document/CRTPSubDocument.hh index 29b1c620eceb0e0c442b238a2d43ac37dd67cd13..f89b26f65e235f3c3b3fd72a22510c7afd7cebdd 100644 --- a/src/Document/CRTPSubDocument.hh +++ b/src/Lib/Document/CRTPSubDocument.hh @@ -5,8 +5,8 @@ #error "This is a C++ header" #endif -#include "../Lib/Utils.hh" -#include "../Lib/Audio.hh" +#include "../Utils.hh" +#include "../Audio.hh" #include "../Ass/Ass.hh" #include <QString> #include <memory> @@ -77,6 +77,9 @@ public: AudioContext::StreamPtr getDefaultStream() const noexcept; int getStreamCount() const noexcept; AudioContext::StreamPtr getStream(int index) const noexcept; + + QString getElementName() const noexcept; + QJsonDocument getProperties() const noexcept; }; // Video document @@ -88,6 +91,10 @@ private: explicit VideoSubDocument() noexcept = default; friend CRTPSubDocument<VideoDocumentType, VideoSubDocument>; + +public: + QString getElementName() const noexcept; + QJsonDocument getProperties() const noexcept; }; // ASS document @@ -99,6 +106,10 @@ class AssSubDocument final : public CRTPSubDocument<AssDocumentType, AssSubDocum explicit AssSubDocument() noexcept = default; friend CRTPSubDocument<AssDocumentType, AssSubDocument>; +public: + QString getElementName() const noexcept; + QJsonDocument getProperties() const noexcept; + private: QVector<Ass::StylePtr> styles; QVector<Ass::LinePtr> lines; diff --git a/src/Document/VivyDocument.cc b/src/Lib/Document/VivyDocument.cc similarity index 80% rename from src/Document/VivyDocument.cc rename to src/Lib/Document/VivyDocument.cc index 53dd53d970cfc5c0a9deb365aa9e8380753d957b..cc2f3aaeffc5847e4dd995be1dcb8233665a4964 100644 --- a/src/Document/VivyDocument.cc +++ b/src/Lib/Document/VivyDocument.cc @@ -1,9 +1,11 @@ #include "VivyDocument.hh" -#include "../Lib/Utils.hh" +#include "../Utils.hh" #include <QFileInfo> #include <QString> #include <QStringList> +#include <QJsonArray> +#include <QJsonObject> using namespace Vivy; @@ -23,10 +25,10 @@ bool VivyDocument::loadSubDocument(const QString &name) noexcept { QFileInfo file(name); - Capabilities type; + Capabilities ableType; - if (detectDocumentType(file, &type)) { - switch (type) { + if (detectDocumentType(file, &ableType)) { + switch (ableType) { case Capabilities::AudioAble: qDebug() << "Auto-detect audio document for" << name; setAudioSubDocument(file.absoluteFilePath()); @@ -52,25 +54,25 @@ bool VivyDocument::loadSubDocument(const QString &name, VivyDocument::Capabilities asType) noexcept { QFileInfo file(name); - Capabilities type; - if (!detectDocumentType(file, &type)) { + Capabilities ableType; + if (!detectDocumentType(file, &ableType)) { qCritical() << "Failed to detect type for file " << name; return false; } - if (type == Capabilities::AssAble && asType == Capabilities::AssAble) { + if (ableType == Capabilities::AssAble && asType == Capabilities::AssAble) { qDebug() << "Create an ass subDocument from " << name; setAssSubDocument(file.absoluteFilePath()); } - else if (type == Capabilities::VideoAble && asType == Capabilities::VideoAble) { + else if (ableType == Capabilities::VideoAble && asType == Capabilities::VideoAble) { qDebug() << "Create a video subDocument from " << name; setVideoSubDocument(file.absoluteFilePath()); } else if (const bool requestAudio = (asType == Capabilities::AudioAble); - (type == Capabilities::VideoAble && requestAudio) || - (type == Capabilities::AudioAble && requestAudio)) { + (ableType == Capabilities::VideoAble && requestAudio) || + (ableType == Capabilities::AudioAble && requestAudio)) { qDebug() << "Create an audio subDocument from " << name; setAudioSubDocument(file.absoluteFilePath()); } @@ -79,20 +81,20 @@ VivyDocument::loadSubDocument(const QString &name, VivyDocument::Capabilities as } bool -VivyDocument::detectDocumentType(const QFileInfo &file, Capabilities *type) noexcept +VivyDocument::detectDocumentType(const QFileInfo &file, Capabilities *ableType) noexcept { Vivy::Utils::DocumentType docType; bool rc = Vivy::Utils::detectDocumentType(file, &docType); switch (docType) { case Vivy::Utils::DocumentType::Video: - *type = Capabilities::VideoAble; + *ableType = Capabilities::VideoAble; break; case Vivy::Utils::DocumentType::Audio: - *type = Capabilities::AudioAble; + *ableType = Capabilities::AudioAble; break; case Vivy::Utils::DocumentType::ASS: - *type = Capabilities::AssAble; + *ableType = Capabilities::AssAble; break; default: return false; @@ -240,3 +242,30 @@ VivyDocument::setAssSubDocument(const QString filename) noexcept documentOptions &= ~Options::UntouchedByDefault; } } + +QString +VivyDocument::getElementName() const noexcept +{ + return getName(); +} +QJsonDocument +VivyDocument::getProperties() const noexcept +{ + QJsonDocument ret; + QJsonObject object; + QJsonObject subDocObject; + + subDocObject.insert("Audio sub-document", + audioDocument ? audioDocument->getProperties().object() : QJsonValue()); + subDocObject.insert("Video sub-document", + videoDocument ? videoDocument->getProperties().object() : QJsonValue()); + subDocObject.insert("ASS sub-document", + assDocument ? assDocument->getProperties().object() : QJsonValue()); + + object.insert("Uuid", getUuid().toString()); + object.insert("Name", getName()); + object.insert("Sub documents", subDocObject); + + ret.setObject(object); + return ret; +} diff --git a/src/Document/VivyDocument.hh b/src/Lib/Document/VivyDocument.hh similarity index 75% rename from src/Document/VivyDocument.hh rename to src/Lib/Document/VivyDocument.hh index 4d7fa9e4031e5eb3375068aae948eca0557e95e6..10a8aeb2a11ee319de000f4c20a0541a2e406405 100644 --- a/src/Document/VivyDocument.hh +++ b/src/Lib/Document/VivyDocument.hh @@ -5,17 +5,19 @@ #error "This is a C++ header" #endif -#include "../Lib/Utils.hh" +#include "../Utils.hh" #include "CRTPSubDocument.hh" +#include "AbstractDocument.hh" #include <memory> #include <QString> #include <QDir> #include <QUuid> +#include <QJsonDocument> namespace Vivy { -class VivyDocument { +class VivyDocument final : public AbstractDocument { public: enum Capabilities : quint64 { AudioAble = (1 << 1), @@ -34,9 +36,15 @@ public: : QUuid(QUuid::createUuid()) { } + + QString toString() const noexcept + { + return QUuid::toString(Uuid::WithoutBraces); + }; }; static inline const QString filePrefix{ "vivy" }; + static inline constexpr Utils::DocumentType type = Utils::DocumentType::Vivy; private: /* The document name */ @@ -53,32 +61,37 @@ private: std::shared_ptr<VideoSubDocument> videoDocument{}; std::shared_ptr<AssSubDocument> assDocument{}; - static bool detectDocumentType(const QFileInfo &file, Capabilities *type) noexcept; + static bool detectDocumentType(const QFileInfo &file, Capabilities *) noexcept; public: - /* Create an empty document */ + // Create an empty document explicit VivyDocument(const QString &name, Options opt = NoOption); + ~VivyDocument() noexcept = default; - bool rename(const QString &) noexcept; + bool rename(const QString &) noexcept override; bool loadSubDocument(const QString &) noexcept; bool loadSubDocument(const QString &, Capabilities) noexcept; - /* Getters */ + // Getters std::shared_ptr<AudioSubDocument> getAudioSubDocument() const noexcept; std::shared_ptr<VideoSubDocument> getVideoSubDocument() const noexcept; std::shared_ptr<AssSubDocument> getAssSubDocument() const noexcept; - QString getName() const noexcept; + QString getName() const noexcept override; Uuid getUuid() const noexcept; QString getDocumentCapabilitiesString() const noexcept; bool checkDocumentCapabilities(Capabilities) const noexcept; bool checkDocumentOption(Options) const noexcept; - /* Setters */ + // Setters void setAudioSubDocument(const QString) noexcept; void setVideoSubDocument(const QString) noexcept; void setAssSubDocument(const QString) noexcept; + + // PropertyConstViewable + QString getElementName() const noexcept; + QJsonDocument getProperties() const noexcept; }; } diff --git a/src/Document/VivyDocumentStore.cc b/src/Lib/Document/VivyDocumentStore.cc similarity index 100% rename from src/Document/VivyDocumentStore.cc rename to src/Lib/Document/VivyDocumentStore.cc diff --git a/src/Document/VivyDocumentStore.hh b/src/Lib/Document/VivyDocumentStore.hh similarity index 97% rename from src/Document/VivyDocumentStore.hh rename to src/Lib/Document/VivyDocumentStore.hh index 047a49ab29172b076b88a43af606f681fde0d5fa..f25fd3f7d374840596fa81ee0df07a38ddc4db66 100644 --- a/src/Document/VivyDocumentStore.hh +++ b/src/Lib/Document/VivyDocumentStore.hh @@ -2,7 +2,7 @@ #define VIVY_DOCUMENTSTORE_H #include "VivyDocument.hh" -#include "../Lib/Utils.hh" +#include "../Utils.hh" #include <QMap> #include <QString> diff --git a/src/Lib/Utils.hh b/src/Lib/Utils.hh index 95f8e7488117d1242f52cf8f5e7b6d8bde94d147..eeba9f84c41a63e6bf3eeb23d2db3b4d602f1a4d 100644 --- a/src/Lib/Utils.hh +++ b/src/Lib/Utils.hh @@ -10,8 +10,13 @@ #include <QtGlobal> #include <type_traits> +// Prety define for OpenMP's parallel for loop with indentation not fucked up +// by clang-format. +#define parallel_for \ + _Pragma("omp parallel for") for + +// Don't move this object, create it in one place and never move it again. #define VIVY_UNMOVABLE_OBJECT(classname) \ - /* Don't move this object around */ \ classname(const classname &) = delete; /* Copy */ \ classname(classname &&) = delete; /* Move */ \ classname &operator=(const classname &) = delete; /* Copy assign */ \ @@ -32,8 +37,9 @@ concept PropertyConstViewable = requires(T element) namespace Vivy::Utils { -static const QStringList audioFileSuffix = { "wave", "wav", "ogg", "mp3", "m4a" }; -static const QStringList videoFileSuffix = { "mkv", "mp4" }; +static const QStringList audioFileSuffix = { "wave", "wav", "ogg", "mp3", "m4a", + "opus", "mp2", "aiff", "flac", "alac" }; +static const QStringList videoFileSuffix = { "mkv", "mp4", "mov", "avi", "av1", "m4v", "flv" }; static const QStringList assFileSuffix = { "ass" }; static const QStringList vivyFileSuffix = { "vivy" }; @@ -48,19 +54,32 @@ enum class DocumentType : quint64 { OGG = (1 << 4), MP3 = (1 << 5), M4A = (1 << 6), + OPUS = (1 << 7), + MP2 = (1 << 8), + AIFF = (1 << 9), + FLAC = (1 << 10), + ALAC = (1 << 11), /* Video */ - MKV = (1 << 7), - MP4 = (1 << 8), + MKV = (1 << 12), + MP4 = (1 << 13), + MOV = (1 << 14), + AVI = (1 << 14), + AV1 = (1 << 15), + M4V = (1 << 16), + FLV = (1 << 17), + + /* Vivy script */ + VivyScript = (1 << 18), /* Meta-types */ - Audio = (WAVE | OGG | MP3), - Video = (MKV | MP4), + Audio = (WAVE | OGG | MP3 | M4A | OPUS | MP2 | AIFF | FLAC | ALAC), + Video = (MKV | MP4 | MOV | AVI | AV1 | M4V | FLV), }; template <typename E> constexpr auto -to_underlying(E e) noexcept +toUnderlying(E e) noexcept { return static_cast<std::underlying_type_t<E>>(e); } @@ -72,20 +91,30 @@ namespace Vivy { // Audio document types enum class AudioDocumentType : quint64 { - MP3 = Vivy::Utils::to_underlying(Vivy::Utils::DocumentType::MP3), - OGG = Vivy::Utils::to_underlying(Vivy::Utils::DocumentType::OGG), - M4A = Vivy::Utils::to_underlying(Vivy::Utils::DocumentType::M4A), + MP3 = Utils::toUnderlying(Utils::DocumentType::MP3), + OGG = Utils::toUnderlying(Utils::DocumentType::OGG), + M4A = Utils::toUnderlying(Utils::DocumentType::M4A), + OPUS = Utils::toUnderlying(Utils::DocumentType::OPUS), + MP2 = Utils::toUnderlying(Utils::DocumentType::MP2), + AIFF = Utils::toUnderlying(Utils::DocumentType::AIFF), + FLAC = Utils::toUnderlying(Utils::DocumentType::FLAC), + ALAC = Utils::toUnderlying(Utils::DocumentType::ALAC), }; // Video document types enum class VideoDocumentType : quint64 { - MKV = Vivy::Utils::to_underlying(Vivy::Utils::DocumentType::MKV), - MP4 = Vivy::Utils::to_underlying(Vivy::Utils::DocumentType::MP4), + MKV = Utils::toUnderlying(Utils::DocumentType::MKV), + MP4 = Utils::toUnderlying(Utils::DocumentType::MP4), + MOV = Utils::toUnderlying(Utils::DocumentType::MOV), + AVI = Utils::toUnderlying(Utils::DocumentType::AVI), + AV1 = Utils::toUnderlying(Utils::DocumentType::AV1), + M4V = Utils::toUnderlying(Utils::DocumentType::M4V), + FLV = Utils::toUnderlying(Utils::DocumentType::FLV), }; // Ass document types enum class AssDocumentType : quint64 { - ASS = Vivy::Utils::to_underlying(Vivy::Utils::DocumentType::ASS), + ASS = Utils::toUnderlying(Utils::DocumentType::ASS), }; } diff --git a/src/UI/AbstractDocumentView.cc b/src/UI/AbstractDocumentView.cc new file mode 100644 index 0000000000000000000000000000000000000000..e4b1d3867c0424f2fa5ac98773fd87ea35c49e7c --- /dev/null +++ b/src/UI/AbstractDocumentView.cc @@ -0,0 +1,85 @@ +#include "AbstractDocumentView.hh" + +#include <QtGlobal> +#include <QDockWidget> +#include <QLayoutItem> +#include <QLayout> +#include <QWidget> +#include <functional> + +using namespace Vivy; + +AbstractDocumentView::AbstractDocumentView(AbstractDocumentView::Type type, + QWidget *parent) noexcept + : QMainWindow(parent) + , documentType(type) +{ + setStatusBar(nullptr); + setMenuBar(nullptr); + setTabPosition(Qt::TopDockWidgetArea, QTabWidget::North); + setTabPosition(Qt::BottomDockWidgetArea, QTabWidget::South); + setTabPosition(Qt::LeftDockWidgetArea, QTabWidget::West); + setTabPosition(Qt::RightDockWidgetArea, QTabWidget::East); +} + +// A utility function to delete all the child widgets +void +AbstractDocumentView::deleteAllContent() noexcept +{ + // Delete all widgets + if (layout() != NULL) { + QLayoutItem *item; + while ((item = layout()->takeAt(0)) != NULL) { + delete item->widget(); + delete item; + } + delete layout(); + } +} + +// Returns actions that must be placed in the view actions to show/hide some of +// the document's widgets +const QList<QAction *> & +AbstractDocumentView::getViewsActions() const noexcept +{ + return viewsActions; +} + +AbstractDocumentView::Type +AbstractDocumentView::getType() const noexcept +{ + return documentType; +} + +void +AbstractDocumentView::delDockWidget(QDockWidget **dock) noexcept +{ + // Remove the toggle view action + QAction *act = (*dock)->toggleViewAction(); + if (viewsActions.contains(act)) + viewsActions.removeAll(act); + + // Remove the widget + removeDockWidget(*dock); + delete *dock; + *dock = nullptr; + + // Emit the signal + emit viewActionsChanged(); +} +void +AbstractDocumentView::addDockWidget(Qt::DockWidgetArea area, QDockWidget *dock, + Qt::Orientation orientation) noexcept +{ + QMainWindow::addDockWidget(area, dock, orientation); + QAction *act = dock->toggleViewAction(); + if (!viewsActions.contains(act)) + viewsActions.prepend(act); + emit viewActionsChanged(); +} + +bool +AbstractDocumentView::isType(const Utils::DocumentType t) const noexcept +{ + return Utils::toUnderlying(documentType) == Utils::toUnderlying(t); +} diff --git a/src/UI/AbstractDocumentView.hh b/src/UI/AbstractDocumentView.hh new file mode 100644 index 0000000000000000000000000000000000000000..0ce9a6e03a4957ca3e9f8b5e3018bff4f3af9b87 --- /dev/null +++ b/src/UI/AbstractDocumentView.hh @@ -0,0 +1,60 @@ +#ifndef VIVY_DOCUMENT_VIEW_H +#define VIVY_DOCUMENT_VIEW_H + +#ifndef __cplusplus +#error "This is a C++ header" +#endif + +#include "../Lib/Utils.hh" +#include "../Lib/Document/AbstractDocument.hh" +#include <QMainWindow> +#include <QDockWidget> +#include <QAction> + +namespace Vivy +{ +// Any document added to the tabs in the main window must inherit from the +// AbstractDocumentView widget. This widget will permit to query the type of +// the document in the tab. +class AbstractDocumentView : public QMainWindow { + Q_OBJECT + VIVY_UNMOVABLE_OBJECT(AbstractDocumentView) + +public: + enum class Type : quint64 { + Vivy = Utils::toUnderlying(Utils::DocumentType::Vivy), + Script = Utils::toUnderlying(Utils::DocumentType::VivyScript), + }; + +public: + explicit AbstractDocumentView(Type, QWidget *parent = nullptr) noexcept; + virtual ~AbstractDocumentView() noexcept = default; + + virtual void closeDocument() noexcept = 0; + virtual void openProperties() noexcept = 0; + + virtual QString getDocumentTabName() const noexcept = 0; + virtual QString getDocumentTabToolTip() const noexcept = 0; + virtual QIcon getDocumentTabIcon() const noexcept = 0; + virtual AbstractDocument *getDocument() const noexcept = 0; + + Type getType() const noexcept; + bool isType(const Utils::DocumentType t) const noexcept; + const QList<QAction *> &getViewsActions() const noexcept; + +signals: + void viewActionsChanged(); + +protected: + void deleteAllContent() noexcept; + + void delDockWidget(QDockWidget **) noexcept; + void addDockWidget(Qt::DockWidgetArea, QDockWidget *, Qt::Orientation) noexcept; + QList<QAction *> viewsActions{}; + +private: + const Type documentType; +}; +} + +#endif // VIVY_DOCUMENT_VIEW_H diff --git a/src/UI/DialogHelp.cc b/src/UI/DialogHelp.cc index 1eabd5c73d68bd6c479fec5db26206813c92f41a..bd0abab31efb949dabe7df5a357e9eedb98b9953 100644 --- a/src/UI/DialogHelp.cc +++ b/src/UI/DialogHelp.cc @@ -8,6 +8,21 @@ using namespace Vivy; +// See https://doc.qt.io/qt-5/richtext-html-subset.html for the supported +// rich text subset. +static const char *aboutContentHeader = + "<body>" + "<h1>About Vivy</h1>" + "<p>Vivy is a replacement for Aegisub, writen in Qt5+and with less segfaults - hopefully.</p>" + "<p>The following libraries where used:</p>" + "<ul>" + " <li>Qt5</li>" + " <li>libavutils</li>" + " <li>libavcodec</li>" + " <li>libavformat</li>" + "</ul>"; +static const char *aboutContentFooter = "</body>"; + DialogHelp::DialogHelp(QWidget *parent) noexcept : QMessageBox(parent) { @@ -18,8 +33,22 @@ DialogHelp::DialogHelp(QWidget *parent) noexcept setSizePolicy(sizePolicy); setTextFormat(Qt::RichText); - setText(aboutContent); + setText(getAboutContent()); setTextInteractionFlags(Qt::NoTextInteraction); adjustSize(); } + +QString +DialogHelp::getAboutContent() const noexcept +{ + QString ret; + ret = aboutContentHeader; + + ret.append("<p>Vivy will handle the following most of the video and audio formats."); + ret.append("The audio formats are: " + Utils::audioFileSuffix.join(", ")); + ret.append("The video formats are: " + Utils::videoFileSuffix.join(", ") + "</p>"); + + ret.append(aboutContentFooter); + return ret; +} diff --git a/src/UI/DialogHelp.hh b/src/UI/DialogHelp.hh index 1ab9c7d9f3eff84d837438671f09b3b9a9ba917c..0bb5d9e09810ba60765cc462ae16237ccca8814e 100644 --- a/src/UI/DialogHelp.hh +++ b/src/UI/DialogHelp.hh @@ -18,22 +18,7 @@ public: ~DialogHelp() noexcept = default; private: - /* See https://doc.qt.io/qt-5/richtext-html-subset.html for the supported - * rich text subset. */ - // clang-format off - static inline const char *aboutContent = - "<body>" - "<h1>About Vivy</h1>" - "<p>Vivy is a replacement for Aegisub, writen in Qt5+and with less segfaults - hopefully.</p>" - "<p>The following libraries where used:</p>" - "<ul>" - " <li>Qt5</li>" - " <li>libavutils</li>" - " <li>libavcodec</li>" - " <li>libavformat</li>" - "</ul>" - "</body>"; - // clang-format on + QString getAboutContent() const noexcept; }; } diff --git a/src/UI/AudioVisualizer.cc b/src/UI/DocumentViews/AudioVisualizer.cc similarity index 72% rename from src/UI/AudioVisualizer.cc rename to src/UI/DocumentViews/AudioVisualizer.cc index a85ac12664f3ba222972bba19f0b99e4aca84f28..fa8d02c1314b267a7ffb04687bb6c56070372e5c 100644 --- a/src/UI/AudioVisualizer.cc +++ b/src/UI/DocumentViews/AudioVisualizer.cc @@ -1,6 +1,7 @@ #include "AudioVisualizer.hh" -#include "../Lib/Audio.hh" +#include "../../Lib/Audio.hh" +#include <algorithm> #include <QGraphicsPixmapItem> #include <QLabel> #include <QMessageBox> @@ -12,15 +13,6 @@ using namespace Vivy; #define MAXPIXVALUE 7 // Some magix AV magic stuff -#define CAP_VALUE(_value, _lower, _upper) \ - { \ - if (_value > _upper) { \ - _value = _upper; \ - } else if (_value < _lower) { \ - _value = _lower; \ - } \ - } - AudioVisualizer::AudioVisualizer(AudioContext::StreamPtr stream, QWidget *parent) : QWidget(parent) { @@ -55,20 +47,18 @@ AudioVisualizer::AudioVisualizer(AudioContext::StreamPtr stream, QWidget *parent /* Compute the image data */ for (size_t x = 0, i = 0; i < size - height; i += decalage, ++x) { -#pragma omp parallel for - for (size_t j = 0; j < height; j++) { + parallel_for (size_t j = 0; j < height; j++) { const double curr_dat = decodedData[i + j]; const double window_modifier = (1 - cos(2 * M_PI * static_cast<double>(j) / static_cast<double>(height - 1))) / 2; - float value = static_cast<float>(window_modifier * curr_dat); - CAP_VALUE(value, -1.0f, 1.0f); + const float value = + std::clamp(static_cast<float>(window_modifier * curr_dat), -1.0f, 1.0f); chunkData[j] = value; } av_rdft_calc(ctx.get(), chunkData.get()); -#pragma omp parallel for - for (size_t j = 0; j < height / 2; j++) { + parallel_for (size_t j = 0; j < height / 2; j++) { const float im = chunkData[j * 2]; const float re = chunkData[j * 2 + 1]; const float mag = sqrtf(im * im + re * re); diff --git a/src/UI/AudioVisualizer.hh b/src/UI/DocumentViews/AudioVisualizer.hh similarity index 97% rename from src/UI/AudioVisualizer.hh rename to src/UI/DocumentViews/AudioVisualizer.hh index 3273d285bba08de8865e08be657cf62ba414a483..451d50800979bca9f137b3fd2dfcd3842715629b 100644 --- a/src/UI/AudioVisualizer.hh +++ b/src/UI/DocumentViews/AudioVisualizer.hh @@ -6,7 +6,7 @@ #endif #include "TimingView.hh" -#include "../Lib/Audio.hh" +#include "../../Lib/Audio.hh" #include <QWidget> #include <QString> diff --git a/src/UI/TimingBar.cc b/src/UI/DocumentViews/TimingBar.cc similarity index 100% rename from src/UI/TimingBar.cc rename to src/UI/DocumentViews/TimingBar.cc diff --git a/src/UI/TimingBar.hh b/src/UI/DocumentViews/TimingBar.hh similarity index 97% rename from src/UI/TimingBar.hh rename to src/UI/DocumentViews/TimingBar.hh index 3dcd5fd0819682a7f81a768266b809ffecdd1e83..6edf64eede549c9284a1a332d05aaca6b8bfdc6a 100644 --- a/src/UI/TimingBar.hh +++ b/src/UI/DocumentViews/TimingBar.hh @@ -5,7 +5,6 @@ #error "This is a C++ header" #endif -#include <QWidget> #include <QGraphicsItem> #include <QGraphicsLineItem> diff --git a/src/UI/TimingScene.cc b/src/UI/DocumentViews/TimingScene.cc similarity index 98% rename from src/UI/TimingScene.cc rename to src/UI/DocumentViews/TimingScene.cc index 7d2e21156582e7c6c77de94958b53785c057cb1a..3c916fd0868d3364b69b0822c233357dd9667f94 100644 --- a/src/UI/TimingScene.cc +++ b/src/UI/DocumentViews/TimingScene.cc @@ -1,5 +1,5 @@ #include "TimingScene.hh" -#include "../Lib/Utils.hh" +#include "../../Lib/Utils.hh" #include <QGraphicsLineItem> #include <QGraphicsPixmapItem> diff --git a/src/UI/TimingScene.hh b/src/UI/DocumentViews/TimingScene.hh similarity index 95% rename from src/UI/TimingScene.hh rename to src/UI/DocumentViews/TimingScene.hh index 860acecbc35628983b137e517385f82da36cf1ef..901de0fa63c48079797dd50018e5ce51cf85e018 100644 --- a/src/UI/TimingScene.hh +++ b/src/UI/DocumentViews/TimingScene.hh @@ -1,8 +1,8 @@ #ifndef VIVY_TIMING_SCENE_H #define VIVY_TIMING_SCENE_H -#include "../Lib/Utils.hh" -#include "../Ass/Ass.hh" +#include "../../Lib/Utils.hh" +#include "../../Lib/Ass/Ass.hh" #include "TimingBar.hh" #include <QWidget> diff --git a/src/UI/TimingView.cc b/src/UI/DocumentViews/TimingView.cc similarity index 82% rename from src/UI/TimingView.cc rename to src/UI/DocumentViews/TimingView.cc index 25f859d3dda9af16b58ebc910f9ee4ebe1a276bf..9614cf18ae6008657248d5e89a91c4c84a348d2e 100644 --- a/src/UI/TimingView.cc +++ b/src/UI/DocumentViews/TimingView.cc @@ -24,6 +24,9 @@ TimingView::TimingView(QImage img, quint64 soundLength, QWidget *parent) noexcep setFixedHeight(img.height()); setMaximumHeight(img.height() + horizontalScrollBar()->height() - TO_ADD_TO_IMAGE_HEIGHT); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + QObject::connect(verticalScrollBar(), &QScrollBar::rangeChanged, this, + &TimingView::moveScrollBarToBottom); setScene(scene); } @@ -43,3 +46,9 @@ TimingView::mousePressEvent(QMouseEvent *event) noexcept QGraphicsView::mousePressEvent(event); } + +void +TimingView::moveScrollBarToBottom(int, int max) noexcept +{ + verticalScrollBar()->setValue(max); +} diff --git a/src/UI/TimingView.hh b/src/UI/DocumentViews/TimingView.hh similarity index 90% rename from src/UI/TimingView.hh rename to src/UI/DocumentViews/TimingView.hh index b2f11f792576e7d66b1e2628ae2cefb12bb51e67..ff5319fe5a19f4892d796b31cf827723adbab7fa 100644 --- a/src/UI/TimingView.hh +++ b/src/UI/DocumentViews/TimingView.hh @@ -5,7 +5,7 @@ #error "This is a C++ header" #endif -#include "../Lib/Utils.hh" +#include "../../Lib/Utils.hh" #include "TimingBar.hh" #include "TimingScene.hh" @@ -31,6 +31,7 @@ private: public slots: void mousePressEvent(QMouseEvent *event) noexcept; + void moveScrollBarToBottom(int, int) noexcept; }; } diff --git a/src/UI/MainWindow.cc b/src/UI/MainWindow.cc index 212abaf9f3312c909a0d2f02a33de2bde7e46c2c..a362c375b6364c810f0530debb0d414d8ab2efde 100644 --- a/src/UI/MainWindow.cc +++ b/src/UI/MainWindow.cc @@ -1,11 +1,15 @@ #include "MainWindow.hh" #include "DialogHelp.hh" +#include "PropertyModel.hh" #include "VivyDocumentView.hh" #include "../Lib/Utils.hh" #include "../VivyApplication.hh" +#include <algorithm> #include <functional> +#include <QWindow> #include <optional> +#include <QTreeView> #include <QFileInfo> #include <QStandardPaths> #include <QPixmap> @@ -41,13 +45,14 @@ using namespace Vivy; MainWindow::MainWindow(QWidget *parent) noexcept : QMainWindow(parent) { - /* Setup the main window other properties */ + // Setup the main window other properties setWindowIcon(QIcon(":/icons/vivy.png")); /* Some declarations */ DCL_MENU(file, "&File"); DCL_MENU(edit, "&Edit"); - DCL_MENU(view, "&View"); + DCL_MENU(viewTmp, "&View"); + viewMenu = viewTmpMenu; // Save the view menu DCL_MENU(simulate, "&Simulate"); DCL_MENU(help, "&Help"); DCL_TOOLBAR(file, "File"); @@ -80,7 +85,7 @@ MainWindow::MainWindow(QWidget *parent) noexcept TOOLBAR_ADD_ACTION(file, saveFileAs); TOOLBAR_ADD_ACTION(other, openDialogHelp); - /* Setup the tabs to display the documents */ + // Setup the tabs to display the documents documents = new QTabWidget(this); documents->setMovable(true); documents->setTabsClosable(true); @@ -88,49 +93,71 @@ MainWindow::MainWindow(QWidget *parent) noexcept documents->setUsesScrollButtons(true); documents->setDocumentMode(true); connect(documents, &QTabWidget::tabCloseRequested, this, &MainWindow::closeDocument); + connect(documents, &QTabWidget::tabBarDoubleClicked, this, &MainWindow::openProperties); setCentralWidget(documents); - /* Enable/disable actions depending on the context */ + // Enable/disable actions depending on the context saveFileAct->setEnabled(false); saveFileAsAct->setEnabled(false); loadSubDocumentAssAct->setEnabled(false); loadSubDocumentVideoAct->setEnabled(false); loadSubDocumentAudioAct->setEnabled(false); - auto enableOnDocument = [](auto *widget, int index) noexcept -> void { - widget->setEnabled(index >= 0); + // Enable actions if the document is save-able + auto enableSaveOnDocument = [this](auto *widget, int index) noexcept -> void { + if (index >= 0) { + auto type = static_cast<AbstractDocumentView *>(documents->widget(index))->getType(); + widget->setEnabled(type == AbstractDocumentView::Type::Vivy || + type == AbstractDocumentView::Type::Script); + } else { + widget->setEnabled(false); + } + }; + + // Enable load sub-document on sub-document able objects + auto enableLoadSubOnDocument = [this](auto *widget, int index) noexcept -> void { + if (index >= 0) { + auto type = static_cast<AbstractDocumentView *>(documents->widget(index))->getType(); + widget->setEnabled(type == AbstractDocumentView::Type::Vivy); + } else { + widget->setEnabled(false); + } }; + connect(documents, &QTabWidget::currentChanged, this, + [this](int) noexcept -> void { documentViewActionsChanged(); }); connect(documents, &QTabWidget::currentChanged, saveFileAct, - std::bind_front(enableOnDocument, saveFileAct)); + std::bind_front(enableSaveOnDocument, saveFileAct)); connect(documents, &QTabWidget::currentChanged, saveFileAsAct, - std::bind_front(enableOnDocument, saveFileAsAct)); + std::bind_front(enableSaveOnDocument, saveFileAsAct)); connect(documents, &QTabWidget::currentChanged, loadSubDocumentAudioAct, - std::bind_front(enableOnDocument, loadSubDocumentAudioAct)); + std::bind_front(enableLoadSubOnDocument, loadSubDocumentAudioAct)); connect(documents, &QTabWidget::currentChanged, loadSubDocumentVideoAct, - std::bind_front(enableOnDocument, loadSubDocumentVideoAct)); + std::bind_front(enableLoadSubOnDocument, loadSubDocumentVideoAct)); connect(documents, &QTabWidget::currentChanged, loadSubDocumentAssAct, - std::bind_front(enableOnDocument, loadSubDocumentAssAct)); + std::bind_front(enableLoadSubOnDocument, loadSubDocumentAssAct)); - /* Add a new empty document that will will be replaced if nothing is added - * to it. */ + // Add a new empty document that will will be replaced if nothing is added + // to it. newDocument(); - /* Main window has finished its construction */ + // Main window has finished its construction statusBar()->showMessage("QSimulate has started"); } -std::weak_ptr<VivyDocument> -MainWindow::getCurrentDocument() const +void +MainWindow::openProperties(int index) noexcept { - if (VivyDocumentView *currentView = getCurrentDocumentView()) { - return currentView->getDocument(); + if (index < 0) { + // TODO: May may want to do something when the user is clicking where + // no tab bar is openned (like open document?) + return; } - else { - throw std::runtime_error("no current document"); - } + qDebug().nospace() << "Tab n°" << index << " was double clicked"; + AbstractDocumentView *current = getTab(index); + current->openProperties(); } void @@ -140,11 +167,22 @@ MainWindow::openDialogHelp() noexcept help_holder->exec(); } +AbstractDocument * +MainWindow::getCurrentDocument() const noexcept +{ + try { + return getCurrentDocumentView()->getDocument(); + } catch (const std::runtime_error &e) { + qCritical() << "No current view in the main window"; + return nullptr; + } +} + void MainWindow::saveFile() noexcept { try { - if (auto document = getCurrentDocument().lock()) { + if (auto document = getCurrentDocument()) { qDebug() << "Request to save the document" << document->getName(); } } catch (const std::runtime_error &e) { @@ -156,7 +194,7 @@ void MainWindow::saveFileAs() noexcept { try { - if (auto document = getCurrentDocument().lock()) { + if (auto document = getCurrentDocument()) { qDebug() << "Request to save the document" << document->getName(); } } catch (const std::runtime_error &e) { @@ -167,27 +205,17 @@ MainWindow::saveFileAs() noexcept void MainWindow::closeDocument(int index) noexcept { - const VivyDocumentView *documentToClose = - reinterpret_cast<const VivyDocumentView *>(documents->widget(index)); - - if (documentToClose == nullptr) { - qCritical() << "Can't close a null document pointer"; + if (index < 0) return; - } - - std::optional<VivyDocument::Uuid> uuid; - if (auto doc = documentToClose->getDocument().lock()) { - uuid = doc->getUuid(); - qDebug() << "Closing the document:" << doc->getName() << "( ref count is" << doc.use_count() - << ")"; - } - QWidget *tab = documents->widget(index); + auto *documentToClose = static_cast<AbstractDocumentView *>(documents->widget(index)); documents->removeTab(index); - delete tab; + disconnect(documentToClose, &AbstractDocumentView::viewActionsChanged, this, + &MainWindow::documentViewActionsChanged); - if (uuid) { - vivyApp->documentStore.closeDocument(*uuid); + if (documentToClose) { + documentToClose->closeDocument(); + delete documentToClose; } } @@ -230,46 +258,45 @@ MainWindow::openDocument() noexcept void MainWindow::loadSubDocumentAss() noexcept { - QString filename = QFileDialog::getOpenFileName(this, "Select an ASS document to load"); - - if (filename.isEmpty()) { - qWarning() << "Found an empty filename, don't open a file"; - return; - } - - if (auto currDoc = getCurrentDocument().lock()) { - currDoc->setAssSubDocument(filename); - } + withOpenFileNameDialog<VivyDocumentView, VivyDocument>( + "Select an ASS document to load", + [](VivyDocumentView *view, VivyDocument *doc, const QString &filename) noexcept -> void { + doc->setAssSubDocument(filename); + view->loadAssView(); + }); } void MainWindow::loadSubDocumentVideo() noexcept { - QString filename = QFileDialog::getOpenFileName(this, "Select a video document to load"); - - if (filename.isEmpty()) { - qWarning() << "Found an empty filename, don't open a file"; - return; - } - - if (auto currDoc = getCurrentDocument().lock()) { - currDoc->setVideoSubDocument(filename); - } + withOpenFileNameDialog<VivyDocumentView, VivyDocument>( + "Select a video document to load", + [](VivyDocumentView *view, VivyDocument *doc, const QString &filename) noexcept -> void { + doc->setVideoSubDocument(filename); + view->loadVideoView(); + }); } void MainWindow::loadSubDocumentAudio() noexcept { - QString filename = QFileDialog::getOpenFileName(this, "Select an audio document to load"); - - if (filename.isEmpty()) { - qWarning() << "Found an empty filename, don't open a file"; - return; - } + withOpenFileNameDialog<VivyDocumentView, VivyDocument>( + "Select an audio document to load", + [](VivyDocumentView *view, VivyDocument *doc, const QString &filename) noexcept -> void { + doc->setAudioSubDocument(filename); + view->loadAudioView(); + }); +} - if (auto currDoc = getCurrentDocument().lock()) { - currDoc->setAudioSubDocument(filename); - } +void +MainWindow::addTab(AbstractDocumentView *tab) +{ + const int index = documents->addTab(tab, tab->getDocumentTabIcon(), tab->getDocumentTabName()); + documents->setTabToolTip(index, tab->getDocumentTabToolTip()); + documents->setCurrentIndex(index); + connect(tab, &AbstractDocumentView::viewActionsChanged, this, + &MainWindow::documentViewActionsChanged); + documentViewActionsChanged(); } void @@ -278,21 +305,25 @@ MainWindow::addTab(VivyDocumentView *tab) int index = -1; if (const int untouched_index = findFirstUntouchedDocument(); untouched_index >= 0) { closeDocument(untouched_index); - index = documents->insertTab(untouched_index, tab, QIcon(":/icons/vivy.png"), + index = documents->insertTab(untouched_index, tab, tab->getDocumentTabIcon(), tab->getDocumentTabName()); } else { - index = documents->addTab(tab, QIcon(":/icons/vivy.png"), tab->getDocumentTabName()); + index = documents->addTab(tab, tab->getDocumentTabIcon(), tab->getDocumentTabName()); } documents->setTabToolTip(index, tab->getDocumentTabToolTip()); + documents->setCurrentIndex(index); + connect(tab, &AbstractDocumentView::viewActionsChanged, this, + &MainWindow::documentViewActionsChanged); + documentViewActionsChanged(); qDebug() << "View constructed successfully"; } -VivyDocumentView * +AbstractDocumentView * MainWindow::getCurrentDocumentView() const { - if (VivyDocumentView *currentView = - reinterpret_cast<VivyDocumentView *>(documents->currentWidget())) { + if (AbstractDocumentView *currentView = + static_cast<AbstractDocumentView *>(documents->currentWidget())) { return currentView; } @@ -301,12 +332,12 @@ MainWindow::getCurrentDocumentView() const } } -VivyDocumentView * +AbstractDocumentView * MainWindow::getTab(const int index) const noexcept { if (index > documents->count()) return nullptr; - return reinterpret_cast<VivyDocumentView *>(documents->widget(index)); + return static_cast<AbstractDocumentView *>(documents->widget(index)); } int @@ -314,10 +345,27 @@ MainWindow::findFirstUntouchedDocument() const noexcept { const int count = documents->count(); for (int index = 0; index < count; ++index) { - if (getTab(index)->getDocument().lock()->checkDocumentOption( - VivyDocument::UntouchedByDefault)) + AbstractDocumentView *documentView = getTab(index); + if (documentView && (documentView->getType() == AbstractDocumentView::Type::Vivy) && + static_cast<VivyDocumentView *>(documentView) + ->getDocument() + ->checkDocumentOption(VivyDocument::UntouchedByDefault)) return index; } return -1; } + +void +MainWindow::documentViewActionsChanged() noexcept +{ + qInfo() << "Document view action changed"; + viewMenu->clear(); + + // Change document view menu if we have a document + try { + viewMenu->addActions(getCurrentDocumentView()->getViewsActions()); + } catch (const std::runtime_error &e) { + qInfo() << "No view to display:" << e.what(); + } +} diff --git a/src/UI/MainWindow.hh b/src/UI/MainWindow.hh index fc3e60ea739a7be2ca75da3e4132855c9afd72af..d42f057cb204720379445e45bc78bae675243f07 100644 --- a/src/UI/MainWindow.hh +++ b/src/UI/MainWindow.hh @@ -6,10 +6,13 @@ #endif #include "../Lib/Utils.hh" -#include "AudioVisualizer.hh" -#include "../Document/VivyDocumentStore.hh" +#include "../Lib/Document/AbstractDocument.hh" +#include "../Lib/Document/VivyDocumentStore.hh" +#include "DocumentViews/AudioVisualizer.hh" #include "VivyDocumentView.hh" #include <QMainWindow> +#include <QMenu> +#include <QFileDialog> namespace Vivy { @@ -17,24 +20,62 @@ class MainWindow final : public QMainWindow { Q_OBJECT QTabWidget *documents{ nullptr }; + QMenu *viewMenu{ nullptr }; public: explicit MainWindow(QWidget *parent = nullptr) noexcept; ~MainWindow() noexcept = default; - std::weak_ptr<VivyDocument> getCurrentDocument() const; + AbstractDocument *getCurrentDocument() const noexcept; + template <class Document> Document *getCurrentDocument() const noexcept + { + if (AbstractDocumentView *currentView = getCurrentDocumentView(); + currentView->getType() == Document::type) { + return currentView->getDocument(); + } else { + return nullptr; + } + } private: + void addTab(AbstractDocumentView *); void addTab(VivyDocumentView *); - VivyDocumentView *getTab(const int) const noexcept; - VivyDocumentView *getCurrentDocumentView() const; + AbstractDocumentView *getTab(const int) const noexcept; + AbstractDocumentView *getCurrentDocumentView() const; + int findFirstUntouchedDocument() const noexcept; + // Do an action with the selected filename. The 'call' variable must be + // callable like this: call(DocumentView, Document, QString) + template <typename DV, typename D> + void withOpenFileNameDialog(const QString &, auto call) noexcept + { + QString filename = QFileDialog::getOpenFileName(this, "Select an ASS document to load"); + + if (filename.isEmpty()) { + qWarning() << "Found an empty filename, don't open a file"; + return; + } + + try { + if (AbstractDocumentView *currView = getCurrentDocumentView(); + currView->isType(D::type)) { + DV *view = static_cast<VivyDocumentView *>(currView); + D *doc = view->getDocument(); + call(view, doc, filename); + } + } catch (const std::runtime_error &e) { + qCritical() << "Operation failed with:" << e.what(); + } + } + private slots: void newDocument() noexcept; void openDocument() noexcept; void closeDocument(int index) noexcept; + void openProperties(int index) noexcept; + void loadSubDocumentAss() noexcept; void loadSubDocumentVideo() noexcept; void loadSubDocumentAudio() noexcept; @@ -43,6 +84,8 @@ private slots: void saveFileAs() noexcept; void openDialogHelp() noexcept; + + void documentViewActionsChanged() noexcept; }; } diff --git a/src/UI/PropertyModel.cc b/src/UI/PropertyModel.cc new file mode 100644 index 0000000000000000000000000000000000000000..b32144f0ed5a76a8dce19077f348a5527ad1ed59 --- /dev/null +++ b/src/UI/PropertyModel.cc @@ -0,0 +1,319 @@ +#include "PropertyModel.hh" +#include <QDebug> + +using namespace Vivy; + +PropertyModel::Item::Item(Item *theParent) noexcept + : parent(theParent) +{ +} + +PropertyModel::Item::~Item() noexcept +{ + qDeleteAll(childs); +} + +void +PropertyModel::Item::appendChild(PropertyModel::Item *item) noexcept +{ + childs.append(item); +} + +PropertyModel::Item * +PropertyModel::Item::getChild(int row) const noexcept +{ + return childs.value(row); +} + +PropertyModel::Item * +PropertyModel::Item::getParent() const noexcept +{ + return parent; +} + +int +PropertyModel::Item::getChildCount() const noexcept +{ + return childs.count(); +} + +int +PropertyModel::Item::getRow() const noexcept +{ + return parent ? parent->childs.indexOf(const_cast<Item *>(this)) : 0; +} + +void +PropertyModel::Item::setKey(const QString &k) noexcept +{ + key = k; +} + +void +PropertyModel::Item::setValue(const QString &v) noexcept +{ + value = v; +} + +void +PropertyModel::Item::setType(const QJsonValue::Type &t) noexcept +{ + type = t; +} + +QString +PropertyModel::Item::getKey() const noexcept +{ + return key; +} + +QString +PropertyModel::Item::getValue() const noexcept +{ + return value; +} + +QJsonValue::Type +PropertyModel::Item::getType() const noexcept +{ + return type; +} + +const QList<PropertyModel::Item *> +PropertyModel::Item::getChilds() const noexcept +{ + return childs; +} + +PropertyModel::Item * +PropertyModel::Item::fromJson(const QJsonValue &value, PropertyModel::Item *parent) +{ + Item *root = new Item(parent); + root->setKey("root"); + + if (value.isObject()) { + for (const QString &key : value.toObject().keys()) { + QJsonValue v = value.toObject().value(key); + Item *child = fromJson(v, root); + child->setKey(key); + child->setType(v.type()); + root->appendChild(child); + } + + } + + else if (value.isArray()) { + int index = 0; + for (const QJsonValue &v : value.toArray()) { + Item *child = fromJson(v, root); + child->setKey(QString::number(index)); + child->setType(v.type()); + root->appendChild(child); + ++index; + } + } + + else { + root->setValue(value.toVariant().toString()); + root->setType(value.type()); + } + + return root; +} + +PropertyModel::PropertyModel(QObject *parent) noexcept + : QAbstractItemModel(parent) +{ + headers.append("key"); + headers.append("value"); +} + +PropertyModel::PropertyModel(const QJsonDocument &properties, QObject *parent) noexcept + : PropertyModel(parent) +{ + try { + loadJson(properties); + } catch (const std::runtime_error &e) { + qCritical() << "Failed to create the property model object:" << e.what(); + } +} + +void +PropertyModel::loadJson(const QJsonDocument &jdoc) +{ + if (!jdoc.isNull()) { + beginResetModel(); + root.reset(); + + if (jdoc.isArray()) { + root.reset(Item::fromJson(QJsonValue(jdoc.array()))); + root->setType(QJsonValue::Array); + + } else { + root.reset(Item::fromJson(QJsonValue(jdoc.object()))); + root->setType(QJsonValue::Object); + } + + endResetModel(); + return; + } + + throw std::runtime_error("failed to load json"); +} + +QVariant +PropertyModel::data(const QModelIndex &index, int role) const noexcept +{ + if (!index.isValid()) + return QVariant(); + + const Item *item = static_cast<Item *>(index.internalPointer()); + + if (role == Qt::DisplayRole) { + if (index.column() == 0) + return QString("%1").arg(item->getKey()); + + if (index.column() == 1) + return QString("%1").arg(item->getValue()); + } + + else if (Qt::EditRole == role) { + if (index.column() == 1) { + return QString("%1").arg(item->getValue()); + } + } + + return QVariant(); +} + +bool +PropertyModel::setData(const QModelIndex &index, const QVariant &value, int role) noexcept +{ + const int col = index.column(); + if (Qt::EditRole == role) { + if (col == 1) { + Item *item = static_cast<Item *>(index.internalPointer()); + item->setValue(value.toString()); + emit dataChanged(index, index, { Qt::EditRole }); + return true; + } + } + + return false; +} + +QVariant +PropertyModel::headerData(int section, Qt::Orientation orientation, int role) const noexcept +{ + if (role != Qt::DisplayRole) + return QVariant(); + + if (orientation == Qt::Horizontal) + return headers.value(section); + + else + return QVariant(); +} + +QModelIndex +PropertyModel::index(int row, int column, const QModelIndex &parent) const noexcept +{ + if (!hasIndex(row, column, parent)) + return QModelIndex(); + + const Item *parentItem = + parent.isValid() ? static_cast<Item *>(parent.internalPointer()) : root.get(); + Item *childItem = parentItem->getChild(row); + return childItem ? createIndex(row, column, childItem) : QModelIndex(); +} + +QModelIndex +PropertyModel::parent(const QModelIndex &index) const noexcept +{ + if (!index.isValid()) + return QModelIndex(); + + const Item *childItem = static_cast<Item *>(index.internalPointer()); + Item *parentItem = childItem->getParent(); + + if (parentItem == root.get()) + return QModelIndex(); + + return createIndex(parentItem->getRow(), 0, parentItem); +} + +int +PropertyModel::rowCount(const QModelIndex &parent) const noexcept +{ + if (parent.column() > 0) + return 0; + + const Item *parentItem = + parent.isValid() ? static_cast<Item *>(parent.internalPointer()) : root.get(); + return parentItem->getChildCount(); +} + +int +PropertyModel::columnCount(const QModelIndex & /* parent */) const noexcept +{ + return 2; +} + +Qt::ItemFlags +PropertyModel::flags(const QModelIndex &index) const noexcept +{ + // Handle the case where the model is not editable + if (!editableState) { + return /* Qt::ItemIsSelectable | */ QAbstractItemModel::flags(index); + } + + // Here the model is editable + const int col = index.column(); + const Item *item = static_cast<Item *>(index.internalPointer()); + const bool isArray = QJsonValue::Array == item->getType(); + const bool isObject = QJsonValue::Object == item->getType(); + + if ((col == 1) && !(isArray || isObject)) + return Qt::ItemIsSelectable | Qt::ItemIsEditable | QAbstractItemModel::flags(index); + + else + return QAbstractItemModel::flags(index); +} + +// Get the json stored inside the model +QJsonDocument +PropertyModel::getJson() const noexcept +{ + QJsonValue v = generateJson(root.get()); + return v.isObject() ? QJsonDocument(v.toObject()) : QJsonDocument(v.toArray()); +} + +// Get the json stored inside the model by calculating it +QJsonValue +PropertyModel::generateJson(PropertyModel::Item *item) const noexcept +{ + if (QJsonValue::Type type = item->getType(); QJsonValue::Object == type) { + QJsonObject jo; + for (const auto &child : item->getChilds()) + jo.insert(child->getKey(), generateJson(child)); + return jo; + } + + else if (QJsonValue::Array == type) { + QJsonArray arr; + for (const auto &child : item->getChilds()) + arr.append(generateJson(child)); + return arr; + } + + else { + QJsonValue va(item->getValue()); + return va; + } +} + +QString +PropertyModel::getName() const noexcept +{ + return root ? "Property for " + root->getKey() : "Property view"; +} diff --git a/src/UI/PropertyModel.hh b/src/UI/PropertyModel.hh new file mode 100644 index 0000000000000000000000000000000000000000..c6cdca5eb16601a03c1a4d46c01c4ae8dba2d454 --- /dev/null +++ b/src/UI/PropertyModel.hh @@ -0,0 +1,105 @@ +#ifndef VIVY_UI_PROPERTY_VIEW_H +#define VIVY_UI_PROPERTY_VIEW_H + +#include "../Lib/Utils.hh" + +#include <QAbstractItemModel> +#include <QJsonDocument> +#include <QJsonValue> +#include <QJsonArray> +#include <QJsonObject> + +// auto model = new PropertyModel(doc); +// QVBoxLayout *layout = new QVBoxLayout(this); +// QTreeView *view = new QTreeView(this); +// view->setModel(model); +// view->header()->setSectionResizeMode(QHeaderView::ResizeToContents); +// view->expandAll(); +// layout->addWidget(view); +// setLayout(layout); + +namespace Vivy +{ +// PropertyModel is used to store a json document and be able to render it +// through a QTreeView +class PropertyModel final : public QAbstractItemModel { + Q_OBJECT + VIVY_UNMOVABLE_OBJECT(PropertyModel) + +private: + // Simple property item to be stored inside the PropertyModel + class Item final { + VIVY_UNMOVABLE_OBJECT(Item) + + public: + Item(Item *parent = nullptr) noexcept; + ~Item() noexcept; + + void appendChild(Item *) noexcept; + Item *getChild(int row) const noexcept; + const QList<Item *> getChilds() const noexcept; + Item *getParent() const noexcept; + int getChildCount() const noexcept; + int getRow() const noexcept; + void setKey(const QString &) noexcept; + void setValue(const QString &) noexcept; + void setType(const QJsonValue::Type &) noexcept; + QString getKey() const noexcept; + QString getValue() const noexcept; + QJsonValue::Type getType() const noexcept; + + static Item *fromJson(const QJsonValue &, Item *parent = nullptr); + + protected: + private: + QString key{}; + QString value{}; + QJsonValue::Type type{ QJsonValue::Null }; + QList<Item *> childs{}; + Item *parent{ nullptr }; + }; + +public: + explicit PropertyModel(QObject *parent = nullptr) noexcept; + explicit PropertyModel(const QJsonDocument &, QObject *parent = nullptr) noexcept; + explicit PropertyModel(const PropertyConstViewable auto &object, + QObject *parent = nullptr) noexcept + : PropertyModel(object.getProperties(), parent) + { + // With concepts it seems that the function must be implemented where + // it is declared. + root->setKey(object.getElementName()); + } + + ~PropertyModel() noexcept = default; + + void loadJson(const QJsonDocument &json); + void setEditable(const bool); + + QVariant data(const QModelIndex &, int role) const noexcept override; + bool setData(const QModelIndex &, const QVariant &value, + int role = Qt::EditRole) noexcept override; + + QVariant headerData(int section, Qt::Orientation, int role) const noexcept override; + QModelIndex parent(const QModelIndex &) const noexcept override; + QModelIndex index(int row, int column, + const QModelIndex &parent = QModelIndex()) const noexcept override; + + int rowCount(const QModelIndex &parent = QModelIndex()) const noexcept override; + int columnCount(const QModelIndex &parent = QModelIndex()) const noexcept override; + + Qt::ItemFlags flags(const QModelIndex &) const noexcept override; + QJsonDocument getJson() const noexcept; + + QString getName() const noexcept; + +private: + QJsonValue generateJson(Item *) const noexcept; + std::unique_ptr<Item> root{ nullptr }; + QStringList headers{}; + bool editableState{ false }; +}; + +} + +#endif // VIVY_UI_PROPERTY_VIEW_H diff --git a/src/UI/ScriptDocumentView.cc b/src/UI/ScriptDocumentView.cc new file mode 100644 index 0000000000000000000000000000000000000000..661a1fbe5e23e8e193161cad68c3515426012511 --- /dev/null +++ b/src/UI/ScriptDocumentView.cc @@ -0,0 +1,36 @@ +#include "ScriptDocumentView.hh" + +using namespace Vivy; + +ScriptDocumentView::ScriptDocumentView(QWidget *parent) noexcept + : AbstractDocumentView(AbstractDocumentView::Type::Script, parent) +{ +} + +void +ScriptDocumentView::closeDocument() noexcept +{ +} + +QString +ScriptDocumentView::getDocumentTabName() const noexcept +{ + return "VS File"; +} + +QString +ScriptDocumentView::getDocumentTabToolTip() const noexcept +{ + return "VS File"; +} + +QIcon +ScriptDocumentView::getDocumentTabIcon() const noexcept +{ + return QIcon::fromTheme("text-x-script"); +} + +void +ScriptDocumentView::openProperties() noexcept +{ +} diff --git a/src/UI/ScriptDocumentView.hh b/src/UI/ScriptDocumentView.hh new file mode 100644 index 0000000000000000000000000000000000000000..4d93378aab9a173786c621c91cb2f7d66dfa5c04 --- /dev/null +++ b/src/UI/ScriptDocumentView.hh @@ -0,0 +1,38 @@ +#ifndef VIVY_SCRIPT_DOCUMENT_VIEW_H +#define VIVY_SCRIPT_DOCUMENT_VIEW_H + +#ifndef __cplusplus +#error "This is a C++ header" +#endif + +#include "../Lib/Utils.hh" +#include "AbstractDocumentView.hh" +#include <QWidget> +#include <QString> +#include <memory> + +namespace Vivy +{ +class ScriptDocumentView final : public AbstractDocumentView { + Q_OBJECT + VIVY_UNMOVABLE_OBJECT(ScriptDocumentView) + +public: + explicit ScriptDocumentView(QWidget *parent = nullptr) noexcept; + virtual ~ScriptDocumentView() noexcept = default; + + void closeDocument() noexcept override; + void openProperties() noexcept override; + + QString getDocumentTabName() const noexcept override; + QString getDocumentTabToolTip() const noexcept override; + QIcon getDocumentTabIcon() const noexcept override; + + AbstractDocument *getDocument() const noexcept override + { + return nullptr; + }; +}; +} + +#endif // VIVY_PROPERTY_DOCUMENT_VIEW_H diff --git a/src/UI/VivyDocumentView.cc b/src/UI/VivyDocumentView.cc index f686215d6aa2f4e3983d4afb1ff571dce2642071..91362576fa42d62df995bace7ca902f11423f2bc 100644 --- a/src/UI/VivyDocumentView.cc +++ b/src/UI/VivyDocumentView.cc @@ -1,18 +1,37 @@ #include "VivyDocumentView.hh" -#include "AudioVisualizer.hh" -#include "../Document/VivyDocument.hh" +#include "PropertyModel.hh" +#include "DocumentViews/AudioVisualizer.hh" +#include "../VivyApplication.hh" +#include "../Lib/Document/VivyDocument.hh" +#include <QHeaderView> +#include <QTreeView> #include <QVBoxLayout> using namespace Vivy; VivyDocumentView::VivyDocumentView(std::shared_ptr<VivyDocument> doc, QWidget *parent) noexcept - : QWidget(parent) + : AbstractDocumentView(AbstractDocumentView::Type::Vivy, parent) , document(doc) { qDebug() << "Create view for document" << doc->getName() << "with capabilities" << doc->getDocumentCapabilitiesString(); + loadAudioView(); + loadVideoView(); + loadAssView(); + + setDockNestingEnabled(true); + + // Add some actions... + QAction *openProperties = new QAction("Open properties", this); + connect(openProperties, &QAction::triggered, this, &VivyDocumentView::openProperties); + viewsActions.append(openProperties); + + // The separator + QAction *separator = new QAction(this); + separator->setSeparator(true); + viewsActions.append(separator); } VivyDocumentView::~VivyDocumentView() noexcept @@ -20,14 +39,13 @@ VivyDocumentView::~VivyDocumentView() noexcept qDebug() << "Deleting the document view: ref count on document" << document->getName() << "is" << document.use_count() << "and" << (visualizer ? "has visualizer" : "without visualizer"); - if (visualizer) - delete visualizer; + closeDocument(); } -std::weak_ptr<VivyDocument> +VivyDocument * VivyDocumentView::getDocument() const noexcept { - return document; + return document.get(); } QString @@ -46,6 +64,16 @@ VivyDocumentView::getDocumentTabToolTip() const noexcept : ""); } +void +VivyDocumentView::loadVideoView() noexcept +{ +} + +void +VivyDocumentView::loadAssView() noexcept +{ +} + void VivyDocumentView::loadAudioView() noexcept { @@ -60,19 +88,61 @@ VivyDocumentView::loadAudioView() noexcept return; } - visualizer = new AudioVisualizer(stream); - if (visualizer == nullptr) { + if (visualizer) + delDockWidget(&visualizer); + visualizer = new QDockWidget("Visualizer", this); + + AudioVisualizer *visualizerInner = new AudioVisualizer(stream, visualizer); + if (visualizerInner == nullptr) { qCritical() << "Failed to create visualizer for" << audioDocument->getFilePath(); return; } - QVBoxLayout *layout = new QVBoxLayout(this); - layout->addWidget(visualizer); - - setLayout(layout); + visualizer->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Expanding); + visualizer->setWidget(visualizerInner); + visualizer->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::TopDockWidgetArea); + visualizer->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetClosable); + addDockWidget(Qt::LeftDockWidgetArea, visualizer, Qt::Horizontal); } else { qDebug() << "The document" << document->getName() << "is not AudioAble"; } } + +void +VivyDocumentView::closeDocument() noexcept +{ + qDebug() << "Closing the document:" << document->getName() << "( ref count is" + << document.use_count() << ")"; + vivyApp->documentStore.closeDocument(document->getUuid()); + + // The visualizer pointer should have been deleted by the + // deleteAllContent() call if it was created. + deleteAllContent(); + visualizer = nullptr; + property = nullptr; +} + +QIcon +VivyDocumentView::getDocumentTabIcon() const noexcept +{ + return QIcon(":/icons/vivy.png"); +} + +void +VivyDocumentView::openProperties() noexcept +{ + if (property) + delDockWidget(&property); + + propertyModel.reset(new PropertyModel(*document.get())); + property = new QDockWidget("Properties", this); + QTreeView *view = new QTreeView(property); + view->setModel(propertyModel.get()); + view->header()->setSectionResizeMode(QHeaderView::ResizeToContents); + view->expandAll(); + property->setWidget(view); + property->setAllowedAreas(Qt::AllDockWidgetAreas); + addDockWidget(Qt::RightDockWidgetArea, property, Qt::Vertical); +} diff --git a/src/UI/VivyDocumentView.hh b/src/UI/VivyDocumentView.hh index 89788e56e748ea8d02f4ed2146eeda27f119a749..493b75e1112f8d4e2e259ef3f173448a5b4aab5b 100644 --- a/src/UI/VivyDocumentView.hh +++ b/src/UI/VivyDocumentView.hh @@ -5,29 +5,43 @@ #error "This is a C++ header" #endif -#include "../Document/VivyDocument.hh" -#include "AudioVisualizer.hh" +#include "../Lib/Document/VivyDocument.hh" +#include "DocumentViews/AudioVisualizer.hh" +#include "AbstractDocumentView.hh" +#include "PropertyModel.hh" #include <QWidget> +#include <QDockWidget> namespace Vivy { -class VivyDocumentView final : public QWidget { +class VivyDocumentView final : public AbstractDocumentView { Q_OBJECT + VIVY_UNMOVABLE_OBJECT(VivyDocumentView) public: explicit VivyDocumentView(std::shared_ptr<VivyDocument>, QWidget *parent = nullptr) noexcept; ~VivyDocumentView() noexcept; - std::weak_ptr<VivyDocument> getDocument() const noexcept; - QString getDocumentTabName() const noexcept; - QString getDocumentTabToolTip() const noexcept; + void closeDocument() noexcept override; + void openProperties() noexcept override; -private: - std::shared_ptr<VivyDocument> document; - AudioVisualizer *visualizer{ nullptr }; + QString getDocumentTabName() const noexcept override; + QString getDocumentTabToolTip() const noexcept override; + QIcon getDocumentTabIcon() const noexcept override; + + VivyDocument *getDocument() const noexcept override; +public slots: void loadAudioView() noexcept; + void loadVideoView() noexcept; + void loadAssView() noexcept; + +private: + std::shared_ptr<VivyDocument> document; + std::unique_ptr<PropertyModel> propertyModel; + QDockWidget *visualizer{ nullptr }; + QDockWidget *property{ nullptr }; }; } diff --git a/src/VivyApplication.hh b/src/VivyApplication.hh index 4492f05473899d8e764ae4c2598cdd3520f8e4c3..579f9b78c273d7ba936d3e98b0960d702f529c66 100644 --- a/src/VivyApplication.hh +++ b/src/VivyApplication.hh @@ -9,7 +9,7 @@ #include <QApplication> #include <QPixmap> -#include "Document/VivyDocumentStore.hh" +#include "Lib/Document/VivyDocumentStore.hh" #include <memory> namespace Vivy