docs(c++): OriginManager singleton API reference
Document the OriginManager singleton: origin registration/unregistration lifecycle, document-to-origin resolution algorithm (non-local checked first, local as fallback), explicit vs detected ownership, preference persistence, signal catalog, and memory model. Closes #132
This commit is contained in:
@@ -50,3 +50,4 @@
|
||||
# C++ API Reference
|
||||
|
||||
- [FileOrigin Interface](./reference/cpp-file-origin.md)
|
||||
- [OriginManager](./reference/cpp-origin-manager.md)
|
||||
|
||||
226
docs/src/reference/cpp-origin-manager.md
Normal file
226
docs/src/reference/cpp-origin-manager.md
Normal file
@@ -0,0 +1,226 @@
|
||||
# OriginManager — Singleton Registry
|
||||
|
||||
> **Header:** `src/Gui/OriginManager.h`
|
||||
> **Implementation:** `src/Gui/OriginManager.cpp`
|
||||
> **Namespace:** `Gui`
|
||||
|
||||
`OriginManager` is the central singleton that tracks all registered
|
||||
[FileOrigin](./cpp-file-origin.md) instances, maintains the user's current
|
||||
origin selection, and resolves which origin owns a given document.
|
||||
|
||||
## Lifecycle
|
||||
|
||||
```
|
||||
Application startup
|
||||
│
|
||||
├─ OriginManager::instance() ← singleton created
|
||||
│ ├─ ensureLocalOrigin() ← "local" origin always exists
|
||||
│ └─ loadPreferences() ← restore last-used origin from prefs
|
||||
│
|
||||
├─ Python addons register origins ← e.g. SiloOrigin via FreeCADGui.addOrigin()
|
||||
│
|
||||
├─ ... application runs ...
|
||||
│
|
||||
└─ OriginManager::destruct() ← saves prefs, deletes all origins
|
||||
```
|
||||
|
||||
The `"local"` origin is created in the constructor and **cannot be
|
||||
unregistered**. It serves as the universal fallback.
|
||||
|
||||
## Singleton Access
|
||||
|
||||
```cpp
|
||||
// Get the instance (created on first call)
|
||||
OriginManager* mgr = OriginManager::instance();
|
||||
|
||||
// Destroy at application shutdown
|
||||
OriginManager::destruct();
|
||||
```
|
||||
|
||||
## Origin Registration
|
||||
|
||||
### `registerOrigin(FileOrigin* origin) → bool`
|
||||
|
||||
Register a new origin. The manager takes **ownership** via
|
||||
`std::unique_ptr` — do not delete the pointer after registration.
|
||||
|
||||
- Returns `false` and deletes the origin if:
|
||||
- `origin` is `nullptr`
|
||||
- `origin->id()` is empty
|
||||
- An origin with the same ID is already registered
|
||||
- Emits `signalOriginRegistered(id)` on success.
|
||||
|
||||
```cpp
|
||||
auto* cloud = new MyCloudOrigin();
|
||||
bool ok = OriginManager::instance()->registerOrigin(cloud);
|
||||
// cloud is now owned by OriginManager — do not delete it
|
||||
```
|
||||
|
||||
### `unregisterOrigin(const std::string& id) → bool`
|
||||
|
||||
Remove and delete an origin by ID.
|
||||
|
||||
- Returns `false` if:
|
||||
- `id` is `"local"` (built-in, cannot be removed)
|
||||
- No origin with that ID exists
|
||||
- If the unregistered origin was the current origin, the current origin
|
||||
automatically reverts to `"local"`.
|
||||
- Emits `signalOriginUnregistered(id)` on success.
|
||||
|
||||
### `getOrigin(const std::string& id) → FileOrigin*`
|
||||
|
||||
Look up an origin by ID. Returns `nullptr` if not found.
|
||||
|
||||
### `originIds() → std::vector<std::string>`
|
||||
|
||||
Returns all registered origin IDs. Order is the `std::map` key order
|
||||
(alphabetical).
|
||||
|
||||
## Current Origin Selection
|
||||
|
||||
The "current origin" determines which backend is used for **new
|
||||
documents** (File > New) and appears selected in the
|
||||
[OriginSelectorWidget](./cpp-origin-selector-widget.md) toolbar dropdown.
|
||||
|
||||
### `currentOrigin() → FileOrigin*`
|
||||
|
||||
Returns the currently selected origin. Never returns `nullptr` — falls
|
||||
back to `"local"` if the stored ID is somehow invalid.
|
||||
|
||||
### `currentOriginId() → std::string`
|
||||
|
||||
Returns the current origin's ID string.
|
||||
|
||||
### `setCurrentOrigin(const std::string& id) → bool`
|
||||
|
||||
Switch the current origin. Returns `false` if the ID is not registered.
|
||||
|
||||
- Persists the selection to FreeCAD preferences at
|
||||
`User parameter:BaseApp/Preferences/General/Origin/CurrentOriginId`.
|
||||
- Emits `signalCurrentOriginChanged(id)` when the selection actually
|
||||
changes (no signal if setting to the already-current origin).
|
||||
|
||||
### `originForNewDocument() → FileOrigin*`
|
||||
|
||||
Convenience method — returns `currentOrigin()`. Called by the File > New
|
||||
command to determine which origin should handle new document creation.
|
||||
|
||||
## Document-Origin Resolution
|
||||
|
||||
When FreeCAD needs to know which origin owns a document (e.g., for
|
||||
File > Save), it uses the resolution chain below.
|
||||
|
||||
### `originForDocument(App::Document* doc) → FileOrigin*`
|
||||
|
||||
Primary lookup method. Resolution order:
|
||||
|
||||
1. **Explicit association** — check the `_documentOrigins` cache for a
|
||||
prior `setDocumentOrigin()` call.
|
||||
2. **Ownership detection** — call `findOwningOrigin(doc)` to scan all
|
||||
origins.
|
||||
3. **Cache the result** — store in `_documentOrigins` for future lookups.
|
||||
|
||||
Returns `nullptr` only if no origin claims the document (should not
|
||||
happen in practice since `LocalFileOrigin` is the universal fallback).
|
||||
|
||||
### `findOwningOrigin(App::Document* doc) → FileOrigin*`
|
||||
|
||||
Scans all registered origins by calling `ownsDocument(doc)` on each.
|
||||
|
||||
**Algorithm:**
|
||||
|
||||
```
|
||||
1. For each origin where id ≠ "local":
|
||||
if origin->ownsDocument(doc):
|
||||
return origin ← PLM/Cloud origin claims it
|
||||
|
||||
2. if localOrigin->ownsDocument(doc):
|
||||
return localOrigin ← fallback to local
|
||||
|
||||
3. return nullptr ← no owner (shouldn't happen)
|
||||
```
|
||||
|
||||
Non-local origins are checked **first** because they have specific
|
||||
ownership criteria (e.g., presence of `SiloItemId` property). The local
|
||||
origin uses negative-match logic (owns anything not claimed by others),
|
||||
so it must be checked last to avoid false positives.
|
||||
|
||||
### `setDocumentOrigin(doc, origin)`
|
||||
|
||||
Explicitly associate a document with an origin. Used when creating new
|
||||
documents to mark them with the origin that created them.
|
||||
|
||||
- Passing `origin = nullptr` clears the association.
|
||||
- Emits `signalDocumentOriginChanged(doc, originId)`.
|
||||
|
||||
### `clearDocumentOrigin(doc)`
|
||||
|
||||
Remove a document from the association cache. Called when a document is
|
||||
closed to prevent stale pointers.
|
||||
|
||||
## Signals
|
||||
|
||||
All signals use the [fastsignals](https://github.com/nicktrandafil/fastsignals)
|
||||
library (not Qt signals).
|
||||
|
||||
| Signal | Parameters | When |
|
||||
|--------|-----------|------|
|
||||
| `signalOriginRegistered` | `const std::string& id` | After a new origin is registered |
|
||||
| `signalOriginUnregistered` | `const std::string& id` | After an origin is removed |
|
||||
| `signalCurrentOriginChanged` | `const std::string& id` | After the user switches the current origin |
|
||||
| `signalDocumentOriginChanged` | `App::Document*, const std::string& id` | After a document-origin association changes |
|
||||
|
||||
### Subscribers
|
||||
|
||||
- **[OriginSelectorWidget](./cpp-origin-selector-widget.md)** subscribes
|
||||
to `signalOriginRegistered`, `signalOriginUnregistered`, and
|
||||
`signalCurrentOriginChanged` to rebuild the dropdown menu and update
|
||||
the toolbar button.
|
||||
- **[CommandOrigin](./cpp-command-origin.md)** commands query the manager
|
||||
on each `isActive()` call to check document ownership and origin
|
||||
capabilities.
|
||||
|
||||
## Preference Persistence
|
||||
|
||||
| Path | Key | Type | Default |
|
||||
|------|-----|------|---------|
|
||||
| `User parameter:BaseApp/Preferences/General/Origin` | `CurrentOriginId` | ASCII string | `"local"` |
|
||||
|
||||
Loaded in the constructor, saved on destruction and on each
|
||||
`setCurrentOrigin()` call. If the saved origin ID is not registered at
|
||||
load time (e.g., the Silo addon hasn't loaded yet), the manager falls
|
||||
back to `"local"` and will re-check when origins are registered later.
|
||||
|
||||
## Memory Model
|
||||
|
||||
```
|
||||
OriginManager (singleton)
|
||||
│
|
||||
├─ _origins: std::map<string, std::unique_ptr<FileOrigin>>
|
||||
│ ├─ "local" → LocalFileOrigin (always present)
|
||||
│ ├─ "silo" → FileOriginPython (wraps Python SiloOrigin)
|
||||
│ └─ ... → other registered origins
|
||||
│
|
||||
├─ _currentOriginId: std::string
|
||||
│
|
||||
└─ _documentOrigins: std::map<App::Document*, string>
|
||||
├─ doc1 → "silo"
|
||||
├─ doc2 → "local"
|
||||
└─ ... (cache, cleared on document close)
|
||||
```
|
||||
|
||||
- Origins are owned exclusively by the manager via `unique_ptr`.
|
||||
- The document-origin map uses raw `App::Document*` pointers. Callers
|
||||
**must** call `clearDocumentOrigin()` when a document is destroyed to
|
||||
prevent dangling pointers.
|
||||
|
||||
## See Also
|
||||
|
||||
- [FileOrigin Interface](./cpp-file-origin.md) — the abstract interface
|
||||
that registered origins implement
|
||||
- [FileOriginPython](./cpp-file-origin-python.md) — bridge for
|
||||
Python-implemented origins
|
||||
- [CommandOrigin](./cpp-command-origin.md) — commands that dispatch to
|
||||
origins via this manager
|
||||
- [OriginSelectorWidget](./cpp-origin-selector-widget.md) — toolbar UI
|
||||
driven by this manager's signals
|
||||
Reference in New Issue
Block a user