docs(c++): OriginSelectorWidget toolbar integration

Covers widget lifecycle, signal connections (fastsignals), menu
population, origin selection with authentication gate, icon overlays
for connection state, StdCmdOrigin/OriginSelectorAction command
wrappers, and StdWorkbench toolbar placement.

Closes #133
This commit is contained in:
2026-02-10 08:18:05 -06:00
parent 8fd664d509
commit c25aa17591
2 changed files with 225 additions and 0 deletions

View File

@@ -53,4 +53,5 @@
- [LocalFileOrigin](./reference/cpp-local-file-origin.md)
- [OriginManager](./reference/cpp-origin-manager.md)
- [CommandOrigin](./reference/cpp-command-origin.md)
- [OriginSelectorWidget](./reference/cpp-origin-selector-widget.md)
- [FileOriginPython Bridge](./reference/cpp-file-origin-python.md)

View File

@@ -0,0 +1,224 @@
# OriginSelectorWidget
`OriginSelectorWidget` is a toolbar dropdown that lets users switch between registered file origins (Local Files, Silo, etc.). It appears as the first item in the File toolbar across all workbenches.
- **Header:** `src/Gui/OriginSelectorWidget.h`
- **Source:** `src/Gui/OriginSelectorWidget.cpp`
- **Base class:** `QToolButton`
- **Command ID:** `Std_Origin`
## Widget appearance
The button shows the current origin's nickname and icon. Clicking opens a dropdown menu listing all registered origins with a checkmark on the active one.
```
┌────────────────┐
│ Local ▼ │ ← nickname + icon, InstantPopup mode
└────────────────┘
Dropdown:
✓ Local ← checked = current origin
Kindred Silo ← disconnected origins show status overlay
──────────────────
Manage Origins... ← opens OriginManagerDialog
```
### Size constraints
| Property | Value |
|----------|-------|
| Popup mode | `QToolButton::InstantPopup` |
| Button style | `Qt::ToolButtonTextBesideIcon` |
| Minimum width | 80 px |
| Maximum width | 160 px |
| Size policy | `Preferred, Fixed` |
## Lifecycle
### Construction
The constructor calls four setup methods in order:
1. **`setupUi()`** — Configures the button, creates `m_menu` (QMenu) and `m_originActions` (exclusive QActionGroup). Connects the action group's `triggered` signal to `onOriginActionTriggered`.
2. **`connectSignals()`** — Subscribes to three OriginManager fastsignals via `scoped_connection` objects.
3. **`rebuildMenu()`** — Populates the menu from the current OriginManager state.
4. **`updateDisplay()`** — Sets the button text, icon, and tooltip to match the current origin.
### Destruction
The destructor calls `disconnectSignals()`, which explicitly disconnects the three `fastsignals::scoped_connection` members. The scoped connections would auto-disconnect on destruction regardless, but explicit disconnection prevents any signal delivery during teardown.
## Signal connections
The widget subscribes to three `OriginManager` fastsignals:
| OriginManager signal | Widget handler | Response |
|----------------------|----------------|----------|
| `signalOriginRegistered` | `onOriginRegistered(id)` | Rebuild menu |
| `signalOriginUnregistered` | `onOriginUnregistered(id)` | Rebuild menu |
| `signalCurrentOriginChanged` | `onCurrentOriginChanged(id)` | Update display + menu checkmarks |
Connections are stored as `fastsignals::scoped_connection` members for RAII lifetime management:
```cpp
fastsignals::scoped_connection m_connRegistered;
fastsignals::scoped_connection m_connUnregistered;
fastsignals::scoped_connection m_connChanged;
```
## Menu population
`rebuildMenu()` rebuilds the entire dropdown from scratch each time an origin is registered or unregistered:
1. Clear `m_menu` and remove all actions from `m_originActions`.
2. Iterate `OriginManager::originIds()` (returns `std::vector<std::string>` in registration order).
3. For each origin ID, create a `QAction` with:
- Icon from `iconForOrigin(origin)` (with connection-state overlay)
- Text from `origin->nickname()`
- Tooltip from `origin->name()`
- `setCheckable(true)`, checked if this is the current origin
- Data set to the origin ID string
4. Add a separator.
5. Add a "Manage Origins..." action with the `preferences-system` theme icon, connected to `onManageOriginsClicked`.
Origins appear in the menu in the order returned by `OriginManager::originIds()`. The local origin is always first (registered at startup), followed by Python-registered origins in registration order.
## Origin selection
When the user clicks an origin in the dropdown, `onOriginActionTriggered(QAction*)` runs:
1. Extract the origin ID from `action->data()`.
2. Look up the `FileOrigin` from OriginManager.
3. **Authentication gate:** If `origin->requiresAuthentication()` is true and the connection state is `Disconnected` or `Error`:
- Call `origin->connect()`.
- If connection fails, revert the menu checkmark to the previous origin and return without changing.
4. On success, call `OriginManager::setCurrentOrigin(originId)`.
This means selecting a disconnected PLM origin triggers an automatic reconnection attempt. The user sees no change if the connection fails.
## Icon overlays
`iconForOrigin(FileOrigin*)` generates a display icon with optional connection-state indicators:
| Connection state | Overlay | Position |
|------------------|---------|----------|
| `Connected` | None | — |
| `Connecting` | None (TODO: animated) | — |
| `Disconnected` | `dagViewFail` (8x8 px, red) | Bottom-right |
| `Error` | `Warning` (8x8 px, yellow) | Bottom-right |
Overlays are only applied to origins where `requiresAuthentication()` returns `true`. Local origins never get overlays. The merge uses `BitmapFactoryInst::mergePixmap` with `BottomRight` placement.
## Display updates
`updateDisplay()` sets the button face to reflect the current origin:
- **Text:** `origin->nickname()` (e.g. "Local", "Silo")
- **Icon:** Result of `iconForOrigin(origin)` (with possible overlay)
- **Tooltip:** `origin->name()` (e.g. "Local Files", "Kindred Silo")
- **No origin:** Text becomes "No Origin", icon and tooltip are cleared
This method runs on construction, on `signalCurrentOriginChanged`, and after the Manage Origins dialog closes.
## Command wrapper
The widget is exposed to the command/toolbar system through two classes.
### StdCmdOrigin
Defined in `src/Gui/CommandStd.cpp` using `DEF_STD_CMD_AC(StdCmdOrigin)`:
```cpp
StdCmdOrigin::StdCmdOrigin()
: Command("Std_Origin")
{
sGroup = "File";
sMenuText = QT_TR_NOOP("&Origin");
sToolTipText = QT_TR_NOOP("Select file origin (Local Files, Silo, etc.)");
sWhatsThis = "Std_Origin";
sStatusTip = sToolTipText;
sPixmap = "folder";
eType = 0;
}
```
| Property | Value |
|----------|-------|
| Command ID | `Std_Origin` |
| Menu group | `File` |
| Icon | `folder` |
| `isActive()` | Always `true` |
| `activated()` | No-op (widget handles interaction) |
`createAction()` returns an `OriginSelectorAction` instance.
### OriginSelectorAction
Defined in `src/Gui/Action.h` / `Action.cpp`. Bridges the command system and the widget:
```cpp
void OriginSelectorAction::addTo(QWidget* widget)
{
if (widget->inherits("QToolBar")) {
auto* selector = new OriginSelectorWidget(widget);
static_cast<QToolBar*>(widget)->addWidget(selector);
} else {
widget->addAction(action());
}
}
```
When added to a `QToolBar`, it instantiates an `OriginSelectorWidget`. When added to a menu or other container, it falls back to adding a plain `QAction`.
## Toolbar integration
`StdWorkbench::setupToolBars()` in `src/Gui/Workbench.cpp` places the widget:
```cpp
auto file = new ToolBarItem(root);
file->setCommand("File");
*file << "Std_Origin" // ← origin selector (first)
<< "Std_New"
<< "Std_Open"
<< "Std_Save";
```
The widget appears as the **first item** in the File toolbar. Because `StdWorkbench` is the base workbench, this placement is inherited by all workbenches (Part Design, Assembly, Silo, etc.).
A separate "Origin Tools" toolbar follows with PLM-specific commands:
```cpp
auto originTools = new ToolBarItem(root);
originTools->setCommand("Origin Tools");
*originTools << "Origin_Commit" << "Origin_Pull" << "Origin_Push"
<< "Separator"
<< "Origin_Info" << "Origin_BOM";
```
These commands auto-disable based on the current origin's capability flags (`supportsRevisions`, `supportsBOM`, etc.).
## Member variables
```cpp
QMenu* m_menu; // dropdown menu
QActionGroup* m_originActions; // exclusive checkmark group
QAction* m_manageAction; // "Manage Origins..." action
fastsignals::scoped_connection m_connRegistered; // signalOriginRegistered
fastsignals::scoped_connection m_connUnregistered; // signalOriginUnregistered
fastsignals::scoped_connection m_connChanged; // signalCurrentOriginChanged
```
## Behavioural notes
- **Origin scope is global, not per-document.** Switching origin affects all subsequent New/Open/Save operations. Existing open documents retain their origin association via `OriginManager::_documentOrigins`.
- **Menu rebuilds are full rebuilds.** On any registration/unregistration event, the entire menu is cleared and rebuilt. This is simple and correct — origin counts are small (typically 2-3).
- **No document-switch tracking.** The widget does not respond to active document changes. The current origin is a user preference, not derived from the active document.
- **Thread safety.** All operations assume the Qt main thread. Signal emissions from OriginManager are synchronous.
## See also
- [FileOrigin Interface](./cpp-file-origin.md) — abstract base class
- [LocalFileOrigin](./cpp-local-file-origin.md) — built-in default origin
- [OriginManager](./cpp-origin-manager.md) — singleton registry and signal source
- [CommandOrigin](./cpp-command-origin.md) — PLM commands in the Origin Tools toolbar