2#define IPLUG_STRINGIFY_HELPER(x) #x
3#define IPLUG_STRINGIFY(x) IPLUG_STRINGIFY_HELPER(x)
5ReaperExtBase::ReaperExtBase(reaper_plugin_info_t* pRec)
6: EDITOR_DELEGATE_CLASS(0)
9 mTimer = std::unique_ptr<Timer>(Timer::Create(std::bind(&ReaperExtBase::OnTimer,
this, std::placeholders::_1), IDLE_TIMER_RATE));
10 mDockId.Set(IPLUG_STRINGIFY(PLUG_CLASS_NAME));
11 mMenuName.Set(IPLUG_STRINGIFY(PLUG_CLASS_NAME));
16ReaperExtBase::~ReaperExtBase()
21 mSaveStateOnDestroy =
false;
26void ReaperExtBase::OnTimer(
Timer& t)
31auto ClientResize = [](HWND hWnd,
int nWidth,
int nHeight) {
32 RECT rcClient, rcWindow;
34 int screenwidth, screenheight;
37 screenwidth = GetSystemMetrics(SM_CXSCREEN);
38 screenheight = GetSystemMetrics(SM_CYSCREEN);
39 x = (screenwidth / 2) - (nWidth / 2);
40 y = (screenheight / 2) - (nHeight / 2);
42 GetClientRect(hWnd, &rcClient);
43 GetWindowRect(hWnd, &rcWindow);
44 ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right;
45 ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom;
47 SetWindowPos(hWnd, 0, x, y, nWidth + ptDiff.x, nHeight + ptDiff.y, 0);
50bool ReaperExtBase::EditorResizeFromUI(
int viewWidth,
int viewHeight,
bool needsPlatformResize)
52 if (viewWidth != GetEditorWidth() || viewHeight != GetEditorHeight())
55 if (!
IsDocked() && needsPlatformResize)
58#define TITLEBAR_BODGE 22
60 GetWindowRect(gHWND, &r);
61 SetWindowPos(gHWND, 0, r.left, r.bottom - viewHeight - TITLEBAR_BODGE, viewWidth, viewHeight + TITLEBAR_BODGE, 0);
73void ReaperExtBase::EnsureStateLoaded()
83void ReaperExtBase::CreateMainWindow()
90 gHWND = CreateDialog(gHINSTANCE, MAKEINTRESOURCE(IDD_DIALOG_MAIN), gParent, ReaperExtBase::MainDlgProc);
98void ReaperExtBase::UpdateToggleStates()
100 mWindowToggle = (gHWND != NULL) ? 1 : 0;
104void ReaperExtBase::DestroyMainWindow()
110 gPlug->CloseWindow();
111 DockWindowRemove(gHWND);
112 DestroyWindow(gHWND);
115 UpdateToggleStates();
124 DockWindowActivate(gHWND);
139 mDockState.state ^= 2;
141 UpdateToggleStates();
147 GetWindowRect(gHWND, &mDockState.r);
150 mSaveStateOnDestroy =
false;
151 gPlug->CloseWindow();
152 DockWindowRemove(gHWND);
153 DestroyWindow(gHWND);
157 mDockState.state ^= 2;
158 mSaveStateOnDestroy =
true;
163 DockWindowActivate(gHWND);
166void ReaperExtBase::SaveDockState()
168 const char* iniFile = get_ini_file();
174 int dockIdx = DockIsChildOfDock(gHWND, NULL);
176 mDockState.whichdock = dockIdx;
178 GetWindowRect(gHWND, &mDockState.r);
182 if (gHWND != NULL && IsWindowVisible(gHWND))
183 mDockState.state |= 1;
185 mDockState.state &= ~1;
191 REAPER_MAKELEINTMEM(&((
int*)&stateLE)[i]);
193 WritePrivateProfileStruct(
"iPlug2", mDockId.Get(), &stateLE,
sizeof(
ReaperExtDockState), iniFile);
196void ReaperExtBase::LoadDockState()
198 const char* iniFile = get_ini_file();
203 if (GetPrivateProfileStruct(
"iPlug2", mDockId.Get(), &stateLE,
sizeof(
ReaperExtDockState), iniFile))
207 REAPER_MAKELEINTMEM(&((
int*)&stateLE)[i]);
212void ReaperExtBase::RegisterAction(
const char* actionName, std::function<
void()> func,
bool addMenuItem,
int* pToggle,
const char* contextMenuId,
const char* menuLabel)
216 int commandID = mRec->Register(
"command_id", (
void*) actionName );
221 action.accel.accel.cmd = commandID;
222 action.accel.desc = actionName;
223 action.addMenuItem = addMenuItem;
224 action.pToggle = pToggle;
226 action.contextMenuId = contextMenuId;
227 action.menuLabel = menuLabel ? menuLabel : actionName;
228 gActions.push_back(action);
230 mRec->Register(
"gaccel", (
void*) &gActions.back().accel);
238static bool CheckActionMenuItem(HMENU hMenu,
int commandId,
bool checked)
240 const int nItems = GetMenuItemCount(hMenu);
242 for (
int i = 0; i < nItems; i++)
244 MENUITEMINFO mi = {
sizeof(MENUITEMINFO), };
245 mi.fMask = MIIM_ID | MIIM_SUBMENU;
247 if (!GetMenuItemInfo(hMenu, i, TRUE, &mi))
252 if (CheckActionMenuItem(mi.hSubMenu, commandId, checked))
255 else if (
static_cast<int>(mi.wID) == commandId)
257 CheckMenuItem(hMenu, i, MF_BYPOSITION | (checked ? MF_CHECKED : MF_UNCHECKED));
265static void AppendActionMenuItem(HMENU hMenu,
const ReaperAction& action)
267 MENUITEMINFO mi = {
sizeof(MENUITEMINFO), };
268 mi.fMask = MIIM_TYPE | MIIM_ID;
269 mi.fType = MFT_STRING;
270 mi.dwTypeData = LPSTR(action.menuLabel);
271 mi.wID = action.accel.accel.cmd;
273 InsertMenuItem(hMenu, GetMenuItemCount(hMenu), TRUE, &mi);
277void ReaperExtBase::MenuHook(
const char* menuidstr,
void* menu,
int flag)
279 if (menuidstr ==
nullptr || menu ==
nullptr)
282 HMENU hMenu = (HMENU) menu;
284 const bool isExtensionsMenu = strcmp(menuidstr,
"Main extensions") == 0;
290 for (
auto& action : gActions)
292 const bool inThisMenu = (isExtensionsMenu && action.addMenuItem) ||
293 (action.contextMenuId && strcmp(action.contextMenuId, menuidstr) == 0);
295 if (!inThisMenu || action.pToggle ==
nullptr)
298 CheckActionMenuItem(hMenu, action.accel.accel.cmd, *action.pToggle != 0);
310 if (isExtensionsMenu)
312 HMENU hSubMenu = CreatePopupMenu();
314 for (
auto& action : gActions)
316 if (action.addMenuItem)
317 AppendActionMenuItem(hSubMenu, action);
320 if (GetMenuItemCount(hSubMenu) == 0)
322 DestroyMenu(hSubMenu);
326 MENUITEMINFO mi = {
sizeof(MENUITEMINFO), };
327 mi.fMask = MIIM_TYPE | MIIM_SUBMENU;
328 mi.fType = MFT_STRING;
329 mi.dwTypeData = LPSTR(gPlug->mMenuName.Get());
330 mi.hSubMenu = hSubMenu;
331 InsertMenuItem(hMenu, GetMenuItemCount(hMenu), TRUE, &mi);
336 for (
auto& action : gActions)
338 if (action.contextMenuId && strcmp(action.contextMenuId, menuidstr) == 0)
339 AppendActionMenuItem(hMenu, action);
344void ReaperExtBase::PostCommandProc(
int command,
int flag)
347 gPlug->OnActionRun(command, flag);
351void ReaperExtBase::BeginLoadProjectState(
bool isUndo, project_config_extension_t* reg)
354 gPlug->OnBeginLoadProjectState(isUndo);
358bool ReaperExtBase::ProcessExtensionLine(
const char* line, ProjectStateContext* ctx,
bool isUndo, project_config_extension_t* reg)
360 return gPlug ? gPlug->LoadProjectStateLine(line) :
false;
364void ReaperExtBase::SaveExtensionConfig(ProjectStateContext* ctx,
bool isUndo, project_config_extension_t* reg)
367 gPlug->SaveProjectState(ctx);
371bool ReaperExtBase::HookCommandProc(
int command,
int flag)
373 auto it = std::find_if (gActions.begin(), gActions.end(), [&](
const auto& e) { return e.accel.accel.cmd == command; });
375 if (it == gActions.end())
384int ReaperExtBase::ToggleActionCallback(
int command)
386 auto it = std::find_if (gActions.begin(), gActions.end(), [&](
const auto& e) { return e.accel.accel.cmd == command; });
391 if (it == gActions.end() || it->pToggle ==
nullptr)
398WDL_DLGRET ReaperExtBase::MainDlgProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
400 extern float GetScaleForHWND(HWND hWnd);
406 auto scale = GetScaleForHWND(hwnd);
408 if (gPlug->IsDocked())
411 DockWindowAddEx(hwnd, (
char*)gPlug->mDockId.Get(), gPlug->mDockId.Get(),
true);
416 if (gPlug->mDockState.r.left || gPlug->mDockState.r.top ||
417 gPlug->mDockState.r.right || gPlug->mDockState.r.bottom)
419 EnsureNotCompletelyOffscreen(&gPlug->mDockState.r);
420 SetWindowPos(hwnd, NULL,
421 gPlug->mDockState.r.left, gPlug->mDockState.r.top,
422 gPlug->mDockState.r.right - gPlug->mDockState.r.left,
423 gPlug->mDockState.r.bottom - gPlug->mDockState.r.top,
428 ClientResize(hwnd,
static_cast<int>(PLUG_WIDTH * scale),
static_cast<int>(PLUG_HEIGHT * scale));
430 AttachWindowTopmostButton(hwnd);
431 ShowWindow(hwnd, SW_SHOW);
434 gPlug->OpenWindow(hwnd);
440 GetClientRect(hwnd, &r);
441 int w = r.right - r.left;
442 int h = r.bottom - r.top;
443#ifdef WEBVIEW_EDITOR_DELEGATE
448 w =
static_cast<int>(w / scale);
449 h =
static_cast<int>(h / scale);
452 gPlug->OnParentWindowResize(w, h);
455 GetWindowRect(hwnd, &gPrevBounds);
461 if (gPlug->mSaveStateOnDestroy)
462 gPlug->SaveDockState();
464 DockWindowRemove(hwnd);
466 gPlug->UpdateToggleStates();
476 GetClientRect(hwnd, &r);
477 int w = r.right - r.left;
478 int h = r.bottom - r.top;
479#ifdef WEBVIEW_EDITOR_DELEGATE
481 const float scale = GetScaleForHWND(hwnd);
482 w =
static_cast<int>(w / scale);
483 h =
static_cast<int>(h / scale);
486 gPlug->OnParentWindowResize(w, h);
491 gPlug->CloseWindow();
State structure for dock window persistence - matches SWS pattern.
void RegisterAction(const char *actionName, std::function< void()> func, bool addMenuItem=false, int *pToggle=nullptr, const char *contextMenuId=nullptr, const char *menuLabel=nullptr)
Registers an action with the REAPER extension system.
void ShowHideMainWindow()
Toggles the visibility of the main extension window.
bool IsDocked() const
Returns true if the window is currently docked.
void ToggleDocking()
Toggles between docked and floating state.
virtual void OnIdle()
Called during idle processing - override to perform periodic tasks.
Helper struct for registering Reaper Actions.