Covers ownership-by-exclusion algorithm, document identity, open/save workflows, capability flags, connection state, OriginManager integration, and unregister protection. Closes #131
8.4 KiB
LocalFileOrigin
LocalFileOrigin is the built-in, default FileOrigin implementation. It handles documents stored on the local filesystem without external tracking or synchronisation.
- Header:
src/Gui/FileOrigin.h - Source:
src/Gui/FileOrigin.cpp - Origin ID:
"local" - Display name:
"Local Files"(nickname"Local") - Type:
OriginType::Local
Design principles
- Identity is the file path. A document's identity under LocalFileOrigin is its
FileNameproperty — the absolute path on disk. - Ownership by exclusion. LocalFileOrigin claims every document that is not claimed by a PLM origin. It detects this by scanning for the
SiloItemIdproperty; if no object in the document has it, the document is local. - Always available. LocalFileOrigin is created by
OriginManagerat startup and cannot be unregistered. It is the mandatory fallback origin. - Thin delegation. Most operations delegate directly to
App::GetApplication()orApp::Documentmethods. LocalFileOrigin adds no persistence layer of its own.
Identity and capabilities
std::string id() const override; // "local"
std::string name() const override; // "Local Files"
std::string nickname() const override; // "Local"
QIcon icon() const override; // theme icon "document-new"
OriginType type() const override; // OriginType::Local
Capability flags
All capability queries return false:
| Method | Returns | Reason |
|---|---|---|
tracksExternally() |
false |
No external database or server |
requiresAuthentication() |
false |
No login required |
supportsRevisions() |
false |
No revision history |
supportsBOM() |
false |
No Bill of Materials |
supportsPartNumbers() |
false |
No part number system |
supportsAssemblies() |
false |
No native assembly tracking |
These flags control which toolbar buttons and menu items are enabled. With all flags false, the origin toolbar commands (Commit, Pull, Push, Info, BOM) are disabled for local documents.
Connection state
LocalFileOrigin uses the base-class defaults — it is always ConnectionState::Connected:
| Method | Behaviour |
|---|---|
connectionState() |
Returns ConnectionState::Connected |
connect() |
Returns true immediately |
disconnect() |
No-op |
signalConnectionStateChanged |
Never emitted |
Ownership detection
bool ownsDocument(App::Document* doc) const override;
Algorithm:
- Return
falseifdocis null. - Iterate every object in the document (
doc->getObjects()). - For each object, check
obj->getPropertyByName("SiloItemId"). - If any object has the property, return
false— the document belongs to a PLM origin. - If no object has it, return
true.
The constant SiloItemId is defined as a static const char* in FileOrigin.cpp.
Edge cases:
- An empty document (no objects) is owned by LocalFileOrigin.
- A document where only some objects have
SiloItemIdis still considered PLM-owned. - Performance is O(n) where n is the number of objects in the document.
Ownership resolution order
OriginManager::findOwningOrigin() checks non-local origins first, then falls back to LocalFileOrigin. This ensures PLM origins with specific ownership criteria take priority:
for each registered origin (excluding "local"):
if origin.ownsDocument(doc) → return that origin
if localOrigin.ownsDocument(doc) → return localOrigin
return nullptr
Results are cached in OriginManager::_documentOrigins for O(1) subsequent lookups.
Document identity
std::string documentIdentity(App::Document* doc) const override;
std::string documentDisplayId(App::Document* doc) const override;
Both return the same value: the document's FileName property (its absolute filesystem path). Returns an empty string if the document is null, not owned, or has never been saved.
For PLM origins these would differ — documentIdentity might return a UUID while documentDisplayId returns a part number. For local files, the path serves both purposes.
Document operations
Creating documents
App::Document* newDocument(const std::string& name = "") override;
Delegates to App::GetApplication().newDocument(). If name is empty, defaults to "Unnamed". The returned document has no FileName set until saved.
Opening documents
App::Document* openDocument(const std::string& identity) override;
App::Document* openDocumentInteractive() override;
openDocument — Non-interactive. Takes a file path, delegates to App::GetApplication().openDocument(). Returns nullptr if the path is empty or the file cannot be loaded.
openDocumentInteractive — Shows a file dialog with the following behaviour:
- Builds a format filter list from FreeCAD's registered import types, with
.FCStdfirst. - Shows
FileDialog::getOpenFileNames()(multi-file selection). - For each selected file, uses
SelectModule::importHandler()to determine the loader module. - Calls
Application::Instance->open()with theUserInitiatedOpenDocumentflag set. - Runs
checkPartialRestore()andcheckRestoreError()for validation. - Returns the last loaded document, or
nullptrif the dialog was cancelled.
Saving documents
bool saveDocument(App::Document* doc) override;
bool saveDocumentAs(App::Document* doc, const std::string& newIdentity) override;
bool saveDocumentAsInteractive(App::Document* doc) override;
saveDocument — Requires that the document already has a FileName set. If the path is empty (never-saved document), returns false. Otherwise calls doc->save().
saveDocumentAs — Non-interactive. Calls doc->saveAs(newIdentity) with the given path. Updates the document's FileName property as a side effect.
saveDocumentAsInteractive — Gets the Gui::Document wrapper and delegates to guiDoc->saveAs(), which shows a save dialog. Returns false if the GUI document cannot be found or the user cancels.
Save workflow pattern:
First save: saveDocument() → false (no path)
→ command layer calls saveDocumentAsInteractive()
→ user picks path → saveAs()
Later saves: saveDocument() → true (path exists)
→ doc->save()
PLM operations (not implemented)
These inherited base-class methods are no-ops for LocalFileOrigin:
| Method | Returns | Behaviour |
|---|---|---|
commitDocument(doc) |
false |
No external commits |
pullDocument(doc) |
false |
No remote to pull from |
pushDocument(doc) |
false |
No remote to push to |
showInfo(doc) |
void | No-op |
showBOM(doc) |
void | No-op |
OriginManager integration
Lifecycle
LocalFileOrigin is created in the OriginManager constructor via ensureLocalOrigin():
auto localOrigin = std::make_unique<LocalFileOrigin>();
_origins[LOCAL_ORIGIN_ID] = std::move(localOrigin);
_currentOriginId = LOCAL_ORIGIN_ID;
It is set as the default current origin. It is the only origin that exists before any Python workbenches load.
Unregister protection
OriginManager::unregisterOrigin() explicitly blocks removal of the local origin:
if (id == LOCAL_ORIGIN_ID) {
Base::Console().warning(
"OriginManager: Cannot unregister built-in local origin\n");
return false;
}
Preference persistence
The current origin ID is saved to User parameter:BaseApp/Preferences/General/Origin under the key CurrentOriginId. On restart, if the saved ID is not "local" but the corresponding origin hasn't been registered yet, LocalFileOrigin remains the active origin until a Python workbench registers the saved origin.
Constants
Defined in FileOrigin.cpp:
static const char* SILO_ITEM_ID_PROP = "SiloItemId";
Defined in OriginManager.cpp:
static const char* LOCAL_ORIGIN_ID = "local";
static const char* PREF_PATH = "User parameter:BaseApp/Preferences/General/Origin";
static const char* PREF_CURRENT_ORIGIN = "CurrentOriginId";
See also
- FileOrigin Interface — abstract base class
- OriginManager — singleton registry and document resolution
- FileOriginPython Bridge — Python adapter for custom origins
- SiloOrigin — PLM origin that contrasts with LocalFileOrigin