Files
hot-step-cpp-ROCm/engine/vendor/vst3sdk/vstgui4/vstgui/lib/controls/coptionmenu.cpp
T
2026-08-16 18:24:52 +07:00

919 lines
25 KiB
C++

// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "coptionmenu.h"
#include "../cbitmap.h"
#include "../cframe.h"
#include "../cstring.h"
#include "../events.h"
#include "../platform/iplatformoptionmenu.h"
#include "../platform/iplatformframe.h"
namespace VSTGUI {
//------------------------------------------------------------------------
struct CMenuItem::Impl
{
UTF8String title;
UTF8String keyCode;
SharedPointer<COptionMenu> submenu;
SharedPointer<CBitmap> icon;
int32_t flags {0};
int32_t keyModifiers {0};
VirtualKey virtualKey {VirtualKey::None};
int32_t tag {-1};
};
//------------------------------------------------------------------------
// CMenuItem
//------------------------------------------------------------------------
/*! @class CMenuItem
Defines an item of a VSTGUI::COptionMenu
*/
//------------------------------------------------------------------------
CMenuItem::CMenuItem ()
{
impl = std::make_unique<Impl> ();
}
//------------------------------------------------------------------------
CMenuItem::~CMenuItem () noexcept = default;
//------------------------------------------------------------------------
/**
* CMenuItem constructor.
* @param inTitle title of item
* @param inFlags CMenuItem::Flags of item
* @param inKeycode keycode of item
* @param inKeyModifiers keymodifiers of item
* @param inIcon icon of item
*/
//------------------------------------------------------------------------
CMenuItem::CMenuItem (const UTF8String& inTitle, const UTF8String& inKeycode, int32_t inKeyModifiers, CBitmap* inIcon, int32_t inFlags)
: CMenuItem ()
{
impl->flags = inFlags;
setTitle (inTitle);
setKey (inKeycode, inKeyModifiers);
setIcon (inIcon);
}
//------------------------------------------------------------------------
/**
* CMenuItem constructor.
* @param inTitle title of item
* @param inSubmenu submenu of item
* @param inIcon icon of item
*/
//------------------------------------------------------------------------
CMenuItem::CMenuItem (const UTF8String& inTitle, COptionMenu* inSubmenu, CBitmap* inIcon)
: CMenuItem ()
{
setTitle (inTitle);
setSubmenu (inSubmenu);
setIcon (inIcon);
}
//------------------------------------------------------------------------
/**
* CMenuItem constructor.
* @param inTitle title of item
* @param inTag tag of item
*/
//------------------------------------------------------------------------
CMenuItem::CMenuItem (const UTF8String& inTitle, int32_t inTag)
: CMenuItem ()
{
setTitle (inTitle);
setTag (inTag);
}
//------------------------------------------------------------------------
/**
* CMenuItem copy constructor.
* @param item item to copy
*/
//------------------------------------------------------------------------
CMenuItem::CMenuItem (const CMenuItem& item)
: CMenuItem ()
{
impl->flags = item.impl->flags;
setTitle (item.getTitle ());
setIcon (item.getIcon ());
if (item.getVirtualKey () != VirtualKey::None)
setVirtualKey (item.getVirtualKey (), item.getKeyModifiers ());
else
setKey (item.getKeycode (), item.getKeyModifiers ());
setTag (item.getTag ());
setSubmenu (item.getSubmenu ());
}
//------------------------------------------------------------------------
void CMenuItem::setTitle (const UTF8String& inTitle)
{
impl->title = inTitle;
}
//------------------------------------------------------------------------
void CMenuItem::setKey (const UTF8String& inKeycode, int32_t inKeyModifiers)
{
impl->keyCode = inKeycode;
impl->keyModifiers = inKeyModifiers;
impl->virtualKey = VirtualKey::None;
}
#if VSTGUI_ENABLE_DEPRECATED_METHODS
//------------------------------------------------------------------------
void CMenuItem::setVirtualKey (int32_t inVirtualKeyCode, int32_t inKeyModifiers)
{
setKey (nullptr, inKeyModifiers);
impl->virtualKey = fromVstVirtualKey (inVirtualKeyCode);
}
//------------------------------------------------------------------------
int32_t CMenuItem::getVirtualKeyCode () const
{
return toVstVirtualKey (impl->virtualKey);
}
#endif
//------------------------------------------------------------------------
void CMenuItem::setVirtualKey (VirtualKey inVirtualKey, int32_t inKeyModifiers)
{
setKey (nullptr, inKeyModifiers);
impl->virtualKey = inVirtualKey;
}
//------------------------------------------------------------------------
void CMenuItem::setSubmenu (COptionMenu* inSubmenu)
{
impl->submenu = inSubmenu;
}
//------------------------------------------------------------------------
void CMenuItem::setIcon (CBitmap* inIcon)
{
impl->icon = inIcon;
}
//------------------------------------------------------------------------
void CMenuItem::setTag (int32_t t)
{
impl->tag = t;
}
//------------------------------------------------------------------------
void CMenuItem::setEnabled (bool state)
{
setBit (impl->flags, kDisabled, !state);
}
//------------------------------------------------------------------------
void CMenuItem::setChecked (bool state)
{
setBit (impl->flags, kChecked, state);
}
//------------------------------------------------------------------------
void CMenuItem::setIsTitle (bool state)
{
setBit (impl->flags, kTitle, state);
}
//------------------------------------------------------------------------
void CMenuItem::setIsSeparator (bool state)
{
setBit (impl->flags, kSeparator, state);
}
//------------------------------------------------------------------------
bool CMenuItem::isEnabled () const
{
return !hasBit (impl->flags, kDisabled);
}
//------------------------------------------------------------------------
bool CMenuItem::isChecked () const
{
return hasBit (impl->flags, kChecked);
}
//------------------------------------------------------------------------
bool CMenuItem::isTitle () const
{
return hasBit (impl->flags, kTitle);
}
//------------------------------------------------------------------------
bool CMenuItem::isSeparator () const
{
return hasBit (impl->flags, kSeparator);
}
//------------------------------------------------------------------------
const UTF8String& CMenuItem::getTitle () const
{
return impl->title;
}
//------------------------------------------------------------------------
int32_t CMenuItem::getKeyModifiers () const
{
return impl->keyModifiers;
}
//------------------------------------------------------------------------
const UTF8String& CMenuItem::getKeycode () const
{
return impl->keyCode;
}
//------------------------------------------------------------------------
VirtualKey CMenuItem::getVirtualKey () const
{
return impl->virtualKey;
}
//------------------------------------------------------------------------
COptionMenu* CMenuItem::getSubmenu () const
{
return impl->submenu;
}
//------------------------------------------------------------------------
CBitmap* CMenuItem::getIcon () const
{
return impl->icon;
}
//------------------------------------------------------------------------
int32_t CMenuItem::getTag () const
{
return impl->tag;
}
//------------------------------------------------------------------------
/*! @class CCommandMenuItem
The CCommandMenuItem supports setting a category, name and a target. The target will get a @link CBaseObject::notify notify()@endlink call before the item is
displayed and after it was selected. @see CCommandMenuItem::kMsgMenuItemValidate and @see CCommandMenuItem::kMsgMenuItemSelected
*/
//------------------------------------------------------------------------
CCommandMenuItem::CCommandMenuItem (Desc&& args)
: CMenuItem (args.title, args.keycode, args.keyModifiers, args.icon, args.flags)
, commandCategory (std::move (args.commandCategory))
, commandName (std::move (args.commandName))
, itemTarget (std::move (args.target))
{
setTag (args.tag);
}
//------------------------------------------------------------------------
CCommandMenuItem::CCommandMenuItem (const Desc& args)
: CMenuItem (args.title, args.keycode, args.keyModifiers, args.icon, args.flags)
, commandCategory (args.commandCategory)
, commandName (args.commandName)
, itemTarget (args.target)
{
setTag (args.tag);
}
//------------------------------------------------------------------------
CCommandMenuItem::CCommandMenuItem (const CCommandMenuItem& item)
: CMenuItem (item)
, validateFunc (item.validateFunc)
, selectedFunc (item.selectedFunc)
, commandCategory (item.commandCategory)
, commandName (item.commandName)
{
setItemTarget (item.itemTarget);
}
//------------------------------------------------------------------------
void CCommandMenuItem::setItemTarget (ICommandMenuItemTarget* target)
{
itemTarget = target;
}
//------------------------------------------------------------------------
void CCommandMenuItem::setCommandCategory (const UTF8String& category)
{
commandCategory = category;
}
//------------------------------------------------------------------------
bool CCommandMenuItem::isCommandCategory (const UTF8String& category) const
{
return commandCategory == category;
}
//------------------------------------------------------------------------
void CCommandMenuItem::setCommandName (const UTF8String& name)
{
commandName = name;
}
//------------------------------------------------------------------------
bool CCommandMenuItem::isCommandName (const UTF8String& name) const
{
return commandName == name;
}
//------------------------------------------------------------------------
void CCommandMenuItem::setActions (SelectedCallbackFunction&& selected, ValidateCallbackFunction&& validate)
{
selectedFunc = std::move (selected);
validateFunc = std::move (validate);
}
//------------------------------------------------------------------------
void CCommandMenuItem::execute ()
{
if (selectedFunc)
selectedFunc (this);
if (itemTarget)
itemTarget->onCommandMenuItemSelected (this);
}
//------------------------------------------------------------------------
void CCommandMenuItem::validate ()
{
if (validateFunc)
validateFunc (this);
if (itemTarget)
itemTarget->validateCommandMenuItem (this);
}
//------------------------------------------------------------------------
// COptionMenu
//------------------------------------------------------------------------
/*! @class COptionMenu
Define a rectangle view where a text-value can be displayed with a given font and color.
The text-value is centered in the given rect.
A bitmap can be used as background, a second bitmap can be used when the option menu is popuped.
There are 2 styles with or without a shadowed text. When a mouse click occurs, a popup menu is displayed.
*/
//------------------------------------------------------------------------
/**
* COptionMenu constructor.
* @param size the size of this view
* @param listener the listener
* @param tag the control tag
* @param background the background bitmap
* @param bgWhenClick the background bitmap if the option menu is displayed
* @param style the style of the display (see CParamDisplay for styles)
*/
//------------------------------------------------------------------------
COptionMenu::COptionMenu (const CRect& size, IControlListener* listener, int32_t tag, CBitmap* background, CBitmap* bgWhenClick, const int32_t style)
: CParamDisplay (size, background, style)
, bgWhenClick (bgWhenClick)
{
this->listener = listener;
this->tag = tag;
lastButton = kRButton;
menuItems = new CMenuItemList;
setWantsFocus (true);
}
//------------------------------------------------------------------------
COptionMenu::COptionMenu ()
: CParamDisplay (CRect (0, 0, 0, 0))
{
menuItems = new CMenuItemList;
setWantsFocus (true);
}
//------------------------------------------------------------------------
COptionMenu::COptionMenu (const COptionMenu& v)
: CParamDisplay (v)
, menuItems (new CMenuItemList (*v.menuItems))
, nbItemsPerColumn (v.nbItemsPerColumn)
, bgWhenClick (v.bgWhenClick)
{
setWantsFocus (true);
}
//------------------------------------------------------------------------
COptionMenu::~COptionMenu () noexcept
{
removeAllEntry ();
delete menuItems;
}
//------------------------------------------------------------------------
void COptionMenu::registerOptionMenuListener (IOptionMenuListener* listener)
{
if (!listeners)
listeners = std::unique_ptr<MenuListenerList> (new MenuListenerList ());
listeners->add (listener);
}
//------------------------------------------------------------------------
void COptionMenu::unregisterOptionMenuListener (IOptionMenuListener* listener)
{
if (listeners)
listeners->remove (listener);
}
//------------------------------------------------------------------------
void COptionMenu::onKeyboardEvent (KeyboardEvent& event)
{
if (event.type != EventType::KeyUp && event.modifiers.empty () && event.character == 0)
{
if (event.virt == VirtualKey::Return)
{
auto self = shared (this);
getFrame ()->doAfterEventProcessing ([self] () {
self->doPopup ();
});
event.consumed = true;
return;
}
if (!(style & (kMultipleCheckStyle & ~kCheckStyle)))
{
if (event.virt == VirtualKey::Up)
{
int32_t value = (int32_t)getValue ()-1;
if (value >= 0)
{
CMenuItem* entry = getEntry (value);
while (entry && (entry->isSeparator () || entry->isTitle () || !entry->isEnabled () || entry->getSubmenu ()))
entry = getEntry (--value);
if (entry)
{
beginEdit ();
setValue ((float)value);
lastResult = (int32_t)getValue ();
valueChanged ();
endEdit ();
invalid ();
}
}
event.consumed = true;
return;
}
if (event.virt == VirtualKey::Down)
{
int32_t value = (int32_t)getValue ()+1;
if (value < getNbEntries ())
{
CMenuItem* entry = getEntry (value);
while (entry && (entry->isSeparator () || entry->isTitle () || !entry->isEnabled () || entry->getSubmenu ()))
entry = getEntry (++value);
if (entry)
{
beginEdit ();
setValue ((float)value);
lastResult = (int32_t)getValue ();
valueChanged ();
endEdit ();
invalid ();
}
}
event.consumed = true;
return;
}
}
}
CParamDisplay::onKeyboardEvent (event);
}
//------------------------------------------------------------------------
void COptionMenu::beforePopup ()
{
if (listeners)
listeners->forEach ([this] (IOptionMenuListener* l) { l->onOptionMenuPrePopup (this); });
for (auto& menuItem : *menuItems)
{
if (auto* commandItem = menuItem.cast<CCommandMenuItem> ())
commandItem->validate ();
if (menuItem->getSubmenu ())
menuItem->getSubmenu ()->beforePopup ();
}
}
//------------------------------------------------------------------------
void COptionMenu::afterPopup ()
{
for (auto& menuItem : *menuItems)
{
if (menuItem->getSubmenu ())
menuItem->getSubmenu ()->afterPopup ();
}
if (listeners)
listeners->forEach ([this] (IOptionMenuListener* l) { l->onOptionMenuPostPopup (this); });
}
//------------------------------------------------------------------------
bool COptionMenu::doPopup ()
{
if (bgWhenClick)
invalid ();
auto result = popup ();
if (bgWhenClick)
invalid ();
return result;
}
//------------------------------------------------------------------------
bool COptionMenu::popup (const PopupCallback& callback)
{
if (!getFrame ())
return false;
beforePopup ();
lastResult = -1;
lastMenu = nullptr;
if (!menuItems->empty ())
{
getFrame ()->onStartLocalEventLoop ();
if (auto platformMenu = getFrame ()->getPlatformFrame ()->createPlatformOptionMenu ())
{
inPopup = true;
auto self = shared (this);
platformMenu->popup (this, [self, callback] (COptionMenu* menu, PlatformOptionMenuResult result) {
if (result.menu != nullptr)
{
bool preventSettingValue = false;
if (self->listeners)
{
self->listeners->forEach (
[self, &result] (IOptionMenuListener* l) {
return l->onOptionMenuSetPopupResult (self, result.menu,
result.index);
},
[&preventSettingValue] (bool result) {
if (result)
preventSettingValue = true;
return result;
});
}
if (!preventSettingValue)
{
self->beginEdit ();
self->lastMenu = result.menu;
self->lastResult = result.index;
self->lastMenu->setValue (static_cast<float> (self->lastResult));
self->valueChanged ();
self->invalid ();
if (auto commandItem = dynamic_cast<CCommandMenuItem*> (
self->lastMenu->getEntry (self->lastResult)))
commandItem->execute ();
self->endEdit ();
}
}
self->afterPopup ();
if (callback)
callback (self);
self->inPopup = false;
});
}
}
return true;
}
//------------------------------------------------------------------------
bool COptionMenu::popup (CFrame* frame, const CPoint& frameLocation, const PopupCallback& callback)
{
if (frame == nullptr || menuItems->empty ())
return false;
if (isAttached ())
return false;
CView* oldFocusView = frame->getFocusView ();
CRect size (frameLocation, CPoint (0, 0));
setViewSize (size);
frame->addView (this);
auto prevFocusView = shared (oldFocusView);
popup ([prevFocusView, callback] (COptionMenu* menu) {
if (auto frame = menu->getFrame ())
{
frame->removeView (menu, false);
frame->setFocusView (prevFocusView);
}
else
{
// if the selected menu item is a command menu and the command menu has removed this
// option menu from the view hierarchy then we have to make sure the reference count is
// corrected
menu->remember ();
}
if (callback)
callback (menu);
});
return true;
}
//------------------------------------------------------------------------
void COptionMenu::cleanupSeparators (bool deep)
{
if (getItems ()->empty ())
return;
std::list<int32_t>indicesToRemove;
bool lastEntryWasSeparator = true;
for (auto i = 0; i < getNbEntries () - 1; ++i)
{
auto entry = getEntry (i);
vstgui_assert (entry);
if (!entry)
continue;
if (entry->isSeparator ())
{
if (lastEntryWasSeparator)
{
indicesToRemove.push_front (i);
}
lastEntryWasSeparator = true;
}
else
lastEntryWasSeparator = false;
if (deep)
{
if (auto subMenu = entry->getSubmenu ())
{
subMenu->cleanupSeparators (deep);
}
}
}
auto lastIndex = getNbEntries () - 1;
if (getEntry (lastIndex)->isSeparator ())
{
indicesToRemove.push_front (lastIndex);
}
for (auto index : indicesToRemove)
{
removeEntry (index);
}
}
//------------------------------------------------------------------------
void COptionMenu::setPrefixNumbers (int32_t preCount)
{
if (preCount >= 0 && preCount <= 4)
prefixNumbers = preCount;
}
/**
* @param item menu item to add. Takes ownership of item.
* @param index position of insertation. -1 appends the item
*/
//-----------------------------------------------------------------------------
CMenuItem* COptionMenu::addEntry (CMenuItem* item, int32_t index)
{
if (index < 0 || index > getNbEntries ())
menuItems->emplace_back (owned (item));
else
{
menuItems->insert (menuItems->begin () + index, owned (item));
}
return item;
}
//-----------------------------------------------------------------------------
CMenuItem* COptionMenu::addEntry (COptionMenu* submenu, const UTF8String& title)
{
auto* item = new CMenuItem (title, submenu);
return addEntry (item);
}
//-----------------------------------------------------------------------------
CMenuItem* COptionMenu::addEntry (const UTF8String& title, int32_t index, int32_t itemFlags)
{
if (title == "-")
return addSeparator (index);
auto* item = new CMenuItem (title, nullptr, 0, nullptr, itemFlags);
return addEntry (item, index);
}
//-----------------------------------------------------------------------------
CMenuItem* COptionMenu::addSeparator (int32_t index)
{
auto* item = new CMenuItem ("", nullptr, 0, nullptr, CMenuItem::kSeparator);
return addEntry (item, index);
}
//-----------------------------------------------------------------------------
CMenuItem* COptionMenu::getCurrent () const
{
return getEntry (currentIndex);
}
//-----------------------------------------------------------------------------
CMenuItem* COptionMenu::getEntry (int32_t index) const
{
if (index < 0 || menuItems->empty () || index >= getNbEntries ())
return nullptr;
return (*menuItems)[static_cast<size_t> (index)];
}
//-----------------------------------------------------------------------------
int32_t COptionMenu::getNbEntries () const
{
return static_cast<int32_t> (menuItems->size ());
}
//------------------------------------------------------------------------
COptionMenu* COptionMenu::getSubMenu (int32_t idx) const
{
CMenuItem* item = getEntry (idx);
if (item)
return item->getSubmenu ();
return nullptr;
}
//------------------------------------------------------------------------
int32_t COptionMenu::getCurrentIndex (bool countSeparator) const
{
if (countSeparator)
return currentIndex;
int32_t i = 0;
int32_t numSeparators = 0;
for (auto& item : *menuItems)
{
if (item->isSeparator ())
numSeparators++;
if (i == currentIndex)
break;
i++;
}
return currentIndex - numSeparators;
}
//------------------------------------------------------------------------
bool COptionMenu::setCurrent (int32_t index, bool countSeparator)
{
CMenuItem* item = nullptr;
if (countSeparator)
{
item = getEntry (index);
if (!item || item->isSeparator ())
return false;
currentIndex = index;
}
else
{
int32_t i = 0;
for (auto& menuItem : *menuItems)
{
if (i > index)
break;
if (menuItem->isSeparator ())
index++;
i++;
}
currentIndex = index;
item = getEntry (currentIndex);
}
if (item && style & (kMultipleCheckStyle & ~kCheckStyle))
item->setChecked (!item->isChecked ());
// to force the redraw
setDirty ();
return true;
}
//------------------------------------------------------------------------
bool COptionMenu::removeEntry (int32_t index)
{
if (index < 0 || menuItems->empty () || index >= getNbEntries ())
return false;
menuItems->erase (menuItems->begin () + index);
return true;
}
//------------------------------------------------------------------------
bool COptionMenu::removeAllEntry ()
{
menuItems->clear ();
return true;
}
//------------------------------------------------------------------------
bool COptionMenu::checkEntry (int32_t index, bool state)
{
CMenuItem* item = getEntry (index);
if (item)
{
item->setChecked (state);
return true;
}
return false;
}
//------------------------------------------------------------------------
bool COptionMenu::checkEntryAlone (int32_t index)
{
int32_t pos = 0;
for (auto& item : *menuItems)
{
item->setChecked (pos == index);
pos++;
}
return true;
}
//------------------------------------------------------------------------
bool COptionMenu::isCheckEntry (int32_t index) const
{
CMenuItem* item = getEntry (index);
if (item && item->isChecked ())
return true;
return false;
}
//------------------------------------------------------------------------
void COptionMenu::draw (CDrawContext *pContext)
{
CMenuItem* item = getEntry (currentIndex);
drawBack (pContext, inPopup ? bgWhenClick : nullptr);
if (item)
drawPlatformText (pContext, item->getTitle ());
setDirty (false);
}
//------------------------------------------------------------------------
CMouseEventResult COptionMenu::onMouseDown (CPoint& where, const CButtonState& buttons)
{
lastButton = buttons;
if (lastButton & (kLButton|kRButton|kApple))
{
auto self = shared (this);
getFrame ()->doAfterEventProcessing ([self] () {
self->doPopup ();
});
return kMouseDownEventHandledButDontNeedMovedOrUpEvents;
}
return kMouseEventNotHandled;
}
//------------------------------------------------------------------------
COptionMenu *COptionMenu::getLastItemMenu (int32_t &idxInMenu) const
{
idxInMenu = lastMenu ? (int32_t)lastMenu->getValue (): -1;
return lastMenu;
}
//------------------------------------------------------------------------
void COptionMenu::setValue (float val)
{
auto newIndex = static_cast<int32_t> (std::round (val));
if (newIndex < 0 || newIndex >= getNbEntries ())
return;
currentIndex = newIndex;
if (style & (kMultipleCheckStyle & ~kCheckStyle))
{
CMenuItem* item = getCurrent ();
if (item)
item->setChecked (!item->isChecked ());
}
CParamDisplay::setValue (static_cast<float> (newIndex));
// to force the redraw
setDirty ();
}
//------------------------------------------------------------------------
float COptionMenu::getMax () const
{
if (menuItems->empty ())
return 0.f;
return static_cast<float> (menuItems->size () - 1);
}
//------------------------------------------------------------------------
void COptionMenu::takeFocus ()
{
CParamDisplay::takeFocus ();
}
//------------------------------------------------------------------------
void COptionMenu::looseFocus ()
{
CView* receiver = getParentView () ? getParentView () : getFrame ();
while (receiver)
{
if (receiver->notify (this, kMsgLooseFocus) == kMessageNotified)
break;
receiver = receiver->getParentView ();
}
CParamDisplay::looseFocus ();
}
} // VSTGUI