Initial release

This commit is contained in:
civ
2026-08-16 18:24:52 +07:00
commit 876886a39a
13244 changed files with 2353959 additions and 0 deletions
@@ -0,0 +1,806 @@
// 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 "uieditmenucontroller.h"
#if VSTGUI_LIVE_EDITING
#include "uiactions.h"
#include "uieditview.h"
#include "uieditcontroller.h"
#include "../iviewfactory.h"
#include "../uiattributes.h"
#include "../../lib/cvstguitimer.h"
#include "../../lib/events.h"
#include "../../lib/controls/coptionmenu.h"
#include "../../lib/controls/ctextlabel.h"
#include "../detail/uiviewcreatorattributes.h"
#include <cctype>
namespace VSTGUI {
//----------------------------------------------------------------------------------------------------
UIEditMenuController::UIEditMenuController (IController* baseController, UISelection* selection, UIUndoManager* undoManager, UIDescription* description, IActionPerformer* actionPerformer)
: DelegationController (baseController)
, selection (selection)
, undoManager (undoManager)
, description (description)
, actionPerformer (actionPerformer)
{
}
//------------------------------------------------------------------------
UIEditMenuController::~UIEditMenuController () noexcept
{
if (editMenu)
editMenu->unregisterViewListener (this);
if (fileMenu)
fileMenu->unregisterViewListener (this);
}
//----------------------------------------------------------------------------------------------------
static void addEntriesToMenu (const UIEditing::MenuEntry* entries, COptionMenu* menu, ICommandMenuItemTarget* menuItemTarget, int32_t& index)
{
while (entries[index].category != nullptr)
{
if (entries[index].menuFlags & UIEditing::MenuEntry::kSubMenuEnd)
{
break;
}
if (entries[index].category == UIEditing::kMenuSeparator.category)
menu->addSeparator ();
else if (entries[index].menuFlags & UIEditing::MenuEntry::kSubMenu)
{
auto subMenu = makeOwned<COptionMenu> ();
if (entries[index].menuFlags & UIEditing::MenuEntry::kSubMenuCheckStyle)
subMenu->setStyle (COptionMenu::kMultipleCheckStyle|COptionMenu::kCheckStyle);
menu->addEntry (new CMenuItem (entries[index].name, subMenu));
index++;
addEntriesToMenu(entries, subMenu, menuItemTarget, index);
}
else
{
CMenuItem* item = menu->addEntry (new CCommandMenuItem ({entries[index].name, menuItemTarget, entries[index].category, entries[index].name}));
if (entries[index].key)
{
item->setKey (entries[index].key, entries[index].modifier);
}
else if (entries[index].virtualKey != VirtualKey::None)
{
item->setVirtualKey (entries[index].virtualKey, entries[index].modifier);
}
if (entries[index].menuFlags)
{
if (entries[index].menuFlags & UIEditing::MenuEntry::kMenuItemIsTitle)
{
item->setIsTitle (true);
}
}
}
index++;
}
}
//----------------------------------------------------------------------------------------------------
void UIEditMenuController::createFileMenu (COptionMenu* menu)
{
int32_t index = 0;
addEntriesToMenu (UIEditing::fileMenu, menu, this, index);
}
//----------------------------------------------------------------------------------------------------
void UIEditMenuController::createEditMenu (COptionMenu* menu)
{
int32_t index = 0;
menu->setStyle (menu->getStyle () | COptionMenu::kMultipleCheckStyle);
addEntriesToMenu (UIEditing::editMenu, menu, this, index);
}
//----------------------------------------------------------------------------------------------------
bool UIEditMenuController::createUniqueTemplateName (std::list<const std::string*>& names, std::string& name)
{
for (auto& it : names)
{
if (*it == name)
{
int32_t count = 0;
size_t pos = name.find_last_not_of ("0123456789");
if (pos != std::string::npos && pos != name.length () - 1)
{
std::string numberStr = name.substr (pos);
count = static_cast<int32_t> (strtol (numberStr.c_str (), nullptr, 10)) + 1;
name.erase (pos+1);
}
else
{
count = 1;
}
while (name.length () && std::isspace (name[name.length ()-1]))
name.erase (name.length ()-1);
char number[10];
snprintf (number, 10, "%d", count);
name += ' ';
name += number;
return createUniqueTemplateName (names, name);
}
}
return true;
}
//----------------------------------------------------------------------------------------------------
bool UIEditMenuController::validateCommandMenuItem (CCommandMenuItem* item)
{
return validateMenuItem (*item);
}
//----------------------------------------------------------------------------------------------------
bool UIEditMenuController::onCommandMenuItemSelected (CCommandMenuItem* item)
{
return handleCommand (item->getCommandCategory (), item->getCommandName ());
}
//----------------------------------------------------------------------------------------------------
CMessageResult UIEditMenuController::notify (CBaseObject* sender, IdStringPtr message)
{
if (message == CVSTGUITimer::kMsgTimer)
{
editLabel->setTransparency (true);
fileLabel->setTransparency (true);
highlightTimer = nullptr;
}
return kMessageUnknown;
}
//------------------------------------------------------------------------
bool UIEditMenuController::validateMenuItem (CCommandMenuItem& item)
{
UTF8StringView cmdCategory (item.getCommandCategory ());
UTF8StringView cmdName (item.getCommandName ());
item.setChecked (false);
if (cmdCategory == "Edit")
{
if (cmdName == "Undo")
{
if (undoManager->canUndo ())
{
std::string str ("Undo ");
str += undoManager->getUndoName ();
item.setTitle (str.c_str ());
item.setEnabled (true);
}
else
{
item.setTitle ("Undo");
item.setEnabled (false);
}
return true;
}
else if (cmdName == "Redo")
{
if (undoManager->canRedo ())
{
std::string str ("Redo ");
str += undoManager->getRedoName ();
item.setTitle (str.c_str ());
item.setEnabled (true);
}
else
{
item.setTitle ("Redo");
item.setEnabled (false);
}
return true;
}
else if (cmdName == "Cut")
{
item.setEnabled (false);
}
else if (cmdName == "Copy")
{
item.setEnabled (false);
}
else if (cmdName == "Paste")
{
item.setEnabled (false);
}
else if (cmdName == "Delete")
{
CView* view = selection->first ();
int32_t selectionCount = selection->total ();
bool enable = view ? (selectionCount > 1 ? true : dynamic_cast<UIEditView*> (view->getParentView ()) == nullptr) : false;
item.setEnabled (enable);
return true;
}
else if (cmdName == "Add New Template")
{
if (const auto* factory = dynamic_cast<const IViewFactoryEditingSupport*> (
description->getViewFactory ()))
{
auto submenu = makeOwned<COptionMenu> ();
auto viewAndDisplayNames =
factory->collectRegisteredViewAndDisplayNames ("CViewContainer");
viewAndDisplayNames.sort (
[] (const auto& lhs, const auto& rhs) { return lhs.second < rhs.second; });
for (auto& entry : viewAndDisplayNames)
{
submenu->addEntry (new CCommandMenuItem (CCommandMenuItem::Desc {
entry.second.data (), this, "AddTemplate", entry.first->data ()}));
}
item.setSubmenu (submenu);
}
return true;
}
else if (cmdName == "Delete Template")
{
item.setSubmenu (nullptr);
std::list<const std::string*> templateNames;
description->collectTemplateViewNames (templateNames);
item.setEnabled (templateNames.empty () == false);
if (templateNames.empty () == false)
{
templateNames.sort (UIEditController::std__stringCompare);
auto submenu = makeOwned<COptionMenu> ();
item.setSubmenu (submenu);
for (auto& name : templateNames)
{
submenu->addEntry (new CCommandMenuItem (CCommandMenuItem::Desc{name->data (), this, "RemoveTemplate", name->data ()}));
}
}
return true;
}
else if (cmdName == "Duplicate Template")
{
item.setSubmenu (nullptr);
std::list<const std::string*> templateNames;
description->collectTemplateViewNames (templateNames);
item.setEnabled (templateNames.empty () == false);
if (templateNames.empty () == false)
{
templateNames.sort (UIEditController::std__stringCompare);
auto submenu = makeOwned<COptionMenu> ();
item.setSubmenu (submenu);
for (auto& name : templateNames)
{
submenu->addEntry (new CCommandMenuItem (CCommandMenuItem::Desc{name->data (), this, "DuplicateTemplate", name->data ()}));
}
}
return true;
}
else if (cmdName == "Embed Into")
{
item.setSubmenu (nullptr);
bool enable = selection->total () > 0;
for (auto view : *selection)
{
if (dynamic_cast<UIEditView*>(view->getParentView()) != nullptr)
{
enable = false;
break;
}
}
item.setEnabled (enable);
if (enable == false)
return true;
if (const auto* factory = dynamic_cast<const IViewFactoryEditingSupport*> (
description->getViewFactory ()))
{
auto submenu = makeOwned<COptionMenu> ();
item.setSubmenu (submenu);
auto viewAndDisplayNames =
factory->collectRegisteredViewAndDisplayNames ("CViewContainer");
viewAndDisplayNames.sort (
[] (const auto& lhs, const auto& rhs) { return lhs.second < rhs.second; });
for (auto& entry : viewAndDisplayNames)
{
submenu->addEntry (new CCommandMenuItem (CCommandMenuItem::Desc {
entry.second.data (), this, "Embed", entry.first->data ()}));
}
}
return true;
}
else if (cmdName == "Unembed Views")
{
bool enabled = false;
if (selection->total () == 1)
{
CViewContainer* container = selection->first ()->asViewContainer ();
if (container && container->hasChildren () && dynamic_cast<UIEditView*>(container->getParentView ()) == nullptr)
enabled = true;
}
item.setEnabled (enabled);
return true;
}
else if (cmdName == "Size To Fit")
{
item.setEnabled (selection->total() > 0 ? true : false);
return true;
}
else if (cmdName == "Transform View Type")
{
item.setSubmenu (nullptr);
auto numViewContainers = 0u;
auto numNonViewContainers = 0u;
for (auto& entry : *selection)
{
if (entry->asViewContainer ())
++numViewContainers;
else
++numNonViewContainers;
}
if (numViewContainers != 0 && numNonViewContainers != 0)
{
item.setEnabled (false);
return true;
}
if (const auto* factory = dynamic_cast<const IViewFactoryEditingSupport*> (
description->getViewFactory ()))
{
auto submenu = makeOwned<COptionMenu> ();
item.setSubmenu (submenu);
auto viewAndDisplayNames = factory->collectRegisteredViewAndDisplayNames ();
viewAndDisplayNames.sort (
[] (const auto& lhs, const auto& rhs) { return lhs.second < rhs.second; });
for (auto& entry : viewAndDisplayNames)
{
submenu->addEntry (new CCommandMenuItem (CCommandMenuItem::Desc {
entry.second.data (), this, "Transform View Type", entry.first->data ()}));
}
}
return true;
}
else if (cmdName == "Insert Template")
{
item.setSubmenu (nullptr);
item.setEnabled (selection->total () == 1 && selection->first ()->asViewContainer ());
if (item.isEnabled () == false)
return true;
std::list<const std::string*> templateNames;
description->collectTemplateViewNames (templateNames);
item.setEnabled (templateNames.empty () == false);
if (templateNames.empty () == false)
{
templateNames.sort (UIEditController::std__stringCompare);
auto submenu = makeOwned<COptionMenu> ();
item.setSubmenu (submenu);
for (auto& name : templateNames)
{
submenu->addEntry (new CCommandMenuItem (CCommandMenuItem::Desc{name->data (), this, "InsertTemplate", name->data ()}));
}
}
return true;
}
}
else if (cmdCategory == "Selection")
{
if (cmdName == "Select Children Of Type")
{
if (const auto* factory = dynamic_cast<const IViewFactoryEditingSupport*> (
description->getViewFactory ()))
{
auto submenu = makeOwned<COptionMenu> ();
item.setSubmenu (submenu);
auto viewAndDisplayNames = factory->collectRegisteredViewAndDisplayNames ();
viewAndDisplayNames.sort (
[] (const auto& lhs, const auto& rhs) { return lhs.second < rhs.second; });
for (auto& entry : viewAndDisplayNames)
{
submenu->addEntry (new CCommandMenuItem (
CCommandMenuItem::Desc {entry.second.data (), this,
"Select Children Of Type", entry.first->data ()}));
}
}
return true;
}
}
if (auto obj = dynamic_cast<ICommandMenuItemTarget*> (controller))
return obj->validateCommandMenuItem (&item);
return false;
}
//----------------------------------------------------------------------------------------------------
CCommandMenuItem* UIEditMenuController::findKeyCommandItem (COptionMenu* menu, const KeyboardEvent& event)
{
for (auto& item : *menu->getItems ())
{
COptionMenu* subMenu = item->getSubmenu ();
if (subMenu)
{
CCommandMenuItem* result = findKeyCommandItem (subMenu, event);
if (result)
return result;
}
CCommandMenuItem* result = item.cast<CCommandMenuItem>();
if (result)
{
int32_t modifier = 0;
if (event.modifiers.has (ModifierKey::Shift))
modifier |= kShift;
if (event.modifiers.has (ModifierKey::Alt))
modifier |= kAlt;
if (event.modifiers.has (ModifierKey::Control))
modifier |= kControl;
if (event.modifiers.has (ModifierKey::Super))
modifier |= kApple;
if (result->getKeyModifiers () == modifier)
{
if (event.virt != VirtualKey::None && event.virt == result->getVirtualKey ())
{
return result;
}
else if (!result->getKeycode ().empty () &&
static_cast<char32_t> (result->getKeycode ().getString ()[0]) ==
event.character)
{
return result;
}
}
}
}
return nullptr;
}
//------------------------------------------------------------------------
bool UIEditMenuController::handleCommand (const UTF8StringPtr category, const UTF8StringPtr name)
{
UTF8StringView cmdCategory (category);
UTF8StringView cmdName (name);
if (cmdCategory == "Edit")
{
if (cmdName == "Undo")
{
if (undoManager->canUndo ())
{
undoManager->performUndo ();
}
return true;
}
else if (cmdName == "Redo")
{
if (undoManager->canRedo ())
{
undoManager->performRedo ();
}
return true;
}
else if (cmdName == "Delete")
{
IAction* action = new DeleteOperation (selection);
undoManager->pushAndPerform (action);
return true;
}
else if (cmdName == "Unembed Views")
{
IAction* action = new UnembedViewOperation (selection, description->getViewFactory ());
undoManager->pushAndPerform (action);
return true;
}
else if (cmdName == "Size To Fit")
{
IAction* action = new SizeToFitOperation (selection);
undoManager->pushAndPerform (action);
return true;
}
}
if (cmdCategory == "AddTemplate")
{
std::list<const std::string*> tmp;
description->collectTemplateViewNames (tmp);
std::string templateName (cmdName);
if (createUniqueTemplateName (tmp, templateName))
{
actionPerformer->performCreateNewTemplate (templateName.c_str (), cmdName);
}
return true;
}
else if (cmdCategory == "RemoveTemplate")
{
actionPerformer->performDeleteTemplate (cmdName);
return kMessageNotified;
}
else if (cmdCategory == "DuplicateTemplate")
{
std::list<const std::string*> tmp;
description->collectTemplateViewNames (tmp);
std::string templateName (cmdName);
if (createUniqueTemplateName (tmp, templateName))
{
actionPerformer->performDuplicateTemplate (cmdName, templateName.c_str ());
}
return true;
}
else if (cmdCategory == "Embed")
{
const IViewFactory* viewFactory = description->getViewFactory ();
UIAttributes viewAttr;
viewAttr.setAttribute (UIViewCreator::kAttrClass, std::string (cmdName));
if (auto newContainer = viewFactory->createView (viewAttr, description)->asViewContainer ())
{
IAction* action = new EmbedViewOperation (selection, newContainer);
undoManager->pushAndPerform (action);
}
return true;
}
else if (cmdCategory == "Transform View Type")
{
undoManager->startGroupAction ("Transform View Type");
for (auto& entry : *selection)
{
IAction* action = new TransformViewTypeOperation (
selection, entry, cmdName, description, description->getViewFactory ());
undoManager->pushAndPerform (action);
}
undoManager->endGroupAction ();
return true;
}
else if (cmdCategory == "Select Children Of Type")
{
const auto* viewFactory = description->getViewFactory ();
if (!viewFactory)
return false;
std::vector<CView*> newSelection;
for (auto& entry : *selection)
{
if (auto viewContainer = entry->asViewContainer ())
getChildrenOfType (viewContainer, cmdName, newSelection);
}
selection->clear ();
for (auto& view : newSelection)
selection->add (view);
return true;
}
else if (cmdCategory == "InsertTemplate")
{
if (auto parent = selection->first ()->asViewContainer ())
{
CView* view = description->createView (cmdName, description->getController ());
if (view)
{
undoManager->pushAndPerform (new InsertViewOperation (parent, view, selection));
}
}
return true;
}
if (auto obj = dynamic_cast<ICommandMenuItemTarget*> (controller))
{
CCommandMenuItem item (CCommandMenuItem::Desc{"", 0, nullptr, category, name});
if (obj->onCommandMenuItemSelected (&item))
return true;
}
return false;
}
//------------------------------------------------------------------------
bool UIEditMenuController::canHandleCommand (const UTF8StringPtr category, const UTF8StringPtr name) const
{
CCommandMenuItem item (CCommandMenuItem::Desc{"", 0, nullptr, category, name});
if (const_cast<UIEditMenuController*> (this)->validateMenuItem (item))
{
return item.isEnabled ();
}
return false;
}
//----------------------------------------------------------------------------------------------------
void UIEditMenuController::processKeyCommand (KeyboardEvent& event)
{
COptionMenu* baseMenu = editMenu;
CCommandMenuItem* item = baseMenu ? findKeyCommandItem (baseMenu, event) : nullptr;
if (item == nullptr && fileMenu)
{
baseMenu = fileMenu;
item = findKeyCommandItem (baseMenu, event);
}
if (item && item->getItemTarget ())
{
item->getItemTarget ()->validateCommandMenuItem (item);
if (item->isEnabled ())
{
CTextLabel* label = nullptr;
if (baseMenu)
{
switch (baseMenu->getTag ())
{
case kMenuFileTag:
{
label = fileLabel;
break;
}
case kMenuEditTag:
{
label = editLabel;
break;
}
}
}
if (label)
{
label->setTransparency (false);
}
item->getItemTarget ()->onCommandMenuItemSelected (item);
if (label)
{
highlightTimer = makeOwned<CVSTGUITimer> (this, 90u, true);
}
event.consumed = true;
}
}
}
//----------------------------------------------------------------------------------------------------
static void copyMenuItems (COptionMenu* src, COptionMenu* dst)
{
auto srcItems = src->getItems ();
if (!srcItems)
return;
for (auto& item : *srcItems)
{
item->remember ();
dst->addEntry (item);
}
}
//------------------------------------------------------------------------
void UIEditMenuController::viewRemoved (CView* view)
{
if (view == editMenu)
{
view->unregisterViewListener (this);
editMenu = nullptr;
}
else if (view == fileMenu)
{
view->unregisterViewListener (this);
fileMenu = nullptr;
}
}
//----------------------------------------------------------------------------------------------------
CView* UIEditMenuController::verifyView (CView* view, const UIAttributes& attributes, const IUIDescription*)
{
COptionMenu* menu = dynamic_cast<COptionMenu*>(view);
if (menu)
{
switch (menu->getTag ())
{
case kMenuEditTag:
{
if (editMenu)
copyMenuItems (editMenu, menu);
else
{
createEditMenu (menu);
menu->registerViewListener (this);
}
editMenu = menu;
break;
}
case kMenuFileTag:
{
if (fileMenu)
copyMenuItems (fileMenu, menu);
else
{
createFileMenu (menu);
menu->registerViewListener (this);
}
fileMenu = menu;
break;
}
}
}
else
{
CTextLabel* label = dynamic_cast<CTextLabel*>(view);
if (label)
{
switch (label->getTag ())
{
case kMenuEditTag:
{
editLabel = label;
break;
}
case kMenuFileTag:
{
fileLabel = label;
break;
}
}
}
}
return view;
}
//----------------------------------------------------------------------------------------------------
void UIEditMenuController::valueChanged (CControl* control)
{
switch (control->getTag ())
{
case kMenuFileTag:
{
if (fileMenu && control->getValue () == control->getMax ())
{
CRect r (control->getViewSize ());
CPoint p = r.getBottomLeft ();
control->localToFrame (p);
fileMenu->popup (control->getFrame (), p);
}
break;
}
case kMenuEditTag:
{
if (editMenu && control->getValue () == control->getMax ())
{
CRect r (control->getViewSize ());
CPoint p = r.getTopLeft ();
control->localToFrame (p);
editMenu->popup (control->getFrame (), p);
}
break;
}
}
}
//----------------------------------------------------------------------------------------------------
void UIEditMenuController::controlBeginEdit (CControl* pControl)
{
CTextLabel* label = nullptr;
switch (pControl->getTag ())
{
case kMenuFileTag:
{
fileMenu->cleanupSeparators (true);
label = fileLabel;
break;
}
case kMenuEditTag:
{
editMenu->cleanupSeparators (true);
label = editLabel;
break;
}
}
if (label)
label->setTransparency (false);
}
//----------------------------------------------------------------------------------------------------
void UIEditMenuController::controlEndEdit (CControl* pControl)
{
CTextLabel* label = nullptr;
switch (pControl->getTag ())
{
case kMenuFileTag:
{
label = fileLabel;
break;
}
case kMenuEditTag:
{
label = editLabel;
break;
}
}
if (label)
label->setTransparency (true);
}
//------------------------------------------------------------------------
void UIEditMenuController::getChildrenOfType (CViewContainer* container, UTF8StringView type, std::vector<CView*>& result) const
{
container->forEachChild ([&] (auto view) {
if (type == IViewFactory::getViewName (view))
result.emplace_back (view);
if (auto c = view->asViewContainer ())
{
getChildrenOfType (c, type, result);
}
});
}
//------------------------------------------------------------------------
} // VSTGUI
#endif // VSTGUI_LIVE_EDITING