Sélectionner une révision Git
-
Yann LUMIA a rédigéYann LUMIA a rédigé
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);
}