[lua] Add Plugin:newMenuGroup() to add submenus (fix #3731)

This commit is contained in:
David Capello 2023-03-09 16:41:37 -03:00
parent b43f2a3428
commit 23557a190b
6 changed files with 217 additions and 42 deletions

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019-2022 Igara Studio S.A.
// Copyright (C) 2019-2023 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -283,6 +283,19 @@ AppMenuItem::Native get_native_shortcut_for_command(
return native;
}
void destroy_menu_item(ui::Widget* item)
{
if (item->parent())
item->parent()->removeChild(item);
if (auto appItem = dynamic_cast<AppMenuItem*>(item)) {
if (appItem)
appItem->disposeNative();
}
item->deferDelete();
}
} // anonymous namespace
os::Shortcut get_os_shortcut_from_key(const Key* key)
@ -341,9 +354,12 @@ void AppMenus::reload()
for (auto& it : m_groups) {
GroupInfo& group = it.second;
MENUS_TRACE("MENUS: - groups", it.first, "with", group.items.size(), "item(s)");
group.end = nullptr; // This value will be restored later
for (auto& item : group.items)
item->parent()->removeChild(item);
group.menu->removeChild(item);
// These values will be restored later
group.end = nullptr;
}
////////////////////////////////////////
@ -412,11 +428,13 @@ void AppMenus::reload()
for (auto& it : m_groups) {
GroupInfo& group = it.second;
if (group.end) {
if (group.menu) {
MENUS_TRACE("MENUS: - re-adding group ", it.first, "with", group.items.size(), "item(s)");
auto menu = group.end->parent();
auto menu = group.menu;
int insertIndex = menu->getChildIndex(group.end);
if (insertIndex < 0)
insertIndex = -1;
for (auto& item : group.items) {
menu->insertChild(++insertIndex, item);
group.end = item;
@ -590,14 +608,51 @@ bool AppMenus::rebuildRecentList()
return true;
}
void AppMenus::addMenuGroup(const std::string& groupId,
MenuItem* menuItem)
{
GroupInfo& group = m_groups[groupId];
ASSERT(group.menu == nullptr);
ASSERT(group.end == nullptr);
group.menu = menuItem->getSubmenu();
group.end = nullptr;
}
void AppMenus::removeMenuGroup(const std::string& groupId)
{
auto it = m_groups.find(groupId);
if (it != m_groups.end()) {
GroupInfo& group = it->second;
ASSERT(group.items.empty()); // To remove a group, the group must be empty
if (group.menu->getOwnerMenuItem()) {
ui::MenuItem* item = group.menu->getOwnerMenuItem();
removeMenuItemFromGroup(
[item](Widget* i){
return item == i;
});
}
m_groups.erase(it);
}
}
void AppMenus::addMenuItemIntoGroup(const std::string& groupId,
std::unique_ptr<MenuItem>&& menuItem)
{
GroupInfo& group = m_groups[groupId];
Widget* menu = group.end->parent();
Menu* menu = group.menu;
ASSERT(menu);
int insertIndex = menu->getChildIndex(group.end);
menu->insertChild(insertIndex+1, menuItem.get());
if (group.end) {
int insertIndex = menu->getChildIndex(group.end);
ASSERT(insertIndex >= 0);
menu->insertChild(insertIndex+1, menuItem.get());
}
else {
menu->addChild(menuItem.get());
}
group.end = menuItem.get();
group.items.push_back(menuItem.get());
@ -616,12 +671,7 @@ void AppMenus::removeMenuItemFromGroup(Pred pred)
if (item == group.end)
group.end = group.end->previousSibling();
item->parent()->removeChild(item);
if (auto appItem = dynamic_cast<AppMenuItem*>(item)) {
if (appItem)
appItem->disposeNative();
}
item->deferDelete();
destroy_menu_item(item);
it = group.items.erase(it);
}
@ -679,7 +729,7 @@ Menu* AppMenus::convertXmlelemToMenu(TiXmlElement* elem)
TiXmlElement* child = elem->FirstChildElement();
while (child) {
Widget* menuitem = convertXmlelemToMenuitem(child);
Widget* menuitem = convertXmlelemToMenuitem(child, menu);
if (menuitem)
menu->addChild(menuitem);
else
@ -692,7 +742,7 @@ Menu* AppMenus::convertXmlelemToMenu(TiXmlElement* elem)
return menu;
}
Widget* AppMenus::convertXmlelemToMenuitem(TiXmlElement* elem)
Widget* AppMenus::convertXmlelemToMenuitem(TiXmlElement* elem, Menu* menu)
{
const char* id = elem->Attribute("id");
const char* group = elem->Attribute("group");
@ -709,8 +759,10 @@ Widget* AppMenus::convertXmlelemToMenuitem(TiXmlElement* elem)
m_recentFilesPlaceholder = item;
}
}
if (group)
if (group) {
m_groups[group].menu = menu;
m_groups[group].end = item;
}
return item;
}
@ -743,8 +795,10 @@ Widget* AppMenus::convertXmlelemToMenuitem(TiXmlElement* elem)
if (id) menuitem->setId(id);
menuitem->processMnemonicFromText();
if (group)
if (group) {
m_groups[group].menu = menu;
m_groups[group].end = menuitem;
}
if (standard && strcmp(standard, "edit") == 0)
menuitem->setStandardEditMenu();

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2019-2023 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -60,6 +60,9 @@ namespace app {
void syncNativeMenuItemKeyShortcuts();
// Menu item handling in groups
void addMenuGroup(const std::string& groupId,
MenuItem* menuItem);
void removeMenuGroup(const std::string& groupId);
void addMenuItemIntoGroup(const std::string& groupId,
std::unique_ptr<MenuItem>&& menuItem);
void removeMenuItemFromGroup(Command* cmd);
@ -71,7 +74,7 @@ namespace app {
Menu* loadMenuById(TiXmlHandle& handle, const char *id);
Menu* convertXmlelemToMenu(TiXmlElement* elem);
Widget* convertXmlelemToMenuitem(TiXmlElement* elem);
Widget* convertXmlelemToMenuitem(TiXmlElement* elem, Menu* menu);
void applyShortcutToMenuitemsWithCommand(Menu* menu, Command* command, const Params& params,
const KeyPtr& key);
void syncNativeMenuItemKeyShortcuts(Menu* menu);
@ -87,6 +90,7 @@ namespace app {
#endif
struct GroupInfo {
Menu* menu = nullptr;
Widget* end = nullptr;
WidgetsList items;
};

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2020-2022 Igara Studio S.A.
// Copyright (C) 2020-2023 Igara Studio S.A.
// Copyright (C) 2017-2018 David Capello
//
// This program is distributed under the terms of
@ -315,6 +315,27 @@ void Extension::removeCommand(const std::string& id)
}
}
void Extension::addMenuGroup(const std::string& id)
{
PluginItem item;
item.type = PluginItem::MenuGroup;
item.id = id;
m_plugin.items.push_back(item);
}
void Extension::removeMenuGroup(const std::string& id)
{
for (auto it=m_plugin.items.begin(); it != m_plugin.items.end(); ) {
if (it->type == PluginItem::MenuGroup &&
it->id == id) {
it = m_plugin.items.erase(it);
}
else {
++it;
}
}
}
#endif
bool Extension::canBeDisabled() const
@ -685,30 +706,39 @@ void Extension::exitScripts()
m_plugin.pluginRef = LUA_REFNIL;
}
// Remove plugin items automatically
// Remove plugin items automatically (first commands, then menu
// groups)
for (const auto& item : m_plugin.items) {
switch (item.type) {
case PluginItem::Command: {
auto cmds = Commands::instance();
auto cmd = cmds->byId(item.id.c_str());
ASSERT(cmd);
if (item.type == PluginItem::Command) {
auto cmds = Commands::instance();
auto cmd = cmds->byId(item.id.c_str());
ASSERT(cmd);
if (cmd) {
if (cmd) {
#ifdef ENABLE_UI
// TODO use a signal
AppMenus::instance()->removeMenuItemFromGroup(cmd);
// TODO use a signal
AppMenus::instance()->removeMenuItemFromGroup(cmd);
#endif // ENABLE_UI
cmds->remove(cmd);
cmds->remove(cmd);
// This will call ~PluginCommand() and unref the command
// onclick callback.
delete cmd;
}
break;
// This will call ~PluginCommand() and unref the command
// onclick callback.
delete cmd;
}
}
}
for (const auto& item : m_plugin.items) {
if (item.type == PluginItem::MenuGroup) {
#ifdef ENABLE_UI
// TODO use a signal
AppMenus::instance()->removeMenuGroup(item.id);
#endif // ENABLE_UI
break;
}
}
m_plugin.items.clear();
}

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2020-2022 Igara Studio S.A.
// Copyright (C) 2020-2023 Igara Studio S.A.
// Copyright (C) 2017-2018 David Capello
//
// This program is distributed under the terms of
@ -111,6 +111,9 @@ namespace app {
#ifdef ENABLE_SCRIPTING
void addCommand(const std::string& id);
void removeCommand(const std::string& id);
void addMenuGroup(const std::string& id);
void removeMenuGroup(const std::string& id);
#endif
bool isEnabled() const { return m_isEnabled; }
@ -155,7 +158,7 @@ namespace app {
ScriptItem(const std::string& fn);
};
struct PluginItem {
enum Type { Command };
enum Type { Command, MenuGroup };
Type type;
std::string id;
};

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2022 Igara Studio S.A.
// Copyright (C) 2018-2023 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -10,6 +10,6 @@
// Increment this value if the scripting API is modified between two
// released Aseprite versions.
#define API_VERSION 21
#define API_VERSION 22
#endif

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2020-2022 Igara Studio S.A.
// Copyright (C) 2020-2023 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -108,6 +108,16 @@ void deleteCommandIfExistent(Extension* ext, const std::string& id)
}
}
void deleteMenuGroupIfExistent(Extension* ext, const std::string& id)
{
#ifdef ENABLE_UI
if (auto appMenus = AppMenus::instance())
appMenus->removeMenuGroup(id);
#endif
ext->removeMenuGroup(id);
}
int Plugin_gc(lua_State* L)
{
get_obj<Plugin>(L, 1)->~Plugin();
@ -167,7 +177,7 @@ int Plugin_newCommand(lua_State* L)
if (!group.empty() &&
App::instance()->isGui()) { // On CLI menus do not make sense
if (auto appMenus = AppMenus::instance()) {
std::unique_ptr<MenuItem> menuItem(new AppMenuItem(title, id));
auto menuItem = std::make_unique<AppMenuItem>(title, id);
appMenus->addMenuItemIntoGroup(group, std::move(menuItem));
}
}
@ -204,6 +214,78 @@ int Plugin_deleteCommand(lua_State* L)
return 0;
}
int Plugin_newMenuGroup(lua_State* L)
{
auto plugin = get_obj<Plugin>(L, 1);
if (lua_istable(L, 2)) {
std::string id, title, group;
lua_getfield(L, 2, "id"); // This new group ID
if (const char* s = lua_tostring(L, -1)) {
id = s;
}
lua_pop(L, 1);
if (id.empty())
return luaL_error(L, "Empty id field in plugin:newCommand{ id=... }");
lua_getfield(L, 2, "title");
if (const char* s = lua_tostring(L, -1)) {
title = s;
}
lua_pop(L, 1);
lua_getfield(L, 2, "group"); // Parent group
if (const char* s = lua_tostring(L, -1)) {
group = s;
}
lua_pop(L, 1);
// Delete the group if it already exist (e.g. we are overwriting a
// previous registered group)
deleteMenuGroupIfExistent(plugin->ext, id);
plugin->ext->addMenuGroup(id);
#ifdef ENABLE_UI
// Add a new menu option if the "group" is defined
if (!group.empty() &&
App::instance()->isGui()) { // On CLI menus do not make sense
if (auto appMenus = AppMenus::instance()) {
auto menuItem = std::make_unique<AppMenuItem>(title, id);
menuItem->setSubmenu(new Menu);
appMenus->addMenuGroup(id, menuItem.get());
appMenus->addMenuItemIntoGroup(group, std::move(menuItem));
}
}
#endif // ENABLE_UI
}
return 0;
}
int Plugin_deleteMenuGroup(lua_State* L)
{
std::string id;
auto plugin = get_obj<Plugin>(L, 1);
if (lua_istable(L, 2)) {
lua_getfield(L, 2, "id");
if (const char* s = lua_tostring(L, -1)) {
id = s;
}
lua_pop(L, 1);
}
else if (const char* s = lua_tostring(L, 2)) {
id = s;
}
if (id.empty())
return luaL_error(L, "No menu group id specified in plugin:deleteMenuGroup()");
deleteMenuGroupIfExistent(plugin->ext, id);
return 0;
}
int Plugin_get_preferences(lua_State* L)
{
if (!lua_getuservalue(L, 1)) {
@ -225,6 +307,8 @@ const luaL_Reg Plugin_methods[] = {
{ "__gc", Plugin_gc },
{ "newCommand", Plugin_newCommand },
{ "deleteCommand", Plugin_deleteCommand },
{ "newMenuGroup", Plugin_newMenuGroup },
{ "deleteMenuGroup", Plugin_deleteMenuGroup },
{ nullptr, nullptr }
};