Skip to content
Extraits de code Groupes Projets
Sélectionner une révision Git
  • c265e00187758c5cb2cc777b434580bdaf2eb103
  • master par défaut
  • script
  • new-devel
  • devel
  • timingView-edit
  • fix-mpv
7 résultats

MainWindow.cc

Blame
  • MainWindow.cc 18,46 Kio
    #include "MainWindow.hh"
    #include "PropertyModel.hh"
    #include "VivyDocumentView.hh"
    #include "AboutWindow.hh"
    #include "VivyFileIconProvider.hh"
    #include "../Lib/Utils.hh"
    #include "../VivyApplication.hh"
    
    #define DCL_MENU(menu, name) [[maybe_unused]] QMenu *menu##Menu = menuBar()->addMenu(name);
    
    #define DCL_ACTION(method, name, tip, menu)                              \
        [[maybe_unused]] QAction *method##Act = new QAction(tr(name), this); \
        method##Act->setStatusTip(tr(tip));                                  \
        menu##Menu->addAction(method##Act);                                  \
        connect(method##Act, &QAction::triggered, this, &MainWindow::method);
    
    #define ACTION_ADD_ICON(action, icon) action##Act->setIcon(QIcon(icon));
    
    #define ACTION_ADD_SHORTCUT(action, shortcut) action##Act->setShortcut(shortcut);
    
    using namespace Vivy;
    
    MainWindow::MainWindow() noexcept
        : QMainWindow()
    {
        setWindowIcon(QIcon(VIVY_ICON_APP));
        setWindowTitle("Vivy");
        setDocumentMode(true);
        setDockNestingEnabled(true);
        setTabShape(QTabWidget::Rounded);
    
        /* Some declarations */
        DCL_MENU(file, "&File");
        DCL_MENU(edit, "&Edit");
        DCL_MENU(viewTmp, "&View");
        viewMenu = viewTmpMenu; // Save the view menu
        DCL_MENU(simulate, "&Simulate");
        DCL_MENU(help, "&Help");
    
        DCL_ACTION(newDocument, "&New document", "Create a new document", file);
        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);
        DCL_ACTION(loadSubDocumentAss, "&Load ASS", "Load an ASS file as a sub-document", file);
    
        DCL_ACTION(openDialogHelp, "&About", "Open the help dialog", help);
    
        ACTION_ADD_ICON(newDocument, VIVY_ICON_NEW);
        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);
        ACTION_ADD_SHORTCUT(openDocument, QKeySequence::Open);
        ACTION_ADD_SHORTCUT(saveFile, QKeySequence::Save);
    
        // Custom useFakeVim action
        {
            editMenu->addSeparator();
            QAction *useFakeVim = new QAction(tr("Use FakeVim"), this);
            useFakeVim->setStatusTip("Use FakeVim with integrated text editors");
            useFakeVim->setCheckable(true);
            editMenu->addAction(useFakeVim);
            connect(useFakeVim, &QAction::toggled, this, [this](bool checked) noexcept -> void {
                const bool oldState = vivyApp->getUseFakeVimEditor();
                if (oldState != checked) {
                    vivyApp->setUseFakeVimEditor(checked);
                    updateFakeVimUsage(checked);
                }
            });
        }
    
        // Setup the tabs to display the documents
        documents = new QTabWidget(this);
        documents->setMovable(true);
        documents->setTabsClosable(true);
        documents->setElideMode(Qt::ElideRight);
        documents->setUsesScrollButtons(true);
        documents->setDocumentMode(true);
        documents->setTabBarAutoHide(true);
        connect(documents, &QTabWidget::tabCloseRequested, this,
                [this](int index) noexcept -> void { closeDocument(index); });
        connect(documents, &QTabWidget::tabBarDoubleClicked, this, &MainWindow::openProperties);
        setCentralWidget(documents);
        centralWidget()->setContentsMargins(0, 0, 0, 0);
        setContentsMargins(0, 0, 0, 0);
    
        // Enable/disable actions depending on the context
        saveFileAct->setEnabled(false);
        saveFileAsAct->setEnabled(false);
        loadSubDocumentAssAct->setEnabled(false);
        loadSubDocumentVideoAct->setEnabled(false);
        loadSubDocumentAudioAct->setEnabled(false);
    
        // Enable "Save As" action
        auto enableSaveAsOnDocument = [this](auto *widget, int index) noexcept -> void {
            if (index >= 0) {
                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 {
                widget->setEnabled(false);
            }
        };
    
        // 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) {
                auto type = static_cast<AbstractDocumentView *>(documents->widget(index))->getType();
                widget->setEnabled(type == AbstractDocumentView::Type::Vivy);
            } else {
                widget->setEnabled(false);
            }
        };
    
        {
    #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, enableSaveAsOnDocument);
    
            CONNECT_ENABLE(loadSubDocumentAssAct, enableLoadSubOnDocument);
            CONNECT_ENABLE(loadSubDocumentVideoAct, enableLoadSubOnDocument);
            CONNECT_ENABLE(loadSubDocumentAudioAct, enableLoadSubOnDocument);
    
    #undef CONNECT_ENABLE
        }
    
        // Main window has finished its construction
        statusBar()->showMessage("QSimulate has started");
    
        // Minimal size...
        setMinimumHeight(400);
        setMinimumWidth(600);
    
        // Always a new empty document
        newDocument();
    }
    
    void
    MainWindow::closeEvent(QCloseEvent *event) noexcept
    {
        qDebug() << "Closing the main window!";
        forEachViews<VivyDocumentView>(
            [](VivyDocumentView *view, int) { view->closeDocument(); }); // XXX
        // event->accept();
        QMainWindow::closeEvent(event);
    }
    
    void
    MainWindow::updateFakeVimUsage(bool yes) noexcept
    {
        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 *>(docView)->setUseFakeVimEditor(yes);
        });
    }
    
    void
    MainWindow::openProperties(int index) noexcept
    {
        if (index < 0) {
            // TODO: May may want to do something when the user is clicking where
            // no tab bar is openned (like open document?)
            return;
        }
    
        qDebug().nospace() << "Tab n°" << index << " was double clicked";
        AbstractDocumentView *current = getTab(index);
        current->openProperties();
    }
    
    void
    MainWindow::openDialogHelp() noexcept
    {
        if (aboutWindowMutex.try_lock() && aboutWindow == nullptr) {
            aboutWindow = new AboutWindow(this);
            QEventLoop loop;
            connect(aboutWindow, &AboutWindow::closed, &loop, &QEventLoop::quit);
            aboutWindow->show();
            loop.exec();
            delete aboutWindow;
            aboutWindow = nullptr;
            aboutWindowMutex.unlock();
        }
    }
    
    AbstractDocument *
    MainWindow::getCurrentDocument() const
    {
        return getCurrentDocumentView()->getDocument();
    }
    
    void
    MainWindow::saveFile() noexcept
    {
        try {
            const auto document = getCurrentDocument();
            document->save();
        }
    
        catch (const std::runtime_error &e) {
            qCritical() << "Failed to save current document:" << e.what();
        }
    }
    
    void
    MainWindow::renameFile() noexcept
    {
        try {
            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();
        }
    }
    
    void
    MainWindow::saveFileAs() noexcept
    {
        try {
            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();
        }
    }
    
    void
    MainWindow::closeDocument(AbstractDocumentView *const view) noexcept
    {
        if (view == nullptr || !view->getDocument())
            return;
    
        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
    MainWindow::closeDocument(int index) noexcept
    {
        if (index < 0)
            return;
    
        auto *documentToClose = static_cast<AbstractDocumentView *>(documents->widget(index));
        documents->removeTab(index);
        disconnect(documentToClose, &AbstractDocumentView::viewActionsChanged, this,
                   &MainWindow::documentViewActionsChanged);
    
        if (documentToClose) {
            qDebug() << "Delete document view" << documentToClose->getDocumentTabName();
            documentToClose->closeDocument();
            delete documentToClose;
        }
    }
    
    void
    MainWindow::newDocument() noexcept
    {
        try {
            addTab(new VivyDocumentView(
                vivyApp->documentStore.newDocument(VivyDocument::UntouchedByDefault), documents));
        } catch (const std::runtime_error &e) {
            qCritical() << "Failed to create a new empty document:" << e.what();
        }
    }
    
    void
    MainWindow::openDocument() noexcept
    {
        const QString filename = dialogOpenFileName("Select a document to open", QDir::homePath(),
                                                    Utils::getAnyTopLevelDocumentFileSuffixFilter());
        if (filename.isEmpty()) {
            qWarning() << "Found an empty filename, don't open a file";
            return;
        }
    
        const QFileInfo fileInfo(filename);
        Utils::DocumentType fileType;
    
        if (!Utils::detectDocumentType(fileInfo, &fileType)) {
            qWarning() << "Failed to detect file type for" << filename;
            return;
        }
    
        // Handle the different types here
        try {
            if (fileType == Utils::DocumentType::Vivy)
                addTab(new VivyDocumentView(vivyApp->documentStore.loadDocument(filename), documents));
    
            else if (fileType == Utils::DocumentType::VivyScript) {
                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::fromUtf8(desc.c_str()));
                }
    
                addTab(newView);
            }
        } catch (const std::runtime_error &e) {
            qCritical() << "Failed to load document" << filename << "with error:" << e.what();
        }
    }
    
    void
    MainWindow::loadSubDocumentAss() noexcept
    {
        withOpenFileNameDialog<VivyDocumentView, VivyDocument>(
            "Select an ASS document to load", Utils::getAssFileSuffixFilter(),
            [](VivyDocumentView *view, VivyDocument *doc, const QString &filename) noexcept -> void {
                doc->setAssSubDocument(filename);
                view->loadAssView();
            });
    }
    
    void
    MainWindow::loadSubDocumentVideo() noexcept
    {
        withOpenFileNameDialog<VivyDocumentView, VivyDocument>(
            "Select a video document to load", Utils::getVideoFileSuffixFilter(),
            [](VivyDocumentView *view, VivyDocument *doc, const QString &filename) noexcept -> void {
                doc->setVideoSubDocument(filename);
                view->loadVideoView();
            });
    }
    
    void
    MainWindow::loadSubDocumentAudio() noexcept
    {
        withOpenFileNameDialog<VivyDocumentView, VivyDocument>(
            "Select an audio document to load",
            Utils::getAudioFileSuffixFilter() + QStringLiteral(";;") +
                Utils::getVideoFileSuffixFilter(),
            [](VivyDocumentView *view, VivyDocument *doc, const QString &filename) noexcept -> void {
                doc->setAudioSubDocument(filename);
                view->loadAudioView();
            });
    }
    
    void
    MainWindow::addTab(AbstractDocumentView *const tab)
    {
        int index = -1;
        if (const int untouched_index = findFirstUntouchedDocument(); untouched_index >= 0) {
            closeDocument(untouched_index);
            index = documents->insertTab(untouched_index, tab, tab->getDocumentTabIcon(),
                                         tab->getDocumentTabName());
        } else {
            index = documents->addTab(tab, tab->getDocumentTabIcon(), tab->getDocumentTabName());
        }
    
        documents->setTabToolTip(index, tab->getDocumentTabToolTip());
        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
    {
        if (AbstractDocumentView *currentView =
                static_cast<AbstractDocumentView *>(documents->currentWidget())) {
            return currentView;
        }
    
        else {
            throw std::runtime_error("no current document");
        }
    }
    
    AbstractDocumentView *
    MainWindow::getTab(const int index) const noexcept
    {
        if (index > documents->count())
            return nullptr;
        return static_cast<AbstractDocumentView *>(documents->widget(index));
    }
    
    int
    MainWindow::findFirstUntouchedDocument() const noexcept
    {
        int returnIndex = -1;
        forEachViews<VivyDocumentView>(
            [&returnIndex](VivyDocumentView *view, int index) noexcept -> void {
                if (view->getDocument()->checkDocumentOption(VivyDocument::UntouchedByDefault))
                    returnIndex = index;
            });
    
        return returnIndex;
    }
    
    void
    MainWindow::documentViewActionsChanged() noexcept
    {
        qInfo() << "Document view action changed";
        viewMenu->clear();
    
        // Change document view menu if we have a document
        try {
            viewMenu->addActions(getCurrentDocumentView()->getViewsActions());
        } catch (const std::runtime_error &e) {
            qInfo() << "No view to display:" << e.what();
        }
    }
    
    static inline QString
    executeDialog(MainWindow *const self, QFileDialog *const dialog) noexcept
    {
        bool dialogAccepted = false;
        std::unique_ptr<VivyFileIconProvider> iconProvider(new VivyFileIconProvider());
    
        dialog->setIconProvider(iconProvider.get());
        QFileDialog::connect(dialog, &QFileDialog::accepted, self,
                             [&dialogAccepted]() noexcept -> void { dialogAccepted = true; });
    
        dialog->exec();
    
        if (!dialogAccepted)
            return QStringLiteral("");
    
        const QStringList resList = dialog->selectedFiles();
        if (resList.size() != 1) {
            qCritical() << "You must select only one file";
            return QStringLiteral("");
        }
    
        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);
    }