docs: update to remove stale information and add new/updated info
This commit is contained in:
parent
7477821b9b
commit
c83cadbcba
@ -254,7 +254,8 @@ public slots:
|
|||||||
void onActionMyCustomActionValidate(AC_ActionInfo* info) {
|
void onActionMyCustomActionValidate(AC_ActionInfo* info) {
|
||||||
// Enable based on some condition
|
// Enable based on some condition
|
||||||
info->setEnabled(canPerformAction());
|
info->setEnabled(canPerformAction());
|
||||||
info->setChecked(isActionActive());
|
// Checkable state is controlled by an additional (currently-opaque) vfunc
|
||||||
|
// on the `AC_ActionInfo*` implementation, not by a public `setChecked(...)` API.
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -289,6 +290,24 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AC_Result perform(AC_ActionInfo* info) override {
|
||||||
|
if (!info) {
|
||||||
|
return AC_Result::NotHandled;
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDA-verified: internal widget responders call `info->invokeOnQObject(widget)`.
|
||||||
|
// This uses Qt's metaobject (`QMetaObject::indexOfSlot`), so your class must
|
||||||
|
// have `Q_OBJECT` (and be built with moc/automoc) for the slots to be found.
|
||||||
|
// If it returns NotHandled, they propagate to parentResponder().
|
||||||
|
AC_Result result = info->invokeOnQObject(this);
|
||||||
|
if (result == AC_Result::NotHandled) {
|
||||||
|
if (AC_Responder* parent = parentResponder()) {
|
||||||
|
return parent->perform(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void onActionWidgetAction() {
|
void onActionWidgetAction() {
|
||||||
qDebug() << "Widget action triggered!";
|
qDebug() << "Widget action triggered!";
|
||||||
@ -332,10 +351,11 @@ public:
|
|||||||
bool resignSelectionResponder() override { return false; }
|
bool resignSelectionResponder() override { return false; }
|
||||||
|
|
||||||
AC_Result perform(AC_ActionInfo* info) override {
|
AC_Result perform(AC_ActionInfo* info) override {
|
||||||
// Use Qt meta-object to invoke the slot by name
|
if (!info) {
|
||||||
// AC_ActionInfo contains slot name and parameters
|
|
||||||
return AC_Result::NotHandled;
|
return AC_Result::NotHandled;
|
||||||
}
|
}
|
||||||
|
return info->invokeOnQObject(this);
|
||||||
|
}
|
||||||
|
|
||||||
AC_Result performDownToChildren(AC_ActionInfo* info) override {
|
AC_Result performDownToChildren(AC_ActionInfo* info) override {
|
||||||
return AC_Result::NotHandled;
|
return AC_Result::NotHandled;
|
||||||
@ -362,24 +382,29 @@ private:
|
|||||||
|
|
||||||
## AC_ActionInfo Structure
|
## AC_ActionInfo Structure
|
||||||
|
|
||||||
When an action is triggered, an `AC_ActionInfo` object is created:
|
When an action is triggered or validated, Toon Boom passes an `AC_ActionInfo*` to responders.
|
||||||
|
|
||||||
|
**Important ABI correction (IDA-verified):** `AC_ActionInfo` is **not** a `QObject`. RTTI in `ToonBoomActionManager.dll` shows:
|
||||||
|
|
||||||
|
- `AC_ActionInfoImpl` derives from `AC_ActionInfo`
|
||||||
|
- `AC_ActionInfo` derives from `AC_ActionData`
|
||||||
|
|
||||||
|
So, “action info” methods like `setEnabled(bool)` are actually exported as `AC_ActionData::setEnabled(bool)` and are inherited by `AC_ActionInfo`.
|
||||||
|
|
||||||
|
**Important return-value detail (IDA-verified):** action invocation/validation helpers return:
|
||||||
|
- `0` = handled/success
|
||||||
|
- `1` = not handled / slot not found
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
class AC_ActionInfo : public QObject {
|
class AC_ActionData {
|
||||||
// Key methods
|
public:
|
||||||
const QString& slot() const; // Slot signature to invoke
|
bool isValidation() const;
|
||||||
const QString& text() const; // Action display text
|
|
||||||
QVariant itemParameter() const; // Extra parameter from XML
|
|
||||||
|
|
||||||
// State methods
|
|
||||||
bool isEnabled() const;
|
|
||||||
void setEnabled(bool enabled);
|
void setEnabled(bool enabled);
|
||||||
bool isChecked() const;
|
void setVisible(bool visible);
|
||||||
void setChecked(bool checked);
|
};
|
||||||
|
|
||||||
// Responder info
|
class AC_ActionInfo : public AC_ActionData {
|
||||||
AC_Responder* responder() const;
|
// Most fields/methods are opaque; Toon Boom provides concrete impls.
|
||||||
void setResponder(AC_Responder* resp);
|
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -387,20 +412,25 @@ class AC_ActionInfo : public QObject {
|
|||||||
|
|
||||||
Before displaying a menu or when the UI updates, Toon Boom calls validation methods:
|
Before displaying a menu or when the UI updates, Toon Boom calls validation methods:
|
||||||
|
|
||||||
1. For each toolbar item, look for a slot named `<slotName>Validate(AC_ActionInfo*)`
|
1. For each toolbar item, Toon Boom tries to validate via a derived validate slot name (`<slotName>Validate`).
|
||||||
2. If found, invoke it to update enabled/checked state
|
2. If the validate slot exists, it is invoked and is expected to update state (typically via `info->setEnabled(...)`, and optionally a checked/visible-like vfunc).
|
||||||
3. Display the item according to the updated state
|
3. If the validate slot does not exist, Toon Boom can fall back to enabling the action if the action slot exists.
|
||||||
|
4. If neither validate nor action slot exists on the resolved responder, the action is disabled.
|
||||||
|
|
||||||
Example from `TULayoutManager`:
|
Example from `TULayoutManager`:
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
// Address: 0x7ffa0be52e60
|
// Address: 0x7ffa0be52e60
|
||||||
void TULayoutManager::onActionFullscreenValidate(AC_ActionInfo* info) {
|
void TULayoutManager::onActionFullscreenValidate(AC_ActionInfo* info) {
|
||||||
info->setVisible(true); // vtable[7]
|
// 1) Calls AC_ActionInfo vfunc @ +0x38 with a byte flag from `this+0xC0`
|
||||||
|
// (used to update a check/visible-like state on the action).
|
||||||
|
// 2) Always enables the action:
|
||||||
info->setEnabled(true); // AC_ActionData::setEnabled
|
info->setEnabled(true); // AC_ActionData::setEnabled
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
See also: `docs/AC_Toolbar_ButtonEnablement.md:1`
|
||||||
|
|
||||||
## Registration with AC_Manager
|
## Registration with AC_Manager
|
||||||
|
|
||||||
Responders must be registered to be found by name:
|
Responders must be registered to be found by name:
|
||||||
|
|||||||
@ -14,13 +14,46 @@ Toon Boom uses a two-tier toolbar system:
|
|||||||
1. **Global Toolbars** - Application-wide toolbars (FileToolbar, EditToolbar, DrawingToolToolbar, etc.)
|
1. **Global Toolbars** - Application-wide toolbars (FileToolbar, EditToolbar, DrawingToolToolbar, etc.)
|
||||||
2. **View-Specific Toolbars** - Toolbars that appear when a specific view has focus (DrawingViewToolbar, TimelineViewToolbar, CameraViewToolbar, etc.)
|
2. **View-Specific Toolbars** - Toolbars that appear when a specific view has focus (DrawingViewToolbar, TimelineViewToolbar, CameraViewToolbar, etc.)
|
||||||
|
|
||||||
|
## Toolbar Buttons Disabled?
|
||||||
|
|
||||||
|
If your custom view’s toolbar shows but every button is disabled, the root cause is almost always **validation / responder resolution**, not the toolbar XML.
|
||||||
|
|
||||||
|
See `docs/AC_Toolbar_ButtonEnablement.md:1` for the IDA-verified validation flow and why `enabled="true"` is ignored.
|
||||||
|
|
||||||
|
## Important: raiseArea vs Toolbar Display
|
||||||
|
|
||||||
|
**`TULayoutManager::raiseArea()` does NOT directly display toolbars.**
|
||||||
|
|
||||||
|
`raiseArea` is used to bring a view/area to the foreground:
|
||||||
|
```cpp
|
||||||
|
// Signature:
|
||||||
|
TULayoutView* raiseArea(const QString& areaName, TULayoutFrame* frame,
|
||||||
|
bool createNew, const QPoint& pos);
|
||||||
|
|
||||||
|
// Example usage (from HarmonyPremium.exe):
|
||||||
|
layoutManager->raiseArea("Colour", nullptr, true, QPoint(0,0)); // Shows Colour palette
|
||||||
|
layoutManager->raiseArea("Morphing", nullptr, true, QPoint(0,0)); // Shows Morphing view
|
||||||
|
```
|
||||||
|
|
||||||
|
**Toolbar display is triggered separately** through:
|
||||||
|
1. `TULayoutFrame::setCurrentTab(...)` (tab/view change)
|
||||||
|
2. `TULayoutFrame::showViewToolBar()` (updates the view-toolbar for the new current view)
|
||||||
|
3. `view->toolbar()` (returns toolbar definition as a `QDomElement`)
|
||||||
|
|
||||||
|
`TULayoutManager::setCurrentLayoutFrame(frame)` does **not** call `showViewToolBar()` directly (verified in ToonBoomLayout.dll `0x7ffa0be541d0`).
|
||||||
|
|
||||||
|
If you call `raiseArea` and don't see toolbars, the likely causes are:
|
||||||
|
- Your view's `toolbar()` override returns an empty `QDomElement`
|
||||||
|
- The toolbar XML hasn't been registered with `AC_Manager`
|
||||||
|
- The view isn't properly receiving focus after being raised
|
||||||
|
- `LAY_ToolbarInfo::m_isDefault` is still non-zero (the frame hides/resets the view-toolbar in this case)
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
```
|
```
|
||||||
┌─────────────────────────────────────────────────────────────────┐
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
│ TULayoutManager │
|
│ TULayoutManager │
|
||||||
│ - Manages all frames, areas, and global toolbars │
|
│ - Manages all frames, areas, and global toolbars │
|
||||||
│ - Holds reference to AC_Manager at offset +344 │
|
|
||||||
│ - showToolbar() at vtable+416 creates/shows toolbars by name │
|
│ - showToolbar() at vtable+416 creates/shows toolbars by name │
|
||||||
└─────────────────────────────────────────────────────────────────┘
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
│
|
│
|
||||||
@ -56,10 +89,11 @@ During application startup, toolbars are loaded from `toolbars.xml`:
|
|||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
// From HarmonyPremium.exe session initialization (0x140034EB0)
|
// From HarmonyPremium.exe session initialization (0x140034EB0)
|
||||||
// Loads toolbars.xml via AC_Manager::addToolbarFromFile (vtable+376)
|
// Loads toolbars.xml via AC_Manager::loadToolbars(path, outIds) (vtable+0x178)
|
||||||
|
|
||||||
UT_String toolbarsPath = RM_GetResourcePath("toolbars.xml");
|
UT_String toolbarsPath = RM_GetResourcePath("toolbars.xml");
|
||||||
actionManager->addToolbarFromFile(toolbarsPath.ToQString(), &toolbarList);
|
QList<QString> toolbarIds;
|
||||||
|
actionManager->loadToolbars(toolbarsPath.ToQString(), toolbarIds);
|
||||||
```
|
```
|
||||||
|
|
||||||
The `toolbars.xml` file defines toolbar content using XML elements like:
|
The `toolbars.xml` file defines toolbar content using XML elements like:
|
||||||
@ -91,131 +125,267 @@ if (WHO_Features::hasOnionSkinToolbar()) {
|
|||||||
|
|
||||||
**Key Function Addresses (HarmonyPremium.exe):**
|
**Key Function Addresses (HarmonyPremium.exe):**
|
||||||
- Session toolbar init: `0x14002F840`
|
- Session toolbar init: `0x14002F840`
|
||||||
- TULayoutManager::showToolbar import: `0x1409c8ea0`
|
- `TULayoutManager::showToolbar` is called via an import thunk in HarmonyPremium.exe (address is version-specific)
|
||||||
|
|
||||||
### 3. View-Specific Toolbar Definition
|
### 3. View-Specific Toolbar Definition
|
||||||
|
|
||||||
Each view class overrides `TULayoutView::toolbar()` to return its toolbar definition:
|
Each view class can override `TULayoutView::toolbar()` (ToonBoomLayout.dll vtable `+0x58`) to provide a `QDomElement` describing the view-toolbar content.
|
||||||
|
|
||||||
```cpp
|
Implementation detail (MSVC x64 ABI): this return-by-value is implemented with a hidden return buffer pointer (passed in `RDX` for member functions), which is why decompiled code often looks like `toolbar(this, out)`.
|
||||||
// Example: CameraView::toolbar() at 0x1403BC450
|
|
||||||
QDomElement CameraView::toolbar() {
|
|
||||||
// Get AC_Manager from embedded context (offset -56 from TULayoutView*)
|
|
||||||
AC_Manager* manager = *(AC_Manager**)(this - 56);
|
|
||||||
if (manager) {
|
|
||||||
// Call AC_Manager::getToolbarElement (vtable+416 / offset 52*8)
|
|
||||||
QDomElement element;
|
|
||||||
manager->getToolbarElement(&element, "CameraViewToolbar");
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
return QDomElement(); // Empty if no manager
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Known View Toolbar Implementations:**
|
In Harmony Premium, many views inherit from `TUWidgetLayoutView` (via `VL_ViewQt`), so the `TULayoutView*` you get in `toolbar()` is an embedded subobject:
|
||||||
|
|
||||||
| View Class | Toolbar Name | Address |
|
- `TUWidgetLayoutView::m_actionManager` is stored at `+0x30` (in the `TUWidgetLayoutView` object)
|
||||||
|------------|--------------|---------|
|
- the embedded `TULayoutView` base is at `+0x68`
|
||||||
| DrawingView | "DrawingViewToolbar" | `0x1403B9880` |
|
- therefore, `AC_Manager*` is reachable from a `TULayoutView*` as `*(AC_Manager**)((char*)this - 0x38)`
|
||||||
| CameraView | "CameraViewToolbar" | `0x1403BC450` |
|
|
||||||
| TimelineView | "TimelineViewToolbar" | `0x14011C5F0` |
|
**Example: Drawing toolbar override**
|
||||||
| SGV_Graph3DView | (custom impl) | `0x1409e1866` |
|
|
||||||
|
- Function: `VL_BaseDrawingViewQt__toolbar`
|
||||||
|
- Address: `0x1403B9880` (HarmonyPremium.exe)
|
||||||
|
|
||||||
|
Behavior (from disassembly/decompilation):
|
||||||
|
- Read `AC_Manager*` from `[this-0x38]`
|
||||||
|
- Call `AC_Manager` vfunc at vtable `+0x1A0` to fill a `QDomElement` for id `"DrawingViewToolbar"`
|
||||||
|
- Return an empty `QDomElement` if `AC_Manager` is null
|
||||||
|
|
||||||
|
**Example: Reference View toolbar override**
|
||||||
|
|
||||||
|
Toon Boom’s “Reference View” uses the `ModelViewToolbar` definition (see `toolbars.xml`), implemented in:
|
||||||
|
- Function: `VL_ModelViewQt__toolbar`
|
||||||
|
- Address: `0x1403C8A20` (HarmonyPremium.exe)
|
||||||
|
|
||||||
|
Behavior: same as above, but requests `"ModelViewToolbar"`.
|
||||||
|
|
||||||
|
**Relevant TULayoutView virtuals (ToonBoomLayout.dll):**
|
||||||
|
- `toolbar(...)`: vtable `+0x58`
|
||||||
|
- `setToolbarInfo(const LAY_ToolbarInfo&)`: vtable `+0x60`
|
||||||
|
|
||||||
### 4. Toolbar Display Flow
|
### 4. Toolbar Display Flow
|
||||||
|
|
||||||
When a view gains focus, the toolbar is displayed through this flow:
|
When the current tab/view changes, Toon Boom updates the frame’s view-toolbar via `TULayoutFrame::showViewToolBar()` (ToonBoomLayout.dll `0x7ffa0be4bb70`).
|
||||||
|
|
||||||
|
Confirmed call sites include:
|
||||||
|
- `TULayoutFrame::setCurrentTab(TULayoutViewHolder*)` → `showViewToolBar()`
|
||||||
|
- `TULayoutManager::showViewToolBars()` → `TULayoutFrame::showViewToolBar()`
|
||||||
|
|
||||||
|
High-level behavior of `showViewToolBar()` (validated from disassembly):
|
||||||
|
1. `currentView = m_layoutManager->findInstance(this, false)`
|
||||||
|
2. `toolbarEl = currentView->toolbar()` (calls the view’s override)
|
||||||
|
3. `info = currentView->getToolbarInfo()` (copy of `TULayoutView::m_toolbarInfo`)
|
||||||
|
4. Hide/reset cases:
|
||||||
|
- If `info.m_isDefault != 0`: clear + hide the frame toolbar and return
|
||||||
|
- If `toolbarEl` is null: clear + hide the frame toolbar and return
|
||||||
|
5. Ensure `m_viewToolbar` exists (created once via `createEmptyToolBar(info.m_name)` if needed)
|
||||||
|
6. Populate the toolbar:
|
||||||
|
- `m_viewToolbar->setOwner(currentView->getWidget())`
|
||||||
|
- `m_viewToolbar->changeContent(toolbarEl, info.m_buttonConfig, info.m_buttonDefaultConfig)`
|
||||||
|
7. Dock and apply placement:
|
||||||
|
- `m_mainWindow->addToolBar(info.m_toolBarArea, m_viewToolbar->toQToolBar())`
|
||||||
|
- `Layout::Toolbar::loadToolbar(info, m_mainWindow)`
|
||||||
|
- `m_viewToolbar->setVisible(info.m_visible)`
|
||||||
|
|
||||||
|
### 4.1 raiseArea Function Details
|
||||||
|
|
||||||
|
The `raiseArea` function is for **bringing a view area to the foreground**, not for toolbar display:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Function signature (from ToonBoomLayout.dll)
|
||||||
|
TULayoutView* TULayoutManager::raiseArea(
|
||||||
|
const QString& areaName, // e.g., "Colour", "Morphing", "Timeline"
|
||||||
|
TULayoutFrame* frame, // Target frame, or nullptr for auto-selection
|
||||||
|
bool createNew, // true = create new instance if needed
|
||||||
|
const QPoint& pos // Position for floating windows
|
||||||
|
);
|
||||||
```
|
```
|
||||||
1. User clicks on a view widget
|
|
||||||
│
|
**Example usages found in HarmonyPremium.exe:**
|
||||||
▼
|
|
||||||
2. Application event handler detects focus change
|
```cpp
|
||||||
(sub_140059DE0 checks for TULayoutFrame inheritance)
|
// sub_14086E410: Opens the Colour palette view
|
||||||
│
|
void showColourView() {
|
||||||
▼
|
TULayoutManager* mgr = getLayoutManager();
|
||||||
3. TULayoutManager::setCurrentLayoutFrame(frame)
|
QString areaName("Colour");
|
||||||
│
|
QPoint pos(0, 0);
|
||||||
▼
|
mgr->raiseArea(areaName, nullptr, true, pos);
|
||||||
4. TULayoutFrame::showViewToolBar()
|
}
|
||||||
- Gets current view from view holder
|
|
||||||
- Calls view->toolbar() to get QDomElement
|
// sub_1401579C0: Opens Morphing view on double-click
|
||||||
- Creates AC_ToolbarImpl from element
|
// (called from QAbstractItemView::mouseDoubleClickEvent handler)
|
||||||
- Adds toolbar to TULayoutMainWindow
|
void openMorphingOnDoubleClick() {
|
||||||
│
|
TULayoutFrame* frame = view->getLayoutFrame(widget);
|
||||||
▼
|
TULayoutManager* mgr = frame->getLayoutManager();
|
||||||
5. Toolbar becomes visible in frame's toolbar area
|
QString areaName("Morphing");
|
||||||
|
QPoint pos(0, 0);
|
||||||
|
mgr->raiseArea(areaName, nullptr, true, pos);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key points:**
|
||||||
|
- `raiseArea` returns the `TULayoutView*` that was raised/created
|
||||||
|
- The third parameter `createNew` controls whether to create a new tab/instance
|
||||||
|
- Pass `nullptr` for `frame` to let the system choose the target frame
|
||||||
|
- Toolbar display is a **separate mechanism** triggered by focus changes
|
||||||
|
|
||||||
|
### 4.2 Ensuring Toolbar Display After raiseArea
|
||||||
|
|
||||||
|
If you need to ensure toolbars appear after calling `raiseArea`:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Method 1: Let the focus system handle it naturally
|
||||||
|
TULayoutView* view = layoutManager->raiseArea("MyArea", nullptr, true, QPoint(0,0));
|
||||||
|
if (view) {
|
||||||
|
QWidget* widget = view->getWidget();
|
||||||
|
if (widget) {
|
||||||
|
widget->setFocus(Qt::OtherFocusReason); // Triggers focus chain
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method 2: Explicitly trigger the toolbar display
|
||||||
|
TULayoutView* view = layoutManager->raiseArea("MyArea", nullptr, true, QPoint(0,0));
|
||||||
|
if (view) {
|
||||||
|
TULayoutFrame* frame = view->getLayoutFrame(view->getWidget());
|
||||||
|
if (frame) {
|
||||||
|
// Direct refresh paths observed in ToonBoomLayout.dll:
|
||||||
|
// - TULayoutFrame::setCurrentTab(...) calls frame->showViewToolBar()
|
||||||
|
// - TULayoutManager::showViewToolBars() calls into the current frame(s)
|
||||||
|
frame->showViewToolBar();
|
||||||
|
layoutManager->showViewToolBars();
|
||||||
|
}
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## AC_Toolbar Integration
|
## AC_Toolbar Integration
|
||||||
|
|
||||||
|
Toolbar definitions and instances involve three different components:
|
||||||
|
- `AC_Manager` (ToonBoomActionManager.dll): loads `<toolbar>` definitions (e.g., from `toolbars.xml`) and can create `AC_Toolbar` instances.
|
||||||
|
- `TULayoutManager` (ToonBoomLayout.dll / HarmonyPremium.exe): shows/hides *global* toolbars by name.
|
||||||
|
- `TULayoutFrame` (ToonBoomLayout.dll): owns the *view* toolbar instance (`m_viewToolbar`) and updates it on tab/view changes via `showViewToolBar()`.
|
||||||
|
|
||||||
|
### How Toolbars Are Actually Registered
|
||||||
|
|
||||||
|
1. **Toolbar Definitions are loaded from XML** (`toolbars.xml`):
|
||||||
|
```cpp
|
||||||
|
// During session init (sub_140034EB0 in HarmonyPremium.exe)
|
||||||
|
AC_Manager* actionManager = AC_CreateActionManager(keywords);
|
||||||
|
|
||||||
|
// Load toolbars via AC_Manager::loadToolbars(path, outIds) (vtable+0x178)
|
||||||
|
QString path = RM_GetResourcePath("toolbars.xml").ToQString();
|
||||||
|
void** vtable = *reinterpret_cast<void***>(actionManager);
|
||||||
|
using LoadToolbarsFromFileFn = void (__fastcall *)(AC_Manager* self, const QString* path, QList<QString>* outIds);
|
||||||
|
auto loadToolbarsFromFile = reinterpret_cast<LoadToolbarsFromFileFn>(vtable[47]); // vtable+0x178
|
||||||
|
QList<QString> toolbarIds;
|
||||||
|
loadToolbarsFromFile(actionManager, &path, &toolbarIds);
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Toolbars are shown/activated via TULayoutManager::showToolbar**:
|
||||||
|
```cpp
|
||||||
|
// During setupToolbars (0x14002f840)
|
||||||
|
// This is vtable+416 on TULayoutManager, NOT AC_Manager
|
||||||
|
layoutManager->showToolbar("FileToolbar", true);
|
||||||
|
layoutManager->showToolbar("EditToolbar", true);
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **View-specific toolbars are looked up via `AC_Manager::toolbarElement()`** (inside the view’s `toolbar()` override):
|
||||||
|
```cpp
|
||||||
|
// Common pattern in HarmonyPremium.exe view toolbar() overrides:
|
||||||
|
// - AC_Manager* is reachable from a TUWidgetLayoutView-embedded TULayoutView* at [this-0x38]
|
||||||
|
AC_Manager* manager = *(AC_Manager**)((char*)this - 0x38);
|
||||||
|
if (!manager) {
|
||||||
|
return QDomElement();
|
||||||
|
}
|
||||||
|
return manager->toolbarElement(QString("DrawingViewToolbar"));
|
||||||
|
```
|
||||||
|
|
||||||
### AC_Manager Methods Used
|
### AC_Manager Methods Used
|
||||||
|
|
||||||
The AC_Manager provides these key toolbar methods:
|
The AC_Manager provides these key toolbar methods:
|
||||||
|
|
||||||
| Method | Vtable Offset | Description |
|
| Method | Vtable Offset | Description |
|
||||||
|--------|---------------|-------------|
|
|--------|--------------|-------------|
|
||||||
| `getToolbarElement` | +416 (52*8) | Returns QDomElement for toolbar by name |
|
| `toolbarElement(name)` | `+0x1A0` | Returns the `<toolbar ...>` `QDomElement` for `name` (member-function ABI uses a hidden return buffer in `RDX`) |
|
||||||
| `addToolbarFromFile` | +376 (47*8) | Loads toolbar definitions from XML file |
|
| `loadToolbars(element, outIds)` | `+0x170` | Loads toolbar definitions from a `QDomElement` (same structure as `toolbars.xml`) |
|
||||||
| `createToolbar` | +368 (46*8) | Creates AC_ToolbarImpl from element |
|
| `loadToolbars(path, outIds)` | `+0x178` | Loads toolbar definitions from an XML file (e.g., `toolbars.xml`) |
|
||||||
|
| `createToolbar(name, config, mainWindow, area, identity, owner)` | `+0x180` | Creates an `AC_Toolbar` instance from a stored toolbar id (implementation: `AC_ToolbarImpl`) |
|
||||||
|
| `createToolbar(element, config, mainWindow, area, identity, owner)` | `+0x188` | Creates an `AC_Toolbar` instance from a `QDomElement` (implementation: `AC_ToolbarImpl`) |
|
||||||
|
|
||||||
### AC_ToolbarImpl Creation
|
### AC_Toolbar Creation / Reuse
|
||||||
|
|
||||||
When `TULayoutFrame::showViewToolBar()` needs to display a toolbar:
|
`TULayoutFrame::showViewToolBar()` does **not** create a new `AC_Toolbar` every time focus changes.
|
||||||
|
|
||||||
```cpp
|
Observed behavior (ToonBoomLayout.dll `0x7ffa0be4bb70`):
|
||||||
// Pseudocode for toolbar creation
|
- The frame keeps a single `AC_Toolbar*` at `TULayoutFrame + 0xC0` (`m_viewToolbar`)
|
||||||
void TULayoutFrame::showViewToolBar() {
|
- On each tab/view change, it:
|
||||||
TULayoutView* view = getCurrentView();
|
- calls `m_viewToolbar->setOwner(currentView->getWidget())`
|
||||||
if (!view) return;
|
- calls `m_viewToolbar->changeContent(toolbarEl, buttonConfig, buttonDefaultConfig)`
|
||||||
|
- docks `m_viewToolbar->toQToolBar()` into the frame’s `TULayoutMainWindow`
|
||||||
|
|
||||||
// Get toolbar XML element from view
|
The `AC_Toolbar` object itself is created lazily by `TULayoutFrame::createEmptyToolBar(name)`:
|
||||||
QDomElement toolbarElement = view->toolbar();
|
- Builds an empty `<toolbar/>` element
|
||||||
if (toolbarElement.isNull()) return;
|
- Calls `AC_Manager::createToolbar(emptyEl, nullptr, m_layoutManager, 4, nameLatin1, nullptr)`
|
||||||
|
- Stores the result as `m_viewToolbar`
|
||||||
// Create AC_ToolbarImpl via AC_Manager
|
|
||||||
AC_Toolbar* toolbar = m_actionManager->createToolbar(
|
|
||||||
this, // owner
|
|
||||||
toolbarElement.attribute("text"),
|
|
||||||
m_mainWindow, // parent QMainWindow
|
|
||||||
toolbarElement.attribute("id").toUtf8().constData(),
|
|
||||||
toolbarElement,
|
|
||||||
nullptr // default config
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add to frame's main window
|
|
||||||
m_mainWindow->addToolBar(Qt::TopToolBarArea, toolbar->toQToolBar());
|
|
||||||
|
|
||||||
// Store for later removal
|
|
||||||
m_viewToolbar = toolbar;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### LAY_ToolbarInfo Configuration
|
### LAY_ToolbarInfo Configuration
|
||||||
|
|
||||||
Each view can have stored toolbar configuration via `LAY_ToolbarInfo`:
|
Each view can have stored toolbar configuration via `LAY_ToolbarInfo`:
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
// Structure of LAY_ToolbarInfo (104 bytes)
|
// Structure of LAY_ToolbarInfo (~104 bytes)
|
||||||
|
// Analyzed from ToonBoomLayout.dll constructors and getters
|
||||||
class LAY_ToolbarInfo {
|
class LAY_ToolbarInfo {
|
||||||
int m_x; // +0x00 - X position
|
int m_x; // +0x00 - X position (default: 0)
|
||||||
int m_y; // +0x04 - Y position
|
int m_y; // +0x04 - Y position (default: 0)
|
||||||
int m_index; // +0x08 - Toolbar index
|
int m_index; // +0x08 - Toolbar index (default: -1)
|
||||||
int m_width; // +0x0C - Width
|
int m_width; // +0x0C - Width (default: -1)
|
||||||
int m_height; // +0x10 - Height
|
int m_height; // +0x10 - Height (default: -1)
|
||||||
bool m_newline; // +0x14 - Break to new line
|
bool m_newline; // +0x14 - Break to new line (default: false)
|
||||||
bool m_visible; // +0x15 - Visibility
|
bool m_visible; // +0x15 - Visibility (default: true)
|
||||||
bool m_isDefault; // +0x16 - Using default config
|
bool m_isDefault; // +0x16 - Default/unconfigured state (Layout hides view-toolbar when true)
|
||||||
QString m_name; // +0x18 - Toolbar name
|
// padding // +0x17
|
||||||
Qt::Orientation m_orientation; // +0x30 - Horizontal/Vertical
|
QString m_name; // +0x18 - Toolbar name (24 bytes with SSO)
|
||||||
Qt::ToolBarArea m_toolBarArea; // +0x34 - Docking area
|
Qt::Orientation m_orientation; // +0x30 - Orientation (default: Horizontal)
|
||||||
QList<QString> m_buttonConfig; // +0x38 - Current button order
|
Qt::ToolBarArea m_toolBarArea; // +0x34 - Docking area (default: TopToolBarArea=4)
|
||||||
QList<QString> m_buttonDefaultConfig; // +0x50 - Default button order
|
QList<QString> m_buttonConfig; // +0x38 - Current button order (24 bytes)
|
||||||
|
QList<QString> m_buttonDefaultConfig; // +0x50 - Default button order (24 bytes)
|
||||||
|
// Total size: ~104 bytes
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
The view receives its toolbar info via `TULayoutView::setToolbarInfo()`:
|
**Key LAY_ToolbarInfo Methods (ToonBoomLayout.dll):**
|
||||||
- Import: `0x1409c8f4e` (thunk to ToonBoomLayout.dll)
|
|
||||||
- Called by TULayoutFrame when loading saved layout preferences
|
| Method | Address | Description |
|
||||||
|
|--------|---------|-------------|
|
||||||
|
| `LAY_ToolbarInfo()` | `0x7ffa0be6a3a0` | Default constructor |
|
||||||
|
| `LAY_ToolbarInfo(QString, int, int, int, int, int, bool, bool)` | `0x7ffa0be6a2c0` | Initializes `name,x,y,index,width,height,newline,visible` (sets `m_isDefault=1`) |
|
||||||
|
| `getName()` | `0x7ffa0be392b0` | Returns toolbar name |
|
||||||
|
| `getHeight()` | `0x7ffa0be39280` | Returns height |
|
||||||
|
| `isVisible()` | `0x7ffa0be39570` | Returns visibility |
|
||||||
|
| `isDefault()` | `0x7ffa0be39560` | Returns `m_isDefault` (Layout treats non-zero as “hide/reset view-toolbar”) |
|
||||||
|
| `getButtonConfig()` | `0x7ffa0be6a990` | Returns `nullptr` when `m_isDefault!=0`, else `&m_buttonConfig` |
|
||||||
|
| `setButtonConfig()` | `0x7ffa0be6a9a0` | Copies config into `m_buttonConfig` and sets `m_isDefault=0` |
|
||||||
|
| `setButtonDefaultConfig()` | `0x7ffa0be6aaf0` | Copies config into `m_buttonDefaultConfig` |
|
||||||
|
| `setName()` | `0x7ffa0be39bd0` | Sets toolbar name |
|
||||||
|
| `setVisible()` | `0x7ffa0be39c10` | Sets visibility |
|
||||||
|
| `fromXml()` | `0x7ffa0be6a520` | Loads from QDomElement |
|
||||||
|
| `toXml()` | `0x7ffa0be6ab00` | Saves to QDomElement |
|
||||||
|
|
||||||
|
Note: the `m_buttonDefaultConfig` accessor is ICF-folded with another trivial accessor and may show up in IDA as `?getChildren@TULayoutSplitter@@...` at `0x7ffa0be5ac60`; `TULayoutFrame::showViewToolBar()` uses that address as `&m_buttonDefaultConfig`.
|
||||||
|
|
||||||
|
### How `m_toolbarInfo` Gets Initialized (Critical)
|
||||||
|
|
||||||
|
`TULayoutFrame::showViewToolBar()` will *hide* the view-toolbar when `TULayoutView::m_toolbarInfo.m_isDefault != 0`, so the question becomes: **where does non-default toolbar info come from?**
|
||||||
|
|
||||||
|
This happens when a view is added to a frame (e.g., via `raiseArea()` / layout activation):
|
||||||
|
- `TULayoutArea::add` (ToonBoomLayout.dll `0x7ffa0be41c10`) first checks the frame’s cache (`getToolbarInfoForView(viewKey)`).
|
||||||
|
- If no cached entry exists, it calls `view->toolbar()` and reads `toolbarEl.attribute("id")` to get the toolbar identity string (e.g., `DrawingViewToolbar`).
|
||||||
|
- It then queries `TULayoutStorage::getViewToolbarConfig(viewKey, wantDefault)` (ToonBoomLayout.dll `0x7ffa0be5cff0`) to fetch button config lists keyed by `viewKey` (typically `view->getCaption(true)`).
|
||||||
|
- If configs exist, it builds a `LAY_ToolbarInfo` with:
|
||||||
|
- `m_name = toolbarId`
|
||||||
|
- `m_buttonConfig` + `m_buttonDefaultConfig` populated (which flips `m_isDefault -> 0`)
|
||||||
|
- applies it via `view->setToolbarInfo(info)` and caches it in the frame.
|
||||||
|
|
||||||
|
Persisted customization updates the config lists via:
|
||||||
|
- `TULayoutFrame::toolbarWasCustomized` (ToonBoomLayout.dll `0x7ffa0be4be20`) → `TULayoutStorage::setViewToolbarConfig(viewKey, config, isDefault=false)` + `saveLayoutToolbar()`.
|
||||||
|
|
||||||
## Implementing Custom View Toolbars
|
## Implementing Custom View Toolbars
|
||||||
|
|
||||||
@ -238,24 +408,16 @@ Create a toolbar definition that will be loaded by AC_Manager:
|
|||||||
class MyCustomView : public TULayoutView {
|
class MyCustomView : public TULayoutView {
|
||||||
public:
|
public:
|
||||||
QDomElement toolbar() override {
|
QDomElement toolbar() override {
|
||||||
// Get AC_Manager - depends on your view's structure
|
// Get AC_Manager - depends on your view's structure.
|
||||||
// For TUWidgetLayoutView subclasses, manager is at offset -56
|
// In HarmonyPremium.exe views derived from TUWidgetLayoutView, AC_Manager* is reachable from the embedded
|
||||||
AC_Manager* manager = getActionManager();
|
// TULayoutView* at [this-0x38]. For a custom TULayoutView subclass, store AC_Manager* yourself (or fetch
|
||||||
|
// it via PLUG_Services::getActionManager()).
|
||||||
|
AC_Manager* manager = getActionManager(); // your helper
|
||||||
if (!manager) {
|
if (!manager) {
|
||||||
return QDomElement();
|
return QDomElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use AC_Manager to get toolbar element by name
|
return manager->toolbarElement(QString("MyCustomViewToolbar"));
|
||||||
QDomElement element;
|
|
||||||
// Call via vtable[52] - getToolbarElement
|
|
||||||
auto getToolbarElement = reinterpret_cast<void(*)(AC_Manager*, QDomElement*, const QString&)>(
|
|
||||||
(*reinterpret_cast<void***>(manager))[52]
|
|
||||||
);
|
|
||||||
|
|
||||||
QString toolbarName("MyCustomViewToolbar");
|
|
||||||
getToolbarElement(manager, &element, toolbarName);
|
|
||||||
|
|
||||||
return element;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
@ -270,19 +432,39 @@ Ensure your toolbar XML is loaded during initialization:
|
|||||||
// Option 2: Load programmatically
|
// Option 2: Load programmatically
|
||||||
AC_Manager* manager = PLUG_Services::getActionManager();
|
AC_Manager* manager = PLUG_Services::getActionManager();
|
||||||
if (manager) {
|
if (manager) {
|
||||||
// Use addToolbarFromElement (vtable+376)
|
// Use AC_Manager::loadToolbars(element, outIds) (vtable+0x170)
|
||||||
|
// Note: loadToolbars expects the same structure as toolbars.xml; easiest is to wrap your <toolbar> inside a <toolbars> root.
|
||||||
QDomDocument doc;
|
QDomDocument doc;
|
||||||
doc.setContent(myToolbarXml);
|
doc.setContent(myToolbarXml);
|
||||||
QDomElement element = doc.documentElement();
|
QDomElement element = doc.documentElement();
|
||||||
|
|
||||||
// Call via vtable
|
QList<QString> ids;
|
||||||
auto addToolbarFromElement = reinterpret_cast<void(*)(AC_Manager*, const QDomElement&, const QString&, bool)>(
|
manager->loadToolbars(element, ids);
|
||||||
(*reinterpret_cast<void***>(manager))[47]
|
|
||||||
);
|
|
||||||
addToolbarFromElement(manager, element, "MyCustomViewToolbar", false);
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Step 4: Ensure `LAY_ToolbarInfo` Is Non-Default
|
||||||
|
|
||||||
|
`TULayoutFrame::showViewToolBar()` (ToonBoomLayout.dll `0x7ffa0be4bb70`) will hide/reset the view-toolbar when `TULayoutView::m_toolbarInfo.m_isDefault != 0`.
|
||||||
|
|
||||||
|
For a custom view, you must ensure:
|
||||||
|
- `LAY_ToolbarInfo::m_name` is set to the toolbar element’s `"id"` (used as the toolbar instance identity when `createEmptyToolBar(name)` creates `m_viewToolbar`)
|
||||||
|
- `LAY_ToolbarInfo::m_buttonConfig` (and typically `m_buttonDefaultConfig`) are populated, so `m_isDefault` flips to `0`
|
||||||
|
|
||||||
|
Built-in views typically get this via `TULayoutArea::add()` reading config from `TULayoutStorage` (see the section above). If your view has no stored config, the toolbar will remain hidden until you seed it.
|
||||||
|
|
||||||
|
Minimal pattern (conceptual):
|
||||||
|
```cpp
|
||||||
|
LAY_ToolbarInfo info = this->getToolbarInfo();
|
||||||
|
info.setName(QString("MyCustomViewToolbar"));
|
||||||
|
|
||||||
|
QList<QString> buttons = {/* item ids in desired order */};
|
||||||
|
info.setButtonConfig(&buttons); // flips m_isDefault -> 0
|
||||||
|
info.setButtonDefaultConfig(&buttons); // used for reset/customize UI
|
||||||
|
|
||||||
|
this->setToolbarInfo(info);
|
||||||
|
```
|
||||||
|
|
||||||
## Key Memory Offsets
|
## Key Memory Offsets
|
||||||
|
|
||||||
### TULayoutView (from TULayoutView*)
|
### TULayoutView (from TULayoutView*)
|
||||||
@ -310,23 +492,35 @@ if (manager) {
|
|||||||
### HarmonyPremium.exe
|
### HarmonyPremium.exe
|
||||||
| Symbol | Address | Description |
|
| Symbol | Address | Description |
|
||||||
|--------|---------|-------------|
|
|--------|---------|-------------|
|
||||||
| Session toolbar init | `0x14002F840` | Initializes all global toolbars |
|
| TULayoutManager__setupToolbars | `0x14002F840` | Initializes/shows global toolbars by name |
|
||||||
| DrawingView::toolbar | `0x1403B9880` | Returns DrawingViewToolbar element |
|
| VL_BaseDrawingViewQt__toolbar | `0x1403B9880` | Returns `DrawingViewToolbar` element |
|
||||||
| CameraView::toolbar | `0x1403BC450` | Returns CameraViewToolbar element |
|
| CV_CameraViewQt__toolbar | `0x1403BC450` | Returns `CameraViewToolbar` element |
|
||||||
| TimelineView::toolbar | `0x14011C5F0` | Returns TimelineViewToolbar element |
|
| VL_ModelViewQt__toolbar | `0x1403C8A20` | Returns `ModelViewToolbar` element (Reference View) |
|
||||||
| TULayoutView::setToolbarInfo import | `0x1409c8f4e` | Sets toolbar configuration |
|
| TL_DockWindowQT__toolbar | `0x14011C5F0` | Returns `TimelineViewToolbar` element |
|
||||||
| TULayoutView::toolbar import | `0x1409c8fc0` | Returns toolbar QDomElement |
|
|
||||||
| TULayoutManager::showToolbar import | `0x1409c8ea0` | Shows/creates toolbar by name |
|
|
||||||
| TULayoutManager::addToolbar import | `0x1409c8e4c` | Adds toolbar to manager |
|
|
||||||
|
|
||||||
### ToonBoomActionManager.dll
|
### ToonBoomActionManager.dll
|
||||||
| Symbol | Address | Description |
|
| Symbol | Address | Description |
|
||||||
|--------|---------|-------------|
|
|--------|---------|-------------|
|
||||||
| AC_ToolbarImpl::AC_ToolbarImpl | `0x180032df0` | Constructor |
|
| AC_ManagerImpl__toolbarElement | `0x180016240` | `AC_Manager::toolbarElement(name)` implementation (returns `QDomElement` by value) |
|
||||||
| AC_ToolbarImpl::~AC_ToolbarImpl | `0x180033080` | Destructor |
|
| AC_ToolbarImpl__changeContent_fromElement | `0x1800332F0` | `AC_Toolbar::changeContent(element, config, defaultConfig)` |
|
||||||
| AC_ToolbarImpl::create | `0x180033910` | Creates toolbar from XML |
|
| AC_ToolbarImpl::create | `0x180033910` | Build toolbar from XML element |
|
||||||
| AC_ToolbarImpl::insert | `0x1800345e0` | Inserts item at position |
|
| AC_ToolbarImpl::create (with config) | `0x180033D50` | Build toolbar from XML element + config list |
|
||||||
| AC_ToolbarImpl vtable | `0x180054eb0` | Main vtable |
|
|
||||||
|
### ToonBoomLayout.dll (Toolbar Infrastructure)
|
||||||
|
| Symbol | Address | Description |
|
||||||
|
|--------|---------|-------------|
|
||||||
|
| TULayoutArea::add | `0x7ffa0be41c10` | Initializes `TULayoutView::m_toolbarInfo` from `view->toolbar().attribute(\"id\")` + `TULayoutStorage` configs |
|
||||||
|
| TULayoutFrame::showViewToolBar | `0x7ffa0be4bb70` | Core view-toolbar update flow (calls view->toolbar(), then `changeContent` + docking) |
|
||||||
|
| TULayoutFrame::createEmptyToolBar | `0x7ffa0be47730` | Lazily creates/clears `m_viewToolbar` |
|
||||||
|
| TULayoutFrame::removeViewToolBar | `0x7ffa0be4a700` | Hides `m_viewToolbar` |
|
||||||
|
| TULayoutFrame::getCurToolbarState | `0x7ffa0be48b20` | Captures geometry/visibility from `m_viewToolbar->toQToolBar()` |
|
||||||
|
| TULayoutFrame::toolbarWasCustomized | `0x7ffa0be4be20` | Persists button order into `TULayoutStorage` (flips `m_isDefault -> 0`) |
|
||||||
|
| TULayoutView::toolbar | `0x7ffa0be610d0` | Base implementation (returns empty `QDomElement`) |
|
||||||
|
| TULayoutView::setToolbarInfo | `0x7ffa0be610c0` | Stores `LAY_ToolbarInfo` into the view |
|
||||||
|
| TULayoutView::getToolbarInfo | `0x7ffa0be60e80` | Returns pointer to embedded `LAY_ToolbarInfo` |
|
||||||
|
| TULayoutStorage::getViewToolbarConfig | `0x7ffa0be5cff0` | Returns stored `QList<QString>` pointer for a view-toolbar config |
|
||||||
|
| TULayoutStorage::setViewToolbarConfig | `0x7ffa0be5fa20` | Stores a view-toolbar config list (current or default) |
|
||||||
|
| LAY_ToolbarInfo::setButtonConfig | `0x7ffa0be6a9a0` | Copies config + flips `m_isDefault` |
|
||||||
|
|
||||||
## Toolbar Names Reference
|
## Toolbar Names Reference
|
||||||
|
|
||||||
@ -354,3 +548,137 @@ if (manager) {
|
|||||||
- `LibraryViewToolbar` - Library view
|
- `LibraryViewToolbar` - Library view
|
||||||
- `ModuleLibraryViewToolbar` - Module library view
|
- `ModuleLibraryViewToolbar` - Module library view
|
||||||
- `FreeViewToolbar` - Free-form view
|
- `FreeViewToolbar` - Free-form view
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Toolbar Not Appearing After raiseArea
|
||||||
|
|
||||||
|
If you call `TULayoutManager::raiseArea()` and the view's toolbar doesn't appear:
|
||||||
|
|
||||||
|
1. **`raiseArea` doesn't trigger toolbars directly**
|
||||||
|
- `raiseArea` only brings a view to the foreground
|
||||||
|
- Toolbar display is handled by `TULayoutFrame::showViewToolBar()` (typically reached via tab/view changes or `TULayoutManager::showViewToolBars()`)
|
||||||
|
|
||||||
|
2. **Manual toolbar trigger**
|
||||||
|
```cpp
|
||||||
|
TULayoutView* view = layoutManager->raiseArea("MyArea", nullptr, true, QPoint(0,0));
|
||||||
|
if (view) {
|
||||||
|
TULayoutFrame* frame = view->getLayoutFrame(view->getWidget());
|
||||||
|
if (frame) {
|
||||||
|
// Direct refresh options:
|
||||||
|
frame->showViewToolBar();
|
||||||
|
layoutManager->showViewToolBars();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Set focus to trigger natural flow**
|
||||||
|
```cpp
|
||||||
|
TULayoutView* view = layoutManager->raiseArea("MyArea", nullptr, true, QPoint(0,0));
|
||||||
|
if (view && view->getWidget()) {
|
||||||
|
view->getWidget()->setFocus(Qt::OtherFocusReason);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### View toolbar() Returns Empty Element
|
||||||
|
|
||||||
|
If your custom view's `toolbar()` method doesn't work:
|
||||||
|
|
||||||
|
1. **Check AC_Manager access**
|
||||||
|
- For `TUWidgetLayoutView` subclasses: `AC_Manager* mgr = TULayoutView_getActionManager(this);`
|
||||||
|
- The manager must be non-null
|
||||||
|
|
||||||
|
2. **Verify toolbar is registered**
|
||||||
|
- The toolbar XML must be loaded via `AC_Manager::loadToolbars(path, outIds)` or `loadToolbars(element, outIds)`
|
||||||
|
- Check that the toolbar `id` attribute matches what you're requesting
|
||||||
|
|
||||||
|
3. **Debug the vtable call**
|
||||||
|
```cpp
|
||||||
|
QDomElement MyView::toolbar() {
|
||||||
|
AC_Manager* manager = TULayoutView_getActionManager(this);
|
||||||
|
if (!manager) {
|
||||||
|
qDebug() << "No AC_Manager available";
|
||||||
|
return QDomElement();
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: AC_Manager::toolbarElement(...) returns QDomElement by value.
|
||||||
|
// MSVC x64 member-function ABI uses a hidden return buffer pointer (RDX),
|
||||||
|
// so a raw vtable call must include an explicit QDomElement* out.
|
||||||
|
void** vtable = *reinterpret_cast<void***>(manager);
|
||||||
|
using ToolbarElementFn = QDomElement* (__fastcall *)(AC_Manager* self, QDomElement* out, const QString* name);
|
||||||
|
auto toolbarElement = reinterpret_cast<ToolbarElementFn>(vtable[52]);
|
||||||
|
|
||||||
|
QDomElement element;
|
||||||
|
QString name("MyViewToolbar");
|
||||||
|
toolbarElement(manager, &element, &name);
|
||||||
|
|
||||||
|
if (element.isNull()) {
|
||||||
|
qDebug() << "Toolbar" << name << "not found in AC_Manager";
|
||||||
|
}
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Focus/Frame Issues
|
||||||
|
|
||||||
|
1. **Frame not set as current**
|
||||||
|
- The system uses `setCurrentLayoutFrame` to track which frame is active
|
||||||
|
- `showViewToolBar()` is triggered by tab/view changes or `TULayoutManager::showViewToolBars()`, not directly by `setCurrentLayoutFrame`
|
||||||
|
|
||||||
|
2. **View not in frame's current tab**
|
||||||
|
- The raised view may be in a tab but not the current one
|
||||||
|
- Use `TULayoutFrame::setCurrentTab()` if needed
|
||||||
|
|
||||||
|
See **Database Reference** for verified addresses used in this document.
|
||||||
|
|
||||||
|
### Example: Complete Toolbar Integration
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// 1. During initialization - register your toolbar XML
|
||||||
|
void MyPlugin::initialize(AC_Manager* manager) {
|
||||||
|
QString toolbarXml = R"(
|
||||||
|
<toolbar id="MyViewToolbar" trContext="MyPlugin">
|
||||||
|
<item id="MyResponder.DoSomething" />
|
||||||
|
<separator />
|
||||||
|
<item id="MyResponder.DoOther" />
|
||||||
|
</toolbar>
|
||||||
|
)";
|
||||||
|
|
||||||
|
QDomDocument doc;
|
||||||
|
doc.setContent(toolbarXml);
|
||||||
|
|
||||||
|
// Register with AC_Manager (loadToolbars(element, outIds))
|
||||||
|
QList<QString> ids;
|
||||||
|
manager->loadToolbars(doc.documentElement(), ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. In your view class - override toolbar()
|
||||||
|
QDomElement MyView::toolbar() {
|
||||||
|
AC_Manager* manager = TULayoutView_getActionManager(this);
|
||||||
|
if (!manager) return QDomElement();
|
||||||
|
|
||||||
|
void** vtable = *reinterpret_cast<void***>(manager);
|
||||||
|
auto getToolbarElement = reinterpret_cast<void(*)(AC_Manager*, QDomElement*, const QString&)>(vtable[52]);
|
||||||
|
|
||||||
|
QDomElement element;
|
||||||
|
getToolbarElement(manager, &element, QString("MyViewToolbar"));
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. When raising your view and ensuring toolbar appears
|
||||||
|
void showMyView(TULayoutManager* layoutManager) {
|
||||||
|
TULayoutView* view = layoutManager->raiseArea("MyView", nullptr, true, QPoint(0,0));
|
||||||
|
if (view) {
|
||||||
|
// Ensure toolbar displays by triggering focus flow
|
||||||
|
if (QWidget* widget = view->getWidget()) {
|
||||||
|
widget->setFocus(Qt::OtherFocusReason);
|
||||||
|
}
|
||||||
|
// Or explicitly trigger a refresh
|
||||||
|
TULayoutFrame* frame = view->getLayoutFrame(view->getWidget());
|
||||||
|
if (frame) {
|
||||||
|
frame->showViewToolBar();
|
||||||
|
layoutManager->showViewToolBars();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|||||||
@ -83,15 +83,25 @@ Total size: **0x158 (344 bytes)** on x64
|
|||||||
#### Responder Management
|
#### Responder Management
|
||||||
- `firstResponder()` - Returns top of responder stack (offset +248)
|
- `firstResponder()` - Returns top of responder stack (offset +248)
|
||||||
- `applicationResponder()` - Returns m_applicationResponder (offset +296)
|
- `applicationResponder()` - Returns m_applicationResponder (offset +296)
|
||||||
- `registerResponder(responder, widget)` - Adds responder to registry
|
- `registerResponder(responder, widget)` - Adds responder to registry; if `widget != nullptr`, inserts into a global widget map and calls `QObject::connect(widget, destroyed(QObject*), ...)` (see `docs/AC_Manager_registerResponder_QBitArrayCrash.md:1`)
|
||||||
- `setFirstResponder(responder)` - Sets active first responder
|
- `setFirstResponder(responder)` - Sets active first responder
|
||||||
- `pushUp(responder)` / `pushOut(responder)` - Stack manipulation
|
- `pushUp(responder)` - Stack manipulation (note: `pushOut` exists on the `AC_ManagerImpl` QObject vtable in this build, but is not part of the `AC_Manager*` vtable at `0x18004e7c8`)
|
||||||
|
|
||||||
#### Menu/Toolbar Creation
|
#### Menu/Toolbar Creation
|
||||||
- `createMenuBar(element, parent)` - Create menu from XML
|
- `createMenuBar(element, parent)` - Create menu from XML
|
||||||
- `createToolbar(element, ids, mainWindow, area, name, owner)` - Create toolbar
|
- `createToolbar(element, ids, mainWindow, area, name, owner)` - Create toolbar
|
||||||
- `loadMenus(path)` / `loadToolbars(path, ids)` - Load from file
|
- `loadMenus(path)` / `loadToolbars(path, ids)` - Load from file
|
||||||
|
|
||||||
|
#### AC_Manager Toolbar Vtable Slots (Verified in IDA)
|
||||||
|
- `loadToolbars(const QDomElement&, QList<QString>&)` - slot 46 (`+0x170`) at `0x180015940`
|
||||||
|
- `loadToolbars(const QString&, QList<QString>&)` - slot 47 (`+0x178`) at `0x180015f20` (parses file then forwards to slot 46)
|
||||||
|
- `toolbarElement(const QString&)` - slot 52 (`+0x1a0`) at `0x180016240` (returns `QDomElement` by value via MSVC x64 hidden sret out buffer)
|
||||||
|
- `updateToolbars()` - slot 53 (`+0x1a8`) at `0x180016350` (schedules `fireUpdateToolbars()` via `QTimer::singleShot(300, ...)`)
|
||||||
|
|
||||||
|
Note: `loadToolbars(...)` does not call `updateToolbars()` directly (verified by decompilation of `0x180015940` / `0x180015f20`).
|
||||||
|
|
||||||
|
Framework ABI note: `AC_Manager` is a vtable interface; `loadToolbars(const QDomElement&, ...)` must be declared before `loadToolbars(const QString&, ...)` to match slots 46/47. If your call appears to hit `updateToolbars()` or the wrong `loadToolbars` overload, suspect an ABI mismatch (wrong virtual order and/or wrong base pointer/vptr).
|
||||||
|
|
||||||
#### Action Triggering
|
#### Action Triggering
|
||||||
- `trigger(responderIdentity, actionName, forEach)` - Execute action
|
- `trigger(responderIdentity, actionName, forEach)` - Execute action
|
||||||
- `performValidation(responderIdentity, actionName, enabled, checked)` - Validate action state
|
- `performValidation(responderIdentity, actionName, enabled, checked)` - Validate action state
|
||||||
@ -204,8 +214,8 @@ Total size: **0x58 (88 bytes)** on x64
|
|||||||
### AC_Result
|
### AC_Result
|
||||||
```cpp
|
```cpp
|
||||||
enum class AC_Result : int {
|
enum class AC_Result : int {
|
||||||
NotHandled = 0, // Action not handled by any responder
|
Handled = 0, // Action handled successfully
|
||||||
Handled = 1, // Action handled successfully
|
NotHandled = 1, // Action not handled by any responder
|
||||||
Error = 2 // Error during handling
|
Error = 2 // Error during handling
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|||||||
@ -195,6 +195,16 @@ When you call `TULayoutManager::addArea`, the following happens:
|
|||||||
|
|
||||||
This is why `widget()` must return `this` - the calling code treats the return as `TULayoutView*` to make further virtual calls.
|
This is why `widget()` must return `this` - the calling code treats the return as `TULayoutView*` to make further virtual calls.
|
||||||
|
|
||||||
|
## View Toolbars
|
||||||
|
|
||||||
|
Some built-in views display a view-specific toolbar when they are the active tab (e.g., Drawing and Reference View).
|
||||||
|
|
||||||
|
This is driven by:
|
||||||
|
- `TULayoutView::toolbar()` returning a non-null `QDomElement` for the toolbar definition
|
||||||
|
- `TULayoutView::m_toolbarInfo` (`LAY_ToolbarInfo`) being non-default so the frame actually shows/updates the toolbar
|
||||||
|
|
||||||
|
Details and concrete implementations are documented in `docs/TULayoutView_Toolbar_Integration.md`.
|
||||||
|
|
||||||
## Opening Views at Runtime
|
## Opening Views at Runtime
|
||||||
|
|
||||||
To programmatically show a view that's already registered:
|
To programmatically show a view that's already registered:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user