diff --git a/CMakeLists.txt b/CMakeLists.txt index 0b90ba619e018d0102484656f92623c26c04e6ac..edb6c4f9740ebf5f5958aaf16fd6b525f8bfea6c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -67,24 +67,9 @@ target_link_libraries(Vivy PRIVATE ${MPV_LIBRARY}) target_link_libraries(Vivy PRIVATE lua) # Headers related things +include("${CMAKE_CURRENT_SOURCE_DIR}/PreCompiledHeaders.cmake") target_include_directories(Vivy PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/inc) -target_precompile_headers(Vivy PRIVATE - # Private Vivy headers - ${Vivy_INC} - - # STL headers - <memory> - <vector> - <atomic> - - # Qt headers - <QString> - <QList> - <QVector> - <QMap> - <QWidget> - <QIcon> -) +target_precompile_headers(Vivy PRIVATE ${Vivy_PRECOMPILED_INC}) # Set Vivy's needed C++ features target_compile_features(Vivy PRIVATE @@ -121,7 +106,7 @@ target_link_libraries(Vivy PRIVATE -fopenmp) target_compile_definitions(Vivy PRIVATE QT_DISABLE_DEPRECATED_BEFORE=0x050F00 QT_NO_CAST_TO_ASCII - # QT_RESTRICTED_CAST_FROM_ASCII + QT_RESTRICTED_CAST_FROM_ASCII QTCREATOR_UTILS_STATIC_LIB ) @@ -130,9 +115,10 @@ if (${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang") target_compile_options(Vivy PRIVATE -Weverything - # Disable some things because we want C++20 and don't constrol some Qt - # generated files... Also permit VLA + # Disable some things because we want C++20 and don't control some Qt + # generated files... -Wno-c++98-compat -Wno-c++98-c++11-c++14-c++17-compat-pedantic + -Wno-c++98-compat-pedantic -Wno-extra-semi-stmt -Wno-redundant-parens -Wno-padded @@ -143,7 +129,13 @@ if (${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang") -fopenmp ) elseif (${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU") - target_compile_options(Vivy PRIVATE -fopenmp) + target_compile_options(Vivy PRIVATE + -fopenmp + -Wno-subobject-linkage # Kubat: Some proglems here, it seems they + # occure because of the usage of "using" for + # type aliasing... As I won't get ride of the + # "using"s the warning is disabled + ) endif() set_target_properties(Vivy PROPERTIES diff --git a/PreCompiledHeaders.cmake b/PreCompiledHeaders.cmake new file mode 100644 index 0000000000000000000000000000000000000000..4509f913c7712f48d801e1a550e829929b81da57 --- /dev/null +++ b/PreCompiledHeaders.cmake @@ -0,0 +1,143 @@ +# System headers +set(STL_INC + PRIVATE + <memory> + <vector> + <atomic> + <algorithm> + <stdexcept> + <sstream> + <map> + <locale> + <functional> + <initializer_list> + <cstring> + <string> + <string_view> + <cstdio> + <iostream> + <optional> + <type_traits> + <chrono> + <climits> + <ctype.h> + <mutex> +) + +set(EXT_INC PRIVATE + <mpv/client.h> + "${CMAKE_CURRENT_SOURCE_DIR}/vendor/lua-5.4.3/src/lua.hpp" +) + +set(QT_STRUCT_INC + PRIVATE + <QtGlobal> + <QObject> + <QRegularExpression> + <QRegExp> + <QString> + <QVariant> + <QFile> + <QDebug> + <QHash> + <QDir> + <QTextStream> + <QFileInfo> + <QMessageLogger> + <QUuid> + <QPointer> + <QProcess> + <QTimer> + <QStack> + <QTemporaryFile> + <QStandardPaths> + <QList> + <QVector> + <QMap> + <QStringList> + <QJsonObject> + <QJsonArray> + <QJsonDocument> + <QJsonValue> +) + +set(QT_WIDGET_INC + PRIVATE + <QCoreApplication> + <QTextDocument> + <QColor> + <QWidget> + <QIcon> + <QFont> + <QPixmap> + <QApplication> + <QCloseEvent> + <QLabel> + <QPushButton> + <QVBoxLayout> + <QHBoxLayout> + <QEvent> + <QMouseEvent> + <QTextEdit> + <QDockWidget> + <QMenu> + <QFileDialog> + <QMutex> + <QMainWindow> + <QPainter> + <QPlainTextEdit> + <QSlider> + <QAbstractItemModel> + <QHeaderView> + <QPaintEvent> + <QTableView> + <QStyledItemDelegate> + <QMessageBox> + <QScrollBar> + <QScrollArea> + <QGraphicsPixmapItem> + <QGraphicsSceneMouseEvent> + <QPen> + <QAction> + <QGraphicsItem> + <QGraphicsLineItem> + <QTreeView> + <QGraphicsView> + <QGraphicsScene> + <QWindow> + <QAbstractScrollArea> + <QGraphicsLineItem> + <QGraphicsPixmapItem> + <QClipboard> + <QInputMethodEvent> + <QKeyEvent> + <QLineEdit> + <QTextBlock> + <QTextCursor> + <QTextDocumentFragment> + <QMimeData> + <QSharedPointer> + <QStatusBar> + <QBrush> + <QFrame> + <QSyntaxHighlighter> + <QTextCharFormat> + <QTabWidget> + <QLayoutItem> + <QLayout> + <QEventLoop> + <QImage> + <QToolBar> + <QMenuBar> + <QDialogButtonBox> + <QFileIconProvider> + <QFontDatabase> + <QTextCodec> +) + +set(Vivy_PRECOMPILED_INC PRIVATE + ${STL_INC} + ${QT_STRUCT_INC} + ${QT_WIDGET_INC} + ${EXT_INC} +) diff --git a/rsc/VivyRessources.qrc b/rsc/VivyRessources.qrc index 9078302c70ef82f2cf1a5eda9c0d82fc0b3ec51a..f86514d871aa19755d64bb9729a9771dad6b6240 100644 --- a/rsc/VivyRessources.qrc +++ b/rsc/VivyRessources.qrc @@ -33,6 +33,7 @@ <file alias="document-open.svg">icons/breeze-dark/document-open.svg</file> <file alias="document-save.svg">icons/breeze-dark/document-save.svg</file> <file alias="document-save-as.svg">icons/breeze-dark/document-save-as.svg</file> + <file alias="document-rename.svg">icons/breeze-dark/edit-rename.svg</file> <file alias="folder.svg">icons/breeze-dark/folder.svg</file> <file alias="text-x-generic.svg">icons/breeze-dark/text-x-generic.svg</file> <file alias="help-about.svg">icons/breeze-dark/help-about.svg</file> @@ -45,6 +46,7 @@ <file alias="document-open.svg">icons/breeze-light/document-open.svg</file> <file alias="document-save.svg">icons/breeze-light/document-save.svg</file> <file alias="document-save-as.svg">icons/breeze-light/document-save-as.svg</file> + <file alias="document-rename.svg">icons/breeze-light/edit-rename.svg</file> <file alias="folder.svg">icons/breeze-light/folder.svg</file> <file alias="text-x-generic.svg">icons/breeze-light/text-x-generic.svg</file> <file alias="help-about.svg">icons/breeze-light/help-about.svg</file> diff --git a/rsc/icons/breeze-dark/edit-rename.svg b/rsc/icons/breeze-dark/edit-rename.svg new file mode 100644 index 0000000000000000000000000000000000000000..6a844965eafca88604c3705f6f364bfc2ad28b52 --- /dev/null +++ b/rsc/icons/breeze-dark/edit-rename.svg @@ -0,0 +1,13 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"> + <defs id="defs3051"> + <style type="text/css" id="current-color-scheme"> + .ColorScheme-Text { + color:#eff0f1; + } + </style> + </defs> + <path style="fill:currentColor;fill-opacity:1;stroke:none" + d="M 10.398438 2 L 5.2871094 7.1113281 L 2 10.398438 L 2 14 L 5.6015625 14 L 14 5.6015625 L 10.398438 2 z M 8.3496094 5.4902344 L 10.509766 7.6503906 L 7.3359375 10.826172 L 7.3359375 10.150391 L 6.3222656 10.171875 L 5.2871094 10.171875 L 5.2871094 9.1367188 L 5.2871094 8.5507812 L 6.7285156 7.1113281 L 8.3496094 5.4902344 z M 4.2734375 9.5644531 L 4.2734375 11.185547 L 5.3085938 11.185547 L 6.3007812 11.185547 L 6.3222656 11.837891 L 5.2421875 12.919922 L 3.8007812 12.919922 L 3.0800781 12.199219 L 3.0800781 10.757812 L 4.2734375 9.5644531 z " + class="ColorScheme-Text" + /> +</svg> diff --git a/rsc/icons/breeze-light/edit-rename.svg b/rsc/icons/breeze-light/edit-rename.svg new file mode 100644 index 0000000000000000000000000000000000000000..18ccc58a83cd18f21997fafe18029787255f6117 --- /dev/null +++ b/rsc/icons/breeze-light/edit-rename.svg @@ -0,0 +1,13 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"> + <defs id="defs3051"> + <style type="text/css" id="current-color-scheme"> + .ColorScheme-Text { + color:#232629; + } + </style> + </defs> + <path style="fill:currentColor;fill-opacity:1;stroke:none" + d="M 10.398438 2 L 5.2871094 7.1113281 L 2 10.398438 L 2 14 L 5.6015625 14 L 14 5.6015625 L 10.398438 2 z M 8.3496094 5.4902344 L 10.509766 7.6503906 L 7.3359375 10.826172 L 7.3359375 10.150391 L 6.3222656 10.171875 L 5.2871094 10.171875 L 5.2871094 9.1367188 L 5.2871094 8.5507812 L 6.7285156 7.1113281 L 8.3496094 5.4902344 z M 4.2734375 9.5644531 L 4.2734375 11.185547 L 5.3085938 11.185547 L 6.3007812 11.185547 L 6.3222656 11.837891 L 5.2421875 12.919922 L 3.8007812 12.919922 L 3.0800781 12.199219 L 3.0800781 10.757812 L 4.2734375 9.5644531 z " + class="ColorScheme-Text" + /> +</svg> diff --git a/src/Lib/AbstractDocument.cc b/src/Lib/AbstractDocument.cc index 4cf89ed4bb61bd50ba7149d0d6bb81bf2269a647..def32ead006169ef84f7ac23979d74f1f77598c3 100644 --- a/src/Lib/AbstractDocument.cc +++ b/src/Lib/AbstractDocument.cc @@ -1,7 +1,7 @@ #include "AbstractDocument.hh" bool -Vivy::operator==(const AbstractDocument &a, const AbstractDocument &b) noexcept +operator==(const Vivy::AbstractDocument &a, const Vivy::AbstractDocument &b) noexcept { return a.getUuid() == b.getUuid(); } diff --git a/src/Lib/AbstractDocument.hh b/src/Lib/AbstractDocument.hh index 5a22f96aa7eb51b416dafc004d1b34b2d006e5be..51ed90b0f6c1b4aae910e30d4554d42b32d5442d 100644 --- a/src/Lib/AbstractDocument.hh +++ b/src/Lib/AbstractDocument.hh @@ -7,8 +7,6 @@ #include "Utils.hh" #include "Uuid.hh" -#include <QFileInfo> -#include <QDir> namespace Vivy { @@ -16,6 +14,41 @@ class AbstractDocument : public QObject { Q_OBJECT VIVY_UNMOVABLE_OBJECT(AbstractDocument) + void copyOrRenameWith(const QFileInfo &newFile, auto action, auto success) + { + const QFileInfo oldFile(getName()); + + // Create folder if needed + QDir dirOp; + const QString newAbsDirPath = newFile.dir().absolutePath(); + if (!dirOp.exists(newAbsDirPath)) { + qInfo() << "Create folder " << newAbsDirPath; + dirOp.mkpath(newAbsDirPath); + } + + if (newFile.absoluteFilePath() == oldFile.absoluteFilePath()) { + throw std::runtime_error("Can't rename or copy a file to itself!"); + } + + if (newFile.exists()) { + qWarning() << "Deleting the already existing" << newFile; + if (!dirOp.remove(newFile.absoluteFilePath())) + throw std::runtime_error("Failed to remove " + + newFile.absoluteFilePath().toStdString()); + } + + if (action(oldFile.absoluteFilePath(), newFile.absoluteFilePath())) { + success(); + save(); + } + + else { + throw std::runtime_error("Failed to copy or rename " + + oldFile.absoluteFilePath().toStdString() + " to " + + newFile.absoluteFilePath().toStdString()); + } + } + public: enum class Type : quint64 { Vivy = Utils::toUnderlying(Utils::DocumentType::Vivy), @@ -31,25 +64,22 @@ protected: // Automate a part of the rename process, just need to provide a "success" // callback and the new file info. - bool renameWith(const QFileInfo &newFile, auto success) noexcept + void copyWith(const QFileInfo &newFile, auto success) { - const QFileInfo oldFile(getName()); - - // Create folder if needed - QDir dirOp; - const QString newAbsDirPath = newFile.dir().absolutePath(); - if (!dirOp.exists(newAbsDirPath)) { - qInfo() << "Create folder " << newAbsDirPath; - dirOp.mkpath(newAbsDirPath); - } - - if (dirOp.rename(oldFile.absoluteFilePath(), newFile.absoluteFilePath())) { - success(); - return true; - } + auto action = [](const QString &oldFile, const QString &newFileName) noexcept -> bool { + return QFile::copy(oldFile, newFileName); + }; + copyOrRenameWith(newFile, action, success); + } - qCritical() << "Failed to rename" << oldFile << "to" << newFile; - return false; + // Automate a part of the rename process, just need to provide a "success" + // callback and the new file info. + void renameWith(const QFileInfo &newFile, auto success) + { + auto action = [](const QString &oldFile, const QString &newFileName) noexcept -> bool { + return QFile::rename(oldFile, newFileName); + }; + copyOrRenameWith(newFile, action, success); } Type type; @@ -57,7 +87,9 @@ protected: const Uuid uuid{}; public: - virtual bool rename(const QString &) noexcept = 0; + virtual void copy(const QString &) = 0; + virtual void rename(const QString &) = 0; + virtual void save() = 0; QString getName() const noexcept { return name; } Type getType() const noexcept { return type; } @@ -66,8 +98,8 @@ public: signals: void documentChanged(); }; - -bool operator==(const AbstractDocument &a, const AbstractDocument &b) noexcept; } +bool operator==(const Vivy::AbstractDocument &a, const Vivy::AbstractDocument &b) noexcept; + #endif // VIVY_ABSTRACT_DOCUMENT_H diff --git a/src/Lib/AbstractMediaContext.hh b/src/Lib/AbstractMediaContext.hh new file mode 100644 index 0000000000000000000000000000000000000000..f4a09ed6c040a96dcbea04d0b71fe2491430f727 --- /dev/null +++ b/src/Lib/AbstractMediaContext.hh @@ -0,0 +1,225 @@ +#pragma once + +#ifndef __cplusplus +#error "This is a C++ header" +#endif + +extern "C" { +#include <libavutil/opt.h> +#include <libavcodec/avcodec.h> +#include <libavformat/avformat.h> +#include <libswresample/swresample.h> +#include <libavcodec/avfft.h> +#include <memory.h> +} + +#include "Utils.hh" + +namespace Vivy +{ +static inline constexpr auto codecContexteleter = [](AVCodecContext *ptr) noexcept -> void { + if (ptr) + avcodec_free_context(&ptr); +}; +static constexpr inline auto dataDeleter = [](double *ptr) noexcept -> void { + if (ptr) + av_free(ptr); +}; +static constexpr inline auto avFrameDeleter = [](AVFrame *ptr) noexcept -> void { + if (ptr) + av_frame_free(&ptr); +}; + +static constexpr inline auto swrContenxtDeleter = [](SwrContext *swr) noexcept -> void { + if (swr) + swr_free(&swr); +}; + +static inline constexpr auto avFormatContextDeleter = [](AVFormatContext *ptr) noexcept -> void { + if (ptr) + avformat_free_context(ptr); +}; + +using AVFormatContextPtr = std::unique_ptr<AVFormatContext, decltype(avFormatContextDeleter)>; +using AVCodecContextPtr = std::unique_ptr<AVCodecContext, decltype(codecContexteleter)>; +using AVFramePtr = std::unique_ptr<AVFrame, decltype(avFrameDeleter)>; +using SwrContextPtr = std::unique_ptr<SwrContext, decltype(swrContenxtDeleter)>; + +// Simple abstract class to be inherited by video streams and audio streams. +template <size_t AVMEDIA_TYPE> class AbstractMediaStream { + VIVY_UNMOVABLE_OBJECT(AbstractMediaStream) + +public: + static inline constexpr AVMediaType avMediaType = static_cast<AVMediaType>(AVMEDIA_TYPE); + +protected: + using Super = AbstractMediaStream<AVMEDIA_TYPE>; + +protected: + AbstractMediaStream(AVCodec *streamCodec, AVFormatContext *formatArg, AVStream *streamArg, + int index) + : codecId(streamArg->codecpar->codec_id) + , codec(streamCodec) + , codecParams(streamArg->codecpar) + , dataFormat(formatArg) + , stream(streamArg) + , streamIndexInContext(index) + { + if (codec == nullptr) + throw std::runtime_error("failed to find a decoder for stream"); + + codecContext.reset(avcodec_alloc_context3(codec)); + if (!codecContext) + throw std::runtime_error("failed to allocate codec context"); + + if (avcodec_parameters_to_context(codecContext.get(), codecParams) < 0) + throw std::runtime_error("failed to copy parameters to codec context"); + + if (avcodec_open2(codecContext.get(), codec, nullptr) < 0) + throw std::runtime_error("failed to open audio decoder for a stream"); + + qDebug() << "[Stream] Codec" << codec->name << "id:" << codecId; + qDebug() << "[Stream] sample rate:" << codecParams->sample_rate; + qDebug() << "[Stream] bit rate: " << codecParams->bit_rate; + qDebug() << "[Stream] channels: " << codecParams->channels; + } + +public: + virtual ~AbstractMediaStream() noexcept {} + int getStreamIndex() const noexcept { return streamIndexInContext; } + QString getName() const noexcept { return QString::fromUtf8(codec->name); } + AVCodecID getCodecId() const noexcept { return codecId; } + + virtual QJsonObject getProperties() const noexcept + { + QJsonObject ret; + ret.insert("Codec name", codec->name); + return ret; + } + +protected: + // Codec related informations + AVCodecID codecId{ AV_CODEC_ID_NONE }; + AVCodec *codec{ nullptr }; + AVCodecParameters *codecParams{ nullptr }; + AVCodecContextPtr codecContext{ nullptr }; + AVFormatContext *dataFormat{ nullptr }; + + // Stream is held by AudioContext + AVStream *stream{ nullptr }; + + // Store the index of this stream + int streamIndexInContext; +}; + +template <typename Stream> class AbstractMediaContext { + VIVY_UNMOVABLE_OBJECT(AbstractMediaContext) + +public: + static inline constexpr AVMediaType avMediaType = Stream::avMediaType; + using StreamPtr = std::shared_ptr<Stream>; + using StreamWeakPtr = std::weak_ptr<Stream>; + +protected: + using Super = AbstractMediaContext<Stream>; + +public: + AbstractMediaContext(const QString &path) + : filePath(path) + { + if (!format) + throw std::runtime_error("out of memory, can't create allocate the AVFormatContext"); + + const std::string filePathStdHolder = filePath.toStdString(); + const char *filename = filePathStdHolder.c_str(); + AVFormatContext *formatPtr = format.get(); + + // Get the format from the file + if (avformat_open_input(&formatPtr, filename, nullptr, nullptr) != 0) { + [[maybe_unused]] void *relatedOnFailure = + format.release(); // freed by avformat_open_input + throw std::runtime_error("failed to open file"); + } + + if (avformat_find_stream_info(formatPtr, nullptr) < 0) { + throw std::runtime_error("failed to get audio stream info"); + } + + // Populate all the stream indexes + for (uint i = 0; i < format->nb_streams; ++i) { + AVStream *itFmt = format->streams[i]; + AVCodecParameters *params = itFmt->codecpar; + AVCodec *streamCodec = avcodec_find_decoder(params->codec_id); + if (streamCodec && streamCodec->type == avMediaType) + audioStreams.insert(i, std::make_shared<Stream>(streamCodec, formatPtr, itFmt, i)); + } + + // Get the default stream + defaultStreamIndex = av_find_best_stream(formatPtr, avMediaType, + -1, // Let AV find one stream + -1, // We don't want related streams + nullptr, 0); + if (defaultStreamIndex < 0) + qCritical() << "Could not find the best stream"; + + qDebug() << "Opened context for" << path << "with duration" << formatPtr->duration + << "and default stream index" << defaultStreamIndex; + } + + virtual ~AbstractMediaContext() {} + + StreamWeakPtr getStream(int index) const noexcept + { + if (index < 0) + return StreamWeakPtr{ spareNullSreamPtr }; + + uint unsignedIndex = static_cast<uint>(index); + const auto found = audioStreams.find(unsignedIndex); + + if (found != audioStreams.end()) + return StreamWeakPtr{ *found }; + + return StreamWeakPtr{ spareNullSreamPtr }; + } + + StreamWeakPtr getDefaultStream() const noexcept + { + return (defaultStreamIndex < 0) ? StreamWeakPtr{ spareNullSreamPtr } + : getStream(defaultStreamIndex); + } + + virtual QString getElementName() const noexcept + { + return "Context: " + QFileInfo(filePath).baseName(); + } + + virtual QJsonDocument getProperties() const noexcept + { + QJsonDocument ret; + QJsonArray streams; + + QFileInfo file(filePath); + + for (const auto &audioStreamPtr : audioStreams) { + streams.append(audioStreamPtr->getProperties()); + } + + QJsonObject self{ { "Streams", streams }, { "Base name", file.baseName() } }; + + ret.setObject(self); + return ret; + } + +private: + AVFormatContextPtr format{ avformat_alloc_context(), avFormatContextDeleter }; + + const QString filePath; // Usefull information + QMap<uint, StreamPtr> audioStreams{}; // THe audio streams of the file + + int defaultStreamIndex{ -1 }; + + // Spare always null shared pointer, to be used when the audioStream[i] was + // not found. + StreamPtr spareNullSreamPtr{ nullptr }; +}; +} diff --git a/src/Lib/Ass/Ass.hh b/src/Lib/Ass/Ass.hh index acb14ff2f5c568a0db4277cb455a278526f210d0..d7081d6a3d9afc747c36a78539c847d1a274ecd0 100644 --- a/src/Lib/Ass/Ass.hh +++ b/src/Lib/Ass/Ass.hh @@ -7,6 +7,5 @@ #include "AssPrivate.hh" #include "AssFactory.hh" #include "StyleProperties.hh" -#include <memory.h> #endif // VIVY_ASS_ASS_H diff --git a/src/Lib/Ass/AssFactory.cc b/src/Lib/Ass/AssFactory.cc index a8768767f6b4e9e3cb5b0a68e354be62ffdc0131..08ca4b7e08e93d57f0ca273adb463fb27b2032cf 100644 --- a/src/Lib/Ass/AssFactory.cc +++ b/src/Lib/Ass/AssFactory.cc @@ -1,8 +1,5 @@ #include "AssFactory.hh" -#include <algorithm> -#include <stdexcept> - using namespace Vivy::Ass; bool diff --git a/src/Lib/Ass/AssFactory.hh b/src/Lib/Ass/AssFactory.hh index f21fddb15a6b33a60917b0eb0371b4cd33592599..b220c0f1995a4c633664cb42554505080b494452 100644 --- a/src/Lib/Ass/AssFactory.hh +++ b/src/Lib/Ass/AssFactory.hh @@ -6,12 +6,6 @@ #include "Line.hh" #include "AssPrivate.hh" -#include <memory> -#include <QFile> -#include <QMap> -#include <QVariant> -#include <QString> - // An ASS file is basically an INI file, but some keys are present multiple // times (V4+ Styles/Style, Events/Dialogue, Events/Comment). @@ -21,11 +15,7 @@ class AssFactory final { VIVY_UNMOVABLE_OBJECT(AssFactory) public: - enum class Section { - ScriptInfo = 1, - Styles = 2, - Events = 3, - }; + enum class Section { ScriptInfo = 1, Styles = 2, Events = 3 }; using SectionContent = QMap<QString, QVariant>; diff --git a/src/Lib/Ass/AssPrivate.hh b/src/Lib/Ass/AssPrivate.hh index 71f9c7ee1b3fcfddb33e7961a01d7a96ab39bc1d..39d931a99d8c92c1d0d83d0d44de97cdaed20e38 100644 --- a/src/Lib/Ass/AssPrivate.hh +++ b/src/Lib/Ass/AssPrivate.hh @@ -1,7 +1,5 @@ #pragma once -#include <memory> - namespace Vivy::Ass { class Style; diff --git a/src/Lib/Ass/Line.cc b/src/Lib/Ass/Line.cc index c509af7449227b27421f446ed348c3c23121474d..fe0b257ab4585b7d0d962f2577177238cd328db9 100644 --- a/src/Lib/Ass/Line.cc +++ b/src/Lib/Ass/Line.cc @@ -1,8 +1,6 @@ #include "Line.hh" #include "AssFactory.hh" -#include <QRegularExpression> - using namespace Vivy::Ass; Line::Line(AssFactory *const factory, const QString &lineString) diff --git a/src/Lib/Ass/Line.hh b/src/Lib/Ass/Line.hh index 7463dad6692230fe9627ff83f21d4f32290f1862..15984da129990d512c3cdc388b67143a4d808d08 100644 --- a/src/Lib/Ass/Line.hh +++ b/src/Lib/Ass/Line.hh @@ -1,8 +1,5 @@ -#ifndef VIVY_ASS_LINE_H -#define VIVY_ASS_LINE_H +#pragma once -#include <QString> -#include <QtGlobal> #include "Syl.hh" #include "StyleProperties.hh" #include "Style.hh" @@ -47,7 +44,4 @@ public: private: void initSylFromString(const QString &) noexcept; }; - } - -#endif // VIVY_ASS_LINE_H diff --git a/src/Lib/Ass/Style.cc b/src/Lib/Ass/Style.cc index 4c2fd133bbfbb691d292047865a68dd0d9044eb4..1a6ce05cc1658ca8169c81a338917cb37f535ba8 100644 --- a/src/Lib/Ass/Style.cc +++ b/src/Lib/Ass/Style.cc @@ -1,8 +1,5 @@ #include "Style.hh" - -#include <stdexcept> -#include <QJsonDocument> -#include <QJsonObject> +#include "../Utils.hh" using namespace Vivy::Ass; @@ -179,13 +176,13 @@ Style::getProperties() const noexcept }; QJsonObject styleFontProperties{ - { "Scale X", static_cast<const double>(scaleX) }, - { "Scale Y", static_cast<const double>(scaleY) }, - { "Spacing", static_cast<const double>(spacing) }, - { "Angle", static_cast<const double>(angle) }, - { "Border Style", static_cast<const double>(borderStyle) }, - { "Outline", static_cast<const double>(outline) }, - { "Shadow", static_cast<const double>(shadow) }, + { "Scale X", static_cast<double>(scaleX) }, + { "Scale Y", static_cast<double>(scaleY) }, + { "Spacing", static_cast<double>(spacing) }, + { "Angle", static_cast<double>(angle) }, + { "Border Style", static_cast<double>(borderStyle) }, + { "Outline", static_cast<double>(outline) }, + { "Shadow", static_cast<double>(shadow) }, }; QJsonObject styleMargins{ diff --git a/src/Lib/Ass/Style.hh b/src/Lib/Ass/Style.hh index 9b9bba686f6e61e10b8227a791c73ef288f6e2fc..696cfc79e4b082a14e8b3ad295e31455611f283c 100644 --- a/src/Lib/Ass/Style.hh +++ b/src/Lib/Ass/Style.hh @@ -1,20 +1,17 @@ -#ifndef VIVY_STYLE_H -#define VIVY_STYLE_H - -#include <QString> -#include <QVector> -#include <QtGlobal> -#include <QColor> -#include <QObject> +#pragma once + #include "AssPrivate.hh" namespace Vivy::Ass { -namespace Color -{ -QColor fromString(const QString &) noexcept; -QString toString(const QColor &) noexcept; -static inline const QColor defaultValue = QColor(0, 0, 0, 0); +struct Color { + static QColor fromString(const QString &) noexcept; + static QString toString(const QColor &) noexcept; + + static inline const QColor defaultValue = QColor(0, 0, 0, 0); + +private: + Color() = default; }; class Style final { @@ -47,7 +44,4 @@ public: QString getElementName() const noexcept; QJsonDocument getProperties() const noexcept; }; - } - -#endif diff --git a/src/Lib/Ass/StyleProperties.hh b/src/Lib/Ass/StyleProperties.hh index 374d9e2afb3a42f4c5d195c33fafe5a1494f09c8..a72acf98782c66af25a4c8f960e81e1fd6022b05 100644 --- a/src/Lib/Ass/StyleProperties.hh +++ b/src/Lib/Ass/StyleProperties.hh @@ -1,8 +1,4 @@ -#ifndef VIVY_ASS_STYLE_PROPERTIES_H -#define VIVY_ASS_STYLE_PROPERTIES_H - -#include <QColor> -#include <QString> +#pragma once #include "Style.hh" namespace Vivy::Ass @@ -31,7 +27,4 @@ struct StyleProperties final { // Alignement is bottom center int alignment{ 2 }; }; - } - -#endif // VIVY_ASS_STYLE_PROPERTIES_H diff --git a/src/Lib/Ass/Syl.hh b/src/Lib/Ass/Syl.hh index 79c7a9bc376c028ff9b283d7d12aca3ece0414c0..084e14e5f93ea11e001260bae8543aeef48c96d9 100644 --- a/src/Lib/Ass/Syl.hh +++ b/src/Lib/Ass/Syl.hh @@ -21,8 +21,8 @@ public: public: enum class ConstructMode { - Raw, // Don't read ASS tags - ReadAssTags, // Read ass tags + Raw, // Don't read ASS tags + ReadAssTags // Read ass tags }; explicit Syl(const Syl &) noexcept = default; diff --git a/src/Lib/Audio.cc b/src/Lib/Audio.cc index 8c492175f4849f7381af719a860cca4d73f1e706..218763c297441fe96745decac1d076922f209f15 100644 --- a/src/Lib/Audio.cc +++ b/src/Lib/Audio.cc @@ -1,133 +1,34 @@ #include "Audio.hh" -#include <QJsonObject> -#include <QJsonArray> - using namespace Vivy; // AudioContext class implementation // Create an audio contecxt from a file AudioContext::AudioContext(const QString &path) - : filePath(path) -{ - if (!format) - throw std::runtime_error("out of memory, can't create allocate the AVFormatContext"); - - const std::string filePathStdHolder = filePath.toStdString(); - const char *filename = filePathStdHolder.c_str(); - AVFormatContext *formatPtr = format.get(); - - // Get the format from the audio file - if (avformat_open_input(&formatPtr, filename, nullptr, nullptr) != 0) { - [[maybe_unused]] void *relatedOnFailure = format.release(); // freed by avformat_open_input - throw std::runtime_error("failed to open file"); - } - - if (avformat_find_stream_info(formatPtr, nullptr) < 0) { - throw std::runtime_error("failed to get audio stream info"); - } - - // Populate all the stream indexes - for (uint i = 0; i < format->nb_streams; ++i) { - AVStream *itFormat = format->streams[i]; - AVCodecParameters *params = itFormat->codecpar; - AVCodec *streamCodec = avcodec_find_decoder(params->codec_id); - if (streamCodec && streamCodec->type == AVMEDIA_TYPE_AUDIO) { - audioStreams.insert(i, std::make_shared<Stream>(streamCodec, formatPtr, itFormat, i)); - } - } - - // Get the default stream - defaultStreamIndex = av_find_best_stream(formatPtr, AVMEDIA_TYPE_AUDIO, - -1, // Let AV find one stream - -1, // We don't want related streams - nullptr, 0); - if (defaultStreamIndex < 0) { - qCritical() << "Could not find the best audio stream"; - } - - qDebug() << "Opened audio context for" << path << "with duration" << formatPtr->duration - << "and default stream index" << defaultStreamIndex; -} - -// Get a specific audio stream, try to lock the weak pointer. if the index was -// not present the used_count will be 0 and you won't be able to lock it. -AudioContext::StreamWeakPtr -AudioContext::getStream(int index) const noexcept + : Super(path) { - if (index < 0) - return StreamWeakPtr{ spareNullSreamPtr }; - - uint unsignedIndex = static_cast<uint>(index); - const auto found = audioStreams.find(unsignedIndex); - - if (found != audioStreams.end()) - return StreamWeakPtr{ *found }; - - return StreamWeakPtr{ spareNullSreamPtr }; -} - -// Get the default stream of this audio context. If no default audio stream is -// present you won't be able to lock the weak pointer. -AudioContext::StreamWeakPtr -AudioContext::getDefaultStream() const noexcept -{ - if (defaultStreamIndex < 0) - return StreamWeakPtr{ spareNullSreamPtr }; - return getStream(defaultStreamIndex); } QString AudioContext::getElementName() const noexcept { - QFileInfo file(filePath); - return "AudioContext: " + file.baseName(); + return "Audio" + Super::getElementName(); } QJsonDocument AudioContext::getProperties() const noexcept { - QJsonDocument ret; - QJsonArray streams; - - QFileInfo file(filePath); - - for (const auto &audioStreamPtr : audioStreams) { - streams.append(audioStreamPtr->getProperties()); - } - - QJsonObject self{ { "Streams", streams }, { "Base name", file.baseName() } }; - - ret.setObject(self); - return ret; + return Super::getProperties(); } -// AudioContext::Stream class implementation +// AudioStream class implementation // Constructor, need an AVFormat and an AVStream -AudioContext::Stream::Stream(AVCodec *streamCodec, AVFormatContext *formatPtr, AVStream *stream, - int index) - : codecId(stream->codecpar->codec_id) - , codec(streamCodec) - , codecParams(stream->codecpar) - , audioStream(stream) - , streamIndexInAudioContext(index) - , dataFormat(formatPtr) +AudioStream::AudioStream(AVCodec *streamCodec, AVFormatContext *formatPtr, AVStream *streamArg, + int index) + : Super(streamCodec, formatPtr, streamArg, index) { - if (codec == nullptr) - throw std::runtime_error("failed to find a decoder for stream"); - - codecContext.reset(avcodec_alloc_context3(codec)); - if (!codecContext) - throw std::runtime_error("failed to allocate codec context"); - - if (avcodec_parameters_to_context(codecContext.get(), codecParams) < 0) - throw std::runtime_error("failed to copy parameters to codec context"); - - if (avcodec_open2(codecContext.get(), codec, nullptr) < 0) - throw std::runtime_error("failed to open audio decoder for a stream"); - SwrContext *s = dataSwrContext.get(); av_opt_set_int(s, "in_channel_count", codecContext->channels, 0); av_opt_set_int(s, "out_channel_count", 1, 0); @@ -140,14 +41,9 @@ AudioContext::Stream::Stream(AVCodec *streamCodec, AVFormatContext *formatPtr, A swr_init(s); if (!swr_is_initialized(s)) throw std::runtime_error("failed to initialize SwrContext resampler"); - - qDebug() << "[Stream] Codec" << codec->name << "id:" << codecId; - qDebug() << "[Stream] sample rate:" << codecParams->sample_rate; - qDebug() << "[Stream] bit rate: " << codecParams->bit_rate; - qDebug() << "[Stream] channels: " << codecParams->channels; } -AudioContext::Stream::~Stream() noexcept +AudioStream::~AudioStream() noexcept { if (dataPtr) { qDebug() << "Free data ptr"; @@ -157,10 +53,9 @@ AudioContext::Stream::~Stream() noexcept } QJsonObject -AudioContext::Stream::getProperties() const noexcept +AudioStream::getProperties() const noexcept { - QJsonObject ret; - ret.insert("Codec name", codec->name); + QJsonObject ret = Super::getProperties(); ret.insert("Sample rate", codecParams->sample_rate); ret.insert("Bit rate", static_cast<int>(codecParams->bit_rate)); ret.insert("Channels", codecParams->channels); @@ -169,11 +64,11 @@ AudioContext::Stream::getProperties() const noexcept // Decode the data, don't call it if data already decoded void -AudioContext::Stream::decodeData() +AudioStream::decodeData() { if (isDecoded()) throw std::logic_error("audio stream is already resampled"); - qDebug() << "Launch decoding of stream" << streamIndexInAudioContext; + qDebug() << "Launch decoding of stream" << streamIndexInContext; AVPacket packet; av_init_packet(&packet); @@ -181,7 +76,7 @@ AudioContext::Stream::decodeData() // Iterate through frames while (av_read_frame(dataFormat, &packet) >= 0) { // Only decode audio - if (packet.stream_index != streamIndexInAudioContext) { + if (packet.stream_index != streamIndexInContext) { av_packet_unref(&packet); continue; } @@ -242,13 +137,13 @@ AudioContext::Stream::decodeData() av_packet_unref(&packet); } - qDebug() << "Decoding data finished for stream" << streamIndexInAudioContext + qDebug() << "Decoding data finished for stream" << streamIndexInContext << "dataPtr =" << dataPtr << "with dataSize =" << dataSize; } // Delete decoded data, clean up thing void -AudioContext::Stream::cleanUpData() noexcept +AudioStream::cleanUpData() noexcept { free(dataPtr); dataPtr = nullptr; @@ -257,14 +152,14 @@ AudioContext::Stream::cleanUpData() noexcept // Get the number of channels in the audio context stream int -AudioContext::Stream::getChannels() const noexcept +AudioStream::getChannels() const noexcept { return codecContext->channels; } // Get the sample rate int -AudioContext::Stream::getSampleRate() const noexcept +AudioStream::getSampleRate() const noexcept { return codecContext->sample_rate; } @@ -272,35 +167,21 @@ AudioContext::Stream::getSampleRate() const noexcept // Get the sample rate of the decoded data (need to call decodeData for that // value to have a meaning). int -AudioContext::Stream::getDataSampleRate() const noexcept +AudioStream::getDataSampleRate() const noexcept { return resamplerSampleRate; } -// Get the name of the codec used to decode the stream -QString -AudioContext::Stream::getName() const noexcept -{ - return QString(codec->name); -} - -// Get the codec's ID used to decode the stream -AVCodecID -AudioContext::Stream::getCodecId() const noexcept -{ - return codecId; -} - // Get the bit rate of the stream qint64 -AudioContext::Stream::getBitRate() const noexcept +AudioStream::getBitRate() const noexcept { return codecContext->bit_rate; } // Get the information about the decoded state of this stream bool -AudioContext::Stream::isDecoded() const noexcept +AudioStream::isDecoded() const noexcept { return dataPtr != nullptr; } @@ -308,7 +189,7 @@ AudioContext::Stream::isDecoded() const noexcept // Get the decoded data's size, will return 0 if the data is not decoded. Don't // rely on this behaviour to get the decoded state of the stream! size_t -AudioContext::Stream::getDecodedDataSize() const noexcept +AudioStream::getDecodedDataSize() const noexcept { return dataSize; } @@ -316,28 +197,22 @@ AudioContext::Stream::getDecodedDataSize() const noexcept // Get the decoded data, safe to call even if the data is not decoded has the // pointer will be null. You must check it with an `if` statement. double * -AudioContext::Stream::getDecodedData() const noexcept +AudioStream::getDecodedData() const noexcept { return dataPtr; } // Get the chunk size of the decoded data size_t -AudioContext::Stream::getDecodedChunkSize() const noexcept +AudioStream::getDecodedChunkSize() const noexcept { return 512; } // Get the decalage of the decoded data size_t -AudioContext::Stream::getDecodedDecalage() const noexcept +AudioStream::getDecodedDecalage() const noexcept { constexpr size_t overlap = 128; // The overlap return getDecodedChunkSize() - overlap; } - -int -AudioContext::Stream::getStreamIndex() const noexcept -{ - return streamIndexInAudioContext; -} diff --git a/src/Lib/Audio.hh b/src/Lib/Audio.hh index 3a7ad817e1a6c65d76b36c7084b8098d1e6f63eb..920b1033228943d2ffc2a63c435682e06a7883fe 100644 --- a/src/Lib/Audio.hh +++ b/src/Lib/Audio.hh @@ -1,157 +1,69 @@ -#ifndef VIVY_AUDIO_H -#define VIVY_AUDIO_H +#pragma once #ifndef __cplusplus #error "This is a C++ header" #endif -extern "C" { -#include <libavutil/opt.h> -#include <libavcodec/avcodec.h> -#include <libavformat/avformat.h> -#include <libswresample/swresample.h> -#include <libavcodec/avfft.h> -} - -#include "Utils.hh" -#include <QtGlobal> -#include <QMap> -#include <QVector> -#include <QString> -#include <memory.h> +#include "AbstractMediaContext.hh" namespace Vivy { class AudioContext; +// Hold all the data for a stream in an audio file. Should only be owned by +// the AudioContext class. +class AudioStream final : public AbstractMediaStream<AVMEDIA_TYPE_AUDIO> { + VIVY_UNMOVABLE_OBJECT(AudioStream) + +public: + AudioStream(AVCodec *, AVFormatContext *, AVStream *, int index); + ~AudioStream() noexcept override; + + // Decode the stream + void decodeData(); + void cleanUpData() noexcept; + bool isDecoded() const noexcept; + int getDataSampleRate() const noexcept; + size_t getDecodedDataSize() const noexcept; + size_t getDecodedChunkSize() const noexcept; + size_t getDecodedDecalage() const noexcept; + double *getDecodedData() const noexcept; + + // Some getters + int getChannels() const noexcept; + int getSampleRate() const noexcept; + qint64 getBitRate() const noexcept; + QJsonObject getProperties() const noexcept override; + +private: + // Resampled frame data + SwrContextPtr dataSwrContext{ swr_alloc(), swrContenxtDeleter }; + AVFramePtr dataFrame{ av_frame_alloc(), avFrameDeleter }; + double *dataPtr{ nullptr }; + size_t dataSize{ 0 }; + + // Constants for the resampler + static constexpr uint resamplerChunkSize = 512; + static constexpr uint resamplerOverlap = 128; + static constexpr uint resamplerDecalage = resamplerChunkSize - resamplerOverlap; + static constexpr uint resamplerSampleRate = 44100; +}; + // The memory representation of an audio file. It is created inplace and should // not be moved around. It should be used to get the properties of an audio // file and its streams. The user can use a stream from the file and get data // from it. -class AudioContext final { +class AudioContext final : public AbstractMediaContext<AudioStream> { VIVY_UNMOVABLE_OBJECT(AudioContext) public: - // Hold all the data for a stream in an audio file. Should only be owned by - // the AudioContext class. - class Stream final { - VIVY_UNMOVABLE_OBJECT(Stream) - - // All the needed deleters - static inline constexpr auto codecContexteleter = [](AVCodecContext *ptr) noexcept -> void { - if (ptr) - avcodec_free_context(&ptr); - }; - static constexpr inline auto dataDeleter = [](double *ptr) noexcept -> void { - if (ptr) - av_free(ptr); - }; - static constexpr inline auto avFrameDeleter = [](AVFrame *ptr) noexcept -> void { - if (ptr) - av_frame_free(&ptr); - }; - - static constexpr inline auto swrContenxtDeleter = [](SwrContext *swr) noexcept -> void { - if (swr) - swr_free(&swr); - }; - - // All the used types - using AVCodecContextPtr = std::unique_ptr<AVCodecContext, decltype(codecContexteleter)>; - using DataPtr = std::shared_ptr<double[]>; - using AVFramePtr = std::unique_ptr<AVFrame, decltype(avFrameDeleter)>; - using SwrContextPtr = std::unique_ptr<SwrContext, decltype(swrContenxtDeleter)>; - - public: - Stream(AVCodec *, AVFormatContext *, AVStream *, int index); - ~Stream() noexcept; - - // The non-owning view of the stream's data - using DataWeakPtr = std::weak_ptr<double[]>; - - // Decode the stream - void decodeData(); - void cleanUpData() noexcept; - bool isDecoded() const noexcept; - int getDataSampleRate() const noexcept; - size_t getDecodedDataSize() const noexcept; - size_t getDecodedChunkSize() const noexcept; - size_t getDecodedDecalage() const noexcept; - double *getDecodedData() const noexcept; - - // Some getters - int getChannels() const noexcept; - int getSampleRate() const noexcept; - QString getName() const noexcept; - AVCodecID getCodecId() const noexcept; - qint64 getBitRate() const noexcept; - - // Get the index from the audio context - int getStreamIndex() const noexcept; - - QJsonObject getProperties() const noexcept; - - private: - // Codec related informations - AVCodecID codecId{ AV_CODEC_ID_NONE }; - AVCodec *codec{ nullptr }; - AVCodecParameters *codecParams{ nullptr }; - AVCodecContextPtr codecContext{ nullptr }; - - // Stream is held by AudioContext - AVStream *audioStream{ nullptr }; - - // Store the index of this stream - int streamIndexInAudioContext; - - // Resampled frame data - AVFormatContext *dataFormat; - SwrContextPtr dataSwrContext{ swr_alloc(), swrContenxtDeleter }; - AVFramePtr dataFrame{ av_frame_alloc(), avFrameDeleter }; - double *dataPtr{ nullptr }; - size_t dataSize{ 0 }; - - // Constants for the resampler - static constexpr uint resamplerChunkSize = 512; - static constexpr uint resamplerOverlap = 128; - static constexpr uint resamplerDecalage = resamplerChunkSize - resamplerOverlap; - static constexpr uint resamplerSampleRate = 44100; - }; - - using StreamPtr = std::shared_ptr<Stream>; + using StreamPtr = std::shared_ptr<AudioStream>; + using StreamWeakPtr = std::weak_ptr<AudioStream>; public: AudioContext(const QString &path); - ~AudioContext() noexcept = default; - - // The stream non-owning view pointer - using StreamWeakPtr = std::weak_ptr<Stream>; - StreamWeakPtr getStream(int) const noexcept; - StreamWeakPtr getDefaultStream() const noexcept; - - QString getElementName() const noexcept; - QJsonDocument getProperties() const noexcept; - -private: - // Regarding the format - static inline constexpr auto avFormatContextDeleter = - [](AVFormatContext *ptr) noexcept -> void { - if (ptr) - avformat_free_context(ptr); - }; - using AVFormatContextPtr = std::unique_ptr<AVFormatContext, decltype(avFormatContextDeleter)>; - AVFormatContextPtr format{ avformat_alloc_context(), avFormatContextDeleter }; - - QString filePath; // Usefull information - QMap<uint, StreamPtr> audioStreams{}; // THe audio streams of the file - - int defaultStreamIndex{ -1 }; - - // Spare always null shared pointer, to be used when the audioStream[i] was - // not found. - StreamPtr spareNullSreamPtr{ nullptr }; + QString getElementName() const noexcept override; + QJsonDocument getProperties() const noexcept override; }; } - -#endif // VIVY_AUDIO_H diff --git a/src/Lib/CRTPStore.hh b/src/Lib/CRTPStore.hh index 94b03353c2fd1c9ed525109d4981374ad2a910ae..8324d9c606635812731708f754c7997bbe6c49e2 100644 --- a/src/Lib/CRTPStore.hh +++ b/src/Lib/CRTPStore.hh @@ -6,9 +6,6 @@ #include "Utils.hh" #include "Uuid.hh" -#include <QString> -#include <QMap> -#include <memory> #define VIVY_STORAGE_CLASS(theClassName, theDocumentName) \ VIVY_UNMOVABLE_OBJECT(theClassName) \ diff --git a/src/Lib/Document/CRTPSubDocument.cc b/src/Lib/Document/CRTPSubDocument.cc deleted file mode 100644 index ed788183a5f1acc939f1c28479d9cfeec9951e42..0000000000000000000000000000000000000000 --- a/src/Lib/Document/CRTPSubDocument.cc +++ /dev/null @@ -1,141 +0,0 @@ -#include "CRTPSubDocument.hh" - -#include <QJsonObject> -#include <QJsonDocument> - -using namespace Vivy; - -// AudioSubDocument implementation - -// Get the default stream index or -1 if not possible -int -AudioSubDocument::getDefaultStreamIndex() const noexcept -{ - if (auto ptr = getDefaultStream()) { - return ptr->getStreamIndex(); - } else { - return -1; - } -} - -// Get a pointer to the default stream, nullptr if not possible -AudioContext::StreamPtr -AudioSubDocument::getDefaultStream() const noexcept -{ - if (auto ptr = contextPtr->getDefaultStream().lock()) { - return ptr; - } else { - qCritical() << "Document deleted!"; - return nullptr; - } -} - -// Get the stream asked for, nullptr if no stream or if the index is invalid -AudioContext::StreamPtr -AudioSubDocument::getStream(int index) const noexcept -{ - if (auto ptr = contextPtr->getStream(index).lock()) { - return ptr; - } else { - return nullptr; - } -} - -// Init an audio sub-document from a file -void -AudioSubDocument::initFromPath(const QString &path) -{ - if (contextPtr) - qDebug() << "Replacing the audio contetx by a new one for file" << path; - contextPtr.reset(new AudioContext(path)); // May throw - - qDebug() << "Audio OK for" << path; -} - -QString -AudioSubDocument::getElementName() const noexcept -{ - return "AudioSubDocument"; -} - -QJsonDocument -AudioSubDocument::getProperties() const noexcept -{ - QJsonDocument ret; - QJsonObject object{ - { "Audio context", contextPtr->getProperties().object() }, - { "File", filePath }, - }; - ret.setObject(object); - return ret; -} - -// VideoSubDocument implementation - -// Init a video sub-document from a file -void -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) -{ - Ass::AssFactory factory(path); - factory.getStyles(styles); - factory.getLines(lines); -} - -QString -AssSubDocument::getElementName() const noexcept -{ - return "AssSubDocument"; -} - -const QVector<Ass::LinePtr> & -AssSubDocument::getLines() const noexcept -{ - return lines; -} - -const QVector<Ass::StylePtr> & -AssSubDocument::getStyles() const noexcept -{ - return styles; -} - -QJsonDocument -AssSubDocument::getProperties() const noexcept -{ - QJsonDocument ret; - - QJsonObject styleObject; - for (const Ass::StylePtr &style : styles) { - styleObject.insert(style->getElementName(), style->getProperties().object()); - } - - QJsonObject object{ - { "Styles", styleObject }, - { "File", filePath }, - }; - ret.setObject(object); - return ret; -} diff --git a/src/Lib/Document/CRTPSubDocument.hh b/src/Lib/Document/CRTPSubDocument.hh index eb6683d58a6b3eeddf3ed47bc1265b67791beee8..c89f25c587539613cc5ad1f0c00305629f2eb984 100644 --- a/src/Lib/Document/CRTPSubDocument.hh +++ b/src/Lib/Document/CRTPSubDocument.hh @@ -1,5 +1,4 @@ -#ifndef VIVY_CRTP_DOCUMENT_H -#define VIVY_CRTP_DOCUMENT_H +#pragma once #ifndef __cplusplus #error "This is a C++ header" @@ -7,14 +6,16 @@ #include "../Utils.hh" #include "../Audio.hh" +#include "../Video.hh" #include "../Ass/Ass.hh" -#include <QString> -#include <memory> + +#define VIVY_ENABLE_IF_TYPE(Type) \ + template <typename = typename std::enable_if<!std::is_same<Type, void>::value>> namespace Vivy { // The Big CRTP class for all common things to all the subdocuments -template <class CRTPSubDocumentType, class Document> class CRTPSubDocument { +template <class CRTPSubDocumentType, class Document, class Context> class CRTPSubDocument { public: using Type = CRTPSubDocumentType; friend std::unique_ptr<Document>; @@ -48,41 +49,68 @@ public: return ret; } + // Get the default stream index or -1 if not possible. + VIVY_ENABLE_IF_TYPE(Context) + int getDefaultStreamIndex() const noexcept + { + if (auto ptr = getDefaultStream()) + return ptr->getStreamIndex(); + return -1; + } + + // Get a pointer to the default stream, nullptr if not possible + VIVY_ENABLE_IF_TYPE(Context) + auto getDefaultStream() const + { + const Document *const self = static_cast<const Document *const>(this); + auto weakPtr = self->contextPtr->getDefaultStream(); + if (auto ptr = weakPtr.lock()) + return ptr; + throw std::runtime_error("SubDocument has been deleted"); + } + + // Get the stream asked for, nullptr if no stream or if the index is invalid + VIVY_ENABLE_IF_TYPE(Context) + auto getStream(int index) const + { + const Document *const self = static_cast<const Document *const>(this); + auto weakPtr = self->contextPtr->getStream(index); + if (auto ptr = weakPtr.lock()) + return ptr; + throw std::runtime_error("SubDocument has been deleted"); + } + inline Type getType() const noexcept { return fileType; } inline QString getFilePath() const noexcept { return filePath; } }; // Audio document -class AudioSubDocument final : public CRTPSubDocument<AudioDocumentType, AudioSubDocument> { -private: +class AudioSubDocument final + : public CRTPSubDocument<AudioDocumentType, AudioSubDocument, AudioContext> { const QStringList &suffixList = Vivy::Utils::audioFileSuffix; + std::unique_ptr<AudioContext> contextPtr{ nullptr }; void initFromPath(const QString &); explicit AudioSubDocument() = default; - friend CRTPSubDocument<AudioDocumentType, AudioSubDocument>; - -private: - std::unique_ptr<AudioContext> contextPtr; + friend CRTPSubDocument<AudioDocumentType, AudioSubDocument, AudioContext>; public: - int getDefaultStreamIndex() const noexcept; - AudioContext::StreamPtr getDefaultStream() const noexcept; - AudioContext::StreamPtr getStream(int index) const noexcept; - QString getElementName() const noexcept; QJsonDocument getProperties() const noexcept; }; // Video document -class VideoSubDocument final : public CRTPSubDocument<VideoDocumentType, VideoSubDocument> { -private: - const QStringList &suffixList = Vivy::Utils::videoFileSuffix; +class VideoSubDocument final + : public CRTPSubDocument<VideoDocumentType, VideoSubDocument, VideoContext> { + static constexpr inline bool hasContext = true; + const QStringList &suffixList = Vivy::Utils::videoFileSuffix; + std::unique_ptr<VideoContext> contextPtr{ nullptr }; void initFromPath(const QString &); explicit VideoSubDocument() noexcept = default; - friend CRTPSubDocument<VideoDocumentType, VideoSubDocument>; + friend CRTPSubDocument<VideoDocumentType, VideoSubDocument, VideoContext>; public: QString getElementName() const noexcept; @@ -90,13 +118,14 @@ public: }; // ASS document -class AssSubDocument final : public CRTPSubDocument<AssDocumentType, AssSubDocument> { - const QStringList &suffixList = Vivy::Utils::assFileSuffix; +class AssSubDocument final : public CRTPSubDocument<AssDocumentType, AssSubDocument, void> { + static constexpr inline bool hasContext = false; + const QStringList &suffixList = Vivy::Utils::assFileSuffix; void initFromPath(const QString &); explicit AssSubDocument() noexcept = default; - friend CRTPSubDocument<AssDocumentType, AssSubDocument>; + friend CRTPSubDocument<AssDocumentType, AssSubDocument, void>; public: QString getElementName() const noexcept; @@ -109,7 +138,4 @@ private: QVector<Ass::StylePtr> styles; QVector<Ass::LinePtr> lines; }; - } - -#endif // VIVY_CRTP_DOCUMENT_H diff --git a/src/Lib/Document/CRTPSubDocument/AssSubDocument.cc b/src/Lib/Document/CRTPSubDocument/AssSubDocument.cc new file mode 100644 index 0000000000000000000000000000000000000000..b465093754d4fa0cf412182ce733eb34bdc1ff10 --- /dev/null +++ b/src/Lib/Document/CRTPSubDocument/AssSubDocument.cc @@ -0,0 +1,45 @@ +#include "../CRTPSubDocument.hh" +#include "../../JsonBuilder.hh" + +using namespace Vivy; + +// Init a ASS sub-document from a file +void +AssSubDocument::initFromPath(const QString &path) +{ + Ass::AssFactory factory(path); + factory.getStyles(styles); + factory.getLines(lines); +} + +QString +AssSubDocument::getElementName() const noexcept +{ + return "AssSubDocument"; +} + +const QVector<Ass::LinePtr> & +AssSubDocument::getLines() const noexcept +{ + return lines; +} + +const QVector<Ass::StylePtr> & +AssSubDocument::getStyles() const noexcept +{ + return styles; +} + +QJsonDocument +AssSubDocument::getProperties() const noexcept +{ + QJsonObject styleObject; + for (const Ass::StylePtr &style : styles) { + styleObject.insert(style->getElementName(), style->getProperties().object()); + } + + return JsonBuilder::createOrderedJsonDocument({ + { "File", filePath }, + { "Styles", styleObject }, + }); +} diff --git a/src/Lib/Document/CRTPSubDocument/AudioSubDocument.cc b/src/Lib/Document/CRTPSubDocument/AudioSubDocument.cc new file mode 100644 index 0000000000000000000000000000000000000000..c91c3752109af9df968d21d46d32ca433557c1b9 --- /dev/null +++ b/src/Lib/Document/CRTPSubDocument/AudioSubDocument.cc @@ -0,0 +1,30 @@ +#include "../CRTPSubDocument.hh" +#include "../../JsonBuilder.hh" + +using namespace Vivy; + +// Init an audio sub-document from a file +void +AudioSubDocument::initFromPath(const QString &path) +{ + if (contextPtr) + qDebug() << "Replacing the audio contetx by a new one for file" << path; + contextPtr.reset(new AudioContext(path)); // May throw + + qDebug() << "Audio OK for" << path; +} + +QString +AudioSubDocument::getElementName() const noexcept +{ + return "AudioSubDocument"; +} + +QJsonDocument +AudioSubDocument::getProperties() const noexcept +{ + return JsonBuilder::createOrderedJsonDocument({ + { "File", filePath }, + { "Audio context", contextPtr->getProperties().object() }, + }); +} diff --git a/src/Lib/Document/CRTPSubDocument/VideoSubDocument.cc b/src/Lib/Document/CRTPSubDocument/VideoSubDocument.cc new file mode 100644 index 0000000000000000000000000000000000000000..6f837e9c219e4747f71100191cad27c9298f102e --- /dev/null +++ b/src/Lib/Document/CRTPSubDocument/VideoSubDocument.cc @@ -0,0 +1,30 @@ +#include "../CRTPSubDocument.hh" +#include "../../JsonBuilder.hh" + +using namespace Vivy; + +// Init a video sub-document from a file +void +VideoSubDocument::initFromPath(const QString &path) +{ + if (contextPtr) + qDebug() << "Replacing the video contetx by a new one for file" << path; + contextPtr.reset(new VideoContext(path)); // May throw + + qDebug() << "Video OK for" << path; +} + +QString +VideoSubDocument::getElementName() const noexcept +{ + return "VideoSubDocument"; +} + +QJsonDocument +VideoSubDocument::getProperties() const noexcept +{ + return JsonBuilder::createOrderedJsonDocument({ + { "Video context", contextPtr->getProperties().object() }, + { "File", filePath }, + }); +} diff --git a/src/Lib/Document/VivyDocument.cc b/src/Lib/Document/VivyDocument.cc index ae4c12355717d9ab4bb5246724259ed58a4cc389..6749fcc3cce6e5acb44bf584ccb9c345903835cc 100644 --- a/src/Lib/Document/VivyDocument.cc +++ b/src/Lib/Document/VivyDocument.cc @@ -1,25 +1,148 @@ #include "VivyDocument.hh" #include "../Utils.hh" - -#include <QFileInfo> -#include <QString> -#include <QStringList> -#include <QJsonArray> -#include <QJsonObject> +#include "../JsonBuilder.hh" using namespace Vivy; +// Runtime document version, don't set it in the header!!! +QString VivyDocument::runtimeVersion = QStringLiteral("alpha"); + VivyDocument::VivyDocument(const QString &documentPath, Options opt) : AbstractDocument(AbstractDocument::Type::Vivy, documentPath) , documentOptions(opt) { - QFileInfo file(name); - documentName = file.baseName(); - documentLocation = file.absoluteDir(); + // Ignore some things in case of memory document creation because no real + // document with an extension is available. + if (opt & MemoryDocumentCreation) { + documentName = documentPath; + documentLocation = QDir::home(); + } + + // Load a real document + else { + QFileInfo fileInfo(name); + QFile file(name); + documentName = fileInfo.baseName(); + documentLocation = fileInfo.absoluteDir(); + + if ((fileInfo.completeSuffix() != fileSuffix)) + throw std::runtime_error("Invalid file suffix"); + + if (!file.exists() || !fileInfo.isFile() || + !file.open(QIODevice::ReadOnly | QIODevice::Text)) + throw std::runtime_error("Document doesn't exists"); + + QTextStream fileContent(&file); + QJsonParseError jsonError; + QJsonDocument json = QJsonDocument::fromJson(fileContent.readAll().toUtf8(), &jsonError); + if (json.isNull()) + throw std::runtime_error("Json error " + jsonError.errorString().toStdString()); + + loadSaveJsonDocumentFile(json); + } +} + +void +VivyDocument::save() +{ + if (documentOptions & MemoryDocumentCreation) + throw std::logic_error("You can't save a memory document"); + + const bool needExt = !documentName.endsWith(fileSuffix); + const QString filepath = documentLocation.absolutePath() + QDir::separator() + documentName + + (needExt ? "." + fileSuffix : ""); + QFile file(filepath); + + if (!file.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) + throw std::runtime_error("Failed to open file in WO+Text mode: " + filepath.toStdString()); + + QTextStream writter(&file); + QJsonDocument json = getSaveJsonDocumentFile(); + writter << json.toJson(QJsonDocument::Compact); + file.close(); + documentOptions = static_cast<Options>(documentOptions & (~UntouchedByDefault)); +} + +void +VivyDocument::loadSaveJsonDocumentFile_ALPHA(VivyDocument *const self, const QJsonDocument &json) +{ + // Init from the JSON + self->documentType = json[KeyCapabilities].toString().toULongLong() & possibleCapabilities; + if (json[KeyName].toString() != self->documentName) + throw std::runtime_error("The document was edited outside of Vivy"); + + if (QJsonValue audio = json[KeySubDocuments][KeySubAudio]; + !audio.isNull() && !self->loadSubDocument(audio.toString(), Capabilities::AudioAble)) + throw std::runtime_error("Failed to load audio sub document"); + + if (QJsonValue video = json[KeySubDocuments][KeySubVideo]; + !video.isNull() && !self->loadSubDocument(video.toString(), Capabilities::VideoAble)) + throw std::runtime_error("Failed to load video sub document"); + + if (QJsonValue internalAssSource = json[KeySubDocuments][KeyInternalAssSource]; + !internalAssSource.isNull() && internalAssSource.toBool()) { + // ASS is inside Vivy document + throw std::runtime_error("The internal ASS feature is not supported for now"); + } else if (QJsonValue ass = json[KeySubDocuments][KeySubAss]; !ass.isNull()) { + // ASS in its own ASS file + if (!self->loadSubDocument(ass.toString(), Capabilities::AssAble)) + throw std::runtime_error("Failed to load ASS sub document"); + } +} + +void +VivyDocument::loadSaveJsonDocumentFile(const QJsonDocument &json) +{ + if (json.isNull()) + throw std::logic_error("Json is null, you can't pass a null json " + "document to this function"); + + const QJsonValue savedVersionJson = json[KeyDocumentVersion]; + if (savedVersionJson.isNull()) + throw std::runtime_error("No version present in the saved VivyDocument"); + + const QString savedVersion = savedVersionJson.toString(); + for (const auto &[version, callback] : loadFunctions) { + if (savedVersion == QString::fromUtf8(version)) + return callback(this, json); + } + + throw std::runtime_error("Version is undefined! No way has been " + "found to load the document"); +} + +QJsonDocument +VivyDocument::getSaveJsonDocumentFile() const +{ + QJsonDocument ret; + QJsonObject json; + QJsonObject subDocumentJson; + + json.insert(KeyCapabilities, QString::number(possibleCapabilities & documentType)); + json.insert(KeyName, documentName); + json.insert(KeyDocumentVersion, runtimeVersion); - loadSubDocument(name); - qDebug() << "CONSTRUCTOR: VivyDocument(" << name << "," << opt << "," - << getDocumentCapabilitiesString() << ")"; + if (documentType & Capabilities::AudioAble) + subDocumentJson.insert(KeySubAudio, audioDocument->getFilePath()); + + if (documentType & Capabilities::VideoAble) + subDocumentJson.insert(KeySubVideo, videoDocument->getFilePath()); + + if (documentType & Capabilities::AssAble) { + // ASS is inside Vivy document + if ((assDocument->getFilePath() == getName())) { + subDocumentJson.insert(KeyInternalAssSource, true); + throw std::runtime_error("Unsupported save of internal ASS for now"); + } + + // ASS is ints own ASS file + else + subDocumentJson.insert(KeySubAss, assDocument->getFilePath()); + } + + json.insert(KeySubDocuments, subDocumentJson); + ret.setObject(json); + return ret; } bool @@ -82,7 +205,7 @@ VivyDocument::loadSubDocument(const QString &subName, VivyDocument::Capabilities } bool -VivyDocument::detectDocumentType(const QFileInfo &file, Capabilities *ableType) noexcept +VivyDocument::detectDocumentType(const QFileInfo &file, Capabilities *const ableType) noexcept { Vivy::Utils::DocumentType docType; bool rc = Vivy::Utils::detectDocumentType(file, &docType); @@ -103,18 +226,68 @@ VivyDocument::detectDocumentType(const QFileInfo &file, Capabilities *ableType) return rc; } -bool -VivyDocument::rename(const QString &newName) noexcept +void +VivyDocument::copy(const QString &newName) +{ + const QFileInfo newPath = computeFileInfo(newName); + + // Rename + create the disk on disk as it's a memory document + if (documentOptions & MemoryDocumentCreation) + saveMemoryFile(newPath); + + // Compute new paths, the document is really on disk initially + else { + qDebug() << "Renaming a real file"; + copyWith(newPath, [=, this]() noexcept -> void { + documentLocation = newPath.dir(); + documentName = newPath.baseName(); + name = newPath.absoluteFilePath(); + }); + } + + emit documentChanged(); +} + +void +VivyDocument::rename(const QString &newName) +{ + const QFileInfo newPath = computeFileInfo(newName); + + // Rename + create the disk on disk as it's a memory document + if (documentOptions & MemoryDocumentCreation) + saveMemoryFile(newPath); + + // Compute new paths, the document is really on disk initially + else { + qDebug() << "Renaming a real file"; + renameWith(newPath, [=, this]() noexcept -> void { + documentLocation = newPath.dir(); + documentName = newPath.baseName(); + name = newPath.absoluteFilePath(); + }); + } + + emit documentChanged(); +} + +QFileInfo +VivyDocument::computeFileInfo(const QString &newName) const noexcept { - /* Compute new paths */ const QString newNameWithExtension = - newName.right(filePrefix.size()) == filePrefix ? newName : newName + "." + filePrefix; - const QFileInfo newPath(documentLocation, newNameWithExtension); + (newName.right(fileSuffix.size()) == fileSuffix) ? newName : newName + "." + fileSuffix; + return QFileInfo(documentLocation, newNameWithExtension); +} - return renameWith(newPath, [=, this]() noexcept -> void { - documentLocation = newPath.dir(); - documentName = newPath.baseName(); - }); +void +VivyDocument::saveMemoryFile(const QFileInfo &newPath) +{ + documentName = newPath.baseName(); + documentLocation = newPath.absoluteDir(); + name = newPath.absoluteFilePath(); + documentOptions = static_cast<Options>(documentOptions & (~MemoryDocumentCreation)); + qDebug().nospace() << "Renaming a memory file => create it on disk with { " << documentLocation + << ", " << documentName << " }"; + save(); } std::shared_ptr<AudioSubDocument> @@ -152,14 +325,11 @@ VivyDocument::getDocumentCapabilitiesString() const noexcept { QStringList ret; if (documentType & AudioAble) - ret.push_back("AudioAble" + - QString(audioDocument ? QStringLiteral("") : QStringLiteral("(ack)"))); + ret.push_back("AudioAble" + QString::fromUtf8(audioDocument ? "" : "(ack)")); if (documentType & VideoAble) - ret.push_back("VideoAble" + - QString(videoDocument ? QStringLiteral("") : QStringLiteral("(ack)"))); + ret.push_back("VideoAble" + QString::fromUtf8(videoDocument ? "" : "(ack)")); if (documentType & AssAble) - ret.push_back("AssAble" + - QString(assDocument ? QStringLiteral("") : QStringLiteral("(ack)"))); + ret.push_back("AssAble" + QString::fromUtf8(assDocument ? "" : "(ack)")); return ret.join(", "); } @@ -170,7 +340,7 @@ VivyDocument::setAudioSubDocument(const QString filename) noexcept QFileInfo fileInfo(filename); const QString baseName = fileInfo.baseName(); - if (auto doc = audioDocument.get()) { + if ([[maybe_unused]] auto doc = audioDocument.get()) { // Here we may want to do some confirmation / cleanup audioDocument.reset(); } @@ -187,7 +357,7 @@ VivyDocument::setVideoSubDocument(const QString filename) noexcept QFileInfo fileInfo(filename); const QString baseName = fileInfo.baseName(); - if (auto doc = videoDocument.get()) { + if ([[maybe_unused]] auto doc = videoDocument.get()) { // Here we may want to do some confirmation / cleanup videoDocument.reset(); } @@ -204,7 +374,7 @@ VivyDocument::setAssSubDocument(const QString filename) noexcept QFileInfo fileInfo(filename); const QString baseName = fileInfo.baseName(); - if (auto doc = assDocument.get()) { + if ([[maybe_unused]] auto doc = assDocument.get()) { // Here we may want to do some confirmation / cleanup assDocument.reset(); } @@ -231,21 +401,13 @@ VivyDocument::getElementName() const noexcept 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; + return JsonBuilder::createOrderedJsonDocument( + { { QStringLiteral("Uuid"), getUuid().toString() }, + { QStringLiteral("Name"), getName() }, + { QStringLiteral("Sub documents"), + JsonBuilder::createOrderedJsonObject({ + JsonBuilder::getSubDocumentJson("Audio sub document", audioDocument), + JsonBuilder::getSubDocumentJson("Video sub document", videoDocument), + JsonBuilder::getSubDocumentJson("ASS sub document", assDocument), + }) } }); } diff --git a/src/Lib/Document/VivyDocument.hh b/src/Lib/Document/VivyDocument.hh index dc36549f76177c90d5d9d19c8f2c7a441546d2c2..b1476d7ca29f272adcd8807a804da9f9e6e00501 100644 --- a/src/Lib/Document/VivyDocument.hh +++ b/src/Lib/Document/VivyDocument.hh @@ -10,10 +10,7 @@ #include "../Uuid.hh" #include "CRTPSubDocument.hh" -#include <memory> -#include <QString> -#include <QDir> -#include <QJsonDocument> +#define DCL_VIVY_SAVE_KEY(name) static const inline QString Key##name = QStringLiteral(#name); namespace Vivy { @@ -21,23 +18,34 @@ class VivyDocument final : public AbstractDocument { Q_OBJECT VIVY_UNMOVABLE_OBJECT(VivyDocument) -public: - enum Capabilities : quint64 { - AudioAble = (1 << 1), - VideoAble = (1 << 2), - AssAble = (1 << 3), - }; + DCL_VIVY_SAVE_KEY(Capabilities) + DCL_VIVY_SAVE_KEY(Name) + DCL_VIVY_SAVE_KEY(SubDocuments) + DCL_VIVY_SAVE_KEY(SubAudio) + DCL_VIVY_SAVE_KEY(SubAss) + DCL_VIVY_SAVE_KEY(SubVideo) + DCL_VIVY_SAVE_KEY(InternalAssSource) + DCL_VIVY_SAVE_KEY(DocumentVersion) + using LoadFunctionForVersion = + std::pair<const char *, void (*)(VivyDocument *const, const QJsonDocument &)>; + +public: + enum Capabilities : quint64 { AudioAble = (1 << 1), VideoAble = (1 << 2), AssAble = (1 << 3) }; enum Options : quint64 { - NoOption = 0, - UntouchedByDefault = (1 << 1), + NoOption = 0, + UntouchedByDefault = (1 << 1), + MemoryDocumentCreation = (1 << 2) }; - static inline const QString filePrefix{ "vivy" }; - static inline constexpr Utils::DocumentType type = Utils::DocumentType::Vivy; + static QString runtimeVersion; + + static inline const QString fileSuffix = QStringLiteral("vivy"); + static inline constexpr Utils::DocumentType type = Utils::DocumentType::Vivy; + static inline constexpr quint64 possibleCapabilities = AudioAble | VideoAble | AssAble; private: - /* The document name */ + /* The document name, the filepath is {location}/{name}.{suffix} */ QString documentName; QDir documentLocation{ QDir::current() }; @@ -46,19 +54,31 @@ private: quint64 documentOptions{ 0 }; /* Links to other files, they're not embeded inside the vivy file */ - std::shared_ptr<AudioSubDocument> audioDocument{}; - std::shared_ptr<VideoSubDocument> videoDocument{}; - std::shared_ptr<AssSubDocument> assDocument{}; + std::shared_ptr<AudioSubDocument> audioDocument{ nullptr }; + std::shared_ptr<VideoSubDocument> videoDocument{ nullptr }; + std::shared_ptr<AssSubDocument> assDocument{ nullptr }; - static bool detectDocumentType(const QFileInfo &file, Capabilities *) noexcept; + static bool detectDocumentType(const QFileInfo &file, Capabilities *const) noexcept; void addDocumentType(Capabilities) noexcept; + void saveMemoryFile(const QFileInfo &); + QFileInfo computeFileInfo(const QString &) const noexcept; + + QJsonDocument getSaveJsonDocumentFile() const; + void loadSaveJsonDocumentFile(const QJsonDocument &); + + static void loadSaveJsonDocumentFile_ALPHA(VivyDocument *const, const QJsonDocument &); + + static constexpr inline auto loadFunctions = { LoadFunctionForVersion{ + "alpha", loadSaveJsonDocumentFile_ALPHA } }; public: // Create an empty document explicit VivyDocument(const QString &name, Options opt = NoOption); - bool rename(const QString &) noexcept override; + void copy(const QString &) override; + void rename(const QString &) override; + void save() override; bool loadSubDocument(const QString &) noexcept; bool loadSubDocument(const QString &, Capabilities) noexcept; diff --git a/src/Lib/Document/VivyDocumentStore.cc b/src/Lib/Document/VivyDocumentStore.cc index 5e2ca6fb98915c88d3c89d10dd0eeee6c57649f0..909d9e270367c90526b04c3c5c5f52d75d0fd93a 100644 --- a/src/Lib/Document/VivyDocumentStore.cc +++ b/src/Lib/Document/VivyDocumentStore.cc @@ -1,13 +1,14 @@ #include "VivyDocumentStore.hh" #include "VivyDocument.hh" -#include <stdexcept> - using namespace Vivy; std::shared_ptr<VivyDocument> VivyDocumentStore::newDocument(VivyDocument::Options opt) { + // Add the memory flag + opt = static_cast<VivyDocument::Options>(opt | VivyDocument::MemoryDocumentCreation); + const QString newDocName = newDocumentBaseName + QString::number(newDocumentNumber++); auto ret = std::make_shared<VivyDocument>(newDocName, opt); diff --git a/src/Lib/HostOsInfo.cc b/src/Lib/HostOsInfo.cc index 4951b7c5ef605b150267f43a56c8e274b6463e06..48f5bc6d82b88abad196be8db98ad895555cd467 100644 --- a/src/Lib/HostOsInfo.cc +++ b/src/Lib/HostOsInfo.cc @@ -1,7 +1,5 @@ #include "HostOsInfo.hh" -#include <QCoreApplication> - #if !defined(QT_NO_OPENGL) && defined(QT_GUI_LIB) #include <QOpenGLContext> #endif diff --git a/src/Lib/HostOsInfo.hh b/src/Lib/HostOsInfo.hh index 090135fcd2516c2c5c9f4e7e42d44b31dbf27daf..97b2889621b1348bfdea6b5c88afd68db1090417 100644 --- a/src/Lib/HostOsInfo.hh +++ b/src/Lib/HostOsInfo.hh @@ -1,7 +1,6 @@ #pragma once #include "Utils.hh" -#include <QString> #ifdef Q_OS_WIN #define VIVY_HOST_EXE_SUFFIX VIVY_WIN_EXE_SUFFIX diff --git a/src/Lib/JsonBuilder.cc b/src/Lib/JsonBuilder.cc new file mode 100644 index 0000000000000000000000000000000000000000..105445ea825b94a0a6abac131139ea2621f38ffa --- /dev/null +++ b/src/Lib/JsonBuilder.cc @@ -0,0 +1,25 @@ +#include "JsonBuilder.hh" + +using namespace Vivy; + +QJsonArray +JsonBuilder::createOrderedJsonObject( + std::initializer_list<QPair<QString, QJsonValue>> elements) noexcept +{ + QJsonArray ret; + int index = 0; + + for (const QPair<QString, QJsonValue> &elem : elements) { + ret.insert(index, QJsonObject({ elem })); + ++index; + } + + return ret; +} + +QJsonDocument +JsonBuilder::createOrderedJsonDocument( + std::initializer_list<QPair<QString, QJsonValue>> elements) noexcept +{ + return QJsonDocument(createOrderedJsonObject(elements)); +} diff --git a/src/Lib/JsonBuilder.hh b/src/Lib/JsonBuilder.hh new file mode 100644 index 0000000000000000000000000000000000000000..4534b46381f19e29a24b084c3771c32b46f13adf --- /dev/null +++ b/src/Lib/JsonBuilder.hh @@ -0,0 +1,31 @@ +#pragma once + +#include "Utils.hh" + +namespace Vivy +{ +// Struct used to englobe all json build facilities. This is a class used as a +// private-able namespace that can't be 'using' and thus create unreadable +// code. You can't build this thing, everything is static inside. +struct JsonBuilder final { + VIVY_UNMOVABLE_OBJECT(JsonBuilder) + + static QJsonArray + createOrderedJsonObject(std::initializer_list<QPair<QString, QJsonValue>> elements) noexcept; + + static QJsonDocument + createOrderedJsonDocument(std::initializer_list<QPair<QString, QJsonValue>> elements) noexcept; + + template <PropertyConstViewable T> static QPair<QString, QJsonValue> + getSubDocumentJson(const QString &name, std::shared_ptr<T> subDocPtr) noexcept + { + if (subDocPtr == nullptr) + return QPair{ name, QJsonValue() }; + else + return QPair{ name, subDocPtr->getProperties().object() }; + } + +private: + JsonBuilder() noexcept {} +}; +} diff --git a/src/Lib/Script/CRTPLuaScriptObject.hh b/src/Lib/Script/CRTPLuaScriptObject.hh index 8e9920da1f8c51f0a158168bd9ebcda2c73d5e7c..3a5a51e7477de51cd507d6afe569d9f0e9f172b9 100644 --- a/src/Lib/Script/CRTPLuaScriptObject.hh +++ b/src/Lib/Script/CRTPLuaScriptObject.hh @@ -4,8 +4,6 @@ #include "ScriptOption.hh" #include "lua.hpp" -#include <vector> -#include <cstdio> namespace Vivy::Script { @@ -48,12 +46,12 @@ getJobIteratorTypeFromString(const std::string_view it) noexcept // CRTP to expose objects to Lua template <class Object> class CRTPLuaScriptObject { protected: - static void __attribute__((__noreturn__)) - luaGlobalError(lua_State *const L, const std::string &str) noexcept + [[noreturn]] static void luaGlobalError(lua_State *const L, const std::string &str) noexcept { const auto *const context = LuaContext::getContext(L); lua_pushfstring(L, "%s:0:%s", context->getCurrentLuaFile().c_str(), str.c_str()); lua_error(L); + exit(EXIT_FAILURE); // lua_error should not return anything } static bool PushFunctionFromRegistry(lua_State *const L, const int key) noexcept @@ -72,11 +70,7 @@ protected: { luaL_checktype(L, tblIndex, LUA_TTABLE); const std::size_t count = lua_rawlen(L, tblIndex); - if (count >= std::numeric_limits<int>::max()) - luaL_error(L, "To many items (%lu) in table to iterate over!", count); - - const int countInt = static_cast<const int>(count); - for (int i = 1; i <= countInt; ++i) { + for (int i = 1; std::cmp_less(i, count); ++i) { luaL_checktype(L, tblIndex, LUA_TTABLE); lua_pushinteger(L, i); // i -> top stack lua_gettable(L, tblIndex); // T[i] -> top stack and pop i diff --git a/src/Lib/Script/CRTPLuaScriptObject/FreeFunctions.cc b/src/Lib/Script/CRTPLuaScriptObject/FreeFunctions.cc index b2c3127aa97ce23e229ac651ca7b6c7ae7c6a5fe..981491023738cdd7b2b0a3f03ce238b9497dd2ad 100644 --- a/src/Lib/Script/CRTPLuaScriptObject/FreeFunctions.cc +++ b/src/Lib/Script/CRTPLuaScriptObject/FreeFunctions.cc @@ -1,7 +1,4 @@ #include "../CRTPLuaScriptObject.hh" -#include "lua.hpp" -#include <QFileInfo> -#include <QString> using namespace Vivy::Script; diff --git a/src/Lib/Script/CRTPLuaScriptObject/FunctionDeclaration.cc b/src/Lib/Script/CRTPLuaScriptObject/FunctionDeclaration.cc index 82afe1daa0b10b2824dbd4b9b1fbda7588f9f371..13fd29aa064784ebb97fd2eb404bb7fb40737d1d 100644 --- a/src/Lib/Script/CRTPLuaScriptObject/FunctionDeclaration.cc +++ b/src/Lib/Script/CRTPLuaScriptObject/FunctionDeclaration.cc @@ -1,7 +1,4 @@ #include "../CRTPLuaScriptObject.hh" -#include "lua.hpp" -#include <QFileInfo> -#include <QString> using namespace Vivy::Script; diff --git a/src/Lib/Script/CRTPLuaScriptObject/JobDeclaration.cc b/src/Lib/Script/CRTPLuaScriptObject/JobDeclaration.cc index 39c676d96f83d05072484122761ea0b91dc34d59..f55a170b30fb15fab2b424ddfd0da5f94394673c 100644 --- a/src/Lib/Script/CRTPLuaScriptObject/JobDeclaration.cc +++ b/src/Lib/Script/CRTPLuaScriptObject/JobDeclaration.cc @@ -1,7 +1,4 @@ #include "../CRTPLuaScriptObject.hh" -#include "lua.hpp" -#include <QFileInfo> -#include <QString> using namespace Vivy::Script; diff --git a/src/Lib/Script/CRTPLuaScriptObject/ModuleDeclaration.cc b/src/Lib/Script/CRTPLuaScriptObject/ModuleDeclaration.cc index b904ecae569de30341dc05f14799fe4d08400ecc..5d535ea67c07b15911b7efff0225ea5dca6a3bc9 100644 --- a/src/Lib/Script/CRTPLuaScriptObject/ModuleDeclaration.cc +++ b/src/Lib/Script/CRTPLuaScriptObject/ModuleDeclaration.cc @@ -1,8 +1,4 @@ #include "../CRTPLuaScriptObject.hh" -#include "lua.hpp" -#include <QFileInfo> -#include <QString> -#include <cstring> using namespace Vivy::Script; diff --git a/src/Lib/Script/CRTPLuaScriptObject/OptionDeclaration.cc b/src/Lib/Script/CRTPLuaScriptObject/OptionDeclaration.cc index 7030865fadffd8f6d7c8a896f6f31d937ce05f47..3efa0998034804d90a5bd21946f7788ce7b2aaac 100644 --- a/src/Lib/Script/CRTPLuaScriptObject/OptionDeclaration.cc +++ b/src/Lib/Script/CRTPLuaScriptObject/OptionDeclaration.cc @@ -1,7 +1,4 @@ #include "../CRTPLuaScriptObject.hh" -#include "lua.hpp" -#include <QFileInfo> -#include <QString> using namespace Vivy::Script; diff --git a/src/Lib/Script/LuaContext.cc b/src/Lib/Script/LuaContext.cc index a7c26e659ec30d9e216967d501df263b5c84c285..6d5f32e54fa19cc88720710353817d6813c7e6ea 100644 --- a/src/Lib/Script/LuaContext.cc +++ b/src/Lib/Script/LuaContext.cc @@ -1,16 +1,11 @@ #include "CRTPLuaScriptObject.hh" #include "ScriptDocument.hh" #include "LuaContext.hh" -#include "lua.hpp" -#include <cstdio> -#include <QtGlobal> -#include <QString> -#include <QFile> // LuaContext implementation using namespace Vivy::Script; -LuaContext::LuaContext(ScriptStore::LoggerType &out, ScriptStore::LoggerType &err) noexcept +LuaContext::LuaContext(LoggerType &out, LoggerType &err) noexcept : L(luaL_newstate()) , streamOut(out) , streamErr(err) @@ -129,8 +124,7 @@ LuaContext::getLastLuaError() noexcept return QStringLiteral("No lua error detected..."); } - const char *error = lua_tostring(L, -1); - QString ret = QStringLiteral("%1").arg(error); + QString ret = QString::fromUtf8(lua_tostring(L, -1)); lua_pop(L, 1); return ret; } diff --git a/src/Lib/Script/LuaContext.hh b/src/Lib/Script/LuaContext.hh index a852aac6ab4528084e026251bbc6906ccef2b541..afc55e82b59d60d58631d393aa54d16bb6b5e5fb 100644 --- a/src/Lib/Script/LuaContext.hh +++ b/src/Lib/Script/LuaContext.hh @@ -1,12 +1,8 @@ #pragma once -#include "ScriptStore.hh" -#include <sstream> -#include <vector> -#include <map> +#include "../Utils.hh" struct lua_State; -class QString; namespace Vivy { @@ -15,6 +11,7 @@ class ScriptDocument; namespace Vivy::Script { +class ScriptStore; class ModuleDeclaration; class FunctionDeclaration; class JobDeclaration; @@ -26,6 +23,10 @@ namespace Vivy::Script class LuaContext final { VIVY_UNMOVABLE_OBJECT(LuaContext) +public: + using LoggerType = std::stringstream; + +private: using string_view = const std::string_view; lua_State *L{ nullptr }; @@ -40,15 +41,15 @@ class LuaContext final { static inline std::map<const lua_State *const, LuaContext *const> contextList = {}; - ScriptStore::LoggerType &streamOut; - ScriptStore::LoggerType &streamErr; + LoggerType &streamOut; + LoggerType &streamErr; public: enum class Code { Success, Error }; static constexpr inline Code Success = Code::Success; static constexpr inline Code Error = Code::Error; - LuaContext(ScriptStore::LoggerType &out, ScriptStore::LoggerType &err) noexcept; + LuaContext(LoggerType &out, LoggerType &err) noexcept; ~LuaContext() noexcept; Code loadDocument(std::shared_ptr<ScriptDocument>) noexcept; diff --git a/src/Lib/Script/ScriptDocument.cc b/src/Lib/Script/ScriptDocument.cc index 2f4483e324d5ea3d03a5e8037a7d30306a89409e..6294656caf585053e565d7926239742f00dd9926 100644 --- a/src/Lib/Script/ScriptDocument.cc +++ b/src/Lib/Script/ScriptDocument.cc @@ -1,6 +1,8 @@ #include "ScriptDocument.hh" #include <QFileInfo> +#include <QFile> +#include <QTextStream> using namespace Vivy; @@ -12,10 +14,42 @@ ScriptDocument::ScriptDocument(const QString &path) throw std::runtime_error("File don't exists, don't create ScriptDocument"); } -bool -ScriptDocument::rename(const QString &newName) noexcept +void +ScriptDocument::copy(const QString &newName) { /* Compute new paths */ const QFileInfo newPath(newName); - return renameWith(newPath, [=, this]() noexcept { name = newName; }); + copyWith(newPath, [=, this]() noexcept { name = newName; }); + emit documentChanged(); +} + +void +ScriptDocument::rename(const QString &newName) +{ + /* Compute new paths */ + const QFileInfo newPath(newName); + renameWith(newPath, [=, this]() noexcept { name = newName; }); + emit documentChanged(); +} + +void +ScriptDocument::save() +{ + if (!textDocument) + throw std::runtime_error("no text document attached to the ScriptDocument!"); + + QFile file(name); + if (file.open(QIODevice::WriteOnly, QIODevice::Text)) { + throw std::runtime_error("Failed to open file in WO+Text mode: " + name.toStdString()); + } + + QTextStream writter(&file); + writter << textDocument->toPlainText(); + file.close(); +} + +void +ScriptDocument::attachTextDocument(const QTextDocument *const txt) noexcept +{ + textDocument = txt; } diff --git a/src/Lib/Script/ScriptDocument.hh b/src/Lib/Script/ScriptDocument.hh index 6bb03570dfd53d1c1ba6753cb29f0ab2b5012370..3799669c62b2b804c16ab65fe8f952b9cbb5eb35 100644 --- a/src/Lib/Script/ScriptDocument.hh +++ b/src/Lib/Script/ScriptDocument.hh @@ -7,12 +7,21 @@ #include "../AbstractDocument.hh" #include "../Uuid.hh" +class QTextDocument; + namespace Vivy { class ScriptDocument final : public AbstractDocument { public: explicit ScriptDocument(const QString &name); - bool rename(const QString &) noexcept override; + void copy(const QString &) override; + void rename(const QString &) override; + void save() override; + + void attachTextDocument(const QTextDocument *const) noexcept; + +private: + const QTextDocument *textDocument{ nullptr }; }; } diff --git a/src/Lib/Script/ScriptOption.hh b/src/Lib/Script/ScriptOption.hh index db4076b86fab2f02ca62d72bdecd86525acb8815..0cfef834096ac036983e9b6c9539de012d422ee8 100644 --- a/src/Lib/Script/ScriptOption.hh +++ b/src/Lib/Script/ScriptOption.hh @@ -1,8 +1,5 @@ #pragma once -#include <string> -#include <string_view> - namespace Vivy::Script { // The options diff --git a/src/Lib/Script/ScriptStore.cc b/src/Lib/Script/ScriptStore.cc index 61a970d59026592d3b7d10c4e8222f56ba7185f0..593809f7bb314fe5692c92be568b9d05f7c552da 100644 --- a/src/Lib/Script/ScriptStore.cc +++ b/src/Lib/Script/ScriptStore.cc @@ -1,6 +1,5 @@ #include "ScriptStore.hh" #include "../Uuid.hh" -#include <iostream> using namespace Vivy; using namespace std::string_literals; @@ -63,7 +62,7 @@ ScriptStore::executeScript(Uuid id) noexcept QRegExp errorDetect(QStringLiteral("^([^\\:]*)\\:(\\d+)\\:(.*)\\n?$")); while (std::getline(streamErr, buffer, '\n')) { std::cout << '\t' << buffer << '\n'; - if (errorDetect.indexIn(buffer.c_str()) != -1) { + if (errorDetect.indexIn(QString::fromUtf8(buffer.c_str())) != -1) { std::string errorDesc = errorDetect.cap(3).toStdString(); return std::tuple{ QString(errorDetect.cap(2)).toInt(), Utils::trim(errorDesc) }; } diff --git a/src/Lib/Script/ScriptStore.hh b/src/Lib/Script/ScriptStore.hh index dd4266a998dc370119156b000c1444d8dbc9ad09..21d0b98309535e90ec5a3b183b79af5783bc28dd 100644 --- a/src/Lib/Script/ScriptStore.hh +++ b/src/Lib/Script/ScriptStore.hh @@ -2,8 +2,7 @@ #include "../CRTPStore.hh" #include "ScriptDocument.hh" -#include <sstream> -#include <optional> +#include "LuaContext.hh" namespace Vivy::Script { @@ -20,7 +19,7 @@ class ScriptStore final : public CRTPStore<ScriptStore, ScriptDocument> { public: using Item = std::tuple<Uuid, QString>; - using LoggerType = std::stringstream; + using LoggerType = Script::LuaContext::LoggerType; explicit ScriptStore() noexcept; diff --git a/src/Lib/Utils.cc b/src/Lib/Utils.cc index cbce31685828d8155449a1de096616c39ef3899d..d023dd7bd8ae39c87b06f71242bab94c8e2373f3 100644 --- a/src/Lib/Utils.cc +++ b/src/Lib/Utils.cc @@ -1,8 +1,5 @@ #include "Utils.hh" -#include <QRegExp> -#include <QFileInfo> - using namespace Vivy; std::string & @@ -162,6 +159,13 @@ Utils::Time::toString() const noexcept "." + QString::number(centisecond); } +QString +Utils::getBaseName(const QString &file) noexcept +{ + QFileInfo info(file); + return info.baseName(); +} + const QString & Utils::getAudioFileSuffixFilter() noexcept { @@ -262,7 +266,29 @@ Utils::getAnyDocumentFileSuffixFilter() noexcept isInitialized = true; ret = anyFilter + separator + getVivyDocumentFileSuffixFilter() + separator + getScriptFileSuffixFilter() + separator + getAudioFileSuffixFilter() + separator + - getVideoFileSuffixFilter() + separator + getAssFileSuffixFilter() + separator; + getVideoFileSuffixFilter() + separator + getAssFileSuffixFilter(); + return ret; +} + +const QString & +Utils::getAnyTopLevelDocumentFileSuffixFilter() noexcept +{ + static bool isInitialized = false; + static QString ret = QStringLiteral(""); + static QString anyFilter = QStringLiteral("Any Top Level Vivy File (*."); + + if (isInitialized) + return ret; + + anyFilter.append(scriptFileSuffix.join(" *.")); + anyFilter.append(QStringLiteral(" *.")); + anyFilter.append(vivyFileSuffix.join(" *.")); + anyFilter.append(QStringLiteral(")")); + + const QString separator = QStringLiteral(";;"); + isInitialized = true; + ret = anyFilter + separator + getVivyDocumentFileSuffixFilter() + separator + + getScriptFileSuffixFilter(); return ret; } diff --git a/src/Lib/Utils.hh b/src/Lib/Utils.hh index 3283a2c8707f61376a5e96e0baa9ac92dbb57ce6..eca3e66396085bfef587c7bca936e44898774dda 100644 --- a/src/Lib/Utils.hh +++ b/src/Lib/Utils.hh @@ -4,16 +4,6 @@ #error "This is a C++ header" #endif -#include <QString> -#include <QFileInfo> -#include <QStringList> -#include <QDebug> -#include <QMessageLogger> -#include <QJsonDocument> -#include <QtGlobal> -#include <type_traits> -#include <chrono> - #include <qglobal.h> #define VIVY_PRAGMA(x) _Pragma(#x) @@ -61,6 +51,9 @@ concept PropertyConstViewable = requires(T element) { element.getProperties() } -> std::same_as<QJsonDocument>; // clang-format on }; + +template <class T, class U> +concept Derived = std::is_base_of<U, T>::value; } namespace Vivy::Utils @@ -81,6 +74,7 @@ const QString &getAssFileSuffixFilter() noexcept; const QString &getScriptFileSuffixFilter() noexcept; const QString &getVivyDocumentFileSuffixFilter() noexcept; const QString &getAnyDocumentFileSuffixFilter() noexcept; +const QString &getAnyTopLevelDocumentFileSuffixFilter() noexcept; static constexpr std::size_t pointerAlignement = alignof(void *); @@ -167,6 +161,8 @@ bool decodeLineToBoolean(const QString &item, const QString &error); int decodeLineToInteger(const QString &item, const QString &error); float decodeLineToFloating(const QString &item, const QString &error); +QString getBaseName(const QString &) noexcept; + void writeAssertLocation(const char *msg); struct OsSpecificAspects final { diff --git a/src/Lib/Uuid.hh b/src/Lib/Uuid.hh index a45b43700e293f3d52bdcdb95b532887c8ed291d..068277deeac9f8b33673fa2243a0c5e30e9c2223 100644 --- a/src/Lib/Uuid.hh +++ b/src/Lib/Uuid.hh @@ -1,7 +1,5 @@ #pragma once -#include <QUuid> - namespace Vivy { class Uuid : public QUuid { diff --git a/src/Lib/Video.cc b/src/Lib/Video.cc new file mode 100644 index 0000000000000000000000000000000000000000..e6b9a07022e0e77f352b5a90e664ea2111c9d882 --- /dev/null +++ b/src/Lib/Video.cc @@ -0,0 +1,57 @@ +#include "Video.hh" +#include "Utils.hh" + +using namespace Vivy; + +VideoStream::VideoStream(AVCodec *streamCodec, AVFormatContext *formatArg, AVStream *streamArg, + int index) + : Super(streamCodec, formatArg, streamArg, index) +{ +} + +QJsonObject +VideoStream::getProperties() const noexcept +{ + return Super::getProperties(); +} + +VideoContext::VideoContext(const QString &path) + : Super(path) +{ +} + +QJsonDocument +VideoContext::getProperties() const noexcept +{ + return Super::getProperties(); +} + +QString +VideoContext::getElementName() const noexcept +{ + return "Video" + Super::getElementName(); +} + +int +VideoStream::getWidth() const noexcept +{ + return codecContext->width; +} + +int +VideoStream::getHeight() const noexcept +{ + return codecContext->height; +} + +qint64 +VideoStream::getDuration() const noexcept +{ + return dataFormat->duration; +} + +double +VideoStream::getFramesPerSecond() const noexcept +{ + return av_q2d(stream->r_frame_rate); +} diff --git a/src/Lib/Video.hh b/src/Lib/Video.hh new file mode 100644 index 0000000000000000000000000000000000000000..400feb30480d89cae9741a98c5cd9b2d7edabe2d --- /dev/null +++ b/src/Lib/Video.hh @@ -0,0 +1,38 @@ +#pragma once + +#ifndef __cplusplus +#error "This is a C++ header" +#endif + +#include "AbstractMediaContext.hh" + +namespace Vivy +{ +// Hold all the data for a video stream. Should only be owned by the parent +// VideoContext instance. +class VideoStream final : public AbstractMediaStream<AVMEDIA_TYPE_VIDEO> { + VIVY_UNMOVABLE_OBJECT(VideoStream) + +public: + VideoStream(AVCodec *, AVFormatContext *, AVStream *, int index); + ~VideoStream() noexcept override = default; + + int getWidth() const noexcept; + int getHeight() const noexcept; + qint64 getDuration() const noexcept; + double getFramesPerSecond() const noexcept; + + QJsonObject getProperties() const noexcept override; +}; + +// Like an audio context, but for videos. +class VideoContext final : public AbstractMediaContext<VideoStream> { + VIVY_UNMOVABLE_OBJECT(VideoContext) + +public: + VideoContext(const QString &path); + + QString getElementName() const noexcept override; + QJsonDocument getProperties() const noexcept override; +}; +} diff --git a/src/UI/AboutWindow.cc b/src/UI/AboutWindow.cc index 4bb7e87b24f480d6f45cb70eb4e6c33ac08cd0a9..420dae7bfbd3ef45a4273245400656a912d64962 100644 --- a/src/UI/AboutWindow.cc +++ b/src/UI/AboutWindow.cc @@ -1,14 +1,5 @@ #include "AboutWindow.hh" - -#include <QCloseEvent> -#include <QLabel> -#include <QPushButton> -#include <QVBoxLayout> -#include <QHBoxLayout> -#include <QEvent> -#include <QMouseEvent> -#include <QApplication> -#include <QTextEdit> +#include "../VivyApplication.hh" using namespace Vivy; @@ -46,13 +37,16 @@ static const char *libContent = " </ul>" "</body>"; +AboutWindow::SimpleLabel::~SimpleLabel() noexcept {} +AboutWindow::LicenceLabel::~LicenceLabel() noexcept {} + AboutWindow::SimpleLabel::SimpleLabel(QWidget *parent, const char *text) : QLabel(parent) { setTextFormat(Qt::RichText); setTextInteractionFlags(Qt::NoTextInteraction | Qt::LinksAccessibleByMouse | Qt::LinksAccessibleByKeyboard); - setText(QString(text)); + setText(QString::fromUtf8(text)); setAlignment(Qt::AlignJustify | Qt::AlignTop); setWordWrap(true); } @@ -78,8 +72,8 @@ AboutWindow::LicenceLabel::LicenceLabel(QWidget *parent, const QString &url, switch (format) { case Qt::PlainText: - case Qt::RichText: setText(QString(content.readAll())); break; - case Qt::MarkdownText: setMarkdown(content.readAll()); break; + case Qt::RichText: setText(QString::fromUtf8(content.readAll())); break; + case Qt::MarkdownText: setMarkdown(QString::fromUtf8(content.readAll())); break; case Qt::AutoText: qCritical() << "Invalid text format for LicenceLabel" << format; } } diff --git a/src/UI/AboutWindow.hh b/src/UI/AboutWindow.hh index acbd437c0a2b3513f3f1bdcdf356771d8dc8c705..9bff80cfce81f53374d4e7658698224387bb150d 100644 --- a/src/UI/AboutWindow.hh +++ b/src/UI/AboutWindow.hh @@ -4,11 +4,6 @@ #error "This is a C++ header" #endif -#include <QMainWindow> -#include <QTabWidget> -#include <QLabel> -#include <QTextEdit> - namespace Vivy { class AboutWindow final : public QMainWindow { @@ -20,17 +15,19 @@ class AboutWindow final : public QMainWindow { class SimpleLabel final : public QLabel { public: explicit SimpleLabel(QWidget *parent, const char *text); + ~SimpleLabel() noexcept override; }; // Simple QLabel for licences class LicenceLabel final : public QTextEdit { public: explicit LicenceLabel(QWidget *parent, const QString &url, const Qt::TextFormat format); + ~LicenceLabel() noexcept override; }; public: explicit AboutWindow(QWidget *parent) noexcept; - ~AboutWindow() noexcept = default; + ~AboutWindow() noexcept override = default; signals: void closed(); diff --git a/src/UI/AbstractDocumentView.cc b/src/UI/AbstractDocumentView.cc index 51ece8ec6952c8fcd8fa0fcdef8cf5109c7af82c..d56a35bd2e8523d1a0511f5843403cbfa9ae9389 100644 --- a/src/UI/AbstractDocumentView.cc +++ b/src/UI/AbstractDocumentView.cc @@ -1,12 +1,5 @@ #include "AbstractDocumentView.hh" -#include <QtGlobal> -#include <QDockWidget> -#include <QLayoutItem> -#include <QLayout> -#include <QWidget> -#include <functional> - using namespace Vivy; AbstractDocumentView::AbstractDocumentView(AbstractDocumentView::Type type, @@ -22,6 +15,14 @@ AbstractDocumentView::AbstractDocumentView(AbstractDocumentView::Type type, setTabPosition(Qt::RightDockWidgetArea, QTabWidget::East); } +void +AbstractDocumentView::allowToCloseAllDocks() noexcept +{ + // Allow to close all dock widgets + for (UnclosableDockWidget *const dock : dockWidgets) + dock->allowClose(); +} + // A utility function to delete all the child widgets void AbstractDocumentView::deleteAllContent() noexcept @@ -52,7 +53,7 @@ AbstractDocumentView::getType() const noexcept } void -AbstractDocumentView::delDockWidget(QDockWidget **dock) noexcept +AbstractDocumentView::delDockWidget(UnclosableDockWidget **dock) noexcept { // Remove the toggle view action QAction *act = (*dock)->toggleViewAction(); @@ -60,6 +61,7 @@ AbstractDocumentView::delDockWidget(QDockWidget **dock) noexcept viewsActions.removeAll(act); // Remove the widget + (*dock)->allowClose(); removeDockWidget(*dock); delete *dock; *dock = nullptr; @@ -68,7 +70,7 @@ AbstractDocumentView::delDockWidget(QDockWidget **dock) noexcept emit viewActionsChanged(); } void -AbstractDocumentView::addDockWidget(Qt::DockWidgetArea area, QDockWidget *dock, +AbstractDocumentView::addDockWidget(Qt::DockWidgetArea area, UnclosableDockWidget *dock, Qt::Orientation orientation) noexcept { // dock->setTitleBarWidget(new QWidget(dock)); // <- to disable the header bar @@ -76,6 +78,8 @@ AbstractDocumentView::addDockWidget(Qt::DockWidgetArea area, QDockWidget *dock, QAction *act = dock->toggleViewAction(); if (!viewsActions.contains(act)) viewsActions.prepend(act); + if (!dockWidgets.contains(dock)) + dockWidgets.append(dock); emit viewActionsChanged(); } diff --git a/src/UI/AbstractDocumentView.hh b/src/UI/AbstractDocumentView.hh index 7b1f4c807333e799fc4aa795b90fd548fc6f4eb3..7745665b35e2bc5f669c86c4223afc0b839a0060 100644 --- a/src/UI/AbstractDocumentView.hh +++ b/src/UI/AbstractDocumentView.hh @@ -6,9 +6,7 @@ #include "../Lib/Utils.hh" #include "../Lib/AbstractDocument.hh" -#include <QMainWindow> -#include <QDockWidget> -#include <QAction> +#include "UnclosableDockWidget.hh" namespace Vivy { @@ -43,15 +41,18 @@ public: signals: void viewActionsChanged(); + void documentPropertyChanged(); protected: void deleteAllContent() noexcept; + void allowToCloseAllDocks() noexcept; - void delDockWidget(QDockWidget **) noexcept; - void addDockWidget(Qt::DockWidgetArea, QDockWidget *, Qt::Orientation) noexcept; + void delDockWidget(UnclosableDockWidget **) noexcept; + void addDockWidget(Qt::DockWidgetArea, UnclosableDockWidget *, Qt::Orientation) noexcept; QList<QAction *> viewsActions{}; private: const Type documentType; + QVector<UnclosableDockWidget *> dockWidgets{}; }; } diff --git a/src/UI/DockWidgetTitleBar.cc b/src/UI/DockWidgetTitleBar.cc new file mode 100644 index 0000000000000000000000000000000000000000..2e1cdaa099d9246e785e46919a317dd51cbb6785 --- /dev/null +++ b/src/UI/DockWidgetTitleBar.cc @@ -0,0 +1,24 @@ +#include "DockWidgetTitleBar.hh" + +using namespace Vivy; + +DockWidgetTitleBar::DockWidgetTitleBar(QDockWidget *parent) noexcept + : QWidget(parent) + , attachedDock(parent) +{ + if (parent == nullptr) + qFatal("Can't pass a nullptr as a parent widget pointer"); + + auto *box = new QHBoxLayout(this); + box->addWidget(new QLabel(parent->windowTitle(), this)); + qobject_cast<QHBoxLayout *>(layout())->setStretch(0, 1); +} + +void +DockWidgetTitleBar::addToDock(QDockWidget *const dock) noexcept +{ + DockWidgetTitleBar *const titleBar = new DockWidgetTitleBar(dock); + qDebug() << "Adding" << dock->windowTitle() << "to dock..."; + Utils::setTransparentBackgroundForWidget(titleBar); + dock->setTitleBarWidget(titleBar); +} diff --git a/src/UI/DockWidgetTitleBar.hh b/src/UI/DockWidgetTitleBar.hh new file mode 100644 index 0000000000000000000000000000000000000000..620c9a947e3537b566ee45a69be521cf63b97005 --- /dev/null +++ b/src/UI/DockWidgetTitleBar.hh @@ -0,0 +1,17 @@ +#pragma once + +#include "Utils.hh" + +namespace Vivy +{ +class DockWidgetTitleBar final : public QWidget { + Q_OBJECT + VIVY_UNMOVABLE_OBJECT(DockWidgetTitleBar) + + QDockWidget *attachedDock{ nullptr }; + explicit DockWidgetTitleBar(QDockWidget *parent) noexcept; + +public: + static void addToDock(QDockWidget *const) noexcept; +}; +} diff --git a/src/UI/DocumentViews/AssLinesModel.hh b/src/UI/DocumentViews/AssLinesModel.hh index 2dfdbcd1c709a55ce9c02e902f628358109dc2ed..60cc550b4cbdcbbf56b921187f8a671d42bf844c 100644 --- a/src/UI/DocumentViews/AssLinesModel.hh +++ b/src/UI/DocumentViews/AssLinesModel.hh @@ -2,8 +2,6 @@ #include "../../Lib/Utils.hh" #include "../../Lib/Ass/Ass.hh" -#include <QAbstractItemModel> -#include <QStringList> namespace Vivy { @@ -21,9 +19,7 @@ private: enum class Field : int { Text, - - // Last, the count - TotalFieldCount, + TotalFieldCount // Last, the count }; bool getIsComment() const noexcept; @@ -39,7 +35,7 @@ private: public: explicit AssLinesModel(const QVector<Ass::LinePtr> &) noexcept; - ~AssLinesModel() noexcept; + ~AssLinesModel() noexcept override; QVariant data(const QModelIndex &, int role) const noexcept override; bool setData(const QModelIndex &, const QVariant &v, int r = Qt::EditRole) noexcept override; diff --git a/src/UI/DocumentViews/AssLinesView.cc b/src/UI/DocumentViews/AssLinesView.cc index 9134238f1f64d578ba6636f883fda227b1504cc4..022b8cfdcd0010806296269aef26132bb3ce367b 100644 --- a/src/UI/DocumentViews/AssLinesView.cc +++ b/src/UI/DocumentViews/AssLinesView.cc @@ -1,8 +1,5 @@ #include "AssLinesView.hh" #include "../../VivyApplication.hh" -#include <string_view> -#include <QPaintEvent> -#include <QHeaderView> using namespace Vivy; diff --git a/src/UI/DocumentViews/AssLinesView.hh b/src/UI/DocumentViews/AssLinesView.hh index 54c58c660067f55270af7fa92d3ede42074823ae..635b320cd95f7fea2661f8492a3dff7899f4c47e 100644 --- a/src/UI/DocumentViews/AssLinesView.hh +++ b/src/UI/DocumentViews/AssLinesView.hh @@ -1,9 +1,6 @@ #pragma once #include "../../Lib/Utils.hh" -#include <QAbstractItemModel> -#include <QTableView> -#include <QStyledItemDelegate> namespace Vivy { @@ -23,7 +20,7 @@ private: public: Delegate(AssLinesView *, QWidget *parent = nullptr) noexcept; - ~Delegate() noexcept = default; + ~Delegate() noexcept override = default; void initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const noexcept override; @@ -38,7 +35,7 @@ private: public: AssLinesView(QAbstractItemModel *, QWidget *parent = nullptr) noexcept; - ~AssLinesView() noexcept = default; + ~AssLinesView() noexcept override = default; void paintEvent(QPaintEvent *) noexcept override; diff --git a/src/UI/DocumentViews/AudioVisualizer.cc b/src/UI/DocumentViews/AudioVisualizer.cc index b7813c425d8f0c39d88a0d10f96b5cece7dd2c62..a0215bda855b3e744f4cfe23ad6cec2b06f8ebaa 100644 --- a/src/UI/DocumentViews/AudioVisualizer.cc +++ b/src/UI/DocumentViews/AudioVisualizer.cc @@ -1,14 +1,6 @@ #include "AudioVisualizer.hh" #include "../../Lib/Audio.hh" -#include <algorithm> -#include <QGraphicsPixmapItem> -#include <QLabel> -#include <QMessageBox> -#include <QScrollArea> -#include <QScrollBar> -#include <QVBoxLayout> - using namespace Vivy; #define MAXPIXVALUE 7 // Some magix AV magic stuff diff --git a/src/UI/DocumentViews/AudioVisualizer.hh b/src/UI/DocumentViews/AudioVisualizer.hh index 451d50800979bca9f137b3fd2dfcd3842715629b..47032201caaf6ef875e0555c8c46205542c44150 100644 --- a/src/UI/DocumentViews/AudioVisualizer.hh +++ b/src/UI/DocumentViews/AudioVisualizer.hh @@ -7,8 +7,6 @@ #include "TimingView.hh" #include "../../Lib/Audio.hh" -#include <QWidget> -#include <QString> namespace Vivy { @@ -33,7 +31,7 @@ private: public: explicit AudioVisualizer(AudioContext::StreamPtr, QWidget *parent = nullptr); - ~AudioVisualizer() noexcept = default; + ~AudioVisualizer() noexcept override = default; public slots: void printSpectrum(QImage) noexcept; diff --git a/src/UI/DocumentViews/MpvContainer.cc b/src/UI/DocumentViews/MpvContainer.cc index ef64b08f76860e1aed2df00318ea67590e7ebdea..425346ed36a1ef646bb5644b76034ccd307f773b 100644 --- a/src/UI/DocumentViews/MpvContainer.cc +++ b/src/UI/DocumentViews/MpvContainer.cc @@ -1,7 +1,5 @@ #include "MpvContainer.hh" -#include <mpv/client.h> - using namespace Vivy; using namespace std::string_literals; @@ -30,18 +28,19 @@ MpvContainer::MpvContainer(QWidget *parent) mpv_set_option_string(mpv, "sid", "no"); mpv_set_option_string(mpv, "input-default-bindings", "no"); mpv_set_option_string(mpv, "input-vo-keyboard", "no"); + mpv_set_option_string(mpv, "no-ytdl", "yes"); mpv_request_log_messages(mpv, "info"); mpv_observe_property(mpv, 0, "pause", MPV_FORMAT_FLAG); mpv_observe_property(mpv, 0, "duration", MPV_FORMAT_DOUBLE); mpv_observe_property(mpv, 0, "time-pos", MPV_FORMAT_DOUBLE); - connect(this, &MpvContainer::mpvEvent, this, &MpvContainer::onMpvEvent, Qt::QueuedConnection); - mpv_set_wakeup_callback(mpv, &MpvContainer::mpvEventWakeUpCB, this); - if (int rc = mpv_initialize(mpv); rc < 0) { printMpvError(rc); throw std::runtime_error("Failed to initialize the mpv context"); } + + connect(this, &MpvContainer::mpvEvent, this, &MpvContainer::onMpvEvent, Qt::QueuedConnection); + mpv_set_wakeup_callback(mpv, &MpvContainer::mpvEventWakeUpCB, this); } void @@ -77,10 +76,9 @@ MpvContainer::closeMpv() noexcept MpvContainer::~MpvContainer() noexcept { closeMpv(); } void -MpvContainer::handleMpvEvent(mpv_event *event) noexcept +MpvContainer::handleMpvEvent(const mpv_event *const event) noexcept { // Declare here variables that can be used in the switch-case statements - qint64 w, h; double time; QString msgText; union { @@ -88,25 +86,17 @@ MpvContainer::handleMpvEvent(mpv_event *event) noexcept mpv_event_property *prop; }; - auto checkProp = [](mpv_event_property *prop_, const std::string &str, - int format) noexcept -> bool { + auto checkProp = [](const mpv_event_property *const prop_, const std::string_view str, + const int format) noexcept -> bool { return (prop_->name == str) && (prop_->format == format); }; switch (event->event_id) { case MPV_EVENT_SHUTDOWN: closeMpv(); break; - case MPV_EVENT_VIDEO_RECONFIG: - // TODO: Those are sync calls, prefer async calls - if (mpv_get_property(mpv, "dwidth", MPV_FORMAT_INT64, &w) >= 0 && - mpv_get_property(mpv, "dheight", MPV_FORMAT_INT64, &h) >= 0 && (w > 0 && h > 0)) { - qDebug() << "Reconfigure video to" << w << "x" << h; - } - break; - case MPV_EVENT_LOG_MESSAGE: msg = reinterpret_cast<mpv_event_log_message *>(event->data); - msgText = QString(msg->text); + msgText = QString::fromUtf8(msg->text); msgText.replace('\n', ""); qDebug().nospace().noquote() << "MPV - MSG [" << msg->prefix << "] " << msg->level << ": " << msgText; @@ -114,17 +104,17 @@ MpvContainer::handleMpvEvent(mpv_event *event) noexcept case MPV_EVENT_PROPERTY_CHANGE: prop = reinterpret_cast<mpv_event_property *>(event->data); - if (checkProp(prop, "time-pos"s, MPV_FORMAT_DOUBLE) && mpvTimeCallback) { + if (checkProp(prop, "time-pos", MPV_FORMAT_DOUBLE) && mpvTimeCallback) { time = *reinterpret_cast<double *>(prop->data); mpvTimeCallback(time); } - else if (checkProp(prop, "duration"s, MPV_FORMAT_DOUBLE) && mpvDurationCallback) { + else if (checkProp(prop, "duration", MPV_FORMAT_DOUBLE) && mpvDurationCallback) { time = *reinterpret_cast<double *>(prop->data); mpvDurationCallback(time); } - else if (checkProp(prop, "pause"s, MPV_FORMAT_FLAG)) { + else if (checkProp(prop, "pause", MPV_FORMAT_FLAG)) { isPlaybackPaused = *reinterpret_cast<bool *>(prop->data); emit mpvPlaybackToggled(!isPlaybackPaused); qDebug() << "MPV -> set to" << (isPlaybackPaused ? "pause" : "play"); @@ -145,7 +135,6 @@ MpvContainer::handleMpvEvent(mpv_event *event) noexcept break; case MPV_EVENT_START_FILE: qDebug() << "MPV: Begin of file"; break; - case MPV_EVENT_END_FILE: qDebug() << "MPV: Reached end of file!"; break; case MPV_EVENT_COMMAND_REPLY: @@ -154,6 +143,7 @@ MpvContainer::handleMpvEvent(mpv_event *event) noexcept break; // Explicitly ignored + case MPV_EVENT_VIDEO_RECONFIG: case MPV_EVENT_NONE: case MPV_EVENT_GET_PROPERTY_REPLY: case MPV_EVENT_SET_PROPERTY_REPLY: @@ -179,7 +169,7 @@ MpvContainer::getAssSid() const noexcept { bool conversionOk = false; const char *result = mpv_get_property_string(mpv, "sid"); - const int ret = QString(result).toInt(&conversionOk); + const int ret = QString::fromUtf8(result).toInt(&conversionOk); return (result == nullptr || !conversionOk) ? -1 : ret; } @@ -220,7 +210,7 @@ void MpvContainer::onMpvEvent() noexcept { while (mpv) { - mpv_event *event = mpv_wait_event(mpv, 0); + const mpv_event *const event = mpv_wait_event(mpv, 0); if (event == nullptr || event->event_id == MPV_EVENT_NONE) break; handleMpvEvent(event); @@ -234,7 +224,8 @@ MpvContainer::onMpvEvent() noexcept void MpvContainer::loadFile(const QString &filename) noexcept { - if (filename.isEmpty()) + qDebug() << "Loading file" << filename; + if (filename.size() == 0) return; const QByteArray cFileName = filename.toUtf8(); diff --git a/src/UI/DocumentViews/MpvContainer.hh b/src/UI/DocumentViews/MpvContainer.hh index 8296cac69656bfd2b8d14b3eb2bac70722744884..760d5e4a32c75fc5ef6ab4d04abe4033bd4cb184 100644 --- a/src/UI/DocumentViews/MpvContainer.hh +++ b/src/UI/DocumentViews/MpvContainer.hh @@ -6,9 +6,6 @@ #include "../../Lib/Utils.hh" -#include <functional> -#include <initializer_list> - extern "C" { struct mpv_handle; struct mpv_event; @@ -27,12 +24,12 @@ class MpvContainer final : public QWidget { ReloadAss, UnloadAss, SeekTime, - TogglePlayback, + TogglePlayback }; private: bool isPlaybackPaused{ true }; - mpv_handle *mpv{ nullptr }; + mpv_handle *volatile mpv{ nullptr }; qint64 sid{ -1 }; std::function<void(double)> mpvTimeCallback{ nullptr }; std::function<void(double)> mpvDurationCallback{ nullptr }; @@ -53,7 +50,7 @@ public: void registerMpvDurationCallback(std::function<void(double)>) noexcept; private: - void handleMpvEvent(mpv_event *) noexcept; + void handleMpvEvent(const mpv_event *const) noexcept; void closeMpv() noexcept; void printMpvError(int) const noexcept; void asyncCommand(const AsyncCmdType, std::initializer_list<const char *>) noexcept; diff --git a/src/UI/DocumentViews/MpvControls.cc b/src/UI/DocumentViews/MpvControls.cc index 696ea6bb716952f0202f79e9294a9415dbc401f3..931239ac66d8dd4ed04255ac40f46ca73699c50c 100644 --- a/src/UI/DocumentViews/MpvControls.cc +++ b/src/UI/DocumentViews/MpvControls.cc @@ -1,10 +1,6 @@ #include "MpvControls.hh" #include "MpvContainer.hh" -#include <QSlider> -#include <QHBoxLayout> -#include <QPushButton> - using namespace Vivy; MpvControls::MpvControls(MpvContainer *passedContainer, QWidget *parent) noexcept diff --git a/src/UI/DocumentViews/MpvControls.hh b/src/UI/DocumentViews/MpvControls.hh index db00d5900753f8411c2c2f2896aab0d8458708dd..51b1e127450c31a2fd291c46db08b5663aa05f38 100644 --- a/src/UI/DocumentViews/MpvControls.hh +++ b/src/UI/DocumentViews/MpvControls.hh @@ -6,7 +6,6 @@ #include "../../Lib/Utils.hh" #include "../../VivyApplication.hh" -#include <QIcon> namespace Vivy { diff --git a/src/UI/DocumentViews/TimingBar.cc b/src/UI/DocumentViews/TimingBar.cc index 9353c174b4b778aff7fcc129f627b6863d69891a..ac5fed95ed31e9700ce3fbe69eeaded26d5c975f 100644 --- a/src/UI/DocumentViews/TimingBar.cc +++ b/src/UI/DocumentViews/TimingBar.cc @@ -1,15 +1,5 @@ #include "TimingBar.hh" -#include <QApplication> -#include <QGraphicsSceneMouseEvent> -#include <QLabel> -#include <QMessageBox> -#include <QMouseEvent> -#include <QPen> -#include <QScrollArea> -#include <QScrollBar> -#include <QVBoxLayout> - using namespace Vivy; TimingBar::TimingBar(QLine line, QColor color) noexcept diff --git a/src/UI/DocumentViews/TimingBar.hh b/src/UI/DocumentViews/TimingBar.hh index 6edf64eede549c9284a1a332d05aaca6b8bfdc6a..583ee164782b1f1909a2c8f23a6dde6454d625cd 100644 --- a/src/UI/DocumentViews/TimingBar.hh +++ b/src/UI/DocumentViews/TimingBar.hh @@ -1,19 +1,15 @@ -#ifndef VIVY_TIMING_BAR_H -#define VIVY_TIMING_BAR_H +#pragma once #ifndef __cplusplus #error "This is a C++ header" #endif -#include <QGraphicsItem> -#include <QGraphicsLineItem> - namespace Vivy { class TimingBar final : public QGraphicsLineItem { public: explicit TimingBar(QLine, QColor) noexcept; - ~TimingBar() noexcept = default; + ~TimingBar() noexcept override = default; private: static inline constexpr int bar_demi_width = 2; @@ -27,7 +23,4 @@ protected: void hoverLeaveEvent (QGraphicsSceneHoverEvent*) override; */ }; - } - -#endif // VIVY_TIMING_BAR_H diff --git a/src/UI/DocumentViews/TimingScene.hh b/src/UI/DocumentViews/TimingScene.hh index 02f20e90904772202d507760dbb6a119ab40ff00..cb81a141d1e83c61d7f24fab55e140e956c2eacb 100644 --- a/src/UI/DocumentViews/TimingScene.hh +++ b/src/UI/DocumentViews/TimingScene.hh @@ -1,17 +1,9 @@ -#ifndef VIVY_TIMING_SCENE_H -#define VIVY_TIMING_SCENE_H +#pragma once #include "../../Lib/Utils.hh" #include "../../Lib/Ass/Ass.hh" #include "TimingBar.hh" -#include <QWidget> -#include <QColor> -#include <QVector> -#include <QGraphicsView> -#include <QGraphicsScene> -#include <memory> - namespace Vivy { class TimingScene final : public QGraphicsScene { @@ -52,5 +44,3 @@ public slots: //Only if combinaisons of mode is allowed, it shouldn't be here //Q_DECLARE_OPERATORS_FOR_FLAGS(TimingScene::TimingModes) - -#endif // VIVY_TIMING_SCENE_H diff --git a/src/UI/DocumentViews/TimingView.cc b/src/UI/DocumentViews/TimingView.cc index a140ac56ef5e24214a63269cbeba76668e532d12..c5391b1611aff4110d1a73336206bf10c4f3a680 100644 --- a/src/UI/DocumentViews/TimingView.cc +++ b/src/UI/DocumentViews/TimingView.cc @@ -1,18 +1,5 @@ #include "TimingView.hh" -#include <QAbstractScrollArea> -#include <QGraphicsLineItem> -#include <QGraphicsPixmapItem> -#include <QGraphicsView> -#include <QLabel> -#include <QMessageBox> -#include <QMouseEvent> -#include <QPainter> -#include <QScrollArea> -#include <QScrollBar> -#include <QVBoxLayout> -#include <QtGlobal> - using namespace Vivy; TimingView::TimingView(QImage img, quint64 soundLength, QWidget *parent) noexcept diff --git a/src/UI/DocumentViews/TimingView.hh b/src/UI/DocumentViews/TimingView.hh index 5b0fc0e03971d36de6aa48a578997d330a8d563e..0d68b4fa9f09d3e3fe9a42b8c5364894153ad25b 100644 --- a/src/UI/DocumentViews/TimingView.hh +++ b/src/UI/DocumentViews/TimingView.hh @@ -1,5 +1,4 @@ -#ifndef VIVY_TIMING_VIEW_H -#define VIVY_TIMING_VIEW_H +#pragma once #ifndef __cplusplus #error "This is a C++ header" @@ -9,11 +8,6 @@ #include "TimingBar.hh" #include "TimingScene.hh" -#include <QWidget> -#include <QColor> -#include <QVector> -#include <QGraphicsView> - namespace Vivy { class TimingView final : public QGraphicsView { @@ -26,7 +20,7 @@ public: static inline constexpr QColor endColour = QColor(0, 127, 0); explicit TimingView(QImage, quint64, QWidget * = nullptr) noexcept; - ~TimingView() noexcept = default; + ~TimingView() noexcept override = default; void wheelEvent(QWheelEvent *) noexcept override; @@ -37,7 +31,4 @@ public slots: void mousePressEvent(QMouseEvent *event) noexcept override; void moveScrollBarToBottom(int, int) noexcept; }; - } - -#endif // VIVY_TIMING_VIEW_H diff --git a/src/UI/DocumentViews/VideoView.cc b/src/UI/DocumentViews/VideoView.cc index bc73922e714944733f788251d866f26441bba4e0..30eee0e0863deaac58c2187232336f20395939a0 100644 --- a/src/UI/DocumentViews/VideoView.cc +++ b/src/UI/DocumentViews/VideoView.cc @@ -2,8 +2,6 @@ #include "MpvContainer.hh" #include "MpvControls.hh" -#include <QVBoxLayout> - using namespace Vivy; VideoView::VideoView(QWidget *parent) noexcept diff --git a/src/UI/FakeVim/FakeVimActions.cc b/src/UI/FakeVim/FakeVimActions.cc index 1c36b444d5647feb830b9a31ad0ce16468ba748c..7042af0745e7a304c5e9534e61439ffce665f387 100644 --- a/src/UI/FakeVim/FakeVimActions.cc +++ b/src/UI/FakeVim/FakeVimActions.cc @@ -2,12 +2,16 @@ #include "FakeVimHandler.hh" #include "../../Lib/Utils.hh" -#include <QDebug> namespace FakeVim::Internal { #ifdef FAKEVIM_STANDALONE FvBaseAspect::FvBaseAspect() {} +FvBaseAspect::~FvBaseAspect() {} +FvBoolAspect::~FvBoolAspect() {} +FvIntegerAspect::~FvIntegerAspect() {} +FvStringAspect::~FvStringAspect() {} +FvAspectContainer::~FvAspectContainer() {} void FvBaseAspect::setValue(const QVariant &value) diff --git a/src/UI/FakeVim/FakeVimActions.hh b/src/UI/FakeVim/FakeVimActions.hh index b1cea03d6697830e2637ca2efbbca89ff8ecdf0e..13895c395ead827054ff3c7dc76ea6ceb4f8b7c8 100644 --- a/src/UI/FakeVim/FakeVimActions.hh +++ b/src/UI/FakeVim/FakeVimActions.hh @@ -2,18 +2,12 @@ #define FAKEVIM_STANDALONE -#include <QCoreApplication> -#include <QHash> -#include <QObject> -#include <QString> -#include <QVariant> - namespace FakeVim::Internal { class FvBaseAspect { public: FvBaseAspect(); - virtual ~FvBaseAspect() {} + virtual ~FvBaseAspect(); void setValue(const QVariant &value); QVariant value() const; @@ -34,21 +28,25 @@ private: class FvBoolAspect : public FvBaseAspect { public: + ~FvBoolAspect() override; bool value() const { return FvBaseAspect::value().toBool(); } }; class FvIntegerAspect : public FvBaseAspect { public: + ~FvIntegerAspect() override; qint64 value() const { return FvBaseAspect::value().toLongLong(); } }; class FvStringAspect : public FvBaseAspect { public: + ~FvStringAspect() override; QString value() const { return FvBaseAspect::value().toString(); } }; class FvAspectContainer : public FvBaseAspect { public: + ~FvAspectContainer() override; }; class FakeVimSettings final : public FvAspectContainer { diff --git a/src/UI/FakeVim/FakeVimHandler.cc b/src/UI/FakeVim/FakeVimHandler.cc index 153b5a53ddf3c9ba9695da5e4da3c554f133e930..3d455d8b3b63e5900169c59ab00a53fd737fdb12 100644 --- a/src/UI/FakeVim/FakeVimHandler.cc +++ b/src/UI/FakeVim/FakeVimHandler.cc @@ -33,36 +33,6 @@ #include "FakeVimTr.hh" #include "../../Lib/Utils.hh" -#include <QDebug> -#include <QFile> -#include <QObject> -#include <QPointer> -#include <QProcess> -#include <QRegularExpression> -#include <QTextStream> -#include <QTimer> -#include <QStack> - -#include <QApplication> -#include <QClipboard> -#include <QInputMethodEvent> -#include <QKeyEvent> -#include <QLineEdit> -#include <QPlainTextEdit> -#include <QScrollBar> -#include <QTextBlock> -#include <QTextCursor> -#include <QTextDocumentFragment> -#include <QTextEdit> -#include <QMimeData> -#include <QSharedPointer> -#include <QDir> - -#include <algorithm> -#include <climits> -#include <ctype.h> -#include <functional> - //#define DEBUG_KEY 1 #if defined(DEBUG_KEY) && DEBUG_KEY #define KEY_DEBUG(s) qDebug() << s diff --git a/src/UI/FakeVim/FakeVimHandler.hh b/src/UI/FakeVim/FakeVimHandler.hh index 1894680b7715fd951180df9af6772252b5b89b01..a84a97ee71e5033f7976aec554c2f929d7d9a209 100644 --- a/src/UI/FakeVim/FakeVimHandler.hh +++ b/src/UI/FakeVim/FakeVimHandler.hh @@ -2,12 +2,6 @@ #define FAKEVIM_STANDALONE -#include <QObject> -#include <QTextEdit> - -#include <functional> -#include <vector> - namespace FakeVim::Internal { enum RangeMode { diff --git a/src/UI/FakeVim/FakeVimTr.hh b/src/UI/FakeVim/FakeVimTr.hh index b298d4f77421612544e68be9f83d66aca95fed24..d2cf884ba9c40e54daabeb5f603100fa59bbf450 100644 --- a/src/UI/FakeVim/FakeVimTr.hh +++ b/src/UI/FakeVim/FakeVimTr.hh @@ -1,7 +1,5 @@ #pragma once -#include <QCoreApplication> - namespace FakeVim { struct Tr { diff --git a/src/UI/MainWindow.cc b/src/UI/MainWindow.cc index 31d2a7cc395210bc8797868b9741dfa4640e2a22..d789104ff1878a53cfab37214a84f64492798a48 100644 --- a/src/UI/MainWindow.cc +++ b/src/UI/MainWindow.cc @@ -6,27 +6,6 @@ #include "../Lib/Utils.hh" #include "../VivyApplication.hh" -#include <mutex> -#include <algorithm> -#include <functional> -#include <QWindow> -#include <optional> -#include <QTreeView> -#include <QPushButton> -#include <QDialogButtonBox> -#include <QFileInfo> -#include <QStandardPaths> -#include <QPixmap> -#include <QVBoxLayout> -#include <QMessageBox> -#include <QFileDialog> -#include <QStatusBar> -#include <QMenuBar> -#include <QImage> -#include <QToolBar> -#include <QTabWidget> -#include <QEventLoop> - #define DCL_MENU(menu, name) [[maybe_unused]] QMenu *menu##Menu = menuBar()->addMenu(name); #define DCL_ACTION(method, name, tip, menu) \ @@ -62,6 +41,7 @@ MainWindow::MainWindow() noexcept DCL_ACTION(openDocument, "&Open document", "Open a document", file); DCL_ACTION(saveFile, "&Save file", "Save the current document", file); DCL_ACTION(saveFileAs, "&Save file as", "Save the current document as", file); + DCL_ACTION(renameFile, "&Rename the file", "Rename the current document", file); fileMenu->addSeparator(); DCL_ACTION(loadSubDocumentAudio, "&Load audio", "Load an audio file as a sub-document", file); DCL_ACTION(loadSubDocumentVideo, "&Load video", "Load a vide file as a sub-document", file); @@ -73,6 +53,7 @@ MainWindow::MainWindow() noexcept ACTION_ADD_ICON(openDocument, VIVY_ICON_OPEN); ACTION_ADD_ICON(saveFile, VIVY_ICON_SAVE); ACTION_ADD_ICON(saveFileAs, VIVY_ICON_SAVE_AS); + ACTION_ADD_ICON(renameFile, VIVY_ICON_RENAME); ACTION_ADD_ICON(openDialogHelp, VIVY_ICON_ABOUT); ACTION_ADD_SHORTCUT(newDocument, QKeySequence::New); @@ -117,10 +98,11 @@ MainWindow::MainWindow() noexcept loadSubDocumentVideoAct->setEnabled(false); loadSubDocumentAudioAct->setEnabled(false); - // Enable actions if the document is save-able - auto enableSaveOnDocument = [this](auto *widget, int index) noexcept -> void { + // Enable "Save As" action + auto enableSaveAsOnDocument = [this](auto *widget, int index) noexcept -> void { if (index >= 0) { - auto type = static_cast<AbstractDocumentView *>(documents->widget(index))->getType(); + const auto doc = static_cast<AbstractDocumentView *>(documents->widget(index)); + const auto type = doc->getType(); widget->setEnabled(type == AbstractDocumentView::Type::Vivy || type == AbstractDocumentView::Type::Script); } else { @@ -128,6 +110,22 @@ MainWindow::MainWindow() noexcept } }; + // Enable actions if the document is save-able + auto enableSaveOnDocument = [this](auto *widget, int index) noexcept -> void { + if (index >= 0) { + const auto doc = static_cast<AbstractDocumentView *>(documents->widget(index)); + const auto type = doc->getType(); + const bool isVivyMemoryDoc = + (type == AbstractDocumentView::Type::Vivy) && + dynamic_cast<VivyDocument *>(doc->getDocument()) + ->checkDocumentOption(VivyDocument::MemoryDocumentCreation); + widget->setEnabled((!isVivyMemoryDoc) && (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) { @@ -138,22 +136,18 @@ MainWindow::MainWindow() noexcept } }; - { -#define CONNECT_ENABLE(act, func) \ - connect(documents, &QTabWidget::currentChanged, act, std::bind_front(func, act)); - - connect(documents, &QTabWidget::currentChanged, this, - [this](int) noexcept -> void { documentViewActionsChanged(); }); - - CONNECT_ENABLE(saveFileAct, enableSaveOnDocument); - CONNECT_ENABLE(saveFileAsAct, enableSaveOnDocument); + auto enableConnection = [this](QAction *const act, auto func) noexcept -> void { + connect(documents, &QTabWidget::currentChanged, act, std::bind_front(func, act)); + }; - CONNECT_ENABLE(loadSubDocumentAssAct, enableLoadSubOnDocument); - CONNECT_ENABLE(loadSubDocumentVideoAct, enableLoadSubOnDocument); - CONNECT_ENABLE(loadSubDocumentAudioAct, enableLoadSubOnDocument); + connect(documents, &QTabWidget::currentChanged, this, + [this](int) noexcept -> void { documentViewActionsChanged(); }); -#undef CONNECT_ENABLE - } + enableConnection(saveFileAct, enableSaveOnDocument); + enableConnection(saveFileAsAct, enableSaveAsOnDocument); + enableConnection(loadSubDocumentAssAct, enableLoadSubOnDocument); + enableConnection(loadSubDocumentVideoAct, enableLoadSubOnDocument); + enableConnection(loadSubDocumentAudioAct, enableLoadSubOnDocument); // Main window has finished its construction statusBar()->showMessage("QSimulate has started"); @@ -166,19 +160,25 @@ MainWindow::MainWindow() noexcept newDocument(); } +void +MainWindow::closeEvent(QCloseEvent *event) noexcept +{ + qDebug() << "Closing the main window!"; + forEachViews<VivyDocumentView>([](VivyDocumentView *view, int) { view->closeDocument(); }); + QMainWindow::closeEvent(event); +} + void MainWindow::updateFakeVimUsage(bool yes) noexcept { - const int count = documents->count(); - for (int index = 0; index < count; ++index) { - AbstractDocumentView *docView = getTab(index); - const bool documentExists = docView && docView->getDocument(); + forEachViews<AbstractDocumentView>([yes](AbstractDocumentView *docView, int) noexcept -> void { + const bool documentExists = docView && docView->getDocument(); const bool isScriptEditor = documentExists && (docView->getDocument()->getType() == AbstractDocument::Type::Script); if (isScriptEditor) - static_cast<ScriptDocumentView *const>(docView)->setUseFakeVimEditor(yes); - } + static_cast<ScriptDocumentView *>(docView)->setUseFakeVimEditor(yes); + }); } void @@ -211,24 +211,42 @@ MainWindow::openDialogHelp() noexcept } AbstractDocument * -MainWindow::getCurrentDocument() const noexcept +MainWindow::getCurrentDocument() const +{ + return getCurrentDocumentView()->getDocument(); +} + +void +MainWindow::saveFile() noexcept { try { - return getCurrentDocumentView()->getDocument(); - } catch (const std::runtime_error &e) { - qCritical() << "No current view in the main window:" << e.what(); - return nullptr; + const auto document = getCurrentDocument(); + document->save(); + } + + catch (const std::runtime_error &e) { + qCritical() << "Failed to save current document:" << e.what(); } } void -MainWindow::saveFile() noexcept +MainWindow::renameFile() noexcept { try { - if (auto document = getCurrentDocument()) { - qDebug() << "Request to save the document" << document->getName(); - } - } catch (const std::runtime_error &e) { + const auto docView = getCurrentDocumentView(); + auto document = docView->getDocument(); + const QString filename = + dialogSaveFileName("Select the target file to rename the current file to", + QDir::homePath(), Utils::getVivyDocumentFileSuffixFilter()); + + if (filename.isEmpty()) + throw std::runtime_error("No filename passed"); + + else + document->rename(filename); // Kubat: Save is called by rename! + } + + catch (const std::runtime_error &e) { qCritical() << "Failed to save current document:" << e.what(); } } @@ -237,10 +255,20 @@ void MainWindow::saveFileAs() noexcept { try { - if (auto document = getCurrentDocument()) { - qDebug() << "Request to save the document" << document->getName(); - } - } catch (const std::runtime_error &e) { + const auto docView = getCurrentDocumentView(); + auto document = docView->getDocument(); + const QString filename = + dialogSaveFileName("Select the target file to save into", QDir::homePath(), + Utils::getVivyDocumentFileSuffixFilter()); + + if (filename.isEmpty()) + throw std::runtime_error("No filename passed"); + + else + document->copy(filename); // Kubat: Save is called by copy! + } + + catch (const std::runtime_error &e) { qCritical() << "Failed to save current document:" << e.what(); } } @@ -251,13 +279,12 @@ MainWindow::closeDocument(AbstractDocumentView *const view) noexcept if (view == nullptr || !view->getDocument()) return; - const int count = documents->count(); - for (int index = 0; index < count; ++index) { - const AbstractDocumentView *const documentView = getTab(index); - const bool documentExists = documentView && documentView->getDocument(); - if (documentExists && (*documentView->getDocument()) == (*view->getDocument())) - closeDocument(index); - } + forEachViews<AbstractDocumentView>( + [this, view](AbstractDocumentView *documentView, int index) noexcept -> void { + const bool documentExists = documentView && documentView->getDocument(); + if (documentExists && (*documentView->getDocument()) == (*view->getDocument())) + closeDocument(index); + }); } void @@ -293,7 +320,7 @@ void MainWindow::openDocument() noexcept { const QString filename = dialogOpenFileName("Select a document to open", QDir::homePath(), - Utils::getAnyDocumentFileSuffixFilter()); + Utils::getAnyTopLevelDocumentFileSuffixFilter()); if (filename.isEmpty()) { qWarning() << "Found an empty filename, don't open a file"; return; @@ -309,21 +336,17 @@ MainWindow::openDocument() noexcept // Handle the different types here try { - if ((fileType == Utils::DocumentType::Video) || (fileType == Utils::DocumentType::Vivy) || - (fileType == Utils::DocumentType::Audio) || (fileType == Utils::DocumentType::ASS)) + if (fileType == Utils::DocumentType::Vivy) addTab(new VivyDocumentView(vivyApp->documentStore.loadDocument(filename), documents)); else if (fileType == Utils::DocumentType::VivyScript) { - // FIXME: First interpretation of the file to get the error, can do better. - std::shared_ptr<ScriptDocument> scriptDocument = - vivyApp->scriptStore.loadDocument(filename); - std::optional<std::tuple<int, std::string>> errorTuple = - vivyApp->scriptStore.executeScript(scriptDocument->getUuid()); + auto scriptDocument = vivyApp->scriptStore.loadDocument(filename); + auto errorTuple = vivyApp->scriptStore.executeScript(scriptDocument->getUuid()); ScriptDocumentView *newView = new ScriptDocumentView(scriptDocument, documents); if (errorTuple.has_value()) { const auto &[line, desc] = errorTuple.value(); - emit newView->luaErrorFound(line, QString{ desc.c_str() }); + emit newView->luaErrorFound(line, QString::fromUtf8(desc.c_str())); } addTab(newView); @@ -369,7 +392,7 @@ MainWindow::loadSubDocumentAudio() noexcept } void -MainWindow::addTab(AbstractDocumentView *tab) +MainWindow::addTab(AbstractDocumentView *const tab) { int index = -1; if (const int untouched_index = findFirstUntouchedDocument(); untouched_index >= 0) { @@ -384,10 +407,48 @@ MainWindow::addTab(AbstractDocumentView *tab) documents->setCurrentIndex(index); connect(tab, &AbstractDocumentView::viewActionsChanged, this, &MainWindow::documentViewActionsChanged); + connect(tab, &AbstractDocumentView::documentPropertyChanged, this, + [this, tab]() noexcept -> void { + int tabIndex = getIndexOfTab(tab); + if (tabIndex < 0) { + qDebug() << "Tab was not found!"; + } else { + qDebug() << "Tab was found at index" << tabIndex; + documents->setTabToolTip(tabIndex, tab->getDocumentTabToolTip()); + documents->setTabText(tabIndex, tab->getDocumentTabName()); + } + }); documentViewActionsChanged(); qDebug() << "View constructed successfully"; } +int +MainWindow::getIndexOfTab(const AbstractDocumentView *const viewA) const noexcept +{ + if (viewA == nullptr) + return -1; + + const AbstractDocument *const docA = viewA->getDocument(); + if (docA == nullptr) + return -1; + + int returnIndex = -1; + forEachViews<AbstractDocumentView>( + [docA, &returnIndex](AbstractDocumentView *viewB, int index) noexcept -> void { + if (viewB == nullptr) + return; + + const AbstractDocument *const docB = viewB->getDocument(); + if (docB == nullptr) + return; + + if (*docB == *docA) + returnIndex = index; + }); + + return returnIndex; +} + AbstractDocumentView * MainWindow::getCurrentDocumentView() const { @@ -412,17 +473,14 @@ MainWindow::getTab(const int index) const noexcept int MainWindow::findFirstUntouchedDocument() const noexcept { - const int count = documents->count(); - for (int index = 0; index < count; ++index) { - AbstractDocumentView *documentView = getTab(index); - if (documentView && (documentView->getType() == AbstractDocumentView::Type::Vivy) && - static_cast<VivyDocumentView *>(documentView) - ->getDocument() - ->checkDocumentOption(VivyDocument::UntouchedByDefault)) - return index; - } + int returnIndex = -1; + forEachViews<VivyDocumentView>( + [&returnIndex](VivyDocumentView *view, int index) noexcept -> void { + if (view->getDocument()->checkDocumentOption(VivyDocument::UntouchedByDefault)) + returnIndex = index; + }); - return -1; + return returnIndex; } void @@ -438,27 +496,23 @@ MainWindow::documentViewActionsChanged() noexcept qInfo() << "No view to display:" << e.what(); } } -QString -MainWindow::dialogOpenFileName(const QString &title, const QString &folder, - const QString &filter) noexcept + +static inline QString +executeDialog(MainWindow *const self, QFileDialog *const dialog) noexcept { - QFileDialog dialog(this, title, folder, filter); bool dialogAccepted = false; std::unique_ptr<VivyFileIconProvider> iconProvider(new VivyFileIconProvider()); - dialog.setOption(QFileDialog::ReadOnly); - dialog.setIconProvider(iconProvider.get()); - dialog.setFileMode(QFileDialog::ExistingFile); - connect(&dialog, &QFileDialog::accepted, this, - [&dialogAccepted]() noexcept -> void { dialogAccepted = true; }); + dialog->setIconProvider(iconProvider.get()); + QFileDialog::connect(dialog, &QFileDialog::accepted, self, + [&dialogAccepted]() noexcept -> void { dialogAccepted = true; }); - dialog.exec(); + dialog->exec(); - if (!dialogAccepted) { + if (!dialogAccepted) return QStringLiteral(""); - } - const QStringList resList = dialog.selectedFiles(); + const QStringList resList = dialog->selectedFiles(); if (resList.size() != 1) { qCritical() << "You must select only one file"; return QStringLiteral(""); @@ -466,3 +520,24 @@ MainWindow::dialogOpenFileName(const QString &title, const QString &folder, return resList.at(0); } + +QString +MainWindow::dialogOpenFileName(const QString &title, const QString &folder, + const QString &filter) noexcept +{ + QFileDialog dialog(this, title, folder, filter); + dialog.setOption(QFileDialog::ReadOnly); + dialog.setAcceptMode(QFileDialog::AcceptOpen); + dialog.setFileMode(QFileDialog::ExistingFile); + return executeDialog(this, &dialog); +} + +QString +MainWindow::dialogSaveFileName(const QString &title, const QString &folder, + const QString &filter) noexcept +{ + QFileDialog dialog(this, title, folder, filter); + dialog.setOption(QFileDialog::ReadOnly, false); + dialog.setAcceptMode(QFileDialog::AcceptSave); + return executeDialog(this, &dialog); +} diff --git a/src/UI/MainWindow.hh b/src/UI/MainWindow.hh index 3a5d921232f76d2a2808f3bb36eabda6627d772b..7d375a94be296067d5ef6b2a700f04c86921f536 100644 --- a/src/UI/MainWindow.hh +++ b/src/UI/MainWindow.hh @@ -9,11 +9,8 @@ #include "../Lib/Document/VivyDocumentStore.hh" #include "DocumentViews/AudioVisualizer.hh" #include "VivyDocumentView.hh" +#include "ScriptDocumentView.hh" #include "AboutWindow.hh" -#include <QMenu> -#include <QFileDialog> -#include <QMutex> -#include <QMainWindow> namespace Vivy { @@ -29,14 +26,14 @@ class MainWindow final : public QMainWindow { public: explicit MainWindow() noexcept; - AbstractDocument *getCurrentDocument() const noexcept; - template <class Document> Document *getCurrentDocument() const noexcept + AbstractDocument *getCurrentDocument() const; + template <class Document> Document *getCurrentDocument() const { if (AbstractDocumentView *currentView = getCurrentDocumentView(); currentView->getType() == Document::type) { return currentView->getDocument(); } else { - return nullptr; + throw std::runtime_error("No main document view was found"); } } @@ -44,14 +41,17 @@ public slots: void closeDocument(AbstractDocumentView *const) noexcept; private: - void addTab(AbstractDocumentView *); + void addTab(AbstractDocumentView *const); AbstractDocumentView *getTab(const int) const noexcept; AbstractDocumentView *getCurrentDocumentView() const; + int getIndexOfTab(const AbstractDocumentView *const) const noexcept; int findFirstUntouchedDocument() const noexcept; void updateFakeVimUsage(bool yes) noexcept; QString dialogOpenFileName(const QString &title, const QString &folder, const QString &filter) noexcept; + QString dialogSaveFileName(const QString &title, const QString &folder, + const QString &filter) noexcept; // Do an action with the selected filename. The 'call' variable must be // callable like this: call(DocumentView, Document, QString) @@ -77,6 +77,22 @@ private: } } + // Walk throu all views in the QTabWidget and also get the index of the tab + template <Derived<AbstractDocumentView> DocViewType> void forEachViews(auto callback) const + { + const int count = documents->count(); + for (int index = 0; index < count; ++index) { + AbstractDocumentView *docView = getTab(index); + if constexpr (std::is_same_v<AbstractDocumentView, DocViewType>) + callback(docView, index); + else if (DocViewType *view = dynamic_cast<DocViewType *>(docView); view != nullptr) + callback(view, index); + } + } + +protected: + void closeEvent(QCloseEvent *) noexcept override; + private slots: void newDocument() noexcept; void openDocument() noexcept; @@ -90,6 +106,7 @@ private slots: void saveFile() noexcept; void saveFileAs() noexcept; + void renameFile() noexcept; void openDialogHelp() noexcept; diff --git a/src/UI/PropertyModel.cc b/src/UI/PropertyModel.cc index cadec56e0d0749326d54d0de2bceda71d3005297..9530ea77c4ee8315188235eb09b7e8cecf7d7c02 100644 --- a/src/UI/PropertyModel.cc +++ b/src/UI/PropertyModel.cc @@ -1,5 +1,4 @@ #include "PropertyModel.hh" -#include <QDebug> using namespace Vivy; @@ -34,10 +33,11 @@ PropertyModel::Item::getChildCount() const noexcept return childs.count(); } +// Can't be const because of indexOf... int -PropertyModel::Item::getRow() const noexcept +PropertyModel::Item::getRow() noexcept { - return parent ? parent->childs.indexOf(const_cast<Item *>(this)) : 0; + return parent ? parent->childs.indexOf(this) : 0; } void @@ -82,6 +82,54 @@ PropertyModel::Item::getChilds() const noexcept return childs; } +bool +PropertyModel::Item::isOrderedJsonObject(const QJsonValue &value) noexcept +{ + if (value.isArray()) { + for (const QJsonValue v : value.toArray()) { + if (!v.isObject() || v.toObject().count() != 1) + return false; + } + return true; + } + return false; +} + +PropertyModel::Item * +PropertyModel::Item::createChild(Item *root, QJsonObject::ConstIterator const &it) noexcept +{ + const QString key = it.key(); + const QJsonValue v = it.value(); + Item *child = fromJson(v, root); + child->setKey(key); + child->setType(v.type()); + return child; +} + +PropertyModel::Item * +PropertyModel::Item::createChild(Item *root, QJsonObject const &singleElem) noexcept +{ + Q_ASSERT(singleElem.count() == 1); + + const QString key = singleElem.keys().at(0); + const QJsonValue val = singleElem.value(key); + + Item *child = fromJson(val, root); + child->setKey(key); + child->setType(val.type()); + + return child; +} + +PropertyModel::Item * +PropertyModel::Item::createChild(Item *root, int pos, QJsonValue const &val) noexcept +{ + Item *child = fromJson(val, root); + child->setKey(QString::number(pos)); + child->setType(val.type()); + return child; +} + PropertyModel::Item * PropertyModel::Item::fromJson(const QJsonValue &value, PropertyModel::Item *parent) { @@ -89,22 +137,20 @@ PropertyModel::Item::fromJson(const QJsonValue &value, PropertyModel::Item *pare 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); - } + const QJsonObject objValue = value.toObject(); + const auto end = objValue.constEnd(); + for (auto it = objValue.constBegin(); it != end; ++it) + root->appendChild(createChild(root, it)); + } + else if (isOrderedJsonObject(value)) { + for (const QJsonValue v : value.toArray()) + root->appendChild(createChild(root, v.toObject())); } else if (value.isArray()) { for (int index = 0; const QJsonValue v : value.toArray()) { - Item *child = fromJson(v, root); - child->setKey(QString::number(index)); - child->setType(v.type()); - root->appendChild(child); + root->appendChild(createChild(root, index, v)); ++index; } } @@ -120,18 +166,14 @@ PropertyModel::Item::fromJson(const QJsonValue &value, PropertyModel::Item *pare PropertyModel::PropertyModel(QObject *parent) noexcept : QAbstractItemModel(parent) { - headers.append("key"); - headers.append("value"); + headers.append(QStringLiteral("Key")); + headers.append(QStringLiteral("Value")); } -PropertyModel::PropertyModel(const QJsonDocument &properties, QObject *parent) noexcept +PropertyModel::PropertyModel(const QJsonDocument &properties, QObject *parent) : PropertyModel(parent) { - try { - loadJson(properties); - } catch (const std::runtime_error &e) { - qCritical() << "Failed to create the property model object:" << e.what(); - } + loadJson(properties); } void @@ -173,28 +215,12 @@ PropertyModel::data(const QModelIndex &index, int role) const noexcept 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 +PropertyModel::setData(const QModelIndex &, const QVariant &, int) 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; } @@ -250,7 +276,7 @@ PropertyModel::rowCount(const QModelIndex &parent) const noexcept } int -PropertyModel::columnCount(const QModelIndex & /* parent */) const noexcept +PropertyModel::columnCount(const QModelIndex &) const noexcept { return 2; } @@ -261,40 +287,12 @@ PropertyModel::flags(const QModelIndex &index) const noexcept 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 +QTreeView * +PropertyModel::getView(QWidget *parent, PropertyModel *ptr) noexcept { - return root ? "Property for " + root->getKey() : "Property view"; + QTreeView *view = new QTreeView(parent); + view->setModel(ptr); + view->header()->setSectionResizeMode(QHeaderView::ResizeToContents); + view->expandAll(); + return view; } diff --git a/src/UI/PropertyModel.hh b/src/UI/PropertyModel.hh index d22ab6eccb28c96e34f3e00419b66a519d12f3b8..45a21ab01096912dd8a8ee68f2de4982b2111f69 100644 --- a/src/UI/PropertyModel.hh +++ b/src/UI/PropertyModel.hh @@ -1,14 +1,7 @@ -#ifndef VIVY_UI_PROPERTY_VIEW_H -#define VIVY_UI_PROPERTY_VIEW_H +#pragma once #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); @@ -40,7 +33,7 @@ private: const QVector<Item *> getChilds() const noexcept; Item *getParent() const noexcept; int getChildCount() const noexcept; - int getRow() const noexcept; + int getRow() noexcept; void setKey(const QString &) noexcept; void setValue(const QString &) noexcept; void setType(const QJsonValue::Type &) noexcept; @@ -50,8 +43,12 @@ private: static Item *fromJson(const QJsonValue &, Item *parent = nullptr); - protected: private: + static bool isOrderedJsonObject(QJsonValue const &) noexcept; + static Item *createChild(Item *root, QJsonObject::ConstIterator const &) noexcept; + static Item *createChild(Item *root, QJsonObject const &) noexcept; + static Item *createChild(Item *root, int pos, QJsonValue const &) noexcept; + QString key{}; QString value{}; QJsonValue::Type type{ QJsonValue::Null }; @@ -61,9 +58,8 @@ private: 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 + explicit PropertyModel(const QJsonDocument &, QObject *parent = nullptr); + explicit PropertyModel(const PropertyConstViewable auto &object, QObject *parent = nullptr) : PropertyModel(object.getProperties(), parent) { // With concepts it seems that the function must be implemented where @@ -71,7 +67,7 @@ public: root->setKey(object.getElementName()); } - void loadJson(const QJsonDocument &json); + [[nodiscard]] static QTreeView *getView(QWidget *parent, PropertyModel *) noexcept; QVariant data(const QModelIndex &, int role) const noexcept override; bool setData(const QModelIndex &, const QVariant &v, int r = Qt::EditRole) noexcept override; @@ -85,16 +81,11 @@ public: 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; + void loadJson(const QJsonDocument &json); + std::unique_ptr<Item> root{ nullptr }; QStringList headers{}; }; - } - -#endif // VIVY_UI_PROPERTY_VIEW_H diff --git a/src/UI/ScriptDocumentView.cc b/src/UI/ScriptDocumentView.cc index b91e3830cc759f1ca3df43d7ab3e572a159ede0a..b05c137d8e37f7ed26d9f66eb193d9f62a4eec3f 100644 --- a/src/UI/ScriptDocumentView.cc +++ b/src/UI/ScriptDocumentView.cc @@ -3,9 +3,6 @@ #include "ScriptViews/ScriptHighlighter.hh" #include "../VivyApplication.hh" -#include <QRegExp> -#include <QVBoxLayout> - using namespace Vivy; ScriptDocumentView::ScriptDocumentView(std::shared_ptr<ScriptDocument> ptr, QWidget *parent) @@ -19,13 +16,15 @@ ScriptDocumentView::ScriptDocumentView(std::shared_ptr<ScriptDocument> ptr, QWid throw std::runtime_error("Failed to open script file"); } - editor->setPlainText(textFile.readAll()); + editor->setPlainText(QString::fromUtf8(textFile.readAll())); setCentralWidget(editor); editor->setFocus(Qt::OtherFocusReason); setUseFakeVimEditor(vivyApp->getUseFakeVimEditor()); connect(this, &ScriptDocumentView::luaErrorFound, editor, &ScriptEditor::updateLastLuaError); + connect(document.get(), &AbstractDocument::documentChanged, this, + [=, this]() noexcept -> void { emit documentPropertyChanged(); }); // Same style as the editor setStyleSheet(QStringLiteral("* {" @@ -53,9 +52,11 @@ ScriptDocumentView::setUseFakeVimEditor(bool yes) noexcept connect(proxy, &EditorProxy::requestQuit, this, [this, mw]() noexcept -> void { mw->closeDocument(static_cast<AbstractDocumentView *>(this)); }); - TODO(Implement the save and save + quit things) - // connect(proxy, &EditorProxy::requestSave, document, &AbstractDocument::save); // TODO - // connect(proxy, &EditorProxy::requestSaveAndQuit, document, &AbstractDocument::save + &MainWindow::closeTab); // TODO + connect(proxy, &EditorProxy::requestSave, this, [this]() { document->save(); }); + connect(proxy, &EditorProxy::requestSaveAndQuit, this, [this, mw]() noexcept -> void { + document->save(); + mw->closeDocument(static_cast<AbstractDocumentView *>(this)); + }); initHandler(handler); clearUndoRedo(editor); } diff --git a/src/UI/ScriptDocumentView.hh b/src/UI/ScriptDocumentView.hh index 7f2c333bae28c31c6be079cf0faa5839ed40850e..c1acc9c31524571e2b7eedf0703991a12296b8fe 100644 --- a/src/UI/ScriptDocumentView.hh +++ b/src/UI/ScriptDocumentView.hh @@ -6,25 +6,12 @@ #endif #include "../Lib/Utils.hh" +#include "../Lib/Script/ScriptDocument.hh" #include "AbstractDocumentView.hh" -#include <QWidget> -#include <QString> -#include <memory> - -class QPlainTextEdit; - -namespace FakeVim::Internal -{ -class FakeVimHandler; -} - -namespace Vivy -{ -class ScriptEditor; -class ScriptHighlighter; -class ScriptDocument; -class EditorProxy; -} +#include "FakeVim/FakeVimHandler.hh" +#include "ScriptViews/EditorProxy.hh" +#include "ScriptViews/ScriptHighlighter.hh" +#include "ScriptViews/ScriptEditor.hh" namespace Vivy { diff --git a/src/UI/ScriptViews/EditorProxy.cc b/src/UI/ScriptViews/EditorProxy.cc index 16845293ca0bb5e1e2a9f40be4d2f82a4e6f699c..cff3cbd913c2ed04071fd60e8846a6032ea2d4a8 100644 --- a/src/UI/ScriptViews/EditorProxy.cc +++ b/src/UI/ScriptViews/EditorProxy.cc @@ -3,11 +3,6 @@ #include "../FakeVim/FakeVimActions.hh" #include "../../VivyApplication.hh" -#include <QMessageBox> -#include <QStatusBar> -#include <QMainWindow> -#include <QTemporaryFile> - using namespace Vivy; Vivy::EditorProxy * diff --git a/src/UI/ScriptViews/EditorProxy.hh b/src/UI/ScriptViews/EditorProxy.hh index 1c2e4bdadf2ca56f044915d10f43d2928b8518d1..c93a3823f753320f5521b446ddcb309bc5090d39 100644 --- a/src/UI/ScriptViews/EditorProxy.hh +++ b/src/UI/ScriptViews/EditorProxy.hh @@ -1,7 +1,5 @@ #pragma once -#include <QObject> -#include <QTextEdit> #include "ScriptEditor.hh" class QMainWindow; diff --git a/src/UI/ScriptViews/ScriptEditor.cc b/src/UI/ScriptViews/ScriptEditor.cc index 56dff1a6c6a6e6483c44437f4e49c8dcba29efcd..80b88fcfdfaaa8bf24a6107c32b5413f92661bcd 100644 --- a/src/UI/ScriptViews/ScriptEditor.cc +++ b/src/UI/ScriptViews/ScriptEditor.cc @@ -1,11 +1,6 @@ #include "ScriptEditor.hh" #include "ScriptHighlighter.hh" -#include <QTextCursor> -#include <QBrush> -#include <QKeyEvent> -#include <QFrame> - using namespace Vivy; ScriptEditor::LineNumberArea::LineNumberArea(ScriptEditor *editor) noexcept diff --git a/src/UI/ScriptViews/ScriptEditor.hh b/src/UI/ScriptViews/ScriptEditor.hh index 491a427d8bf9b76636764bb20701488724604d67..5a68ef9c71bdd6afe31211179ae7c28dbeed5c8c 100644 --- a/src/UI/ScriptViews/ScriptEditor.hh +++ b/src/UI/ScriptViews/ScriptEditor.hh @@ -4,7 +4,6 @@ #error "This is a C++ header" #endif -#include <QPlainTextEdit> #include "../../Lib/Utils.hh" namespace Vivy diff --git a/src/UI/ScriptViews/ScriptHighlighter.cc b/src/UI/ScriptViews/ScriptHighlighter.cc index 85ea10a8ce763a8bc918fb5928cbbf574f77bda3..2baf555a1ac8e625e4f3e6b4c87546ef4b9b6265 100644 --- a/src/UI/ScriptViews/ScriptHighlighter.cc +++ b/src/UI/ScriptViews/ScriptHighlighter.cc @@ -1,8 +1,6 @@ #include "ScriptHighlighter.hh" #include "../../Lib/Utils.hh" -#include <QTextDocument> - using namespace Vivy; ScriptHighlighter::HighlightingRule::HighlightingRule(QRegExp pttrn, QTextCharFormat frmt) @@ -102,7 +100,7 @@ ScriptHighlighter::previousBlockState() noexcept state != Utils::toUnderlying(BlockState::Comment)) return BlockState::None; - return static_cast<const BlockState>(state); + return static_cast<BlockState>(state); } void diff --git a/src/UI/ScriptViews/ScriptHighlighter.hh b/src/UI/ScriptViews/ScriptHighlighter.hh index 752c8023455608d13e6177704274d5f9ffd73617..492259dad9adc6795952277f02b958396fccc425 100644 --- a/src/UI/ScriptViews/ScriptHighlighter.hh +++ b/src/UI/ScriptViews/ScriptHighlighter.hh @@ -4,10 +4,6 @@ #error "This is a C++ header" #endif -#include <QSyntaxHighlighter> -#include <QTextCharFormat> -#include <QBrush> - class QTextDocument; namespace Vivy diff --git a/src/UI/UnclosableDockWidget.cc b/src/UI/UnclosableDockWidget.cc new file mode 100644 index 0000000000000000000000000000000000000000..e08d2f73c83f754169d92306aba2ddcac6d4b490 --- /dev/null +++ b/src/UI/UnclosableDockWidget.cc @@ -0,0 +1,33 @@ +#include "UnclosableDockWidget.hh" +#include "DockWidgetTitleBar.hh" + +using namespace Vivy; + +UnclosableDockWidget::UnclosableDockWidget(const QString &name, QWidget *parent) noexcept + : QDockWidget(name, parent) +{ + DockWidgetTitleBar::addToDock(this); +} + +UnclosableDockWidget::~UnclosableDockWidget() noexcept +{ + QWidget *w = widget(); + if (w != nullptr) + delete w; + setWidget(nullptr); +} + +void +UnclosableDockWidget::closeEvent(QCloseEvent *event) noexcept +{ + if (allowedToClose) + QDockWidget::closeEvent(event); + else + event->ignore(); +} + +void +UnclosableDockWidget::allowClose() noexcept +{ + allowedToClose = true; +} diff --git a/src/UI/UnclosableDockWidget.hh b/src/UI/UnclosableDockWidget.hh new file mode 100644 index 0000000000000000000000000000000000000000..204a11720b207591c8d50e2422fa8d4555688a19 --- /dev/null +++ b/src/UI/UnclosableDockWidget.hh @@ -0,0 +1,22 @@ +#pragma once + +#include "Utils.hh" + +namespace Vivy +{ +class UnclosableDockWidget final : public QDockWidget { + Q_OBJECT + VIVY_UNMOVABLE_OBJECT(UnclosableDockWidget) + +public: + explicit UnclosableDockWidget(const QString &, QWidget *parent) noexcept; + ~UnclosableDockWidget() noexcept override; + void allowClose() noexcept; + +protected: + void closeEvent(QCloseEvent *event) noexcept override; + +private: + bool allowedToClose{ false }; +}; +} diff --git a/src/UI/Utils.cc b/src/UI/Utils.cc index 95f9269d01b74fab6579a1a68ae28878cea2c62d..531e05119df8a7d1a2022581f2bc75197ccc8334 100644 --- a/src/UI/Utils.cc +++ b/src/UI/Utils.cc @@ -1,5 +1,4 @@ #include "Utils.hh" -#include <QWidget> using namespace Vivy; @@ -10,3 +9,16 @@ Utils::setTransparentBackgroundForWidget(QWidget *const w) noexcept w->setAttribute(Qt::WA_TranslucentBackground); w->setAttribute(Qt::WA_TransparentForMouseEvents); } + +const QString Utils::untouchedTabIndicator = QStringLiteral("(*)"); +const QString Utils::unsavedTabIndicator = QStringLiteral("(+)"); + +void +Utils::deleteInternalWidget(QDockWidget *const dock) noexcept +{ + QWidget *widget = dock->widget(); + if (widget != nullptr) { + delete widget; + dock->setWidget(nullptr); + } +} diff --git a/src/UI/Utils.hh b/src/UI/Utils.hh index 34b57e2a119bcae629be2901e2332a26d7341836..e5b71db13c6228348c797ba619b9be074ceae86d 100644 --- a/src/UI/Utils.hh +++ b/src/UI/Utils.hh @@ -4,9 +4,14 @@ #error "This is a C++ header" #endif +#include "../Lib/Utils.hh" + class QWidget; namespace Vivy::Utils { void setTransparentBackgroundForWidget(QWidget *const) noexcept; +void deleteInternalWidget(QDockWidget *const) noexcept; +extern const QString untouchedTabIndicator; +extern const QString unsavedTabIndicator; } diff --git a/src/UI/VivyDocumentView.cc b/src/UI/VivyDocumentView.cc index a82725074114229ae36477d7aa9ef5a4a5f84f35..be7437916ed688742d5beeb24454295e5ea124b2 100644 --- a/src/UI/VivyDocumentView.cc +++ b/src/UI/VivyDocumentView.cc @@ -7,34 +7,25 @@ #include "../VivyApplication.hh" #include "../Lib/Document/VivyDocument.hh" -#include <QHeaderView> -#include <QTreeView> -#include <QVBoxLayout> -#include <QTableView> -#include <QWidget> -#include <QDockWidget> - using namespace Vivy; VivyDocumentView::VivyDocumentView(std::shared_ptr<VivyDocument> doc, QWidget *parent) noexcept : AbstractDocumentView(AbstractDocumentView::Type::Vivy, parent) , document(doc) { - qDebug() << "Create view for document" << doc->getName() << "with capabilities" - << doc->getDocumentCapabilitiesString(); + setDockNestingEnabled(true); loadAudioView(); loadVideoView(); loadAssView(); - setDockNestingEnabled(true); - // Add some actions... - QAction *openPropertiesAct = new QAction("Open properties", this); + openPropertiesAct = new QAction("Open properties", this); connect(openPropertiesAct, &QAction::triggered, this, &VivyDocumentView::openProperties); - connect(document.get(), &AbstractDocument::documentChanged, this, [=, this]() noexcept -> void { + connect(document.get(), &AbstractDocument::documentChanged, this, [=, this]() { if (property) openProperties(); + emit documentPropertyChanged(); }); viewsActions.append(openPropertiesAct); @@ -49,7 +40,8 @@ VivyDocumentView::~VivyDocumentView() noexcept qDebug() << "Deleting the document view: ref count on document" << document->getName() << "is" << document.use_count() << "and" << (visualizer ? "has visualizer" : "without visualizer"); - closeDocument(); + + deleteAllContent(); } VivyDocument * @@ -61,8 +53,10 @@ VivyDocumentView::getDocument() const noexcept QString VivyDocumentView::getDocumentTabName() const noexcept { - return (document->checkDocumentOption(VivyDocument::UntouchedByDefault) ? "(*) " : "") + - document->getName(); + return (document->checkDocumentOption(VivyDocument::UntouchedByDefault) + ? (Utils::untouchedTabIndicator + " ") + : QString::fromUtf8("")) + + Utils::getBaseName(document->getName()); } QString @@ -73,89 +67,79 @@ VivyDocumentView::getDocumentTabToolTip() const noexcept QStringLiteral("}") + (document->checkDocumentOption(VivyDocument::UntouchedByDefault) ? QStringLiteral("\nUntouched document") + : QStringLiteral("")) + + (document->checkDocumentOption(VivyDocument::MemoryDocumentCreation) + ? QStringLiteral("\nMemory document") : QStringLiteral("")); } void VivyDocumentView::loadVideoView() noexcept { - if (document->checkDocumentCapabilities(VivyDocument::Capabilities::VideoAble)) { - if (!videoView) { - videoView = new QDockWidget("Video View", this); - videoView->setAllowedAreas(Qt::AllDockWidgetAreas); - videoView->setFeatures(QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable | - QDockWidget::DockWidgetClosable); - addDockWidget(Qt::BottomDockWidgetArea, videoView, Qt::Vertical); - videoView->setTitleBarWidget(new QWidget(this)); - Utils::setTransparentBackgroundForWidget(videoView->titleBarWidget()); - } - - // Kubat: because the dock is "closable", when closed the widget itself - // is not deleted, it will be hidden and the content will be deleted. - // TODO: Check if it works on more platforms. - videoView->setWidget(new VideoView(videoView)); - qobject_cast<VideoView *>(videoView->widget()) - ->loadFile(document->getVideoSubDocument()->getFilePath()); + if (!document->checkDocumentCapabilities(VivyDocument::Capabilities::VideoAble)) + return; + + if (!videoView) { + videoView = new UnclosableDockWidget("Video View", this); + videoView->setAllowedAreas(Qt::AllDockWidgetAreas); + videoView->setFeatures(allDockFeatures); + addDockWidget(Qt::BottomDockWidgetArea, videoView, Qt::Vertical); } + + Utils::deleteInternalWidget(videoView); + videoView->setWidget(new VideoView(videoView)); + qobject_cast<VideoView *>(videoView->widget()) + ->loadFile(document->getVideoSubDocument()->getFilePath()); } void VivyDocumentView::loadAssView() noexcept { - if (document->checkDocumentCapabilities(VivyDocument::Capabilities::AssAble)) { - if (!assLines) { - assLines = new QDockWidget("ASS Lines", this); - assLines->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetClosable); - assLines->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea | - Qt::BottomDockWidgetArea); - addDockWidget(Qt::BottomDockWidgetArea, assLines, Qt::Vertical); - assLines->setTitleBarWidget(new QWidget(this)); - Utils::setTransparentBackgroundForWidget(assLines->titleBarWidget()); - } - - assModel.reset(new AssLinesModel(document->getAssSubDocument()->getLines())); - assLines->setWidget(new AssLinesView(assModel.get(), assLines)); + if (!document->checkDocumentCapabilities(VivyDocument::Capabilities::AssAble)) + return; + + if (!assLines) { + assLines = new UnclosableDockWidget("ASS Lines", this); + assLines->setFeatures(onlyClosableDockFeatures); + assLines->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea | + Qt::BottomDockWidgetArea); + addDockWidget(Qt::BottomDockWidgetArea, assLines, Qt::Vertical); } + + assModel.reset(new AssLinesModel(document->getAssSubDocument()->getLines())); + Utils::deleteInternalWidget(assLines); + assLines->setWidget(new AssLinesView(assModel.get(), assLines)); } void VivyDocumentView::loadAudioView() noexcept { - if (document->checkDocumentCapabilities(VivyDocument::AudioAble)) { - std::shared_ptr<AudioSubDocument> audioDocument = document->getAudioSubDocument(); - qDebug() << "Create an audio vizualizer for the audio sub document" - << audioDocument->getFilePath(); - - AudioContext::StreamPtr stream = audioDocument->getDefaultStream(); - if (stream == nullptr) { - qCritical() << "Failed to get default audio stream"; - return; - } - - if (!visualizer) { - visualizer = new QDockWidget("Visualizer", this); - - visualizer->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Minimum); - visualizer->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::TopDockWidgetArea | - Qt::BottomDockWidgetArea); - visualizer->setFeatures(QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetClosable); - addDockWidget(Qt::LeftDockWidgetArea, visualizer, Qt::Horizontal); - visualizer->setTitleBarWidget(new QWidget(this)); - Utils::setTransparentBackgroundForWidget(visualizer->titleBarWidget()); - } - - // Kubat: don't check, may throw an error but don't think we can - // recover from it. - AudioVisualizer *visualizerInner = new AudioVisualizer(stream, visualizer); - visualizer->setWidget(visualizerInner); - visualizer->layout()->setAlignment(visualizerInner, Qt::AlignTop); + if (!document->checkDocumentCapabilities(VivyDocument::AudioAble)) + return; + + std::shared_ptr<AudioSubDocument> audioDocument = document->getAudioSubDocument(); + AudioContext::StreamPtr stream = audioDocument->getDefaultStream(); + + if (stream == nullptr) { + qCritical() << "Failed to get default audio stream"; + return; } - else { - qDebug() << "The document" << document->getName() << "is not AudioAble"; + if (!visualizer) { + visualizer = new UnclosableDockWidget("Visualizer", this); + visualizer->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Minimum); + visualizer->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::TopDockWidgetArea | + Qt::BottomDockWidgetArea); + visualizer->setFeatures(onlyClosableDockFeatures); + addDockWidget(Qt::LeftDockWidgetArea, visualizer, Qt::Horizontal); } + + // Kubat: don't check, may throw an error but don't think we can + // recover from it. + AudioVisualizer *visualizerInner = new AudioVisualizer(stream, visualizer); + Utils::deleteInternalWidget(visualizer); + visualizer->setWidget(visualizerInner); + visualizer->layout()->setAlignment(visualizerInner, Qt::AlignTop); } void @@ -164,12 +148,7 @@ VivyDocumentView::closeDocument() noexcept qDebug() << "Closing the document:" << document->getName() << "( ref count is" << document.use_count() << ")"; vivyApp->documentStore.closeDocument(document->getUuid()); - - // Kubat: the visualizer pointer should have been deleted by the - // deleteAllContent() call if it was created. - deleteAllContent(); - visualizer = nullptr; - property = nullptr; + allowToCloseAllDocks(); } QIcon @@ -181,19 +160,16 @@ VivyDocumentView::getDocumentTabIcon() const noexcept void VivyDocumentView::openProperties() noexcept { - propertyModel.reset(new PropertyModel(*document.get())); - QTreeView *view = new QTreeView(property); - view->setModel(propertyModel.get()); - view->header()->setSectionResizeMode(QHeaderView::ResizeToContents); - view->expandAll(); + propertyModel = std::make_unique<PropertyModel>(*document.get()); + QTreeView *view = PropertyModel::getView(property, propertyModel.get()); if (!property) { - property = new QDockWidget("Properties", this); + property = new UnclosableDockWidget("Properties", this); property->setAllowedAreas(Qt::AllDockWidgetAreas); + property->setFeatures(allDockFeatures); addDockWidget(Qt::RightDockWidgetArea, property, Qt::Vertical); - property->setTitleBarWidget(new QWidget(this)); - Utils::setTransparentBackgroundForWidget(property->titleBarWidget()); } property->setWidget(view); + openPropertiesAct->setVisible(false); } diff --git a/src/UI/VivyDocumentView.hh b/src/UI/VivyDocumentView.hh index 4cdcc8efc8d9cd6d1a68be9c0627b68f410f82ab..0a6f13b4519362f6468632348a854051a44e4b64 100644 --- a/src/UI/VivyDocumentView.hh +++ b/src/UI/VivyDocumentView.hh @@ -6,21 +6,25 @@ #endif #include "AbstractDocumentView.hh" - -class QDockWidget; -class QWidget; +#include "../Lib/Document/VivyDocument.hh" +#include "DocumentViews/VideoView.hh" +#include "DocumentViews/AssLinesView.hh" +#include "DocumentViews/AssLinesModel.hh" +#include "PropertyModel.hh" +#include "UnclosableDockWidget.hh" namespace Vivy { -class PropertyModel; -class AssLinesModel; -class VivyDocument; -class VideoView; - class VivyDocumentView final : public AbstractDocumentView { Q_OBJECT VIVY_UNMOVABLE_OBJECT(VivyDocumentView) + static inline constexpr QDockWidget::DockWidgetFeatures allDockFeatures = + QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetFloatable | + QDockWidget::DockWidgetMovable; + static inline constexpr QDockWidget::DockWidgetFeatures onlyClosableDockFeatures = + QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable; + public: explicit VivyDocumentView(std::shared_ptr<VivyDocument>, QWidget *parent) noexcept; ~VivyDocumentView() noexcept override; @@ -43,10 +47,11 @@ private: std::shared_ptr<VivyDocument> document; std::unique_ptr<PropertyModel> propertyModel{ nullptr }; std::unique_ptr<AssLinesModel> assModel{ nullptr }; - QDockWidget *visualizer{ nullptr }; - QDockWidget *property{ nullptr }; - QDockWidget *assLines{ nullptr }; - QDockWidget *videoView{ nullptr }; + UnclosableDockWidget *visualizer{ nullptr }; + UnclosableDockWidget *property{ nullptr }; + UnclosableDockWidget *assLines{ nullptr }; + UnclosableDockWidget *videoView{ nullptr }; + QAction *openPropertiesAct{ nullptr }; }; } diff --git a/src/UI/VivyFileIconProvider.hh b/src/UI/VivyFileIconProvider.hh index c042aefc963790af8ac2e8f913c16450a80f4416..d219b799ff9ce4677c226021d6c9179cbd8a07a5 100644 --- a/src/UI/VivyFileIconProvider.hh +++ b/src/UI/VivyFileIconProvider.hh @@ -6,10 +6,6 @@ #include "../Lib/Utils.hh" #include "../VivyApplication.hh" -#include <QtGlobal> -#include <QFileIconProvider> -#include <QFileInfo> -#include <QIcon> namespace Vivy { diff --git a/src/VivyApplication.cc b/src/VivyApplication.cc index 075638a9b25784b45ea64be5195a208210b3e330..efb10c01c29927a412fa85c03a65975524f00250 100644 --- a/src/VivyApplication.cc +++ b/src/VivyApplication.cc @@ -1,11 +1,6 @@ #include "VivyApplication.hh" #include "UI/MainWindow.hh" -#include <QtGlobal> -#include <QIcon> -#include <QFontDatabase> -#include <locale> - using namespace Vivy; VivyApplication::VivyApplication(int &argc, char **argv) @@ -35,7 +30,8 @@ VivyApplication::setTheme(Theme theme) noexcept int VivyApplication::exec() noexcept { - // For MPV + // For MPV & Qt + QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF-8")); std::setlocale(LC_NUMERIC, "C"); // Add fonts diff --git a/src/VivyApplication.hh b/src/VivyApplication.hh index 3826063a9838fcb173a0dcc0ff72b6575ce3d5ec..0bfa830a8a20267695db7d08faf19f7ed076da55 100644 --- a/src/VivyApplication.hh +++ b/src/VivyApplication.hh @@ -16,6 +16,7 @@ #define VIVY_ICON_NEW ":icons/dark/document-new.svg" #define VIVY_ICON_SAVE ":icons/dark/document-save.svg" #define VIVY_ICON_SAVE_AS ":icons/dark/document-save-as.svg" +#define VIVY_ICON_RENAME ":icons/dark/document-rename.svg" #define VIVY_ICON_ABOUT ":icons/dark/help-about.svg" #define VIVY_ICON_FILE ":icons/dark/text-x-generic.svg" #define VIVY_ICON_FOLDER ":icons/dark/folder.svg" @@ -23,16 +24,14 @@ #define VIVY_ICON_PAUSE ":icons/dark/media-pause.svg" #define VIVY_ICON_STOP ":icons/dark/media-stop.svg" -#include <QApplication> -#include <QPixmap> -#include <QFont> - // Detect MacOS #if defined(Q_OS_DARWIN) || defined(Q_OS_MACOS) #define VIVY_MACOS #endif +#include "Lib/Script/ScriptStore.hh" #include "Lib/Document/VivyDocumentStore.hh" +#include "UI/MainWindow.hh" namespace Vivy { diff --git a/src/VivyCli.cc b/src/VivyCli.cc index 1305babcbb026ba9c5984a00aae8529bfcdb2202..0dedba63eb20d103bb628c7a8ad915dfa44ce38d 100644 --- a/src/VivyCli.cc +++ b/src/VivyCli.cc @@ -1,24 +1,27 @@ #include "VivyCli.hh" #include <iostream> +#include <QTextCodec> using namespace Vivy; VivyCli::VivyCli(int &argc, char **argv) noexcept { if (argc >= 2) { - selectedDoc = scriptStore.loadDocument(QString(argv[1])); + selectedDoc = scriptStore.loadDocument(QString::fromUtf8(argv[1])); } } int VivyCli::exec() noexcept { + QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF-8")); + if ((selectedDoc == nullptr) || (!scriptStore.executeScript(selectedDoc->getUuid()))) return 1; for (const auto &str : scriptStore.getLoadedModules()) { std::cout << "Module " << str << " was loaded!\n"; - const auto *mod = scriptStore.getModule(str); + [[maybe_unused]] const auto *mod = scriptStore.getModule(str); } return 0; }