docs: update to remove stale information and add new/updated info

This commit is contained in:
☙◦ The Tablet ❀ GamerGirlandCo ◦❧ 2026-01-14 23:53:12 -05:00
parent 7477821b9b
commit c83cadbcba
Signed by: tablet
GPG Key ID: 924A5F6AF051E87C
4 changed files with 646 additions and 268 deletions

View File

@ -251,11 +251,12 @@ public slots:
} }
// Validate slot - called before action to update enabled/checked state // Validate slot - called before action to update enabled/checked state
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:
bool canPerformAction() const { return true; } bool canPerformAction() const { return true; }
@ -271,10 +272,10 @@ For a widget that also acts as a responder:
#include <toon_boom/ac_manager.hpp> #include <toon_boom/ac_manager.hpp>
#include <QWidget> #include <QWidget>
class MyCustomWidget : public QWidget, public AC_ResponderBase { class MyCustomWidget : public QWidget, public AC_ResponderBase {
Q_OBJECT Q_OBJECT
public: public:
MyCustomWidget(const QString& identity, AC_Manager* manager, QWidget* parent = nullptr) MyCustomWidget(const QString& identity, AC_Manager* manager, QWidget* parent = nullptr)
: QWidget(parent) : QWidget(parent)
, AC_ResponderBase(identity, manager) , AC_ResponderBase(identity, manager)
@ -283,16 +284,34 @@ public:
manager->registerResponder(this, this); manager->registerResponder(this, this);
} }
~MyCustomWidget() { ~MyCustomWidget() {
if (AC_Manager* mgr = actionManager()) { if (AC_Manager* mgr = actionManager()) {
mgr->unregisterResponder(this); mgr->unregisterResponder(this);
} }
} }
public slots: AC_Result perform(AC_ActionInfo* info) override {
void onActionWidgetAction() { if (!info) {
qDebug() << "Widget action triggered!"; 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:
void onActionWidgetAction() {
qDebug() << "Widget action triggered!";
}
void onActionWidgetActionValidate(AC_ActionInfo* info) { void onActionWidgetActionValidate(AC_ActionInfo* info) {
info->setEnabled(isEnabled()); info->setEnabled(isEnabled());
@ -328,14 +347,15 @@ public:
bool resignFirstResponder() override { return false; } bool resignFirstResponder() override { return false; }
bool acceptsSelectionResponder() override { return false; } bool acceptsSelectionResponder() override { return false; }
bool becomeSelectionResponder() override { return false; } bool becomeSelectionResponder() override { return false; }
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;
@ -360,46 +380,56 @@ 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.
```cpp **Important ABI correction (IDA-verified):** `AC_ActionInfo` is **not** a `QObject`. RTTI in `ToonBoomActionManager.dll` shows:
class AC_ActionInfo : public QObject {
// Key methods - `AC_ActionInfoImpl` derives from `AC_ActionInfo`
const QString& slot() const; // Slot signature to invoke - `AC_ActionInfo` derives from `AC_ActionData`
const QString& text() const; // Action display text
QVariant itemParameter() const; // Extra parameter from XML So, “action info” methods like `setEnabled(bool)` are actually exported as `AC_ActionData::setEnabled(bool)` and are inherited by `AC_ActionInfo`.
// State methods **Important return-value detail (IDA-verified):** action invocation/validation helpers return:
bool isEnabled() const; - `0` = handled/success
void setEnabled(bool enabled); - `1` = not handled / slot not found
bool isChecked() const;
void setChecked(bool checked); ```cpp
class AC_ActionData {
// Responder info public:
AC_Responder* responder() const; bool isValidation() const;
void setResponder(AC_Responder* resp); void setEnabled(bool enabled);
}; void setVisible(bool visible);
``` };
## Validation Pattern class AC_ActionInfo : public AC_ActionData {
// Most fields/methods are opaque; Toon Boom provides concrete impls.
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*)`
2. If found, invoke it to update enabled/checked state ## Validation Pattern
3. Display the item according to the updated state
Before displaying a menu or when the UI updates, Toon Boom calls validation methods:
Example from `TULayoutManager`:
1. For each toolbar item, Toon Boom tries to validate via a derived validate slot name (`<slotName>Validate`).
```cpp 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).
// Address: 0x7ffa0be52e60 3. If the validate slot does not exist, Toon Boom can fall back to enabling the action if the action slot exists.
void TULayoutManager::onActionFullscreenValidate(AC_ActionInfo* info) { 4. If neither validate nor action slot exists on the resolved responder, the action is disabled.
info->setVisible(true); // vtable[7]
info->setEnabled(true); // AC_ActionData::setEnabled Example from `TULayoutManager`:
}
``` ```cpp
// Address: 0x7ffa0be52e60
void TULayoutManager::onActionFullscreenValidate(AC_ActionInfo* info) {
// 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
}
```
See also: `docs/AC_Toolbar_ButtonEnablement.md:1`
## Registration with AC_Manager ## Registration with AC_Manager

View File

@ -7,22 +7,55 @@ This document explains how toolbars are created, managed, and displayed within T
- `RE/ToonBoomActionManager.dll.i64` - AC_Toolbar and AC_Manager implementations - `RE/ToonBoomActionManager.dll.i64` - AC_Toolbar and AC_Manager implementations
- `RE/ToonBoomLayout.dll.i64` - TULayoutView and TULayoutFrame implementations - `RE/ToonBoomLayout.dll.i64` - TULayoutView and TULayoutFrame implementations
## Overview ## Overview
Toon Boom uses a two-tier toolbar system: 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 views 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 │ └─────────────────────────────────────────────────────────────────┘
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────────┐
@ -55,11 +88,12 @@ Toon Boom uses a two-tier toolbar system:
During application startup, toolbars are loaded from `toolbars.xml`: 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:
@ -89,133 +123,269 @@ 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 can override `TULayoutView::toolbar()` (ToonBoomLayout.dll vtable `+0x58`) to provide a `QDomElement` describing the view-toolbar content.
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)`.
In Harmony Premium, many views inherit from `TUWidgetLayoutView` (via `VL_ViewQt`), so the `TULayoutView*` you get in `toolbar()` is an embedded subobject:
- `TUWidgetLayoutView::m_actionManager` is stored at `+0x30` (in the `TUWidgetLayoutView` object)
- the embedded `TULayoutView` base is at `+0x68`
- therefore, `AC_Manager*` is reachable from a `TULayoutView*` as `*(AC_Manager**)((char*)this - 0x38)`
**Example: Drawing toolbar override**
- 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 Booms “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`
Each view class overrides `TULayoutView::toolbar()` to return its toolbar definition: ### 4. Toolbar Display Flow
When the current tab/view changes, Toon Boom updates the frames 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 views 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 ```cpp
// Example: CameraView::toolbar() at 0x1403BC450 // Function signature (from ToonBoomLayout.dll)
QDomElement CameraView::toolbar() { TULayoutView* TULayoutManager::raiseArea(
// Get AC_Manager from embedded context (offset -56 from TULayoutView*) const QString& areaName, // e.g., "Colour", "Morphing", "Timeline"
AC_Manager* manager = *(AC_Manager**)(this - 56); TULayoutFrame* frame, // Target frame, or nullptr for auto-selection
if (manager) { bool createNew, // true = create new instance if needed
// Call AC_Manager::getToolbarElement (vtable+416 / offset 52*8) const QPoint& pos // Position for floating windows
QDomElement element; );
manager->getToolbarElement(&element, "CameraViewToolbar"); ```
return element;
} **Example usages found in HarmonyPremium.exe:**
return QDomElement(); // Empty if no manager
```cpp
// sub_14086E410: Opens the Colour palette view
void showColourView() {
TULayoutManager* mgr = getLayoutManager();
QString areaName("Colour");
QPoint pos(0, 0);
mgr->raiseArea(areaName, nullptr, true, pos);
}
// sub_1401579C0: Opens Morphing view on double-click
// (called from QAbstractItemView::mouseDoubleClickEvent handler)
void openMorphingOnDoubleClick() {
TULayoutFrame* frame = view->getLayoutFrame(widget);
TULayoutManager* mgr = frame->getLayoutManager();
QString areaName("Morphing");
QPoint pos(0, 0);
mgr->raiseArea(areaName, nullptr, true, pos);
} }
``` ```
**Known View Toolbar Implementations:** **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
| View Class | Toolbar Name | Address | ### 4.2 Ensuring Toolbar Display After raiseArea
|------------|--------------|---------|
| DrawingView | "DrawingViewToolbar" | `0x1403B9880` |
| CameraView | "CameraViewToolbar" | `0x1403BC450` |
| TimelineView | "TimelineViewToolbar" | `0x14011C5F0` |
| SGV_Graph3DView | (custom impl) | `0x1409e1866` |
### 4. Toolbar Display Flow If you need to ensure toolbars appear after calling `raiseArea`:
When a view gains focus, the toolbar is displayed through this flow: ```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
1. User clicks on a view widget TULayoutView* view = layoutManager->raiseArea("MyArea", nullptr, true, QPoint(0,0));
if (view) {
TULayoutFrame* frame = view->getLayoutFrame(view->getWidget());
2. Application event handler detects focus change if (frame) {
(sub_140059DE0 checks for TULayoutFrame inheritance) // Direct refresh paths observed in ToonBoomLayout.dll:
// - TULayoutFrame::setCurrentTab(...) calls frame->showViewToolBar()
// - TULayoutManager::showViewToolBars() calls into the current frame(s)
3. TULayoutManager::setCurrentLayoutFrame(frame) frame->showViewToolBar();
layoutManager->showViewToolBars();
}
4. TULayoutFrame::showViewToolBar() }
- Gets current view from view holder ```
- Calls view->toolbar() to get QDomElement
- Creates AC_ToolbarImpl from element ## AC_Toolbar Integration
- Adds toolbar to TULayoutMainWindow
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.
5. Toolbar becomes visible in frame's toolbar area - `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()`.
## AC_Toolbar Integration ### 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 views `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
`TULayoutFrame::showViewToolBar()` does **not** create a new `AC_Toolbar` every time focus changes.
Observed behavior (ToonBoomLayout.dll `0x7ffa0be4bb70`):
- The frame keeps a single `AC_Toolbar*` at `TULayoutFrame + 0xC0` (`m_viewToolbar`)
- On each tab/view change, it:
- calls `m_viewToolbar->setOwner(currentView->getWidget())`
- calls `m_viewToolbar->changeContent(toolbarEl, buttonConfig, buttonDefaultConfig)`
- docks `m_viewToolbar->toQToolBar()` into the frames `TULayoutMainWindow`
The `AC_Toolbar` object itself is created lazily by `TULayoutFrame::createEmptyToolBar(name)`:
- Builds an empty `<toolbar/>` element
- Calls `AC_Manager::createToolbar(emptyEl, nullptr, m_layoutManager, 4, nameLatin1, nullptr)`
- Stores the result as `m_viewToolbar`
When `TULayoutFrame::showViewToolBar()` needs to display a toolbar: ### LAY_ToolbarInfo Configuration
Each view can have stored toolbar configuration via `LAY_ToolbarInfo`:
```cpp ```cpp
// Pseudocode for toolbar creation // Structure of LAY_ToolbarInfo (~104 bytes)
void TULayoutFrame::showViewToolBar() { // Analyzed from ToonBoomLayout.dll constructors and getters
TULayoutView* view = getCurrentView();
if (!view) return;
// Get toolbar XML element from view
QDomElement toolbarElement = view->toolbar();
if (toolbarElement.isNull()) return;
// 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
Each view can have stored toolbar configuration via `LAY_ToolbarInfo`:
```cpp
// Structure of LAY_ToolbarInfo (104 bytes)
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 frames 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
@ -232,56 +402,68 @@ Create a toolbar definition that will be loaded by AC_Manager:
</toolbar> </toolbar>
``` ```
### Step 2: Override toolbar() in Your View ### Step 2: Override toolbar() in Your View
```cpp ```cpp
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
if (!manager) { // it via PLUG_Services::getActionManager()).
return QDomElement(); AC_Manager* manager = getActionManager(); // your helper
} if (!manager) {
return QDomElement();
// Use AC_Manager to get toolbar element by name }
QDomElement element;
// Call via vtable[52] - getToolbarElement return manager->toolbarElement(QString("MyCustomViewToolbar"));
auto getToolbarElement = reinterpret_cast<void(*)(AC_Manager*, QDomElement*, const QString&)>( }
(*reinterpret_cast<void***>(manager))[52] };
);
QString toolbarName("MyCustomViewToolbar");
getToolbarElement(manager, &element, toolbarName);
return element;
}
};
``` ```
### Step 3: Register Toolbar XML ### Step 3: Register Toolbar XML
Ensure your toolbar XML is loaded during initialization: Ensure your toolbar XML is loaded during initialization:
```cpp ```cpp
// Option 1: Add to existing toolbars.xml (if modifying installation) // Option 1: Add to existing toolbars.xml (if modifying installation)
// 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)
QDomDocument doc; // Note: loadToolbars expects the same structure as toolbars.xml; easiest is to wrap your <toolbar> inside a <toolbars> root.
doc.setContent(myToolbarXml); QDomDocument doc;
QDomElement element = doc.documentElement(); doc.setContent(myToolbarXml);
QDomElement element = doc.documentElement();
// Call via vtable
auto addToolbarFromElement = reinterpret_cast<void(*)(AC_Manager*, const QDomElement&, const QString&, bool)>( QList<QString> ids;
(*reinterpret_cast<void***>(manager))[47] manager->loadToolbars(element, ids);
); }
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 elements `"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
@ -307,26 +489,38 @@ if (manager) {
## Database Reference ## Database Reference
### 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();
}
}
}
```

View File

@ -80,21 +80,31 @@ Total size: **0x158 (344 bytes)** on x64
### Key Methods ### Key Methods
#### 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
#### Action Triggering #### AC_Manager Toolbar Vtable Slots (Verified in IDA)
- `trigger(responderIdentity, actionName, forEach)` - Execute action - `loadToolbars(const QDomElement&, QList<QString>&)` - slot 46 (`+0x170`) at `0x180015940`
- `performValidation(responderIdentity, actionName, enabled, checked)` - Validate action state - `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
- `trigger(responderIdentity, actionName, forEach)` - Execute action
- `performValidation(responderIdentity, actionName, enabled, checked)` - Validate action state
### Signals ### Signals
```cpp ```cpp
@ -203,11 +213,11 @@ 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
}; };
``` ```
### AC_ManagerOption ### AC_ManagerOption

View File

@ -193,11 +193,21 @@ When you call `TULayoutManager::addArea`, the following happens:
5. `addView` calls `view->getWidget()` to get the actual QWidget 5. `addView` calls `view->getWidget()` to get the actual QWidget
6. The QWidget is reparented and displayed 6. The QWidget is reparented and displayed
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.
## Opening Views at Runtime ## View Toolbars
To programmatically show a view that's already registered: 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
To programmatically show a view that's already registered:
```cpp ```cpp
// Using TULayoutManager::raiseArea // Using TULayoutManager::raiseArea