Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 0 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,6 @@ You can open the folder from within REAPER via
[Options] -> [Show REAPER resource path in explorer/finder...]
```

### REAPER Language Packs

Please note that the EAR Production Suite (specifically the `reaper_adm` extension) is currently incompatible with REAPER Language Packs.
Attempting to use the extension with a REAPER installation which has a Language Pack installed may cause REAPER to fail to load.

This will be addressed in a future version.

### OS X Gatekeeper
On macOS Catalina or above you may experience plugin load errors due to the new Gatekeeper feature.
You can disable Gatekeeper globally as per [this site](https://cronotek.net/blog/how-to-disable-gatekeeper-on-macos-mojave-and-catalina)
Expand Down
191 changes: 143 additions & 48 deletions reaper-adm-extension/src/reaper_adm/menu.cpp
Original file line number Diff line number Diff line change
@@ -1,13 +1,35 @@
#include "menu.h"
#include <algorithm>
#include <cassert>
#include <WDL/swell/swell.h>
#include "reaperapi.h"
#include <boost/algorithm/string.hpp>
#include <optional>

using namespace admplug;
using namespace admplug::detail;

namespace {
std::string bufferToString(char const *buffer) { assert(buffer); return buffer; }

#ifdef WIN32
using GetMenuItemInfoT = MENUITEMINFOW;
auto GetMenuItemInfoFn = GetMenuItemInfoW;

std::string bufferToString(wchar_t const* wstr) {
int count = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL);
std::vector<char> buffer(count, 0);
WideCharToMultiByte(CP_UTF8, 0, wstr, -1, &buffer[0], count, NULL, NULL);
return bufferToString(buffer.data());
}
#else
using GetMenuItemInfoT = MENUITEMINFO;
auto GetMenuItemInfoFn = GetMenuItemInfo;
#endif

using CharT = std::remove_pointer_t<decltype(std::declval<GetMenuItemInfoT>().dwTypeData)>;
using BufferT = std::vector<CharT>;

enum class ReaperMenuState {
Initialising = 0,
AboutToShow = 1
Expand All @@ -23,44 +45,96 @@ namespace {
return info;
}

std::optional<std::string> getMenuString(HMENU menu, int position) {
GetMenuItemInfoT info{};
info.cbSize = sizeof(GetMenuItemInfoT);
info.fType = MFT_STRING;
info.fMask = MIIM_TYPE;
// nullptr here means query string size & store in info.cch
info.dwTypeData = nullptr;
auto success = GetMenuItemInfoFn(menu, position, true, &info);
if (success) {
// query doesn't seem to work with WDL shim, 255 bytes should be enough
// for a short menu string. Truncates if too short.
if(info.cch == 0) {
info.cch = 255;
}
// returned size does not include null terminator so increment
BufferT buffer(++info.cch, '\0');
info.dwTypeData = &buffer[0];
// get actual string data
success = GetMenuItemInfoFn(menu, position, true, &info);
if(success) {
return bufferToString(buffer.data());
}
}
return {};
}
bool supportsLocalisation(ReaperAPI const &api) {
return std::stof(api.GetAppVersion()) >= 6.11f;
}

std::string localise(std::string const &name, std::string const &section,
ReaperAPI const &api) {
return api.LocalizeString(name.c_str(), section.c_str(), 0);
}

void checkDefaultItemIndex(int defaultIndex, int actualIndex) {
assert(defaultIndex == actualIndex);
}

void checkDefaultItemIndex(std::optional<int> defaultIndex, int actualIndex) {
if(defaultIndex) {
checkDefaultItemIndex(*defaultIndex, actualIndex);
}
}

int findPositionOfItemWithText(HMENU menu, std::string textOrig) {
std::string text(textOrig);
boost::erase_all(text, "&");
std::vector<char> buffer(256, '\0');
auto itemCount = GetMenuItemCount(menu);
for (int itemPosition = 0; itemPosition != itemCount; ++itemPosition) {
buffer[0] = '\0';
auto info = getStringMenuInfoStruct(&buffer[0], buffer.size());
if (GetMenuItemInfo(menu, itemPosition, true, &info)) {
std::string menuText = buffer.data();
boost::erase_all(menuText, "&"); // Win returns amp preceding kbd shortcut char - strip it for comparison
if (menuText == text) {
return itemPosition;
}
}
boost::erase_all(textOrig, "&");
auto itemCount = GetMenuItemCount(menu);
for (int itemPosition = 0; itemPosition != itemCount; ++itemPosition) {
if (auto menuText = getMenuString(menu, itemPosition)) {
boost::erase_all(*menuText,
"&"); // Win returns amp preceding kbd shortcut
// char - strip it for comparison
if (*menuText == textOrig) {
return itemPosition;
}
}
return -1;
}
return -1;
}

int constrain(int val, int max) {
return std::max<int>(std::min<int>(val, max), 0);
}

int findInsertionPosition(HMENU menu, std::string itemName, int offset, std::optional<int> fallbackPosition) {
auto itemCount = GetMenuItemCount(menu);
auto index = findPositionOfItemWithText(menu, itemName);
if (index >= 0) {
checkDefaultItemIndex(fallbackPosition, index);
return constrain(itemCount, index + offset);
}
return fallbackPosition ? constrain(*fallbackPosition + offset, itemCount)
: itemCount;
}

HMENU findHmenuOfItemWithText(HMENU menu, std::string textOrig) {
std::string text(textOrig);
boost::erase_all(text, "&");
std::vector<char> buffer(256, '\0');
HMENU findHmenuOfItemWithText(HMENU menu, std::string text) {
auto position = findPositionOfItemWithText(menu, text);
if (position >= 0) {
return GetSubMenu(menu, position);
}
return nullptr;
}

HMENU findHmenuOfItemWithPosition(HMENU menu, int itemPosition) {
auto itemCount = GetMenuItemCount(menu);
for (int itemPosition = 0; itemPosition != itemCount; ++itemPosition) {
buffer[0] = '\0';
auto info = getStringMenuInfoStruct(&buffer[0], buffer.size());
if (GetMenuItemInfo(menu, itemPosition, true, &info)) {
std::string menuText = buffer.data();
boost::erase_all(menuText, "&"); // Win returns amp preceding kbd shortcut char - strip it for comparison
if (menuText == text) {
return info.hSubMenu;
}
}
if (itemPosition < (itemCount - 1)) {
return GetSubMenu(menu, itemPosition);
}
return nullptr;
}

}

Insertable::Insertable(std::shared_ptr<MenuItem> item, std::shared_ptr<MenuInserter> inserter) :
Expand Down Expand Up @@ -90,9 +164,6 @@ void Insertable::update(HMENU menu)
item->update(menu);
}

namespace {
}

MenuAction::MenuAction(std::string menuText, std::shared_ptr<ActionIdentifier> action) :
action{action},
text{menuText.begin(), menuText.end()},
Expand Down Expand Up @@ -185,11 +256,25 @@ void RawMenu::init()
}
}

std::shared_ptr<RawMenu> RawMenu::getMenuByText(std::string menuText)
{
auto hmenu = findHmenuOfItemWithText(hMenu, menuText);
if(hmenu == nullptr) return nullptr;
return std::make_shared<RawMenu>(hmenu);
std::shared_ptr<RawMenu> RawMenu::getMenuByText(std::string menuText,
std::string section,
int fallbackPosition,
ReaperAPI const &api) {
if (std::stof(api.GetAppVersion()) >= 6.11f) {
auto localised = localise(menuText, section, api);
if(!localised.empty()) {
menuText = localised;
}
}
auto submenu = findHmenuOfItemWithText(hMenu, menuText);
if(submenu) {
checkDefaultItemIndex(fallbackPosition, positionOfItemWithText(menuText));
}
if(!submenu) {
submenu = findHmenuOfItemWithPosition(hMenu, fallbackPosition);
}
if(!submenu) return nullptr;
return std::make_shared<RawMenu>(submenu);
}

void RawMenu::update(std::string, HMENU)
Expand All @@ -204,6 +289,9 @@ void RawMenu::insert(std::unique_ptr<MenuItem> item, std::shared_ptr<MenuInserte
items.emplace_back(std::move(item), inserter);
}

int RawMenu::positionOfItemWithText(std::string text) const {
return findPositionOfItemWithText(hMenu, std::move(text));
}

SubMenu::SubMenu(std::string menuText) :
text{menuText.begin(), menuText.end()},
Expand Down Expand Up @@ -343,24 +431,31 @@ AfterNamedItem::AfterNamedItem(std::string itemName) : itemName{itemName}
{
}



AfterNamedItem::AfterNamedItem(std::string itemName, std::string section,
int fallbackPosition, ReaperAPI const &api)
: itemName{supportsLocalisation(api) ? localise(itemName, section, api)
: itemName},
fallbackPosition{fallbackPosition} {}

int AfterNamedItem::getIndex(HMENU menu) const
{
auto itemCount = GetMenuItemCount(menu);
auto index = findPositionOfItemWithText(menu, itemName);
if (index >= 0) {
return std::min<int>(itemCount, index + 1);
}
return itemCount;
return findInsertionPosition(menu, itemName, 1, fallbackPosition);
}

BeforeNamedItem::BeforeNamedItem(std::string itemName) : itemName{itemName}
{

}

BeforeNamedItem::BeforeNamedItem(std::string itemName, std::string section,
int fallbackPosition, ReaperAPI const &api)
: itemName{supportsLocalisation(api) ? localise(itemName, section, api)
: itemName},
fallbackPosition{fallbackPosition} {}

int BeforeNamedItem::getIndex(HMENU menu) const
{
auto index = findPositionOfItemWithText(menu, itemName);
index = std::max<int>(0, index);
return index;
}
return findInsertionPosition(menu, itemName, 0, fallbackPosition);
}
32 changes: 19 additions & 13 deletions reaper-adm-extension/src/reaper_adm/menu.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include <memory>
#include <string>
#include <vector>
#include <optional>
#include "actionmanager.h"

namespace admplug {
Expand Down Expand Up @@ -44,7 +45,6 @@ class MenuContainer {

class TopLevelMenu : public MenuContainer {
public:
virtual ~TopLevelMenu() = default;
virtual void init(std::string menuId, HMENU menu) = 0;
virtual void update(std::string menuId, HMENU menu) = 0;
};
Expand All @@ -65,7 +65,7 @@ class MenuAction : public MenuItem {

class SubMenu : public MenuContainer, public MenuItem {
public:
SubMenu(std::string menuText);
explicit SubMenu(std::string menuText);
void addTo(HMENU menu, int position) override;
void insert(std::unique_ptr<MenuItem> item, std::shared_ptr<MenuInserter> inserter) override;
void update(HMENU menu) override;
Expand All @@ -80,7 +80,7 @@ class SubMenu : public MenuContainer, public MenuItem {

class ReaperMenu : public TopLevelMenu {
public:
ReaperMenu(std::string menuId);
explicit ReaperMenu(std::string menuId);
void init(std::string menuId, HMENU menu) override;
void update(std::string menuId, HMENU menu) override;
void insert(std::unique_ptr<MenuItem> item, std::shared_ptr<MenuInserter> inserter) override;
Expand All @@ -91,11 +91,15 @@ class ReaperMenu : public TopLevelMenu {

class RawMenu : public TopLevelMenu {
public:
RawMenu(HMENU menuHandle);
explicit RawMenu(HMENU menuHandle);
void init(std::string menuId, HMENU menu) override;
void update(std::string menuId, HMENU menu) override;
void insert(std::unique_ptr<MenuItem> item, std::shared_ptr<MenuInserter> inserter) override;
std::shared_ptr<admplug::RawMenu> getMenuByText(std::string menuText);
std::shared_ptr<admplug::RawMenu> getMenuByText(std::string menuText,
std::string section,
int fallbackPosition,
ReaperAPI const &api);
int positionOfItemWithText(std::string text) const;
void init();
private:
HMENU hMenu;
Expand All @@ -105,35 +109,40 @@ class RawMenu : public TopLevelMenu {

class StartOffset : public MenuInserter {
public:
StartOffset(std::size_t offset);
explicit StartOffset(std::size_t offset);
int getIndex(HMENU) const override;
private:
std::size_t offset;
};

class EndOffset : public MenuInserter {
public:
EndOffset(std::size_t offset);
explicit EndOffset(std::size_t offset);
int getIndex(HMENU) const override;
private:
std::size_t offset;
};

class AfterNamedItem : public MenuInserter {
public:
AfterNamedItem(std::string itemName);
explicit AfterNamedItem(std::string itemName);
AfterNamedItem(std::string itemName, std::string section,
int fallbackPosition, ReaperAPI const &api);
int getIndex(HMENU) const override;
private:
std::string itemName;
std::optional<int> fallbackPosition;
};

class BeforeNamedItem : public MenuInserter {
public:
BeforeNamedItem(std::string itemName);
explicit BeforeNamedItem(std::string itemName);
BeforeNamedItem(std::string itemName, std::string section,
int fallbackPosition, ReaperAPI const &api);
int getIndex(HMENU) const override;
private:
std::string itemName;

std::optional<int> fallbackPosition;
};

enum class MenuID {
Expand All @@ -156,13 +165,10 @@ class MenuManager {

private:
MenuManager(ReaperAPI const* api, reaper_plugin_info_t *rec);
void init(std::string menuId, HMENU menu);
std::vector<std::shared_ptr<TopLevelMenu>> menus;
std::map<MenuID, std::string> menuIdentifiers {{
{MenuID::MEDIA_ITEM_CONTEXT, "Media item context"}
}};
HMENU mainMenu;
};


}
Loading