From c83cadbcba7e48853ed1e59bfbbb83b32717d752 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=98=99=E2=97=A6=20The=20Tablet=20=E2=9D=80=20GamerGirla?= =?UTF-8?q?ndCo=20=E2=97=A6=E2=9D=A7?= Date: Wed, 14 Jan 2026 23:53:12 -0500 Subject: [PATCH] docs: update to remove stale information and add new/updated info --- docs/AC_Responder_System.md | 164 +++--- docs/TULayoutView_Toolbar_Integration.md | 682 +++++++++++++++++------ docs/ToonBoomActionManager_Classes.md | 48 +- docs/ToonBoomLayout_ViewUsage.md | 20 +- 4 files changed, 646 insertions(+), 268 deletions(-) diff --git a/docs/AC_Responder_System.md b/docs/AC_Responder_System.md index 99e899e..429a565 100644 --- a/docs/AC_Responder_System.md +++ b/docs/AC_Responder_System.md @@ -251,11 +251,12 @@ public slots: } // 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()); - } + void onActionMyCustomActionValidate(AC_ActionInfo* info) { + // Enable based on some condition + info->setEnabled(canPerformAction()); + // Checkable state is controlled by an additional (currently-opaque) vfunc + // on the `AC_ActionInfo*` implementation, not by a public `setChecked(...)` API. + } private: bool canPerformAction() const { return true; } @@ -271,10 +272,10 @@ For a widget that also acts as a responder: #include #include -class MyCustomWidget : public QWidget, public AC_ResponderBase { - Q_OBJECT - -public: +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) @@ -283,16 +284,34 @@ public: manager->registerResponder(this, this); } - ~MyCustomWidget() { - if (AC_Manager* mgr = actionManager()) { - mgr->unregisterResponder(this); - } - } - -public slots: - void onActionWidgetAction() { - qDebug() << "Widget action triggered!"; - } + ~MyCustomWidget() { + if (AC_Manager* mgr = actionManager()) { + mgr->unregisterResponder(this); + } + } + + AC_Result perform(AC_ActionInfo* info) override { + if (!info) { + return AC_Result::NotHandled; + } + + // IDA-verified: internal widget responders call `info->invokeOnQObject(widget)`. + // This uses Qt's metaobject (`QMetaObject::indexOfSlot`), so your class must + // have `Q_OBJECT` (and be built with moc/automoc) for the slots to be found. + // If it returns NotHandled, they propagate to parentResponder(). + AC_Result result = info->invokeOnQObject(this); + if (result == AC_Result::NotHandled) { + if (AC_Responder* parent = parentResponder()) { + return parent->perform(info); + } + } + return result; + } + +public slots: + void onActionWidgetAction() { + qDebug() << "Widget action triggered!"; + } void onActionWidgetActionValidate(AC_ActionInfo* info) { info->setEnabled(isEnabled()); @@ -328,14 +347,15 @@ public: 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; - } + bool becomeSelectionResponder() override { return false; } + bool resignSelectionResponder() override { return false; } + + AC_Result perform(AC_ActionInfo* info) override { + if (!info) { + return AC_Result::NotHandled; + } + return info->invokeOnQObject(this); + } AC_Result performDownToChildren(AC_ActionInfo* info) override { return AC_Result::NotHandled; @@ -360,46 +380,56 @@ private: }; ``` -## 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 `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 -} -``` +## AC_ActionInfo Structure + +When an action is triggered or validated, Toon Boom passes an `AC_ActionInfo*` to responders. + +**Important ABI correction (IDA-verified):** `AC_ActionInfo` is **not** a `QObject`. RTTI in `ToonBoomActionManager.dll` shows: + +- `AC_ActionInfoImpl` derives from `AC_ActionInfo` +- `AC_ActionInfo` derives from `AC_ActionData` + +So, “action info” methods like `setEnabled(bool)` are actually exported as `AC_ActionData::setEnabled(bool)` and are inherited by `AC_ActionInfo`. + +**Important return-value detail (IDA-verified):** action invocation/validation helpers return: +- `0` = handled/success +- `1` = not handled / slot not found + +```cpp +class AC_ActionData { +public: + bool isValidation() const; + void setEnabled(bool enabled); + void setVisible(bool visible); +}; + +class AC_ActionInfo : public AC_ActionData { + // Most fields/methods are opaque; Toon Boom provides concrete impls. +}; +``` + +## Validation Pattern + +Before displaying a menu or when the UI updates, Toon Boom calls validation methods: + +1. For each toolbar item, Toon Boom tries to validate via a derived validate slot name (`Validate`). +2. If the validate slot exists, it is invoked and is expected to update state (typically via `info->setEnabled(...)`, and optionally a checked/visible-like vfunc). +3. If the validate slot does not exist, Toon Boom can fall back to enabling the action if the action slot exists. +4. If neither validate nor action slot exists on the resolved responder, the action is disabled. + +Example from `TULayoutManager`: + +```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 diff --git a/docs/TULayoutView_Toolbar_Integration.md b/docs/TULayoutView_Toolbar_Integration.md index d94f134..b0baaa6 100644 --- a/docs/TULayoutView_Toolbar_Integration.md +++ b/docs/TULayoutView_Toolbar_Integration.md @@ -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/ToonBoomLayout.dll.i64` - TULayoutView and TULayoutFrame implementations -## Overview - -Toon Boom uses a two-tier toolbar system: +## 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.) +2. **View-Specific Toolbars** - Toolbars that appear when a specific view has focus (DrawingViewToolbar, TimelineViewToolbar, CameraViewToolbar, etc.) + +## Toolbar Buttons Disabled? + +If your custom view’s toolbar shows but every button is disabled, the root cause is almost always **validation / responder resolution**, not the toolbar XML. + +See `docs/AC_Toolbar_ButtonEnablement.md:1` for the IDA-verified validation flow and why `enabled="true"` is ignored. + +## Important: raiseArea vs Toolbar Display + +**`TULayoutManager::raiseArea()` does NOT directly display toolbars.** + +`raiseArea` is used to bring a view/area to the foreground: +```cpp +// Signature: +TULayoutView* raiseArea(const QString& areaName, TULayoutFrame* frame, + bool createNew, const QPoint& pos); + +// Example usage (from HarmonyPremium.exe): +layoutManager->raiseArea("Colour", nullptr, true, QPoint(0,0)); // Shows Colour palette +layoutManager->raiseArea("Morphing", nullptr, true, QPoint(0,0)); // Shows Morphing view +``` + +**Toolbar display is triggered separately** through: +1. `TULayoutFrame::setCurrentTab(...)` (tab/view change) +2. `TULayoutFrame::showViewToolBar()` (updates the view-toolbar for the new current view) +3. `view->toolbar()` (returns toolbar definition as a `QDomElement`) + +`TULayoutManager::setCurrentLayoutFrame(frame)` does **not** call `showViewToolBar()` directly (verified in ToonBoomLayout.dll `0x7ffa0be541d0`). + +If you call `raiseArea` and don't see toolbars, the likely causes are: +- Your view's `toolbar()` override returns an empty `QDomElement` +- The toolbar XML hasn't been registered with `AC_Manager` +- The view isn't properly receiving focus after being raised +- `LAY_ToolbarInfo::m_isDefault` is still non-zero (the frame hides/resets the view-toolbar in this case) ## Architecture ``` ┌─────────────────────────────────────────────────────────────────┐ -│ 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 │ -└─────────────────────────────────────────────────────────────────┘ +│ TULayoutManager │ +│ - Manages all frames, areas, and global toolbars │ +│ - 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`: ```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); +// From HarmonyPremium.exe session initialization (0x140034EB0) +// Loads toolbars.xml via AC_Manager::loadToolbars(path, outIds) (vtable+0x178) + +UT_String toolbarsPath = RM_GetResourcePath("toolbars.xml"); +QList toolbarIds; +actionManager->loadToolbars(toolbarsPath.ToQString(), toolbarIds); ``` The `toolbars.xml` file defines toolbar content using XML elements like: @@ -89,133 +123,269 @@ if (WHO_Features::hasOnionSkinToolbar()) { } ``` -**Key Function Addresses (HarmonyPremium.exe):** -- Session toolbar init: `0x14002F840` -- TULayoutManager::showToolbar import: `0x1409c8ea0` +**Key Function Addresses (HarmonyPremium.exe):** +- Session toolbar init: `0x14002F840` +- `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 Boom’s “Reference View” uses the `ModelViewToolbar` definition (see `toolbars.xml`), implemented in: +- Function: `VL_ModelViewQt__toolbar` +- Address: `0x1403C8A20` (HarmonyPremium.exe) + +Behavior: same as above, but requests `"ModelViewToolbar"`. + +**Relevant TULayoutView virtuals (ToonBoomLayout.dll):** +- `toolbar(...)`: vtable `+0x58` +- `setToolbarInfo(const LAY_ToolbarInfo&)`: vtable `+0x60` -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 frame’s view-toolbar via `TULayoutFrame::showViewToolBar()` (ToonBoomLayout.dll `0x7ffa0be4bb70`). + +Confirmed call sites include: +- `TULayoutFrame::setCurrentTab(TULayoutViewHolder*)` → `showViewToolBar()` +- `TULayoutManager::showViewToolBars()` → `TULayoutFrame::showViewToolBar()` + +High-level behavior of `showViewToolBar()` (validated from disassembly): +1. `currentView = m_layoutManager->findInstance(this, false)` +2. `toolbarEl = currentView->toolbar()` (calls the view’s override) +3. `info = currentView->getToolbarInfo()` (copy of `TULayoutView::m_toolbarInfo`) +4. Hide/reset cases: + - If `info.m_isDefault != 0`: clear + hide the frame toolbar and return + - If `toolbarEl` is null: clear + hide the frame toolbar and return +5. Ensure `m_viewToolbar` exists (created once via `createEmptyToolBar(info.m_name)` if needed) +6. Populate the toolbar: + - `m_viewToolbar->setOwner(currentView->getWidget())` + - `m_viewToolbar->changeContent(toolbarEl, info.m_buttonConfig, info.m_buttonDefaultConfig)` +7. Dock and apply placement: + - `m_mainWindow->addToolBar(info.m_toolBarArea, m_viewToolbar->toQToolBar())` + - `Layout::Toolbar::loadToolbar(info, m_mainWindow)` + - `m_viewToolbar->setVisible(info.m_visible)` + +### 4.1 raiseArea Function Details + +The `raiseArea` function is for **bringing a view area to the foreground**, not for toolbar display: ```cpp -// 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 +// Function signature (from ToonBoomLayout.dll) +TULayoutView* TULayoutManager::raiseArea( + const QString& areaName, // e.g., "Colour", "Morphing", "Timeline" + TULayoutFrame* frame, // Target frame, or nullptr for auto-selection + bool createNew, // true = create new instance if needed + const QPoint& pos // Position for floating windows +); +``` + +**Example usages found in HarmonyPremium.exe:** + +```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 | -|------------|--------------|---------| -| DrawingView | "DrawingViewToolbar" | `0x1403B9880` | -| CameraView | "CameraViewToolbar" | `0x1403BC450` | -| TimelineView | "TimelineViewToolbar" | `0x14011C5F0` | -| SGV_Graph3DView | (custom impl) | `0x1409e1866` | +### 4.2 Ensuring Toolbar Display After raiseArea -### 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 + } +} -``` -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 -``` +// Method 2: Explicitly trigger the toolbar display +TULayoutView* view = layoutManager->raiseArea("MyArea", nullptr, true, QPoint(0,0)); +if (view) { + TULayoutFrame* frame = view->getLayoutFrame(view->getWidget()); + if (frame) { + // Direct refresh paths observed in ToonBoomLayout.dll: + // - TULayoutFrame::setCurrentTab(...) calls frame->showViewToolBar() + // - TULayoutManager::showViewToolBars() calls into the current frame(s) + frame->showViewToolBar(); + layoutManager->showViewToolBars(); + } +} +``` + +## AC_Toolbar Integration + +Toolbar definitions and instances involve three different components: +- `AC_Manager` (ToonBoomActionManager.dll): loads `` definitions (e.g., from `toolbars.xml`) and can create `AC_Toolbar` instances. +- `TULayoutManager` (ToonBoomLayout.dll / HarmonyPremium.exe): shows/hides *global* toolbars by name. +- `TULayoutFrame` (ToonBoomLayout.dll): owns the *view* toolbar instance (`m_viewToolbar`) and updates it on tab/view changes via `showViewToolBar()`. -## 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(actionManager); + using LoadToolbarsFromFileFn = void (__fastcall *)(AC_Manager* self, const QString* path, QList* outIds); + auto loadToolbarsFromFile = reinterpret_cast(vtable[47]); // vtable+0x178 + QList toolbarIds; + loadToolbarsFromFile(actionManager, &path, &toolbarIds); + ``` + +2. **Toolbars are shown/activated via TULayoutManager::showToolbar**: + ```cpp + // During setupToolbars (0x14002f840) + // This is vtable+416 on TULayoutManager, NOT AC_Manager + layoutManager->showToolbar("FileToolbar", true); + layoutManager->showToolbar("EditToolbar", true); + ``` + +3. **View-specific toolbars are looked up via `AC_Manager::toolbarElement()`** (inside the view’s `toolbar()` override): + ```cpp + // Common pattern in HarmonyPremium.exe view toolbar() overrides: + // - AC_Manager* is reachable from a TUWidgetLayoutView-embedded TULayoutView* at [this-0x38] + AC_Manager* manager = *(AC_Manager**)((char*)this - 0x38); + if (!manager) { + return QDomElement(); + } + return manager->toolbarElement(QString("DrawingViewToolbar")); + ``` ### AC_Manager Methods Used 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 | +| Method | Vtable Offset | Description | +|--------|--------------|-------------| +| `toolbarElement(name)` | `+0x1A0` | Returns the `` `QDomElement` for `name` (member-function ABI uses a hidden return buffer in `RDX`) | +| `loadToolbars(element, outIds)` | `+0x170` | Loads toolbar definitions from a `QDomElement` (same structure as `toolbars.xml`) | +| `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 frame’s `TULayoutMainWindow` + +The `AC_Toolbar` object itself is created lazily by `TULayoutFrame::createEmptyToolBar(name)`: +- Builds an empty `` 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 -// 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) +// Structure of LAY_ToolbarInfo (~104 bytes) +// Analyzed from ToonBoomLayout.dll constructors and getters 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 m_buttonConfig; // +0x38 - Current button order - QList m_buttonDefaultConfig; // +0x50 - Default button order + int m_x; // +0x00 - X position (default: 0) + int m_y; // +0x04 - Y position (default: 0) + int m_index; // +0x08 - Toolbar index (default: -1) + int m_width; // +0x0C - Width (default: -1) + int m_height; // +0x10 - Height (default: -1) + bool m_newline; // +0x14 - Break to new line (default: false) + bool m_visible; // +0x15 - Visibility (default: true) + bool m_isDefault; // +0x16 - Default/unconfigured state (Layout hides view-toolbar when true) + // padding // +0x17 + QString m_name; // +0x18 - Toolbar name (24 bytes with SSO) + Qt::Orientation m_orientation; // +0x30 - Orientation (default: Horizontal) + Qt::ToolBarArea m_toolBarArea; // +0x34 - Docking area (default: TopToolBarArea=4) + QList m_buttonConfig; // +0x38 - Current button order (24 bytes) + QList m_buttonDefaultConfig; // +0x50 - Default button order (24 bytes) + // Total size: ~104 bytes }; ``` -The view receives its toolbar info via `TULayoutView::setToolbarInfo()`: -- Import: `0x1409c8f4e` (thunk to ToonBoomLayout.dll) -- Called by TULayoutFrame when loading saved layout preferences +**Key LAY_ToolbarInfo Methods (ToonBoomLayout.dll):** + +| Method | Address | Description | +|--------|---------|-------------| +| `LAY_ToolbarInfo()` | `0x7ffa0be6a3a0` | Default constructor | +| `LAY_ToolbarInfo(QString, int, int, int, int, int, bool, bool)` | `0x7ffa0be6a2c0` | Initializes `name,x,y,index,width,height,newline,visible` (sets `m_isDefault=1`) | +| `getName()` | `0x7ffa0be392b0` | Returns toolbar name | +| `getHeight()` | `0x7ffa0be39280` | Returns height | +| `isVisible()` | `0x7ffa0be39570` | Returns visibility | +| `isDefault()` | `0x7ffa0be39560` | Returns `m_isDefault` (Layout treats non-zero as “hide/reset view-toolbar”) | +| `getButtonConfig()` | `0x7ffa0be6a990` | Returns `nullptr` when `m_isDefault!=0`, else `&m_buttonConfig` | +| `setButtonConfig()` | `0x7ffa0be6a9a0` | Copies config into `m_buttonConfig` and sets `m_isDefault=0` | +| `setButtonDefaultConfig()` | `0x7ffa0be6aaf0` | Copies config into `m_buttonDefaultConfig` | +| `setName()` | `0x7ffa0be39bd0` | Sets toolbar name | +| `setVisible()` | `0x7ffa0be39c10` | Sets visibility | +| `fromXml()` | `0x7ffa0be6a520` | Loads from QDomElement | +| `toXml()` | `0x7ffa0be6ab00` | Saves to QDomElement | + +Note: the `m_buttonDefaultConfig` accessor is ICF-folded with another trivial accessor and may show up in IDA as `?getChildren@TULayoutSplitter@@...` at `0x7ffa0be5ac60`; `TULayoutFrame::showViewToolBar()` uses that address as `&m_buttonDefaultConfig`. + +### How `m_toolbarInfo` Gets Initialized (Critical) + +`TULayoutFrame::showViewToolBar()` will *hide* the view-toolbar when `TULayoutView::m_toolbarInfo.m_isDefault != 0`, so the question becomes: **where does non-default toolbar info come from?** + +This happens when a view is added to a frame (e.g., via `raiseArea()` / layout activation): +- `TULayoutArea::add` (ToonBoomLayout.dll `0x7ffa0be41c10`) first checks the frame’s cache (`getToolbarInfoForView(viewKey)`). +- If no cached entry exists, it calls `view->toolbar()` and reads `toolbarEl.attribute("id")` to get the toolbar identity string (e.g., `DrawingViewToolbar`). +- It then queries `TULayoutStorage::getViewToolbarConfig(viewKey, wantDefault)` (ToonBoomLayout.dll `0x7ffa0be5cff0`) to fetch button config lists keyed by `viewKey` (typically `view->getCaption(true)`). +- If configs exist, it builds a `LAY_ToolbarInfo` with: + - `m_name = toolbarId` + - `m_buttonConfig` + `m_buttonDefaultConfig` populated (which flips `m_isDefault -> 0`) + - applies it via `view->setToolbarInfo(info)` and caches it in the frame. + +Persisted customization updates the config lists via: +- `TULayoutFrame::toolbarWasCustomized` (ToonBoomLayout.dll `0x7ffa0be4be20`) → `TULayoutStorage::setViewToolbarConfig(viewKey, config, isDefault=false)` + `saveLayoutToolbar()`. ## Implementing Custom View Toolbars @@ -232,56 +402,68 @@ Create a toolbar definition that will be loaded by AC_Manager: ``` -### Step 2: Override toolbar() in Your View +### 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( - (*reinterpret_cast(manager))[52] - ); - - QString toolbarName("MyCustomViewToolbar"); - getToolbarElement(manager, &element, toolbarName); - - return element; - } -}; +class MyCustomView : public TULayoutView { +public: + QDomElement toolbar() override { + // Get AC_Manager - depends on your view's structure. + // In HarmonyPremium.exe views derived from TUWidgetLayoutView, AC_Manager* is reachable from the embedded + // TULayoutView* at [this-0x38]. For a custom TULayoutView subclass, store AC_Manager* yourself (or fetch + // it via PLUG_Services::getActionManager()). + AC_Manager* manager = getActionManager(); // your helper + if (!manager) { + return QDomElement(); + } + + return manager->toolbarElement(QString("MyCustomViewToolbar")); + } +}; ``` -### Step 3: Register Toolbar XML - -Ensure your toolbar XML is loaded during initialization: +### 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( - (*reinterpret_cast(manager))[47] - ); - addToolbarFromElement(manager, element, "MyCustomViewToolbar", false); -} -``` +// Option 2: Load programmatically +AC_Manager* manager = PLUG_Services::getActionManager(); +if (manager) { + // Use AC_Manager::loadToolbars(element, outIds) (vtable+0x170) + // Note: loadToolbars expects the same structure as toolbars.xml; easiest is to wrap your inside a root. + QDomDocument doc; + doc.setContent(myToolbarXml); + QDomElement element = doc.documentElement(); + + QList ids; + manager->loadToolbars(element, ids); +} +``` + +### Step 4: Ensure `LAY_ToolbarInfo` Is Non-Default + +`TULayoutFrame::showViewToolBar()` (ToonBoomLayout.dll `0x7ffa0be4bb70`) will hide/reset the view-toolbar when `TULayoutView::m_toolbarInfo.m_isDefault != 0`. + +For a custom view, you must ensure: +- `LAY_ToolbarInfo::m_name` is set to the toolbar element’s `"id"` (used as the toolbar instance identity when `createEmptyToolBar(name)` creates `m_viewToolbar`) +- `LAY_ToolbarInfo::m_buttonConfig` (and typically `m_buttonDefaultConfig`) are populated, so `m_isDefault` flips to `0` + +Built-in views typically get this via `TULayoutArea::add()` reading config from `TULayoutStorage` (see the section above). If your view has no stored config, the toolbar will remain hidden until you seed it. + +Minimal pattern (conceptual): +```cpp +LAY_ToolbarInfo info = this->getToolbarInfo(); +info.setName(QString("MyCustomViewToolbar")); + +QList 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 @@ -307,26 +489,38 @@ if (manager) { ## 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 | +### HarmonyPremium.exe +| Symbol | Address | Description | +|--------|---------|-------------| +| TULayoutManager__setupToolbars | `0x14002F840` | Initializes/shows global toolbars by name | +| VL_BaseDrawingViewQt__toolbar | `0x1403B9880` | Returns `DrawingViewToolbar` element | +| CV_CameraViewQt__toolbar | `0x1403BC450` | Returns `CameraViewToolbar` element | +| VL_ModelViewQt__toolbar | `0x1403C8A20` | Returns `ModelViewToolbar` element (Reference View) | +| TL_DockWindowQT__toolbar | `0x14011C5F0` | Returns `TimelineViewToolbar` element | -### 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 | +### ToonBoomActionManager.dll +| Symbol | Address | Description | +|--------|---------|-------------| +| AC_ManagerImpl__toolbarElement | `0x180016240` | `AC_Manager::toolbarElement(name)` implementation (returns `QDomElement` by value) | +| AC_ToolbarImpl__changeContent_fromElement | `0x1800332F0` | `AC_Toolbar::changeContent(element, config, defaultConfig)` | +| AC_ToolbarImpl::create | `0x180033910` | Build toolbar from XML element | +| AC_ToolbarImpl::create (with config) | `0x180033D50` | Build toolbar from XML element + config list | + +### 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` 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 @@ -354,3 +548,137 @@ if (manager) { - `LibraryViewToolbar` - Library view - `ModuleLibraryViewToolbar` - Module library 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(manager); + using ToolbarElementFn = QDomElement* (__fastcall *)(AC_Manager* self, QDomElement* out, const QString* name); + auto toolbarElement = reinterpret_cast(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"( + + + + + + )"; + + QDomDocument doc; + doc.setContent(toolbarXml); + + // Register with AC_Manager (loadToolbars(element, outIds)) + QList 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(manager); + auto getToolbarElement = reinterpret_cast(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(); + } + } +} +``` diff --git a/docs/ToonBoomActionManager_Classes.md b/docs/ToonBoomActionManager_Classes.md index 2c3cbbc..bcf624a 100644 --- a/docs/ToonBoomActionManager_Classes.md +++ b/docs/ToonBoomActionManager_Classes.md @@ -80,21 +80,31 @@ Total size: **0x158 (344 bytes)** on x64 ### 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 +#### Responder Management +- `firstResponder()` - Returns top of responder stack (offset +248) +- `applicationResponder()` - Returns m_applicationResponder (offset +296) +- `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 +- `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 -- `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 +#### 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 + +#### AC_Manager Toolbar Vtable Slots (Verified in IDA) +- `loadToolbars(const QDomElement&, QList&)` - slot 46 (`+0x170`) at `0x180015940` +- `loadToolbars(const QString&, QList&)` - 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 ```cpp @@ -203,11 +213,11 @@ Total size: **0x58 (88 bytes)** on x64 ### 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 -}; +enum class AC_Result : int { + Handled = 0, // Action handled successfully + NotHandled = 1, // Action not handled by any responder + Error = 2 // Error during handling +}; ``` ### AC_ManagerOption diff --git a/docs/ToonBoomLayout_ViewUsage.md b/docs/ToonBoomLayout_ViewUsage.md index dd4886f..abaa70a 100644 --- a/docs/ToonBoomLayout_ViewUsage.md +++ b/docs/ToonBoomLayout_ViewUsage.md @@ -193,11 +193,21 @@ When you call `TULayoutManager::addArea`, the following happens: 5. `addView` calls `view->getWidget()` to get the actual QWidget 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. - -## Opening Views at Runtime - -To programmatically show a view that's already registered: +This is why `widget()` must return `this` - the calling code treats the return as `TULayoutView*` to make further virtual calls. + +## View Toolbars + +Some built-in views display a view-specific toolbar when they are the active tab (e.g., Drawing and Reference View). + +This is driven by: +- `TULayoutView::toolbar()` returning a non-null `QDomElement` for the toolbar definition +- `TULayoutView::m_toolbarInfo` (`LAY_ToolbarInfo`) being non-default so the frame actually shows/updates the toolbar + +Details and concrete implementations are documented in `docs/TULayoutView_Toolbar_Integration.md`. + +## Opening Views at Runtime + +To programmatically show a view that's already registered: ```cpp // Using TULayoutManager::raiseArea