docs: add docs pertaining to the ac_responder system and view toolbars
This commit is contained in:
parent
b491e115ad
commit
044a9b60dd
529
docs/AC_Responder_System.md
Normal file
529
docs/AC_Responder_System.md
Normal file
@ -0,0 +1,529 @@
|
||||
# AC_Responder System - Toolbar Button Click Handling
|
||||
|
||||
This document explains how toolbar button clicks are routed to responders in Toon Boom Harmony Premium and Storyboard Pro, and how to create custom responders to handle actions.
|
||||
|
||||
**IDA Databases:**
|
||||
- `RE/ToonBoomActionManager.dll.i64` - AC_Manager and AC_Responder implementations
|
||||
- `RE/ToonBoomLayout.dll.i64` - Layout responder implementations
|
||||
- `RE/HarmonyPremium.exe.i64` - Application-specific responders
|
||||
|
||||
## Overview
|
||||
|
||||
Toon Boom uses a **Responder Chain** pattern (similar to macOS/Cocoa) for handling toolbar button clicks:
|
||||
|
||||
1. **Toolbar XML** defines button items with a `responder` attribute and a `slot` attribute
|
||||
2. When clicked, `AC_Manager` looks up the responder by name
|
||||
3. The responder's slot is invoked via Qt's meta-object system
|
||||
4. If the responder doesn't handle it, the action propagates up the chain
|
||||
|
||||
## Toolbar XML Structure
|
||||
|
||||
From `toolbars.xml`:
|
||||
|
||||
```xml
|
||||
<toolbar id="DrawingToolToolbar" text="Tools">
|
||||
<!-- Simple item with responder and slot -->
|
||||
<item
|
||||
checkable="true"
|
||||
icon="drawingtool/select.png"
|
||||
id="SelectTool"
|
||||
responder="sceneUI"
|
||||
slot="onActionChooseSelectToolInNormalMode()"
|
||||
text="Select" />
|
||||
|
||||
<!-- Item with owner responder (view that owns the toolbar) -->
|
||||
<item
|
||||
icon="library/refresh.png"
|
||||
id="LIBRARY_REFRESH"
|
||||
responder="owner"
|
||||
slot="onActionFolderRefresh()"
|
||||
text="Refresh" />
|
||||
|
||||
<!-- Item with script execution -->
|
||||
<item
|
||||
icon="script/createkeyframeson.png"
|
||||
id="CREATE_KEYFRAMES_ON"
|
||||
itemParameter="TB_CreateKeyFramesOn in TB_CreateKeyFramesOn.js"
|
||||
responder="scriptResponder"
|
||||
slot="onActionExecuteScript(QString)"
|
||||
text="Create Keyframes On" />
|
||||
</toolbar>
|
||||
```
|
||||
|
||||
### Key XML Attributes
|
||||
|
||||
| Attribute | Description |
|
||||
|-----------|-------------|
|
||||
| `id` | Unique identifier for the toolbar item |
|
||||
| `responder` | Name of the responder to handle clicks |
|
||||
| `slot` | Qt slot signature to invoke |
|
||||
| `icon` | Path to icon image |
|
||||
| `text` | Display text / tooltip |
|
||||
| `checkable` | If "true", button toggles on/off |
|
||||
| `condition` | Expression for conditional visibility |
|
||||
| `itemParameter` | Extra parameter passed to slot |
|
||||
| `shortcut` | Keyboard shortcut name |
|
||||
|
||||
### Common Responder Names
|
||||
|
||||
| Responder | Description |
|
||||
|-----------|-------------|
|
||||
| `owner` | The QObject that "owns" the toolbar (usually the view) |
|
||||
| `sceneUI` | Main scene UI responder (Harmony_SceneUI / SBoard_SceneUI) |
|
||||
| `scene` | Scene data responder |
|
||||
| `selection` | Selection responder (for cut/copy/paste) |
|
||||
| `timelineView` | Timeline view responder |
|
||||
| `xsheetView` | Xsheet view responder |
|
||||
| `onionSkinResponder` | Onion skin feature responder |
|
||||
| `scriptResponder` | Script execution responder |
|
||||
| `artLayerResponder` | Art layer selection responder |
|
||||
|
||||
## The Responder Chain
|
||||
|
||||
When a toolbar button is clicked:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 1. User clicks toolbar button │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 2. AC_ToolbarItemImpl triggers AC_ActionInfo │
|
||||
│ - Creates AC_ActionInfo with slot name, parameters │
|
||||
│ - Gets responder name from XML attribute │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 3. AC_Manager::responder(name) looks up responder │
|
||||
│ - "owner" → toolbar's owner QObject cast to AC_Responder │
|
||||
│ - other names → registered responder by identity │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 4. AC_Responder::perform(AC_ActionInfo*) is called │
|
||||
│ - Sets responder in action info │
|
||||
│ - Invokes slot via QMetaObject::invokeMethod │
|
||||
│ - Returns AC_Result (Handled, NotHandled, Error) │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 5. If NotHandled, propagate up chain │
|
||||
│ - Try parent responder via parentResponder() │
|
||||
│ - Eventually reaches applicationResponder │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## AC_Responder Interface
|
||||
|
||||
The `AC_Responder` abstract interface defines how objects participate in the responder chain:
|
||||
|
||||
```cpp
|
||||
class AC_Responder {
|
||||
public:
|
||||
virtual ~AC_Responder() = 0;
|
||||
|
||||
// Identity
|
||||
virtual const QString& responderIdentity() const = 0;
|
||||
virtual const QString& responderDescription() const = 0;
|
||||
virtual void setResponderDescription(const QString& desc) = 0;
|
||||
|
||||
// Chain navigation
|
||||
virtual AC_Responder* parentResponder() = 0;
|
||||
virtual AC_Responder* proxyResponder() = 0;
|
||||
|
||||
// First responder status (keyboard focus)
|
||||
virtual bool acceptsFirstResponder() = 0;
|
||||
virtual bool becomeFirstResponder() = 0;
|
||||
virtual bool resignFirstResponder() = 0;
|
||||
|
||||
// Selection responder status
|
||||
virtual bool acceptsSelectionResponder() = 0;
|
||||
virtual bool becomeSelectionResponder() = 0;
|
||||
virtual bool resignSelectionResponder() = 0;
|
||||
|
||||
// Action handling
|
||||
virtual AC_Result perform(AC_ActionInfo* info) = 0;
|
||||
virtual AC_Result performDownToChildren(AC_ActionInfo* info) = 0;
|
||||
virtual bool shouldReceiveMessages() const = 0;
|
||||
virtual bool handleShortcuts() const = 0;
|
||||
|
||||
// Event handling
|
||||
virtual AC_Result handleEvent(QEvent* event) = 0;
|
||||
|
||||
// Manager access
|
||||
virtual AC_Manager* actionManager() const = 0;
|
||||
};
|
||||
```
|
||||
|
||||
## AC_ResponderBase Helper Class
|
||||
|
||||
The framework provides `AC_ResponderBase`, a concrete helper class that implements the `AC_Responder` interface with sensible defaults. This makes it easy to create custom responders without implementing every virtual method:
|
||||
|
||||
```cpp
|
||||
// From ac_manager.hpp
|
||||
class AC_ResponderBase : public AC_Responder {
|
||||
public:
|
||||
AC_ResponderBase(const QString& identity, AC_Manager* manager = nullptr,
|
||||
AC_Responder* parent = nullptr);
|
||||
virtual ~AC_ResponderBase() = default;
|
||||
|
||||
// Identity - stored internally
|
||||
const QString& responderIdentity() const override;
|
||||
const QString& responderDescription() const override;
|
||||
void setResponderDescription(const QString& desc) override;
|
||||
|
||||
// Chain - returns parent passed to constructor
|
||||
AC_Responder* parentResponder() override;
|
||||
AC_Responder* proxyResponder() override; // returns nullptr
|
||||
|
||||
// First responder - all return false by default
|
||||
bool acceptsFirstResponder() override;
|
||||
bool becomeFirstResponder() override;
|
||||
bool resignFirstResponder() override;
|
||||
|
||||
// Selection responder - all return false by default
|
||||
bool acceptsSelectionResponder() override;
|
||||
bool becomeSelectionResponder() override;
|
||||
bool resignSelectionResponder() override;
|
||||
|
||||
// Action handling - return NotHandled by default
|
||||
AC_Result perform(AC_ActionInfo* info) override;
|
||||
AC_Result performDownToChildren(AC_ActionInfo* info) override;
|
||||
|
||||
// Message handling - return true by default
|
||||
bool shouldReceiveMessages() const override;
|
||||
bool handleShortcuts() const override;
|
||||
|
||||
// Event handling - returns NotHandled
|
||||
AC_Result handleEvent(QEvent* event) override;
|
||||
|
||||
// Manager access
|
||||
AC_Manager* actionManager() const override;
|
||||
void setActionManager(AC_Manager* manager);
|
||||
|
||||
protected:
|
||||
QString m_identity;
|
||||
QString m_description;
|
||||
AC_Manager* m_manager;
|
||||
AC_Responder* m_parentResponder;
|
||||
};
|
||||
```
|
||||
|
||||
## Creating a Custom Responder
|
||||
|
||||
### Method 1: Using AC_ResponderBase with QObject
|
||||
|
||||
The simplest approach is to create a class that inherits from both `QObject` (for Qt slots) and `AC_ResponderBase` (for responder functionality):
|
||||
|
||||
```cpp
|
||||
#include <toon_boom/ac_manager.hpp>
|
||||
|
||||
class MyResponder : public QObject, public AC_ResponderBase {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MyResponder(const QString& identity, AC_Manager* manager, QObject* parent = nullptr)
|
||||
: QObject(parent)
|
||||
, AC_ResponderBase(identity, manager)
|
||||
{
|
||||
// Register with AC_Manager
|
||||
manager->registerResponder(this, nullptr);
|
||||
}
|
||||
|
||||
~MyResponder() {
|
||||
if (AC_Manager* mgr = actionManager()) {
|
||||
mgr->unregisterResponder(this);
|
||||
}
|
||||
}
|
||||
|
||||
public slots:
|
||||
// Slot that matches toolbar XML slot signature
|
||||
void onActionMyCustomAction() {
|
||||
qDebug() << "My custom action triggered!";
|
||||
}
|
||||
|
||||
void onActionWithParameter(const QString& param) {
|
||||
qDebug() << "Action with parameter:" << param;
|
||||
}
|
||||
|
||||
// Validate slot - called before action to update enabled/checked state
|
||||
void onActionMyCustomActionValidate(AC_ActionInfo* info) {
|
||||
// Enable based on some condition
|
||||
info->setEnabled(canPerformAction());
|
||||
info->setChecked(isActionActive());
|
||||
}
|
||||
|
||||
private:
|
||||
bool canPerformAction() const { return true; }
|
||||
bool isActionActive() const { return false; }
|
||||
};
|
||||
```
|
||||
|
||||
### Method 2: Widget-Based Responder
|
||||
|
||||
For a widget that also acts as a responder:
|
||||
|
||||
```cpp
|
||||
#include <toon_boom/ac_manager.hpp>
|
||||
#include <QWidget>
|
||||
|
||||
class MyCustomWidget : public QWidget, public AC_ResponderBase {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MyCustomWidget(const QString& identity, AC_Manager* manager, QWidget* parent = nullptr)
|
||||
: QWidget(parent)
|
||||
, AC_ResponderBase(identity, manager)
|
||||
{
|
||||
// Register with AC_Manager, passing 'this' as the associated widget
|
||||
manager->registerResponder(this, this);
|
||||
}
|
||||
|
||||
~MyCustomWidget() {
|
||||
if (AC_Manager* mgr = actionManager()) {
|
||||
mgr->unregisterResponder(this);
|
||||
}
|
||||
}
|
||||
|
||||
public slots:
|
||||
void onActionWidgetAction() {
|
||||
qDebug() << "Widget action triggered!";
|
||||
}
|
||||
|
||||
void onActionWidgetActionValidate(AC_ActionInfo* info) {
|
||||
info->setEnabled(isEnabled());
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Method 3: Implementing AC_Responder Directly
|
||||
|
||||
For full control over the responder interface:
|
||||
|
||||
```cpp
|
||||
class MyFullResponder : public QObject, public AC_Responder {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MyFullResponder(const QString& identity, AC_Manager* manager)
|
||||
: m_identity(identity), m_manager(manager)
|
||||
{
|
||||
manager->registerResponder(this, nullptr);
|
||||
}
|
||||
|
||||
// AC_Responder interface - implement all methods
|
||||
const QString& responderIdentity() const override { return m_identity; }
|
||||
const QString& responderDescription() const override { return m_description; }
|
||||
void setResponderDescription(const QString& desc) override { m_description = desc; }
|
||||
|
||||
AC_Responder* parentResponder() override { return nullptr; }
|
||||
AC_Responder* proxyResponder() override { return nullptr; }
|
||||
|
||||
bool acceptsFirstResponder() override { return false; }
|
||||
bool becomeFirstResponder() override { return false; }
|
||||
bool resignFirstResponder() override { return false; }
|
||||
|
||||
bool acceptsSelectionResponder() override { return false; }
|
||||
bool becomeSelectionResponder() override { return false; }
|
||||
bool resignSelectionResponder() override { return false; }
|
||||
|
||||
AC_Result perform(AC_ActionInfo* info) override {
|
||||
// Use Qt meta-object to invoke the slot by name
|
||||
// AC_ActionInfo contains slot name and parameters
|
||||
return AC_Result::NotHandled;
|
||||
}
|
||||
|
||||
AC_Result performDownToChildren(AC_ActionInfo* info) override {
|
||||
return AC_Result::NotHandled;
|
||||
}
|
||||
|
||||
bool shouldReceiveMessages() const override { return true; }
|
||||
bool handleShortcuts() const override { return true; }
|
||||
|
||||
AC_Result handleEvent(QEvent*) override { return AC_Result::NotHandled; }
|
||||
|
||||
AC_Manager* actionManager() const override { return m_manager; }
|
||||
|
||||
public slots:
|
||||
void onActionDoSomething() {
|
||||
// Handle the action
|
||||
}
|
||||
|
||||
private:
|
||||
QString m_identity;
|
||||
QString m_description;
|
||||
AC_Manager* m_manager;
|
||||
};
|
||||
```
|
||||
|
||||
## AC_ActionInfo Structure
|
||||
|
||||
When an action is triggered, an `AC_ActionInfo` object is created:
|
||||
|
||||
```cpp
|
||||
class AC_ActionInfo : public QObject {
|
||||
// Key methods
|
||||
const QString& slot() const; // Slot signature to invoke
|
||||
const QString& text() const; // Action display text
|
||||
QVariant itemParameter() const; // Extra parameter from XML
|
||||
|
||||
// State methods
|
||||
bool isEnabled() const;
|
||||
void setEnabled(bool enabled);
|
||||
bool isChecked() const;
|
||||
void setChecked(bool checked);
|
||||
|
||||
// Responder info
|
||||
AC_Responder* responder() const;
|
||||
void setResponder(AC_Responder* resp);
|
||||
};
|
||||
```
|
||||
|
||||
## Validation Pattern
|
||||
|
||||
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
|
||||
3. Display the item according to the updated state
|
||||
|
||||
Example from `TULayoutManager`:
|
||||
|
||||
```cpp
|
||||
// Address: 0x7ffa0be52e60
|
||||
void TULayoutManager::onActionFullscreenValidate(AC_ActionInfo* info) {
|
||||
info->setVisible(true); // vtable[7]
|
||||
info->setEnabled(true); // AC_ActionData::setEnabled
|
||||
}
|
||||
```
|
||||
|
||||
## Registration with AC_Manager
|
||||
|
||||
Responders must be registered to be found by name:
|
||||
|
||||
```cpp
|
||||
AC_Manager* manager = PLUG_Services::getActionManager();
|
||||
|
||||
// Register a responder
|
||||
bool success = manager->registerResponder(myResponder, myWidget);
|
||||
|
||||
// Unregister when done
|
||||
manager->unregisterResponder(myResponder);
|
||||
|
||||
// Find a responder by identity
|
||||
AC_Responder* resp = manager->responder("myResponderIdentity");
|
||||
|
||||
// Get responder for a widget
|
||||
AC_Responder* widgetResp = manager->responderForWidget(someWidget);
|
||||
```
|
||||
|
||||
## Example: Adding a Custom Toolbar Button
|
||||
|
||||
### Step 1: Define in XML (or load programmatically)
|
||||
|
||||
```xml
|
||||
<toolbar id="MyToolbar" text="My Tools">
|
||||
<item
|
||||
id="MyAction"
|
||||
icon="my/icon.png"
|
||||
responder="myCustomResponder"
|
||||
slot="onActionMyAction()"
|
||||
text="My Action" />
|
||||
</toolbar>
|
||||
```
|
||||
|
||||
### Step 2: Create and Register Responder
|
||||
|
||||
```cpp
|
||||
class MyToolResponder : public QObject, public AC_ResponderBase {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MyToolResponder(AC_Manager* manager)
|
||||
: QObject()
|
||||
, AC_ResponderBase("myCustomResponder", manager)
|
||||
{
|
||||
manager->registerResponder(this, nullptr);
|
||||
}
|
||||
|
||||
~MyToolResponder() {
|
||||
if (AC_Manager* mgr = actionManager()) {
|
||||
mgr->unregisterResponder(this);
|
||||
}
|
||||
}
|
||||
|
||||
public slots:
|
||||
void onActionMyAction() {
|
||||
qDebug() << "My custom toolbar button clicked!";
|
||||
// Do your custom action here
|
||||
}
|
||||
|
||||
void onActionMyActionValidate(AC_ActionInfo* info) {
|
||||
info->setEnabled(true);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Step 3: Initialize
|
||||
|
||||
```cpp
|
||||
void initializeMyToolbar() {
|
||||
AC_Manager* manager = PLUG_Services::getActionManager();
|
||||
|
||||
// Create and register responder
|
||||
MyToolResponder* responder = new MyToolResponder(manager);
|
||||
|
||||
// Load toolbar XML
|
||||
QDomDocument doc;
|
||||
doc.setContent(myToolbarXmlString);
|
||||
QList<QString> ids;
|
||||
manager->loadToolbars(doc.documentElement(), ids);
|
||||
|
||||
// Show the toolbar
|
||||
TULayoutManager* layoutManager = getLayoutManager();
|
||||
layoutManager->showToolbar("MyToolbar", true);
|
||||
}
|
||||
```
|
||||
|
||||
## Owner Responder Pattern
|
||||
|
||||
When `responder="owner"` is used, the toolbar's owner QObject is used:
|
||||
|
||||
```cpp
|
||||
// In AC_ToolbarImpl, owner is stored at offset +0xE0
|
||||
void AC_ToolbarImpl::setOwner(QObject* owner) {
|
||||
m_owner = owner;
|
||||
connect(owner, &QObject::destroyed, this, &AC_ToolbarImpl::ownerDestroyed);
|
||||
}
|
||||
|
||||
// When resolving "owner" responder:
|
||||
if (responderName == "owner") {
|
||||
QObject* owner = toolbar->owner();
|
||||
return qobject_cast<AC_Responder*>(owner);
|
||||
}
|
||||
```
|
||||
|
||||
This pattern is commonly used for view-specific toolbars where the view itself handles the actions.
|
||||
|
||||
## Key Addresses
|
||||
|
||||
### ToonBoomActionManager.dll
|
||||
| Symbol | Address | Description |
|
||||
|--------|---------|-------------|
|
||||
| AC_ManagerImpl::registerResponder | (via vtable) | Registers responder |
|
||||
| AC_ManagerImpl::unregisterResponder | (via vtable) | Unregisters responder |
|
||||
| AC_ManagerImpl::responder | (via vtable) | Finds responder by name |
|
||||
|
||||
## Summary
|
||||
|
||||
To respond to toolbar button clicks:
|
||||
|
||||
1. **Create a class** that inherits from `QObject` and `AC_ResponderBase` (or implement `AC_Responder` directly)
|
||||
2. **Register** with `AC_Manager::registerResponder(responder, widget)`
|
||||
3. **Define slots** matching the toolbar XML `slot` attribute signatures
|
||||
4. **Optionally define validate slots** (`<slotName>Validate(AC_ActionInfo*)`) for state updates
|
||||
5. **Reference your responder** by identity in the toolbar XML `responder` attribute
|
||||
356
docs/TULayoutView_Toolbar_Integration.md
Normal file
356
docs/TULayoutView_Toolbar_Integration.md
Normal file
@ -0,0 +1,356 @@
|
||||
# TULayoutView Toolbar Integration in Toon Boom
|
||||
|
||||
This document explains how toolbars are created, managed, and displayed within TULayoutView instances in Toon Boom Harmony Premium and Storyboard Pro.
|
||||
|
||||
**IDA Databases:**
|
||||
- `RE/HarmonyPremium.exe.i64` - Main application
|
||||
- `RE/ToonBoomActionManager.dll.i64` - AC_Toolbar and AC_Manager implementations
|
||||
- `RE/ToonBoomLayout.dll.i64` - TULayoutView and TULayoutFrame implementations
|
||||
|
||||
## Overview
|
||||
|
||||
Toon Boom uses a two-tier toolbar system:
|
||||
|
||||
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.)
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ TULayoutManager │
|
||||
│ - Manages all frames, areas, and global toolbars │
|
||||
│ - Holds reference to AC_Manager at offset +344 │
|
||||
│ - showToolbar() at vtable+416 creates/shows toolbars by name │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ TULayoutFrame │
|
||||
│ - Contains TULayoutMainWindow for toolbar docking │
|
||||
│ - showViewToolBar() displays view-specific toolbar │
|
||||
│ - Stores LAY_ToolbarInfo per view for configuration │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ TULayoutViewHolder │
|
||||
│ - Contains 1-2 TULayoutView instances │
|
||||
│ - Notifies frame when active view changes │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ TULayoutView │
|
||||
│ - Abstract base class for all views │
|
||||
│ - toolbar() virtual method returns QDomElement │
|
||||
│ - setToolbarInfo() receives LAY_ToolbarInfo │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Toolbar Definition and Creation Flow
|
||||
|
||||
### 1. Toolbar XML Loading
|
||||
|
||||
During application startup, toolbars are loaded from `toolbars.xml`:
|
||||
|
||||
```cpp
|
||||
// From HarmonyPremium.exe session initialization (0x140034EB0)
|
||||
// Loads toolbars.xml via AC_Manager::addToolbarFromFile (vtable+376)
|
||||
|
||||
UT_String toolbarsPath = RM_GetResourcePath("toolbars.xml");
|
||||
actionManager->addToolbarFromFile(toolbarsPath.ToQString(), &toolbarList);
|
||||
```
|
||||
|
||||
The `toolbars.xml` file defines toolbar content using XML elements like:
|
||||
```xml
|
||||
<toolbar id="DrawingViewToolbar" trContext="Toolbars">
|
||||
<item id="DrawingTool.Select" />
|
||||
<separator />
|
||||
<item id="DrawingTool.Brush" />
|
||||
<!-- ... more items ... -->
|
||||
</toolbar>
|
||||
```
|
||||
|
||||
### 2. Global Toolbar Initialization
|
||||
|
||||
Global toolbars are shown during session initialization (`sub_14002F840`):
|
||||
|
||||
```cpp
|
||||
// Show global toolbars via TULayoutManager::showToolbar (vtable+416)
|
||||
layoutManager->showToolbar("FileToolbar", true);
|
||||
layoutManager->showToolbar("EditToolbar", true);
|
||||
layoutManager->showToolbar("DrawingToolToolbar", true);
|
||||
layoutManager->showToolbar("ArtLayerToolbar", true);
|
||||
|
||||
// Conditional toolbars based on features
|
||||
if (WHO_Features::hasOnionSkinToolbar()) {
|
||||
layoutManager->showToolbar("OnionSkinToolbar", false, true, nullptr);
|
||||
}
|
||||
```
|
||||
|
||||
**Key Function Addresses (HarmonyPremium.exe):**
|
||||
- Session toolbar init: `0x14002F840`
|
||||
- TULayoutManager::showToolbar import: `0x1409c8ea0`
|
||||
|
||||
### 3. View-Specific Toolbar Definition
|
||||
|
||||
Each view class overrides `TULayoutView::toolbar()` to return its toolbar definition:
|
||||
|
||||
```cpp
|
||||
// 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:**
|
||||
|
||||
| View Class | Toolbar Name | Address |
|
||||
|------------|--------------|---------|
|
||||
| DrawingView | "DrawingViewToolbar" | `0x1403B9880` |
|
||||
| CameraView | "CameraViewToolbar" | `0x1403BC450` |
|
||||
| TimelineView | "TimelineViewToolbar" | `0x14011C5F0` |
|
||||
| SGV_Graph3DView | (custom impl) | `0x1409e1866` |
|
||||
|
||||
### 4. Toolbar Display Flow
|
||||
|
||||
When a view gains focus, the toolbar is displayed through this flow:
|
||||
|
||||
```
|
||||
1. User clicks on a view widget
|
||||
│
|
||||
▼
|
||||
2. Application event handler detects focus change
|
||||
(sub_140059DE0 checks for TULayoutFrame inheritance)
|
||||
│
|
||||
▼
|
||||
3. TULayoutManager::setCurrentLayoutFrame(frame)
|
||||
│
|
||||
▼
|
||||
4. TULayoutFrame::showViewToolBar()
|
||||
- Gets current view from view holder
|
||||
- Calls view->toolbar() to get QDomElement
|
||||
- Creates AC_ToolbarImpl from element
|
||||
- Adds toolbar to TULayoutMainWindow
|
||||
│
|
||||
▼
|
||||
5. Toolbar becomes visible in frame's toolbar area
|
||||
```
|
||||
|
||||
## AC_Toolbar Integration
|
||||
|
||||
### AC_Manager Methods Used
|
||||
|
||||
The AC_Manager provides these key toolbar methods:
|
||||
|
||||
| Method | Vtable Offset | Description |
|
||||
|--------|---------------|-------------|
|
||||
| `getToolbarElement` | +416 (52*8) | Returns QDomElement for toolbar by name |
|
||||
| `addToolbarFromFile` | +376 (47*8) | Loads toolbar definitions from XML file |
|
||||
| `createToolbar` | +368 (46*8) | Creates AC_ToolbarImpl from element |
|
||||
|
||||
### AC_ToolbarImpl Creation
|
||||
|
||||
When `TULayoutFrame::showViewToolBar()` needs to display a toolbar:
|
||||
|
||||
```cpp
|
||||
// Pseudocode for toolbar creation
|
||||
void TULayoutFrame::showViewToolBar() {
|
||||
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 {
|
||||
int m_x; // +0x00 - X position
|
||||
int m_y; // +0x04 - Y position
|
||||
int m_index; // +0x08 - Toolbar index
|
||||
int m_width; // +0x0C - Width
|
||||
int m_height; // +0x10 - Height
|
||||
bool m_newline; // +0x14 - Break to new line
|
||||
bool m_visible; // +0x15 - Visibility
|
||||
bool m_isDefault; // +0x16 - Using default config
|
||||
QString m_name; // +0x18 - Toolbar name
|
||||
Qt::Orientation m_orientation; // +0x30 - Horizontal/Vertical
|
||||
Qt::ToolBarArea m_toolBarArea; // +0x34 - Docking area
|
||||
QList<QString> m_buttonConfig; // +0x38 - Current button order
|
||||
QList<QString> m_buttonDefaultConfig; // +0x50 - Default button order
|
||||
};
|
||||
```
|
||||
|
||||
The view receives its toolbar info via `TULayoutView::setToolbarInfo()`:
|
||||
- Import: `0x1409c8f4e` (thunk to ToonBoomLayout.dll)
|
||||
- Called by TULayoutFrame when loading saved layout preferences
|
||||
|
||||
## Implementing Custom View Toolbars
|
||||
|
||||
### Step 1: Define Toolbar in XML
|
||||
|
||||
Create a toolbar definition that will be loaded by AC_Manager:
|
||||
|
||||
```xml
|
||||
<toolbar id="MyCustomViewToolbar" trContext="MyToolbars">
|
||||
<item id="MyResponder.Action1" />
|
||||
<item id="MyResponder.Action2" />
|
||||
<separator />
|
||||
<placeholder id="CustomItems" />
|
||||
</toolbar>
|
||||
```
|
||||
|
||||
### Step 2: Override toolbar() in Your View
|
||||
|
||||
```cpp
|
||||
class MyCustomView : public TULayoutView {
|
||||
public:
|
||||
QDomElement toolbar() override {
|
||||
// Get AC_Manager - depends on your view's structure
|
||||
// For TUWidgetLayoutView subclasses, manager is at offset -56
|
||||
AC_Manager* manager = getActionManager();
|
||||
if (!manager) {
|
||||
return QDomElement();
|
||||
}
|
||||
|
||||
// Use AC_Manager to get toolbar element by name
|
||||
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;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Step 3: Register Toolbar XML
|
||||
|
||||
Ensure your toolbar XML is loaded during initialization:
|
||||
|
||||
```cpp
|
||||
// Option 1: Add to existing toolbars.xml (if modifying installation)
|
||||
|
||||
// Option 2: Load programmatically
|
||||
AC_Manager* manager = PLUG_Services::getActionManager();
|
||||
if (manager) {
|
||||
// Use addToolbarFromElement (vtable+376)
|
||||
QDomDocument doc;
|
||||
doc.setContent(myToolbarXml);
|
||||
QDomElement element = doc.documentElement();
|
||||
|
||||
// Call via vtable
|
||||
auto addToolbarFromElement = reinterpret_cast<void(*)(AC_Manager*, const QDomElement&, const QString&, bool)>(
|
||||
(*reinterpret_cast<void***>(manager))[47]
|
||||
);
|
||||
addToolbarFromElement(manager, element, "MyCustomViewToolbar", false);
|
||||
}
|
||||
```
|
||||
|
||||
## Key Memory Offsets
|
||||
|
||||
### TULayoutView (from TULayoutView*)
|
||||
- `+0x00`: vptr
|
||||
- `+0x08`: QString m_internalName
|
||||
- `+0x20`: LAY_ToolbarInfo m_toolbarInfo (104 bytes)
|
||||
- `+0x88`: AC_Menu* m_menuByType[2]
|
||||
- `+0xA0`: QString m_caption
|
||||
|
||||
### TULayoutFrame (from TULayoutFrame*)
|
||||
- `+0x28`: TULayoutManager* m_layoutManager
|
||||
- `+0x48`: TULayoutMainWindow* m_mainWindow (toolbar host)
|
||||
- `+0x90`: AC_Toolbar* m_toolbar
|
||||
- `+0xC0`: AC_Toolbar* m_viewToolbar
|
||||
|
||||
### AC_ToolbarImpl (from QToolBar base)
|
||||
- `+0x28`: AC_ContainerImpl m_container (88 bytes)
|
||||
- `+0x80`: AC_Toolbar vptr
|
||||
- `+0x98`: QString m_responderIdentity
|
||||
- `+0xC8`: bool m_isCustomizable
|
||||
- `+0xE0`: QObject* m_owner
|
||||
|
||||
## Database Reference
|
||||
|
||||
### HarmonyPremium.exe
|
||||
| Symbol | Address | Description |
|
||||
|--------|---------|-------------|
|
||||
| Session toolbar init | `0x14002F840` | Initializes all global toolbars |
|
||||
| DrawingView::toolbar | `0x1403B9880` | Returns DrawingViewToolbar element |
|
||||
| CameraView::toolbar | `0x1403BC450` | Returns CameraViewToolbar element |
|
||||
| TimelineView::toolbar | `0x14011C5F0` | Returns TimelineViewToolbar element |
|
||||
| TULayoutView::setToolbarInfo import | `0x1409c8f4e` | Sets toolbar configuration |
|
||||
| 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
|
||||
| Symbol | Address | Description |
|
||||
|--------|---------|-------------|
|
||||
| AC_ToolbarImpl::AC_ToolbarImpl | `0x180032df0` | Constructor |
|
||||
| AC_ToolbarImpl::~AC_ToolbarImpl | `0x180033080` | Destructor |
|
||||
| AC_ToolbarImpl::create | `0x180033910` | Creates toolbar from XML |
|
||||
| AC_ToolbarImpl::insert | `0x1800345e0` | Inserts item at position |
|
||||
| AC_ToolbarImpl vtable | `0x180054eb0` | Main vtable |
|
||||
|
||||
## Toolbar Names Reference
|
||||
|
||||
### Global Toolbars
|
||||
- `FileToolbar` - File operations
|
||||
- `EditToolbar` - Edit operations
|
||||
- `DrawingToolToolbar` - Drawing tools
|
||||
- `ArtLayerToolbar` - Art layer selection
|
||||
- `SceneplanningToolToolbar` - Scene planning tools
|
||||
- `OnionSkinToolbar` - Onion skin controls
|
||||
- `FlipToolbar` - Flip/mirror tools
|
||||
- `GameToolbar` - Game export tools
|
||||
- `AlignmentGuidesToolbar` - Alignment guides
|
||||
- `MarkDrawingToolBar` - Mark drawing tools
|
||||
|
||||
### View-Specific Toolbars
|
||||
- `DrawingViewToolbar` - Drawing view
|
||||
- `CameraViewToolbar` - Camera view
|
||||
- `TimelineViewToolbar` - Timeline view
|
||||
- `XsheetViewToolbar` - Xsheet view
|
||||
- `NetworkViewToolbar` - Network/Node view
|
||||
- `FunctionViewToolbar` - Function curve view
|
||||
- `PlaybackViewToolbar` - Playback view
|
||||
- `ModelViewToolbar` - Model view
|
||||
- `LibraryViewToolbar` - Library view
|
||||
- `ModuleLibraryViewToolbar` - Module library view
|
||||
- `FreeViewToolbar` - Free-form view
|
||||
263
docs/TUWidgetLayoutView_Analysis.md
Normal file
263
docs/TUWidgetLayoutView_Analysis.md
Normal file
@ -0,0 +1,263 @@
|
||||
# TUWidgetLayoutView Class Analysis
|
||||
|
||||
This document contains the reverse engineering analysis of the `TUWidgetLayoutView` class from `ToonBoomLayout.dll` used in Toon Boom Harmony Premium and Storyboard Pro.
|
||||
|
||||
## Overview
|
||||
|
||||
`TUWidgetLayoutView` is a concrete implementation that combines:
|
||||
- **QWidget** - for Qt UI rendering
|
||||
- **AC_ResponderTemplateWidget<QWidget>** - for action/responder chain handling
|
||||
- **TULayoutView** - for the Toon Boom layout system integration
|
||||
|
||||
This class serves as the base for most view types in Toon Boom's panel/window system.
|
||||
|
||||
## Class Hierarchy
|
||||
|
||||
```
|
||||
QObject
|
||||
└── QWidget
|
||||
└── AC_ResponderTemplateWidget<QWidget>
|
||||
└── TUWidgetLayoutView
|
||||
└── [TULayoutView embedded at offset +104]
|
||||
```
|
||||
|
||||
Note: `TULayoutView` is not a base class in the C++ inheritance sense but is **embedded** as a sub-object at offset +104. This is a form of composition that allows `TULayoutView` to have its own vtable and virtual methods.
|
||||
|
||||
## Memory Layout (x64 MSVC)
|
||||
|
||||
| Offset (hex) | Offset (dec) | Size | Member |
|
||||
|--------------|--------------|------|--------|
|
||||
| +0x00 | +0 | 8 | vptr (QObject) |
|
||||
| +0x08 | +8 | 8 | QObjectData* d_ptr |
|
||||
| +0x10 | +16 | 8 | vptr (QPaintDevice) |
|
||||
| +0x18 | +24 | 16 | QWidget internal data |
|
||||
| +0x28 | +40 | 8 | vptr (AC_ResponderTemplateWidget<QWidget>) |
|
||||
| +0x30 | +48 | 8 | AC_Manager* m_actionManager |
|
||||
| +0x38 | +56 | 24 | QString m_responderIdentity |
|
||||
| +0x50 | +80 | 24 | QString m_responderDescription |
|
||||
| **+0x68** | **+104** | 8 | **vptr (TULayoutView)** - TULayoutView starts here |
|
||||
| +0x70 | +112 | 24 | QString m_internalName (TULayoutView) |
|
||||
| +0x88 | +136 | 104 | LAY_ToolbarInfo m_toolbarInfo (TULayoutView) |
|
||||
| +0xF0 | +240 | 16 | AC_Menu* m_menuByType[2] (TULayoutView) |
|
||||
| +0x100 | +256 | 1+7 | bool m_initializedFromCopy + padding (TULayoutView) |
|
||||
| +0x108 | +264 | 24 | QString m_caption (TULayoutView) |
|
||||
|
||||
**Total size: 0x120 (288 bytes)**
|
||||
|
||||
## VTable Structure
|
||||
|
||||
TUWidgetLayoutView has **4 vtables** due to multiple inheritance:
|
||||
|
||||
### vptr[0] at +0x00: QObject vtable
|
||||
Contains all QObject and QWidget virtual methods including:
|
||||
- `metaObject()`, `qt_metacast()`, `qt_metacall()`
|
||||
- `event()`, `eventFilter()`
|
||||
- `paintEvent()`, `mousePressEvent()`, etc.
|
||||
|
||||
Address in ToonBoomLayout.dll: `??_7TUWidgetLayoutView@@6BQObject@@@`
|
||||
|
||||
### vptr[1] at +0x10: QPaintDevice vtable
|
||||
Contains QPaintDevice virtuals:
|
||||
- `devType()`
|
||||
- `paintEngine()`
|
||||
- `metric()`
|
||||
|
||||
Address in ToonBoomLayout.dll: `??_7TUWidgetLayoutView@@6BQPaintDevice@@@`
|
||||
|
||||
### vptr[2] at +0x28: AC_ResponderTemplateWidget<QWidget> vtable
|
||||
Contains AC_Responder interface methods:
|
||||
- `perform(AC_ActionInfo*)`
|
||||
- `performDownToChildren(AC_ActionInfo*)`
|
||||
- `parentResponder()`
|
||||
- `proxyResponder()`
|
||||
- `acceptsFirstResponder()`
|
||||
- `becomeFirstResponder()`
|
||||
- `resignFirstResponder()`
|
||||
- `handleShortcuts()`
|
||||
- `shouldReceiveMessages()`
|
||||
- `responderIdentity()`
|
||||
- `responderDescription()`
|
||||
- `actionManager()`
|
||||
- `handleEvent(QEvent*)`
|
||||
|
||||
Address in ToonBoomLayout.dll: `??_7TUWidgetLayoutView@@6B?$AC_ResponderTemplateWidget@VQWidget@@@@@`
|
||||
|
||||
### vptr[3] at +0x68: TULayoutView vtable
|
||||
Contains TULayoutView virtual methods (32 slots total):
|
||||
|
||||
| Slot | Method | TUWidgetLayoutView Implementation |
|
||||
|------|--------|-----------------------------------|
|
||||
| 0 | ~TULayoutView() | Thunk to TUWidgetLayoutView dtor |
|
||||
| 1 | widget() | _purecall (pure virtual) |
|
||||
| 2 | initiate(QWidget*) | TULayoutView::initiate |
|
||||
| 3 | getWidget() const | **Returns (this - 104)** |
|
||||
| 4 | getWidget() | **Returns (this - 104)** |
|
||||
| 5 | getParentHolderWidget() const | TULayoutView impl |
|
||||
| 6 | getParentHolderWidget() | TULayoutView impl |
|
||||
| 7 | hasMenu() | TULayoutView impl |
|
||||
| 8 | setMenu(AC_Manager*, const char*, MenuType) | TULayoutView impl |
|
||||
| 9 | setMenu(AC_Menu*, MenuType) | TULayoutView impl |
|
||||
| 10 | menu(MenuType) | TULayoutView impl |
|
||||
| 11 | toolbar() | TULayoutView impl |
|
||||
| 12 | setToolbarInfo(LAY_ToolbarInfo&) | TULayoutView impl |
|
||||
| 13 | connectView() | Empty |
|
||||
| 14 | disconnectView() | Empty |
|
||||
| 15 | initializedFromCopy() | TULayoutView impl |
|
||||
| 16 | getCaption(bool) | TULayoutView impl |
|
||||
| 17 | getDynamicTextForCaption() | TULayoutView impl |
|
||||
| 18 | wantEditionStack() | Returns false |
|
||||
| 19 | displayName() | TULayoutView impl |
|
||||
| 20 | compositeChanged(QString&) | Empty |
|
||||
| 21 | dropOverComposite(QDropEvent*, QString&) | Empty |
|
||||
| 22 | wantComposites() | Returns false |
|
||||
| 23 | initActionManager(AC_Manager*) | Empty |
|
||||
| 24 | wantDisplaySelector() | Returns false |
|
||||
| 25 | isUsingDefaultDisplay() | Returns false |
|
||||
| 26 | storeViewPreferences(QDomElement&) | Returns false |
|
||||
| 27 | loadViewPreferences(QDomElement&) | Empty |
|
||||
| 28 | cshHelpId() | TULayoutView impl |
|
||||
| 29 | **triggerMenuChanged()** | **Emits menuChanged() signal** |
|
||||
| 30 | copy(TULayoutView&) | TULayoutView impl |
|
||||
| 31 | **isTULayoutView()** | **Empty (RTTI marker)** |
|
||||
|
||||
Address in ToonBoomLayout.dll: `??_7TUWidgetLayoutView@@6BTULayoutView@@@`
|
||||
|
||||
## Key Methods
|
||||
|
||||
### Constructor
|
||||
```cpp
|
||||
TUWidgetLayoutView(
|
||||
AC_Manager* manager, // Action manager (stored at +0x30)
|
||||
const QString& objectName, // Qt object name
|
||||
QWidget* parent, // Parent widget
|
||||
const char* className, // Class name string
|
||||
Qt::WindowFlags flags // Window flags
|
||||
)
|
||||
```
|
||||
|
||||
**Construction sequence:**
|
||||
1. Call `AC_ResponderTemplateWidget<QWidget>` ctor with (parent, flags, objectName)
|
||||
2. Call `TULayoutView` default ctor at `this + 104`
|
||||
3. Install all 4 TUWidgetLayoutView vtables
|
||||
4. Set minimum width to 150 pixels
|
||||
5. If parent != nullptr && manager != nullptr:
|
||||
- Call `initActionManager(manager)`
|
||||
6. Else:
|
||||
- Store manager directly at offset +0x30
|
||||
|
||||
### Destructor
|
||||
**Destruction sequence:**
|
||||
1. Reset vtables to TUWidgetLayoutView vtables
|
||||
2. Destroy `TULayoutView::m_caption` (QString at +0x108)
|
||||
3. Destroy `TULayoutView::m_toolbarInfo` (LAY_ToolbarInfo at +0x88)
|
||||
4. Destroy `TULayoutView::m_internalName` (QString at +0x70)
|
||||
5. Jump to `AC_ResponderTemplateWidget<QWidget>::~AC_ResponderTemplateWidget`
|
||||
|
||||
### getWidget()
|
||||
```cpp
|
||||
QWidget* getWidget() {
|
||||
return reinterpret_cast<QWidget*>(
|
||||
reinterpret_cast<char*>(this) - 104);
|
||||
}
|
||||
```
|
||||
Called from TULayoutView* context (at +104), returns the containing QWidget* (at +0).
|
||||
|
||||
### triggerMenuChanged()
|
||||
```cpp
|
||||
void triggerMenuChanged() {
|
||||
// 'this' is TULayoutView* at +104
|
||||
// Emit signal on containing TUWidgetLayoutView (this - 104)
|
||||
TUWidgetLayoutView* widget = (TUWidgetLayoutView*)((char*)this - 104);
|
||||
widget->menuChanged();
|
||||
}
|
||||
```
|
||||
|
||||
### mousePressEvent()
|
||||
```cpp
|
||||
void mousePressEvent(QMouseEvent* event) {
|
||||
QWidget::mousePressEvent(event);
|
||||
if (!event->isAccepted()) {
|
||||
event->accept();
|
||||
setFocus(Qt::MouseFocusReason); // Focus reason = 7
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Qt Meta-Object System
|
||||
|
||||
TUWidgetLayoutView participates in the Qt meta-object system:
|
||||
|
||||
- `staticMetaObject` - Static meta-object for the class
|
||||
- `metaObject()` - Returns `&staticMetaObject` (or dynamic for QML)
|
||||
- `qt_metacast(const char* className)`:
|
||||
- "TUWidgetLayoutView" → returns `this`
|
||||
- "TULayoutView" → returns `this + 104`
|
||||
- Otherwise → delegates to AC_ResponderTemplateWidget<QWidget>::qt_metacast
|
||||
- `tr()` - Static translation method
|
||||
|
||||
### Signals
|
||||
- `menuChanged()` - Emitted when view menu changes (slot 0 in staticMetaObject signals)
|
||||
|
||||
## Exported Symbols
|
||||
|
||||
From ToonBoomLayout.dll:
|
||||
|
||||
| Symbol | Description |
|
||||
|--------|-------------|
|
||||
| `??0TUWidgetLayoutView@@QEAA@...@Z` | Constructor |
|
||||
| `??1TUWidgetLayoutView@@UEAA@XZ` | Destructor |
|
||||
| `?getWidget@TUWidgetLayoutView@@UEAAPEAVQWidget@@XZ` | Non-const getWidget |
|
||||
| `?getWidget@TUWidgetLayoutView@@UEBAPEBVQWidget@@XZ` | Const getWidget |
|
||||
| `?mousePressEvent@TUWidgetLayoutView@@MEAAXPEAVQMouseEvent@@@Z` | Mouse handler |
|
||||
| `?triggerMenuChanged@TUWidgetLayoutView@@MEAAXXZ` | Trigger menu signal |
|
||||
| `?menuChanged@TUWidgetLayoutView@@QEAAXXZ` | Signal implementation |
|
||||
| `?metaObject@TUWidgetLayoutView@@UEBAPEBUQMetaObject@@XZ` | Meta-object |
|
||||
| `?qt_metacast@TUWidgetLayoutView@@UEAAPEAXPEBD@Z` | Runtime cast |
|
||||
| `?qt_metacall@TUWidgetLayoutView@@UEAAHW4Call@QMetaObject@@HPEAPEAX@Z` | Meta-call |
|
||||
| `?qt_static_metacall@TUWidgetLayoutView@@...@Z` | Static meta-call |
|
||||
| `?tr@TUWidgetLayoutView@@SA?AVQString@@PEBD0H@Z` | Translation |
|
||||
|
||||
## Helper Functions
|
||||
|
||||
The header provides inline helper functions for pointer conversion:
|
||||
|
||||
```cpp
|
||||
// Get TULayoutView* from TUWidgetLayoutView*
|
||||
TULayoutView* TUWidgetLayoutView_getLayoutView(TUWidgetLayoutView* widget);
|
||||
|
||||
// Get TUWidgetLayoutView* from TULayoutView* (must be embedded in TUWidgetLayoutView)
|
||||
TUWidgetLayoutView* TULayoutView_getWidgetLayoutView(TULayoutView* view);
|
||||
|
||||
// Get QWidget* from TUWidgetLayoutView* (same address, different type)
|
||||
QWidget* TUWidgetLayoutView_getWidget(TUWidgetLayoutView* widget);
|
||||
|
||||
// Get AC_Manager* from TULayoutView* embedded in TUWidgetLayoutView
|
||||
AC_Manager* TULayoutView_getActionManager(TULayoutView* view);
|
||||
```
|
||||
|
||||
## Subclasses
|
||||
|
||||
Several classes in ToonBoomLayout.dll inherit from TUWidgetLayoutView:
|
||||
|
||||
- `TUCanvasViewLayoutView` - Canvas/drawing view
|
||||
- `TUFrameLayoutView` - Frame view
|
||||
- `TUScrollViewLayoutView` - Scrollable view
|
||||
- `TUTextEditLayoutView` - Text editing view
|
||||
- `TUVBoxLayoutView` - Vertical box layout view
|
||||
|
||||
These share the same `getWidget()` implementation (returning `this - 104`).
|
||||
|
||||
## Analysis Methodology
|
||||
|
||||
This analysis was performed using:
|
||||
1. IDA Pro decompilation of constructor (`0x7ffa0be600a0`) and destructor (`0x7ffa0be60480`)
|
||||
2. Disassembly verification of exact byte offsets
|
||||
3. Cross-reference analysis of vtable addresses
|
||||
4. Examination of related methods (getWidget, triggerMenuChanged, etc.)
|
||||
5. Qt meta-object method analysis (qt_metacast, etc.)
|
||||
|
||||
## References
|
||||
|
||||
- `toon_boom_layout.hpp` - Header file with class declarations
|
||||
- `TULayoutView_Toolbar_Integration.md` - Toolbar integration documentation
|
||||
- `AC_Manager` - Action manager class documentation
|
||||
304
docs/ToonBoomActionManager_Classes.md
Normal file
304
docs/ToonBoomActionManager_Classes.md
Normal file
@ -0,0 +1,304 @@
|
||||
# ToonBoomActionManager.dll Class Analysis
|
||||
|
||||
This document describes the reverse-engineered class structures from `ToonBoomActionManager.dll`.
|
||||
|
||||
**IDA Database:** `RE/ToonBoomActionManager.dll.i64`
|
||||
|
||||
## Overview
|
||||
|
||||
ToonBoomActionManager.dll provides the action/command management infrastructure for Toon Boom applications (Harmony Premium, Storyboard Pro). It implements:
|
||||
|
||||
- **Responder Chain**: First responder and selection responder management
|
||||
- **Menu System**: XML-based menu creation and management
|
||||
- **Toolbar System**: Customizable toolbars with dynamic item insertion
|
||||
- **Shortcut Management**: Keyboard shortcut handling
|
||||
- **Action Triggering**: Command execution and validation
|
||||
|
||||
## Class Hierarchy
|
||||
|
||||
```
|
||||
AC_Manager (abstract interface)
|
||||
└── AC_ManagerImpl (concrete, inherits QObject)
|
||||
|
||||
AC_Object (abstract interface)
|
||||
├── AC_Item (abstract interface)
|
||||
├── AC_Separator (abstract interface)
|
||||
└── AC_Toolbar (abstract, also inherits AC_Container, AC_Help)
|
||||
└── AC_ToolbarImpl (concrete, inherits QToolBar)
|
||||
|
||||
AC_Container (abstract interface)
|
||||
└── AC_ContainerImpl (concrete)
|
||||
|
||||
AC_Help (abstract interface)
|
||||
|
||||
AC_Responder (abstract interface)
|
||||
└── AC_ResponderTemplate (concrete)
|
||||
└── AC_ResponderTemplateWidget<T> (template)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## AC_Manager / AC_ManagerImpl
|
||||
|
||||
### Purpose
|
||||
Central manager for the action/command system. Handles responder registration, menu/toolbar creation, and action triggering.
|
||||
|
||||
### Key Addresses
|
||||
| Symbol | Address |
|
||||
|--------|---------|
|
||||
| AC_Manager vtable | `0x18004e508` |
|
||||
| AC_ManagerImpl vtable (QObject) | `0x18004e750` |
|
||||
| AC_ManagerImpl vtable (AC_Manager) | `0x18004e7c8` |
|
||||
| AC_ManagerImpl::AC_ManagerImpl | `0x18000ef20` |
|
||||
| AC_ManagerImpl::~AC_ManagerImpl | `0x18000f300` |
|
||||
|
||||
### Memory Layout (AC_ManagerImpl)
|
||||
Total size: **0x158 (344 bytes)** on x64
|
||||
|
||||
| Offset | Size | Type | Field |
|
||||
|--------|------|------|-------|
|
||||
| +0x00 | 0x10 | | QObject base |
|
||||
| +0x10 | 0x08 | vptr | AC_Manager vtable |
|
||||
| +0x18 | 0x08 | void* | m_keywords (shared ptr data) |
|
||||
| +0x20 | 0x08 | void* | m_keywords (continued) |
|
||||
| +0x28 | 0x08 | void* | m_keywords (continued) |
|
||||
| +0x30 | 0x01 | bool | m_trimShortcuts (init value) |
|
||||
| +0x38 | 0x18 | | Responder name→list tree |
|
||||
| +0x50 | 0x18 | | Menu tree |
|
||||
| +0x68 | 0x08 | ptr | m_shortcutManager |
|
||||
| +0x78 | 0x18 | QString | m_hoverId |
|
||||
| +0x90 | 0x18 | QString | m_genericImage |
|
||||
| +0xA8 | 0x18 | QString | m_toolbarCustomizeImage |
|
||||
| +0xC0 | 0x18 | | Toolbar tree |
|
||||
| +0xD8 | 0x18 | vector | m_registeredResponders |
|
||||
| +0xF0 | 0x18 | vector | m_responderStack |
|
||||
| +0x108 | 0x08 | ptr | m_menuBar |
|
||||
| +0x118 | 0x08 | ptr | m_applicationResponder |
|
||||
| +0x120 | 0x08 | ptr | m_selectionResponder |
|
||||
| +0x128 | 0x08 | ptr | m_itemGenerator |
|
||||
| +0x130 | 0x01 | bool | m_option_trimShortcuts |
|
||||
|
||||
### Key Methods
|
||||
|
||||
#### Responder Management
|
||||
- `firstResponder()` - Returns top of responder stack (offset +248)
|
||||
- `applicationResponder()` - Returns m_applicationResponder (offset +296)
|
||||
- `registerResponder(responder, widget)` - Adds responder to registry
|
||||
- `setFirstResponder(responder)` - Sets active first responder
|
||||
- `pushUp(responder)` / `pushOut(responder)` - Stack manipulation
|
||||
|
||||
#### Menu/Toolbar Creation
|
||||
- `createMenuBar(element, parent)` - Create menu from XML
|
||||
- `createToolbar(element, ids, mainWindow, area, name, owner)` - Create toolbar
|
||||
- `loadMenus(path)` / `loadToolbars(path, ids)` - Load from file
|
||||
|
||||
#### Action Triggering
|
||||
- `trigger(responderIdentity, actionName, forEach)` - Execute action
|
||||
- `performValidation(responderIdentity, actionName, enabled, checked)` - Validate action state
|
||||
|
||||
### Signals
|
||||
```cpp
|
||||
void firstResponderChanged();
|
||||
void selectionResponderChanged();
|
||||
void updateToolbarsSignal();
|
||||
void updateToolbarsText();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## AC_Toolbar / AC_ToolbarImpl
|
||||
|
||||
### Purpose
|
||||
Customizable toolbar with support for:
|
||||
- Dynamic item insertion from XML
|
||||
- Placeholder-based customization
|
||||
- Configuration save/restore
|
||||
- Context-sensitive help
|
||||
|
||||
### Key Addresses
|
||||
| Symbol | Address |
|
||||
|--------|---------|
|
||||
| AC_ToolbarImpl vtable (QObject) | `0x180054c90` |
|
||||
| AC_ToolbarImpl vtable (QPaintDevice) | `0x180054e70` |
|
||||
| AC_ToolbarImpl vtable (AC_Toolbar) | `0x180054eb0` |
|
||||
| AC_ToolbarImpl vtable (AC_Container) | `0x180054ef8` |
|
||||
| AC_ToolbarImpl vtable (AC_Help) | `0x1800550f8` |
|
||||
| AC_ToolbarImpl::AC_ToolbarImpl | `0x180032df0` |
|
||||
| AC_ToolbarImpl::~AC_ToolbarImpl | `0x180033080` |
|
||||
|
||||
### Memory Layout (AC_ToolbarImpl)
|
||||
Total size: **0x118 (280 bytes)** on x64
|
||||
|
||||
| Offset | Size | Type | Field |
|
||||
|--------|------|------|-------|
|
||||
| +0x00 | 0x28 | | QToolBar base (includes QObject, QPaintDevice) |
|
||||
| +0x28 | 0x58 | AC_ContainerImpl | m_container (embedded) |
|
||||
| +0x80 | 0x08 | vptr | AC_Toolbar vtable |
|
||||
| +0x88 | 0x08 | vptr | AC_Container vtable |
|
||||
| +0x90 | 0x08 | vptr | AC_Help vtable |
|
||||
| +0x98 | 0x18 | QString | m_responderIdentity |
|
||||
| +0xB0 | 0x18 | vector | m_defaultConfig |
|
||||
| +0xC8 | 0x01 | bool | m_isCustomizable |
|
||||
| +0xD0 | 0x08 | ptr | m_customizeButton |
|
||||
| +0xD8 | 0x08 | ptr | m_unknown |
|
||||
| +0xE0 | 0x08 | QObject* | m_owner |
|
||||
| +0xE8 | 0x10 | QWeakPointer | m_mainWindow |
|
||||
| +0xF8 | 0x04 | float | m_scaleFactor (default 1.0) |
|
||||
| +0x100 | 0x18 | QString | m_translationContext |
|
||||
|
||||
### Interface Pointers
|
||||
When you have an `AC_Toolbar*`:
|
||||
- `QToolBar*` = `(char*)this - 128`
|
||||
- `AC_ContainerImpl*` = `(char*)this - 88`
|
||||
- Identity QString is at `(char*)this - 72`
|
||||
|
||||
### Key Methods
|
||||
|
||||
#### Item Management
|
||||
- `insert(beforeObject, element)` - Insert item from XML
|
||||
- `insertAtPlaceholder(placeholder, element)` - Insert at named placeholder
|
||||
- `insertSeparator(beforeObject)` - Insert separator
|
||||
- `removeObject(identity)` - Remove item by identity
|
||||
- `clear()` - Remove all items
|
||||
|
||||
#### Configuration
|
||||
- `config()` - Get current item order as QList<QString>
|
||||
- `setConfig(list)` - Restore from saved configuration
|
||||
- `defaultConfig()` - Get default item order
|
||||
- `isDefaultConfig()` - Check if current matches default
|
||||
|
||||
#### Validation
|
||||
- `validateContent()` - Update enabled/checked states
|
||||
- `validateContentIfVisible()` - Only validate if toolbar is shown
|
||||
- `updateSeparators()` - Hide separators adjacent to hidden items
|
||||
|
||||
### Signals
|
||||
```cpp
|
||||
void customized(QString identity);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## AC_ContainerImpl
|
||||
|
||||
### Purpose
|
||||
Base container implementation for managing AC_Object children.
|
||||
|
||||
### Memory Layout
|
||||
Total size: **0x58 (88 bytes)** on x64
|
||||
|
||||
| Offset | Size | Type | Field |
|
||||
|--------|------|------|-------|
|
||||
| +0x00 | 0x08 | vptr | AC_ContainerImpl vtable |
|
||||
| +0x08 | 0x08 | ptr | m_manager (AC_ManagerImpl*) |
|
||||
| +0x10 | 0x18 | QString | m_identity |
|
||||
| +0x28 | 0x01 | bool | m_enabled |
|
||||
| +0x29 | 0x01 | bool | m_isToolbar |
|
||||
| +0x30 | 0x18 | vector | m_objects |
|
||||
| +0x48 | 0x10 | | Placeholder tree |
|
||||
|
||||
---
|
||||
|
||||
## Enumerations
|
||||
|
||||
### AC_Result
|
||||
```cpp
|
||||
enum class AC_Result : int {
|
||||
NotHandled = 0, // Action not handled by any responder
|
||||
Handled = 1, // Action handled successfully
|
||||
Error = 2 // Error during handling
|
||||
};
|
||||
```
|
||||
|
||||
### AC_ManagerOption
|
||||
```cpp
|
||||
enum class AC_ManagerOption : int {
|
||||
TrimShortcuts = 0 // Whether to trim whitespace from shortcuts
|
||||
};
|
||||
```
|
||||
|
||||
### AC_Help::cshHelpType
|
||||
```cpp
|
||||
enum cshHelpType {
|
||||
Type0 = 0,
|
||||
Type1 = 1,
|
||||
Type2 = 2,
|
||||
Type3 = 3,
|
||||
Type4 = 4,
|
||||
ToolbarHelp = 5
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## XML Element Types
|
||||
|
||||
AC_ToolbarImpl::insert() handles these XML element types:
|
||||
|
||||
| Tag Name | Handler | Creates |
|
||||
|----------|---------|---------|
|
||||
| `item` | sub_1800363E0 | AC_ToolbarItemImpl |
|
||||
| `menu` | sub_1800363E0 | AC_ToolbarItemImpl (menu button) |
|
||||
| `multibutton` | sub_180037F30 | AC_ToolbarMultiButtonImpl |
|
||||
| `combobox` | sub_1800395D0 | AC_ToolbarComboBox |
|
||||
| `placeholder` | sub_18001B9F0 | Placeholder object |
|
||||
| `separator` | sub_18003ABD0 | AC_ToolbarSeparatorImpl |
|
||||
|
||||
---
|
||||
|
||||
## Usage Patterns
|
||||
|
||||
### Getting AC_Manager Instance
|
||||
```cpp
|
||||
// Via PLUG_Services (from ToonBoomPlugInManager.dll)
|
||||
AC_Manager* manager = PLUG_Services::getActionManager();
|
||||
```
|
||||
|
||||
### Creating a Toolbar
|
||||
```cpp
|
||||
QList<QString> ids;
|
||||
AC_Toolbar* toolbar = manager->createToolbar(
|
||||
xmlElement, // QDomElement with toolbar definition
|
||||
&ids, // Output: list of created item IDs
|
||||
mainWindow, // QMainWindow to attach to
|
||||
Qt::TopToolBarArea,// Toolbar area
|
||||
"myToolbar", // Object name
|
||||
owner // Owner QObject for cleanup
|
||||
);
|
||||
```
|
||||
|
||||
### Triggering an Action
|
||||
```cpp
|
||||
AC_Result result = manager->trigger(
|
||||
"NetworkView", // Responder identity
|
||||
"onActionZoomIn", // Action/slot name
|
||||
false // Don't iterate all responders
|
||||
);
|
||||
```
|
||||
|
||||
### Validating an Action
|
||||
```cpp
|
||||
bool enabled, checked;
|
||||
manager->performValidation("NetworkView", "onActionZoomIn", &enabled, &checked);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related Files
|
||||
|
||||
- `framework/include/toon_boom/ac_manager.hpp` - AC_Manager header
|
||||
- `framework/include/toon_boom/toolbar.hpp` - AC_Toolbar header
|
||||
- `framework/include/toon_boom/PLUG_Services.hpp` - Service access
|
||||
|
||||
---
|
||||
|
||||
## Methodology
|
||||
|
||||
This analysis was performed using:
|
||||
1. IDA Pro decompilation of ToonBoomActionManager.dll
|
||||
2. Vtable analysis to identify class hierarchies
|
||||
3. Constructor/destructor analysis for memory layout
|
||||
4. Cross-reference analysis for method identification
|
||||
5. String analysis for signal/slot names
|
||||
|
||||
All offsets verified against x64 Windows builds of Toon Boom Harmony Premium.
|
||||
Loading…
x
Reference in New Issue
Block a user