From 8cef3bf3fafe4acf2c16a445ef30bf8eaeee791a Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Fri, 29 Mar 2024 13:16:08 +0100 Subject: [PATCH] WIP --- src/app/app_module_properties.cpp | 39 +++++++--- src/app/app_module_properties.h | 1 + src/app/commands_file.cpp | 102 ++++++++++++++++++------- src/app/commands_file.h | 11 +++ src/app/document_files_watcher.cpp | 117 +++++++++++++++++++++++++++++ src/app/document_files_watcher.h | 48 ++++++++++++ src/app/widget_main_control.cpp | 70 ++++++++++++++++- src/app/widget_main_control.h | 14 +++- 8 files changed, 360 insertions(+), 42 deletions(-) create mode 100644 src/app/document_files_watcher.cpp create mode 100644 src/app/document_files_watcher.h diff --git a/src/app/app_module_properties.cpp b/src/app/app_module_properties.cpp index 43274bf2..ade11d58 100644 --- a/src/app/app_module_properties.cpp +++ b/src/app/app_module_properties.cpp @@ -44,6 +44,7 @@ AppModuleProperties::AppModuleProperties(Settings* settings) settings->addSetting(&this->recentFiles, groupId_application); settings->addSetting(&this->lastOpenDir, groupId_application); settings->addSetting(&this->lastSelectedFormatFilter, groupId_application); + settings->addSetting(&this->reloadDocumentOnFileChange, groupId_application); settings->addSetting(&this->linkWithDocumentSelector, groupId_application); settings->addSetting(&this->forceOpenGlFallbackWidget, groupId_application); this->recentFiles.setUserVisible(false); @@ -82,6 +83,7 @@ AppModuleProperties::AppModuleProperties(Settings* settings) this->recentFiles.setValue({}); this->lastOpenDir.setValue({}); this->lastSelectedFormatFilter.setValue({}); + this->reloadDocumentOnFileChange.setValue(true); this->linkWithDocumentSelector.setValue(true); #ifndef MAYO_OS_MAC this->forceOpenGlFallbackWidget.setValue(false); @@ -159,10 +161,16 @@ void AppModuleProperties::retranslate() // Application this->language.setDescription( - textIdTr("Language used for the application. Change will take effect after application restart")); + textIdTr("Language used for the application. Change will take effect after application restart") + ); + this->reloadDocumentOnFileChange.setDescription( + textIdTr("Monitors the file system for changes to documents opened in the application\n\n" + "When such a file change is detected then the application proposes to reload(open again) the document") + ); this->linkWithDocumentSelector.setDescription( textIdTr("In case where multiple documents are opened, make sure the document displayed in " - "the 3D view corresponds to what is selected in the model tree")); + "the 3D view corresponds to what is selected in the model tree") + ); this->forceOpenGlFallbackWidget.setDescription( textIdTr("Force usage of the fallback Qt widget to display OpenGL graphics.\n\n" "When `OFF` the application will try to use OpenGL framebuffer for rendering, " @@ -178,33 +186,42 @@ void AppModuleProperties::retranslate() // Meshing this->meshingQuality.setDescription( - textIdTr("Controls precision of the mesh to be computed from the BRep shape")); + textIdTr("Controls precision of the mesh to be computed from the BRep shape") + ); this->meshingChordalDeflection.setDescription( textIdTr("For the tessellation of faces the chordal deflection limits the distance between " - "a curve and its tessellation")); + "a curve and its tessellation") + ); this->meshingAngularDeflection.setDescription( textIdTr("For the tessellation of faces the angular deflection limits the angle between " - "subsequent segments in a polyline")); + "subsequent segments in a polyline") + ); this->meshingRelative.setDescription( textIdTr("Relative computation of edge tolerance\n\n" "If activated, deflection used for the polygonalisation of each edge will be " "`ChordalDeflection` × `SizeOfEdge`. The deflection used for the faces will be " - "the maximum deflection of their edges.")); + "the maximum deflection of their edges.") + ); // Graphics this->navigationStyle.setDescription( - textIdTr("3D view manipulation shortcuts configuration to mimic other common CAD applications")); + textIdTr("3D view manipulation shortcuts configuration to mimic other common CAD applications") + ); this->turnViewAngleIncrement.setDescription( - textIdTr("Angle increment used to turn(rotate) the 3D view around the normal of the view plane(Z axis frame reference)")); + textIdTr("Angle increment used to turn(rotate) the 3D view around the normal of the view plane(Z axis frame reference)") + ); // -- Graphics/ClipPlanes this->defaultShowOriginTrihedron.setDescription( textIdTr("Show or hide by default the trihedron centered at world origin. " - "This doesn't affect 3D view of currently opened documents")); + "This doesn't affect 3D view of currently opened documents") + ); this->clipPlanesCappingOn.setDescription( - textIdTr("Enable capping of currently clipped graphics")); + textIdTr("Enable capping of currently clipped graphics") + ); this->clipPlanesCappingHatchOn.setDescription( - textIdTr("Enable capping hatch texture of currently clipped graphics")); + textIdTr("Enable capping hatch texture of currently clipped graphics") + ); } void AppModuleProperties::onPropertyChanged(Property* prop) diff --git a/src/app/app_module_properties.h b/src/app/app_module_properties.h index 2961e69e..32fcf03e 100644 --- a/src/app/app_module_properties.h +++ b/src/app/app_module_properties.h @@ -51,6 +51,7 @@ class AppModuleProperties : public PropertyGroup { PropertyRecentFiles recentFiles{ this, textId("recentFiles") }; PropertyFilePath lastOpenDir{ this, textId("lastOpenFolder") }; PropertyString lastSelectedFormatFilter{ this, textId("lastSelectedFormatFilter") }; + PropertyBool reloadDocumentOnFileChange{ this, textId("reloadDocumentOnFileChange") }; PropertyBool linkWithDocumentSelector{ this, textId("linkWithDocumentSelector") }; PropertyBool forceOpenGlFallbackWidget{ this, textId("forceOpenGlFallbackWidget") }; // Meshing diff --git a/src/app/commands_file.cpp b/src/app/commands_file.cpp index a4ce96ea..6d2ef758 100644 --- a/src/app/commands_file.cpp +++ b/src/app/commands_file.cpp @@ -100,7 +100,8 @@ struct OpenFileNames { static OpenFileNames get( QWidget* parentWidget, - OpenFileNames::GetOption option = OpenFileNames::GetMany) + OpenFileNames::GetOption option = OpenFileNames::GetMany + ) { OpenFileNames result; result.selectedFormat = IO::Format_Unknown; @@ -118,14 +119,16 @@ struct OpenFileNames { if (option == OpenFileNames::GetOne) { const QString strFilepath = QFileDialog::getOpenFileName( - parentWidget, dlgTitle, dlgOpenDir, dlgFilter, dlgPtrSelFilter); + parentWidget, dlgTitle, dlgOpenDir, dlgFilter, dlgPtrSelFilter + ); result.listFilepath.clear(); result.listFilepath.push_back(filepathFrom(strFilepath)); } else { const QStringList listStrFilePath = QFileDialog::getOpenFileNames( - parentWidget, dlgTitle, dlgOpenDir, dlgFilter, dlgPtrSelFilter); + parentWidget, dlgTitle, dlgOpenDir, dlgFilter, dlgPtrSelFilter + ); result.listFilepath.clear(); for (const QString& strFilePath : listStrFilePath) result.listFilepath.push_back(filepathFrom(strFilePath)); @@ -216,6 +219,40 @@ void FileCommandTools::openDocument(IAppContext* context, const FilePath& fp) FileCommandTools::openDocumentsFromList(context, Span(&fp, 1)); } +void FileCommandTools::importInDocument( + IAppContext* context, const DocumentPtr& targetDoc, Span filepaths + ) +{ + auto appModule = AppModule::get(); + const Document::Identifier targetDocId = targetDoc->identifier(); + const TaskId taskId = context->taskMgr()->newTask([=](TaskProgress* progress) { + QElapsedTimer chrono; + chrono.start(); + + auto doc = Application::instance()->findDocumentByIdentifier(targetDocId); + const bool okImport = appModule->ioSystem()->importInDocument() + .targetDocument(doc) + .withFilepaths(filepaths) + .withParametersProvider(appModule) + .withEntityPostProcess([=](TDF_Label labelEntity, TaskProgress* progress) { + appModule->computeBRepMesh(labelEntity, progress); + }) + .withEntityPostProcessRequiredIf(&IO::formatProvidesBRep) + .withEntityPostProcessInfoProgress(20, Command::textIdTr("Mesh BRep shapes")) + .withMessenger(appModule) + .withTaskProgress(progress) + .execute(); + if (okImport) + appModule->emitInfo(fmt::format(Command::textIdTr("Import time: {}ms"), chrono.elapsed())); + }); + const QString taskTitle = + filepaths.size() > 1 ? + Command::tr("Import") : + filepathTo(filepaths.front().stem()); + context->taskMgr()->setTitle(taskId, to_stdString(taskTitle)); + context->taskMgr()->run(taskId); +} + CommandNewDocument::CommandNewDocument(IAppContext* context) : Command(context) { @@ -354,42 +391,53 @@ void CommandImportInCurrentDocument::execute() if (resFileNames.listFilepath.empty()) return; + FileCommandTools::importInDocument(this->context(), guiDoc->document(), resFileNames.listFilepath); + for (const FilePath& fp : resFileNames.listFilepath) + AppModule::get()->prependRecentFile(fp); +} + +bool CommandImportInCurrentDocument::getEnabledStatus() const +{ + return this->app()->documentCount() != 0 + && this->context()->currentPage() == IAppContext::Page::Documents; +} + +void CommandImportInCurrentDocument::execute( + const DocumentPtr& targetDoc, Span filepaths, TaskManager* taskMgr + ) +{ auto appModule = AppModule::get(); - const TaskId taskId = this->taskMgr()->newTask([=](TaskProgress* progress) { + const Document::Identifier targetDocId = targetDoc->identifier(); + const TaskId taskId = taskMgr->newTask([=](TaskProgress* progress) { QElapsedTimer chrono; chrono.start(); + auto doc = Application::instance()->findDocumentByIdentifier(targetDocId); const bool okImport = appModule->ioSystem()->importInDocument() - .targetDocument(guiDoc->document()) - .withFilepaths(resFileNames.listFilepath) - .withParametersProvider(appModule) - .withEntityPostProcess([=](TDF_Label labelEntity, TaskProgress* progress) { - appModule->computeBRepMesh(labelEntity, progress); - }) - .withEntityPostProcessRequiredIf(&IO::formatProvidesBRep) - .withEntityPostProcessInfoProgress(20, Command::textIdTr("Mesh BRep shapes")) - .withMessenger(appModule) - .withTaskProgress(progress) - .execute(); + .targetDocument(doc) + .withFilepaths(filepaths) + .withParametersProvider(appModule) + .withEntityPostProcess([=](TDF_Label labelEntity, TaskProgress* progress) { + appModule->computeBRepMesh(labelEntity, progress); + }) + .withEntityPostProcessRequiredIf(&IO::formatProvidesBRep) + .withEntityPostProcessInfoProgress(20, Command::textIdTr("Mesh BRep shapes")) + .withMessenger(appModule) + .withTaskProgress(progress) + .execute(); if (okImport) appModule->emitInfo(fmt::format(Command::textIdTr("Import time: {}ms"), chrono.elapsed())); }); const QString taskTitle = - resFileNames.listFilepath.size() > 1 ? - Command::tr("Import") : - filepathTo(resFileNames.listFilepath.front().stem()); - this->taskMgr()->setTitle(taskId, to_stdString(taskTitle)); - this->taskMgr()->run(taskId); - for (const FilePath& fp : resFileNames.listFilepath) + filepaths.size() > 1 ? + Command::tr("Import") : + filepathTo(filepaths.front().stem()); + taskMgr->setTitle(taskId, to_stdString(taskTitle)); + taskMgr->run(taskId); + for (const FilePath& fp : filepaths) appModule->prependRecentFile(fp); } -bool CommandImportInCurrentDocument::getEnabledStatus() const -{ - return this->app()->documentCount() != 0 - && this->context()->currentPage() == IAppContext::Page::Documents; -} - CommandExportSelectedApplicationItems::CommandExportSelectedApplicationItems(IAppContext* context) : Command(context) { diff --git a/src/app/commands_file.h b/src/app/commands_file.h index 65db0ec7..5b8420ec 100644 --- a/src/app/commands_file.h +++ b/src/app/commands_file.h @@ -17,6 +17,11 @@ class FileCommandTools { static void closeDocument(IAppContext* context, Document::Identifier docId); static void openDocumentsFromList(IAppContext* context, Span listFilePath); static void openDocument(IAppContext* context, const FilePath& fp); + static void importInDocument( + IAppContext* context, + const DocumentPtr& targetDoc, + Span filepaths + ); }; class CommandNewDocument : public Command { @@ -52,6 +57,12 @@ class CommandImportInCurrentDocument : public Command { void execute() override; bool getEnabledStatus() const override; + static void execute( + const DocumentPtr& targetDoc, + Span filepaths, + TaskManager* taskMgr + ); + static constexpr std::string_view Name = "import"; }; diff --git a/src/app/document_files_watcher.cpp b/src/app/document_files_watcher.cpp new file mode 100644 index 00000000..242bf164 --- /dev/null +++ b/src/app/document_files_watcher.cpp @@ -0,0 +1,117 @@ +/**************************************************************************** +** Copyright (c) 2024, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "document_files_watcher.h" + +#include "../base/application.h" +#include "../qtcommon/filepath_conv.h" + +#include + +namespace Mayo { + +DocumentFilesWatcher::DocumentFilesWatcher(const ApplicationPtr& app, QObject* parent) + : QObject(parent), + m_app(app) +{ + app->signalDocumentAdded.connectSlot(&DocumentFilesWatcher::onDocumentAdded, this); + app->signalDocumentAboutToClose.connectSlot(&DocumentFilesWatcher::onDocumentAdded, this); + app->signalDocumentFilePathChanged.connectSlot(&DocumentFilesWatcher::onDocumentFilePathChanged, this); +} + +void DocumentFilesWatcher::enable(bool on) +{ + if (m_isEnabled == on) + return; + + m_isEnabled = on; + if (on) { + for (Application::DocumentIterator itDoc(m_app); itDoc.hasNext(); itDoc.next()) { + const FilePath& docFilePath = itDoc.current()->filePath(); + const QString strDocFilePath = filepathTo(docFilePath); + if (filepathExists(docFilePath)) + this->fileSystemWatcher()->addPath(strDocFilePath); + } + } + else { + this->destroyFileSystemWatcher(); + m_vecNonAckDocumentChanged.clear(); + } +} + +bool DocumentFilesWatcher::isEnabled() const +{ + return m_isEnabled; +} + +void DocumentFilesWatcher::acknowledgeDocumentFileChange(const DocumentPtr& doc) +{ + auto it = std::find(m_vecNonAckDocumentChanged.begin(), m_vecNonAckDocumentChanged.end(), doc); + if (it != m_vecNonAckDocumentChanged.end()) + m_vecNonAckDocumentChanged.erase(it); +} + +QFileSystemWatcher* DocumentFilesWatcher::fileSystemWatcher() +{ + if (!m_fileSystemWatcher) { + m_fileSystemWatcher = new QFileSystemWatcher(this); + QObject::connect( + m_fileSystemWatcher, &QFileSystemWatcher::fileChanged, + this, &DocumentFilesWatcher::onFileChanged + ); + } + + return m_fileSystemWatcher; +} + +void DocumentFilesWatcher::destroyFileSystemWatcher() +{ + delete m_fileSystemWatcher; + m_fileSystemWatcher = nullptr; +} + +void DocumentFilesWatcher::onFileChanged(const QString& strFilePath) +{ + if (m_isEnabled) { + const FilePath docFilePath = filepathFrom(strFilePath); + DocumentPtr doc = m_app->findDocumentByLocation(docFilePath); + if (this->isDocumentChangeAcknowledged(doc)) { + m_vecNonAckDocumentChanged.push_back(doc); + this->signalDocumentFileChanged.send(doc); + } + } +} + +bool DocumentFilesWatcher::isDocumentChangeAcknowledged(const DocumentPtr& doc) const +{ + auto it = std::find(m_vecNonAckDocumentChanged.cbegin(), m_vecNonAckDocumentChanged.cend(), doc); + return it == m_vecNonAckDocumentChanged.cend(); +} + +void DocumentFilesWatcher::onDocumentFilePathChanged(const DocumentPtr&, const FilePath& fp) +{ + if (m_isEnabled) { + if (filepathExists(fp)) + this->fileSystemWatcher()->addPath(filepathTo(fp)); + } +} + +void DocumentFilesWatcher::onDocumentAdded(const DocumentPtr& doc) +{ + if (m_isEnabled) { + if (filepathExists(doc->filePath())) + this->fileSystemWatcher()->addPath(filepathTo(doc->filePath())); + } +} + +void DocumentFilesWatcher::onDocumentAboutToClose(const DocumentPtr& doc) +{ + if (m_isEnabled) + this->fileSystemWatcher()->removePath(filepathTo(doc->filePath())); +} + + +} // namespace Mayo diff --git a/src/app/document_files_watcher.h b/src/app/document_files_watcher.h new file mode 100644 index 00000000..d1447cd3 --- /dev/null +++ b/src/app/document_files_watcher.h @@ -0,0 +1,48 @@ +/**************************************************************************** +** Copyright (c) 2024, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "../base/application_ptr.h" +#include "../base/document_ptr.h" +#include "../base/filepath.h" +#include "../base/signal.h" + +#include + +#include + +class QFileSystemWatcher; + +namespace Mayo { + +class DocumentFilesWatcher : public QObject { +public: + DocumentFilesWatcher(const ApplicationPtr& app, QObject* parent = nullptr); + + Signal signalDocumentFileChanged; + + void enable(bool on); + bool isEnabled() const; + + void acknowledgeDocumentFileChange(const DocumentPtr& doc); + +private: + QFileSystemWatcher* fileSystemWatcher(); + void destroyFileSystemWatcher(); + void onFileChanged(const QString& strFilePath); + + bool isDocumentChangeAcknowledged(const DocumentPtr& doc) const; + + void onDocumentFilePathChanged(const DocumentPtr& doc, const FilePath& fp); + void onDocumentAdded(const DocumentPtr& doc); + void onDocumentAboutToClose(const DocumentPtr& doc); + + ApplicationPtr m_app; + QFileSystemWatcher* m_fileSystemWatcher = nullptr; + bool m_isEnabled = false; + std::vector m_vecNonAckDocumentChanged; +}; + +} // namespace Mayo diff --git a/src/app/widget_main_control.cpp b/src/app/widget_main_control.cpp index 42704da2..24667f0a 100644 --- a/src/app/widget_main_control.cpp +++ b/src/app/widget_main_control.cpp @@ -17,9 +17,11 @@ #include "commands_api.h" #include "commands_file.h" #include "commands_window.h" +#include "document_files_watcher.h" #include "document_property_group.h" #include "gui_document_list_model.h" #include "item_view_buttons.h" +#include "qtwidgets_utils.h" #include "theme.h" #include "widget_file_system.h" #include "widget_gui_document.h" @@ -27,8 +29,10 @@ #include "widget_occ_view.h" #include "widget_properties_editor.h" +#include #include #include +#include #include namespace Mayo { @@ -36,7 +40,8 @@ namespace Mayo { WidgetMainControl::WidgetMainControl(GuiApplication* guiApp, QWidget* parent) : IWidgetMainPage(parent), m_ui(new Ui_WidgetMainControl), - m_guiApp(guiApp) + m_guiApp(guiApp), + m_docFilesWatcher(new DocumentFilesWatcher(guiApp->application(), this)) { assert(m_guiApp != nullptr); @@ -85,6 +90,18 @@ WidgetMainControl::WidgetMainControl(GuiApplication* guiApp, QWidget* parent) guiApp->selectionModel()->signalChanged.connectSlot(&WidgetMainControl::onApplicationItemSelectionChanged, this); guiApp->signalGuiDocumentAdded.connectSlot(&WidgetMainControl::onGuiDocumentAdded, this); + // Document files monitoring + auto appModule = AppModule::get(); + const auto& propReloadDocOnFileChange = appModule->properties()->reloadDocumentOnFileChange; + m_docFilesWatcher->enable(propReloadDocOnFileChange); + appModule->settings()->signalChanged.connectSlot([&](const Property* property) { + if (property == &propReloadDocOnFileChange) { + m_docFilesWatcher->enable(propReloadDocOnFileChange); + m_pendingDocsToReload.clear(); + } + }); + m_docFilesWatcher->signalDocumentFileChanged.connectSlot(&WidgetMainControl::onDocumentFileChanged, this); + // Creation of annex objects m_listViewBtns = new ItemViewButtons(m_ui->listView_OpenedDocuments, this); m_listViewBtns->installDefaultItemDelegate(); @@ -310,6 +327,31 @@ QWidget* WidgetMainControl::recreateLeftHeaderPlaceHolder() return placeHolder; } +void WidgetMainControl::reloadDocumentAfterChange(const DocumentPtr& doc) +{ + const QString strQuestion = + tr("Document file `%1` has been changed since it was opened\n\n" + "Do you want to reload that document?\n\n" + "File: `%2`") + .arg(to_QString(doc->name())) + .arg(QDir::toNativeSeparators(filepathTo(doc->filePath()))) + ; + const auto msgBtns = QMessageBox::Yes | QMessageBox::No; + auto msgBox = new QMessageBox(QMessageBox::Question, tr("Question"), strQuestion, msgBtns, this); + msgBox->setTextFormat(Qt::MarkdownText); + QtWidgetsUtils::asyncDialogExec(msgBox); + QObject::connect(msgBox, &QMessageBox::buttonClicked, this, [=](QAbstractButton* btn) { + m_docFilesWatcher->acknowledgeDocumentFileChange(doc); + if (btn == msgBox->button(QMessageBox::Yes)) { + while (doc->entityCount() > 0) + doc->destroyEntity(doc->entityTreeNodeId(0)); + + const auto& docFilePath = doc->filePath(); + FileCommandTools::importInDocument(m_appContext, doc, Span(&docFilePath, 1)); + } + }); +} + WidgetGuiDocument* WidgetMainControl::widgetGuiDocument(int idx) const { return qobject_cast(m_ui->stack_GuiDocuments->widget(idx)); @@ -412,7 +454,33 @@ void WidgetMainControl::onCurrentDocumentIndexChanged(int idx) const FilePath docFilePath = docPtr ? docPtr->filePath() : FilePath(); m_ui->widget_FileSystem->setLocation(filepathTo(docFilePath)); + if (m_docFilesWatcher->isEnabled()) { + auto itDoc = m_pendingDocsToReload.find(docPtr); + if (itDoc != m_pendingDocsToReload.end()) { + m_pendingDocsToReload.erase(itDoc); + this->reloadDocumentAfterChange(docPtr); + } + } + emit this->currentDocumentIndexChanged(idx); } +void WidgetMainControl::onDocumentFileChanged(const DocumentPtr& doc) +{ + WidgetGuiDocument* widgetDoc = nullptr; + for (int i = 0; i < this->widgetGuiDocumentCount() && !widgetDoc; ++i) { + const DocumentPtr& candidateDoc = this->widgetGuiDocument(i)->guiDocument()->document(); + if (candidateDoc->identifier() == doc->identifier()) + widgetDoc = this->widgetGuiDocument(i); + } + + if (!widgetDoc) + return; + + if (widgetDoc == this->currentWidgetGuiDocument()) + this->reloadDocumentAfterChange(doc); + else + m_pendingDocsToReload.insert(doc); +} + } // namespace Mayo diff --git a/src/app/widget_main_control.h b/src/app/widget_main_control.h index 9c9d5270..65298466 100644 --- a/src/app/widget_main_control.h +++ b/src/app/widget_main_control.h @@ -6,10 +6,14 @@ #pragma once +#include "../base/document_ptr.h" +#include "../base/filepath.h" #include "../base/property.h" +#include "../base/tkernel_utils.h" #include "iwidget_main_page.h" #include +#include class QFileInfo; class QMenu; @@ -18,6 +22,7 @@ namespace Mayo { class IAppContext; class CommandContainer; +class DocumentFilesWatcher; class GuiApplication; class GuiDocument; class ItemViewButtons; @@ -57,13 +62,14 @@ class WidgetMainControl : public IWidgetMainPage { void onApplicationItemSelectionChanged(); void onLeftContentsPageChanged(int pageId); void onWidgetFileSystemLocationActivated(const QFileInfo& loc); + void onGuiDocumentAdded(GuiDocument* guiDoc); + void onCurrentDocumentIndexChanged(int idx); + void onDocumentFileChanged(const DocumentPtr& doc); QWidget* findLeftHeaderPlaceHolder() const; QWidget* recreateLeftHeaderPlaceHolder(); - void onGuiDocumentAdded(GuiDocument* guiDoc); - - void onCurrentDocumentIndexChanged(int idx); + void reloadDocumentAfterChange(const DocumentPtr& doc); class Ui_WidgetMainControl* m_ui = nullptr; GuiApplication* m_guiApp = nullptr; @@ -71,6 +77,8 @@ class WidgetMainControl : public IWidgetMainPage { ItemViewButtons* m_listViewBtns = nullptr; std::unique_ptr m_ptrCurrentNodeDataProperties; std::unique_ptr m_ptrCurrentNodeGraphicsProperties; + DocumentFilesWatcher* m_docFilesWatcher = nullptr; + std::unordered_set m_pendingDocsToReload; }; } // namespace Mayo