feat(gui): implement issues #10 and #12 - LocalFileOrigin and Std_* delegation

Issue #10: Local filesystem origin implementation
- Add openDocumentInteractive() method to FileOrigin interface for UI-based
  file opening (shows file dialog)
- Add saveDocumentAsInteractive() method for UI-based save as
- Implement LocalFileOrigin::openDocumentInteractive() with full file dialog
  support, filter list building, and module handler integration
- Implement LocalFileOrigin::saveDocumentAsInteractive() delegating to
  Gui::Document::saveAs()

Issue #12: Modify Std_* commands to delegate to current origin
- StdCmdNew::activated() now delegates to origin->newDocument() and sets
  up view orientation for the new document
- StdCmdOpen::activated() delegates to origin->openDocumentInteractive()
  with connection state checking for authenticated origins
- StdCmdSave::activated() uses document's owning origin (via findOwningOrigin)
  for save, falling back to saveDocumentAsInteractive if no filename
- StdCmdSaveAs::activated() delegates to origin->saveDocumentAsInteractive()
- Updated isActive() methods to check for active document

The Std_* commands now work seamlessly with both LocalFileOrigin and
SiloOrigin. When Local origin is selected, standard file dialogs appear.
When Silo origin is selected, Silo's search/creation dialogs appear.

Import and Export commands are left unchanged as they operate on document
content rather than document lifecycle.

Closes #10, Closes #12
This commit is contained in:
2026-02-05 14:02:26 -06:00
parent 5319387030
commit 38358e431d
4 changed files with 200 additions and 83 deletions

View File

@@ -49,7 +49,9 @@
#include "Control.h" #include "Control.h"
#include "DockWindowManager.h" #include "DockWindowManager.h"
#include "FileDialog.h" #include "FileDialog.h"
#include "FileOrigin.h"
#include "MainWindow.h" #include "MainWindow.h"
#include "OriginManager.h"
#include "Selection.h" #include "Selection.h"
#include "Dialogs/DlgObjectSelection.h" #include "Dialogs/DlgObjectSelection.h"
#include "Dialogs/DlgProjectInformationImp.h" #include "Dialogs/DlgProjectInformationImp.h"
@@ -95,81 +97,25 @@ void StdCmdOpen::activated(int iMsg)
{ {
Q_UNUSED(iMsg); Q_UNUSED(iMsg);
// fill the list of registered endings // Delegate to current origin
QString formatList; FileOrigin* origin = OriginManager::instance()->currentOrigin();
const char* supported = QT_TR_NOOP("Supported formats"); if (!origin) {
const char* allFiles = QT_TR_NOOP("All files (*.*)");
formatList = QObject::tr(supported);
formatList += QLatin1String(" (");
std::vector<std::string> filetypes = App::GetApplication().getImportTypes();
// Make sure FCStd is the very first fileformat
auto it = std::ranges::find(filetypes, "FCStd");
if (it != filetypes.end()) {
filetypes.erase(it);
filetypes.insert(filetypes.begin(), "FCStd");
}
for (it = filetypes.begin(); it != filetypes.end(); ++it) {
formatList += QLatin1String(" *.");
formatList += QLatin1String(it->c_str());
}
formatList += QLatin1String(");;");
std::map<std::string, std::string> FilterList = App::GetApplication().getImportFilters();
std::map<std::string, std::string>::iterator jt;
// Make sure the format name for FCStd is the very first in the list
for (jt = FilterList.begin(); jt != FilterList.end(); ++jt) {
if (jt->first.find("*.FCStd") != std::string::npos) {
formatList += QLatin1String(jt->first.c_str());
formatList += QLatin1String(";;");
FilterList.erase(jt);
break;
}
}
for (jt = FilterList.begin(); jt != FilterList.end(); ++jt) {
formatList += QLatin1String(jt->first.c_str());
formatList += QLatin1String(";;");
}
formatList += QObject::tr(allFiles);
QString selectedFilter;
QStringList fileList = FileDialog::getOpenFileNames(
getMainWindow(),
QObject::tr("Open Document"),
QString(),
formatList,
&selectedFilter
);
if (fileList.isEmpty()) {
return; return;
} }
// load the files with the associated modules // Check connection for origins that require authentication
SelectModule::Dict dict = SelectModule::importHandler(fileList, selectedFilter); if (origin->requiresAuthentication() &&
if (dict.isEmpty()) { origin->connectionState() != ConnectionState::Connected) {
QMessageBox::critical( QMessageBox::warning(
getMainWindow(), getMainWindow(),
qApp->translate("StdCmdOpen", "Cannot Open File"), qApp->translate("StdCmdOpen", "Not Connected"),
qApp->translate("StdCmdOpen", "Loading the file %1 is not supported").arg(fileList.front()) qApp->translate("StdCmdOpen", "Please connect to %1 before opening files.")
.arg(QString::fromStdString(origin->name()))
); );
return;
} }
else {
for (SelectModule::Dict::iterator it = dict.begin(); it != dict.end(); ++it) {
// Set flag indicating that this load/restore has been initiated by the user (not by a macro) origin->openDocumentInteractive();
getGuiApplication()->setStatus(Gui::Application::UserInitiatedOpenDocument, true);
getGuiApplication()->open(it.key().toUtf8(), it.value().toLatin1());
getGuiApplication()->setStatus(Gui::Application::UserInitiatedOpenDocument, false);
App::Document* doc = App::GetApplication().getActiveDocument();
getGuiApplication()->checkPartialRestore(doc);
getGuiApplication()->checkRestoreError(doc);
}
}
} }
//=========================================================================== //===========================================================================
@@ -715,10 +661,27 @@ StdCmdNew::StdCmdNew()
void StdCmdNew::activated(int iMsg) void StdCmdNew::activated(int iMsg)
{ {
Q_UNUSED(iMsg); Q_UNUSED(iMsg);
QString cmd;
cmd = QStringLiteral("App.newDocument()"); // Delegate to current origin
runCommand(Command::Doc, cmd.toUtf8()); FileOrigin* origin = OriginManager::instance()->currentOrigin();
doCommand(Command::Gui, "Gui.activeDocument().activeView().viewDefaultOrientation()"); if (!origin) {
return;
}
App::Document* doc = origin->newDocument();
if (!doc) {
return;
}
// Set default view orientation for the new document
Gui::Document* guiDoc = Application::Instance->getDocument(doc);
if (guiDoc) {
auto views = guiDoc->getMDIViewsOfType(View3DInventor::getClassTypeId());
for (auto* view : views) {
auto view3d = static_cast<View3DInventor*>(view);
view3d->getViewer()->viewDefaultOrientation();
}
}
ParameterGrp::handle hViewGrp = App::GetApplication().GetParameterGroupByPath( ParameterGrp::handle hViewGrp = App::GetApplication().GetParameterGroupByPath(
"User parameter:BaseApp/Preferences/View" "User parameter:BaseApp/Preferences/View"
@@ -749,12 +712,33 @@ StdCmdSave::StdCmdSave()
void StdCmdSave::activated(int iMsg) void StdCmdSave::activated(int iMsg)
{ {
Q_UNUSED(iMsg); Q_UNUSED(iMsg);
doCommand(Command::Gui, "Gui.SendMsgToActiveView(\"Save\")");
App::Document* doc = App::GetApplication().getActiveDocument();
if (!doc) {
return;
}
// Use document's origin for save, not current origin
FileOrigin* origin = OriginManager::instance()->findOwningOrigin(doc);
if (!origin) {
// Document has no origin yet - use current origin for first save
origin = OriginManager::instance()->currentOrigin();
}
if (!origin) {
return;
}
// Try to save the document
if (!origin->saveDocument(doc)) {
// If save failed (e.g., no filename), try SaveAs
origin->saveDocumentAsInteractive(doc);
}
} }
bool StdCmdSave::isActive() bool StdCmdSave::isActive()
{ {
return getGuiApplication()->sendHasMsgToActiveView("Save"); return App::GetApplication().getActiveDocument() != nullptr;
} }
//=========================================================================== //===========================================================================
@@ -778,12 +762,24 @@ StdCmdSaveAs::StdCmdSaveAs()
void StdCmdSaveAs::activated(int iMsg) void StdCmdSaveAs::activated(int iMsg)
{ {
Q_UNUSED(iMsg); Q_UNUSED(iMsg);
doCommand(Command::Gui, "Gui.SendMsgToActiveView(\"SaveAs\")");
App::Document* doc = App::GetApplication().getActiveDocument();
if (!doc) {
return;
}
// SaveAs uses current origin (allows saving to different origin)
FileOrigin* origin = OriginManager::instance()->currentOrigin();
if (!origin) {
return;
}
origin->saveDocumentAsInteractive(doc);
} }
bool StdCmdSaveAs::isActive() bool StdCmdSaveAs::isActive()
{ {
return getGuiApplication()->sendHasMsgToActiveView("SaveAs"); return App::GetApplication().getActiveDocument() != nullptr;
} }
//=========================================================================== //===========================================================================

View File

@@ -22,15 +22,23 @@
#include "PreCompiled.h" #include "PreCompiled.h"
#include <algorithm>
#include <QApplication>
#include <QMessageBox>
#include <App/Application.h> #include <App/Application.h>
#include <App/Document.h> #include <App/Document.h>
#include <App/DocumentObject.h> #include <App/DocumentObject.h>
#include <App/PropertyStandard.h> #include <App/PropertyStandard.h>
#include "FileOrigin.h" #include "FileOrigin.h"
#include "Application.h"
#include "BitmapFactory.h" #include "BitmapFactory.h"
#include "Document.h" #include "Document.h"
#include "Application.h" #include "FileDialog.h"
#include "MainWindow.h"
#include "SelectModule.h"
namespace Gui { namespace Gui {
@@ -99,6 +107,86 @@ App::Document* LocalFileOrigin::openDocument(const std::string& identity)
return App::GetApplication().openDocument(identity.c_str()); return App::GetApplication().openDocument(identity.c_str());
} }
App::Document* LocalFileOrigin::openDocumentInteractive()
{
// Build file filter list for Open dialog
QString formatList;
const char* supported = QT_TR_NOOP("Supported formats");
const char* allFiles = QT_TR_NOOP("All files (*.*)");
formatList = QObject::tr(supported);
formatList += QLatin1String(" (");
std::vector<std::string> filetypes = App::GetApplication().getImportTypes();
// Make sure FCStd is the very first fileformat
auto it = std::find(filetypes.begin(), filetypes.end(), "FCStd");
if (it != filetypes.end()) {
filetypes.erase(it);
filetypes.insert(filetypes.begin(), "FCStd");
}
for (it = filetypes.begin(); it != filetypes.end(); ++it) {
formatList += QLatin1String(" *.");
formatList += QLatin1String(it->c_str());
}
formatList += QLatin1String(");;");
std::map<std::string, std::string> FilterList = App::GetApplication().getImportFilters();
// Make sure the format name for FCStd is the very first in the list
for (auto jt = FilterList.begin(); jt != FilterList.end(); ++jt) {
if (jt->first.find("*.FCStd") != std::string::npos) {
formatList += QLatin1String(jt->first.c_str());
formatList += QLatin1String(";;");
FilterList.erase(jt);
break;
}
}
for (const auto& filter : FilterList) {
formatList += QLatin1String(filter.first.c_str());
formatList += QLatin1String(";;");
}
formatList += QObject::tr(allFiles);
QString selectedFilter;
QStringList fileList = FileDialog::getOpenFileNames(
getMainWindow(),
QObject::tr("Open Document"),
QString(),
formatList,
&selectedFilter
);
if (fileList.isEmpty()) {
return nullptr;
}
// Load the files with the associated modules
SelectModule::Dict dict = SelectModule::importHandler(fileList, selectedFilter);
if (dict.isEmpty()) {
QMessageBox::critical(
getMainWindow(),
qApp->translate("StdCmdOpen", "Cannot Open File"),
qApp->translate("StdCmdOpen", "Loading the file %1 is not supported").arg(fileList.front())
);
return nullptr;
}
App::Document* lastDoc = nullptr;
for (SelectModule::Dict::iterator it = dict.begin(); it != dict.end(); ++it) {
// Set flag indicating that this load/restore has been initiated by the user
Application::Instance->setStatus(Gui::Application::UserInitiatedOpenDocument, true);
Application::Instance->open(it.key().toUtf8(), it.value().toLatin1());
Application::Instance->setStatus(Gui::Application::UserInitiatedOpenDocument, false);
lastDoc = App::GetApplication().getActiveDocument();
Application::Instance->checkPartialRestore(lastDoc);
Application::Instance->checkRestoreError(lastDoc);
}
return lastDoc;
}
bool LocalFileOrigin::saveDocument(App::Document* doc) bool LocalFileOrigin::saveDocument(App::Document* doc)
{ {
if (!doc) { if (!doc) {
@@ -125,4 +213,20 @@ bool LocalFileOrigin::saveDocumentAs(App::Document* doc, const std::string& newI
return doc->saveAs(newIdentity.c_str()); return doc->saveAs(newIdentity.c_str());
} }
bool LocalFileOrigin::saveDocumentAsInteractive(App::Document* doc)
{
if (!doc) {
return false;
}
// Get Gui document for save dialog
Gui::Document* guiDoc = Application::Instance->getDocument(doc);
if (!guiDoc) {
return false;
}
// Use Gui::Document::saveAs() which handles the file dialog
return guiDoc->saveAs();
}
} // namespace Gui } // namespace Gui

View File

@@ -168,7 +168,7 @@ public:
virtual App::Document* newDocument(const std::string& name = "") = 0; virtual App::Document* newDocument(const std::string& name = "") = 0;
/** /**
* Open a document by identity. * Open a document by identity (non-interactive).
* Local: Opens file at path * Local: Opens file at path
* PLM: Opens document by UUID (downloads if needed) * PLM: Opens document by UUID (downloads if needed)
* @param identity Document identity (path or UUID) * @param identity Document identity (path or UUID)
@@ -176,9 +176,17 @@ public:
*/ */
virtual App::Document* openDocument(const std::string& identity) = 0; virtual App::Document* openDocument(const std::string& identity) = 0;
/**
* Open a document interactively (shows dialog).
* Local: Shows file picker dialog
* PLM: Shows search/browse dialog
* @return The opened document or nullptr if cancelled/failed
*/
virtual App::Document* openDocumentInteractive() = 0;
/** /**
* Save the document. * Save the document.
* Local: Saves to disk * Local: Saves to disk (if path known)
* PLM: Saves to disk and syncs with external system * PLM: Saves to disk and syncs with external system
* @param doc The document to save * @param doc The document to save
* @return true if save succeeded * @return true if save succeeded
@@ -186,14 +194,21 @@ public:
virtual bool saveDocument(App::Document* doc) = 0; virtual bool saveDocument(App::Document* doc) = 0;
/** /**
* Save document with new identity. * Save document with new identity (non-interactive).
* Local: File picker for new path
* PLM: Migration or copy workflow
* @param doc The document to save * @param doc The document to save
* @param newIdentity New identity (path or part number) * @param newIdentity New identity (path or part number)
* @return true if save succeeded * @return true if save succeeded
*/ */
virtual bool saveDocumentAs(App::Document* doc, const std::string& newIdentity) = 0; virtual bool saveDocumentAs(App::Document* doc, const std::string& newIdentity) = 0;
/**
* Save document interactively (shows dialog).
* Local: Shows file picker for new path
* PLM: Shows migration or copy workflow dialog
* @param doc The document to save
* @return true if save succeeded
*/
virtual bool saveDocumentAsInteractive(App::Document* doc) = 0;
//@} //@}
///@name Extended Operations (PLM-specific, default to no-op) ///@name Extended Operations (PLM-specific, default to no-op)
@@ -250,8 +265,10 @@ public:
// Document operations // Document operations
App::Document* newDocument(const std::string& name = "") override; App::Document* newDocument(const std::string& name = "") override;
App::Document* openDocument(const std::string& identity) override; App::Document* openDocument(const std::string& identity) override;
App::Document* openDocumentInteractive() override;
bool saveDocument(App::Document* doc) override; bool saveDocument(App::Document* doc) override;
bool saveDocumentAs(App::Document* doc, const std::string& newIdentity) override; bool saveDocumentAs(App::Document* doc, const std::string& newIdentity) override;
bool saveDocumentAsInteractive(App::Document* doc) override;
}; };
} // namespace Gui } // namespace Gui