mirror of
https://github.com/aseprite/aseprite.git
synced 2024-10-06 14:59:50 +00:00
[lua] Add Plugin:newMenuGroup() to add submenus (fix #3731)
This commit is contained in:
parent
b43f2a3428
commit
23557a190b
@ -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);
|
||||
|
||||
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();
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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,10 +706,10 @@ 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: {
|
||||
if (item.type == PluginItem::Command) {
|
||||
auto cmds = Commands::instance();
|
||||
auto cmd = cmds->byId(item.id.c_str());
|
||||
ASSERT(cmd);
|
||||
@ -705,10 +726,19 @@ void Extension::exitScripts()
|
||||
// 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();
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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 }
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user