aseprite/src/app/script/plugin_class.cpp

415 lines
9.8 KiB
C++

// Aseprite
// Copyright (C) 2020-2023 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/app.h"
#include "app/app_menus.h"
#include "app/commands/command.h"
#include "app/commands/commands.h"
#include "app/console.h"
#include "app/script/engine.h"
#include "app/script/luacpp.h"
#include "app/ui/app_menuitem.h"
#include "base/version.h"
namespace app { namespace script {
namespace {
struct Plugin {
Extension* ext;
Plugin(Extension* ext) : ext(ext) {}
};
class PluginCommand : public Command {
public:
PluginCommand(const std::string& id,
const std::string& title,
int onclickRef,
int onenabledRef,
int oncheckedRef)
: Command(id.c_str(), CmdUIOnlyFlag)
, m_title(title)
, m_onclickRef(onclickRef)
, m_onenabledRef(onenabledRef)
, m_oncheckedRef(oncheckedRef)
{
}
~PluginCommand()
{
auto app = App::instance();
ASSERT(app);
if (!app)
return;
if (script::Engine* engine = app->scriptEngine()) {
lua_State* L = engine->luaState();
luaL_unref(L, LUA_REGISTRYINDEX, m_onclickRef);
}
}
protected:
std::string onGetFriendlyName() const override { return m_title; }
void onExecute(Context* context) override
{
script::Engine* engine = App::instance()->scriptEngine();
lua_State* L = engine->luaState();
lua_rawgeti(L, LUA_REGISTRYINDEX, m_onclickRef);
if (lua_pcall(L, 0, 1, 0)) {
if (const char* s = lua_tostring(L, -1)) {
Console().printf("Error: %s", s);
}
}
else {
lua_pop(L, 1);
}
}
bool onEnabled(Context* context) override
{
if (m_onenabledRef) {
return callScriptRef(m_onenabledRef);
}
return true;
}
bool onChecked(Context* context) override
{
if (m_oncheckedRef) {
return callScriptRef(m_oncheckedRef);
}
return false;
}
private:
bool callScriptRef(int ref)
{
ASSERT(ref);
script::Engine* engine = App::instance()->scriptEngine();
lua_State* L = engine->luaState();
lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
if (lua_pcall(L, 0, 1, 0)) {
if (const char* s = lua_tostring(L, -1)) {
Console().printf("Error: %s", s);
return false;
}
}
else {
bool ret = lua_toboolean(L, -1);
lua_pop(L, 1);
return ret;
}
}
std::string m_title;
int m_onclickRef;
int m_onenabledRef;
int m_oncheckedRef;
};
void deleteCommandIfExistent(Extension* ext, const std::string& id)
{
auto cmd = Commands::instance()->byId(id.c_str());
if (cmd) {
Commands::instance()->remove(cmd);
ext->removeCommand(id);
delete cmd;
}
}
void deleteMenuGroupIfExistent(Extension* ext, const std::string& id)
{
if (auto* appMenus = AppMenus::instance())
appMenus->removeMenuGroup(id);
ext->removeMenuGroup(id);
}
int Plugin_gc(lua_State* L)
{
get_obj<Plugin>(L, 1)->~Plugin();
return 0;
}
int Plugin_newCommand(lua_State* L)
{
auto plugin = get_obj<Plugin>(L, 1);
if (lua_istable(L, 2)) {
std::string id, title, group;
int onenabledRef = 0;
int oncheckedRef = 0;
lua_getfield(L, 2, "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");
if (const char* s = lua_tostring(L, -1)) {
group = s;
}
lua_pop(L, 1);
int type = lua_getfield(L, 2, "onenabled");
if (type == LUA_TFUNCTION) {
onenabledRef = luaL_ref(L, LUA_REGISTRYINDEX); // does a pop
}
else {
lua_pop(L, 1);
}
type = lua_getfield(L, 2, "onchecked");
if (type == LUA_TFUNCTION) {
oncheckedRef = luaL_ref(L, LUA_REGISTRYINDEX); // does a pop
}
else {
lua_pop(L, 1);
}
type = lua_getfield(L, 2, "onclick");
if (type == LUA_TFUNCTION) {
int onclickRef = luaL_ref(L, LUA_REGISTRYINDEX);
// Delete the command if it already exist (e.g. we are
// overwriting a previous registered command)
deleteCommandIfExistent(plugin->ext, id);
auto cmd = new PluginCommand(id, title, onclickRef, onenabledRef, oncheckedRef);
Commands::instance()->add(cmd);
plugin->ext->addCommand(id);
// 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->processMnemonicFromText();
appMenus->addMenuItemIntoGroup(group, std::move(menuItem));
}
}
}
else {
lua_pop(L, 1);
}
}
return 0;
}
int Plugin_deleteCommand(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 command id specified in plugin:deleteCommand()");
// TODO this can crash if we delete the command from the same command
deleteCommandIfExistent(plugin->ext, id);
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:newMenuGroup{ 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);
// 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));
}
}
}
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_newMenuSeparator(lua_State* L)
{
auto* plugin = get_obj<Plugin>(L, 1);
if (lua_istable(L, 2)) {
std::string group;
lua_getfield(L, 2, "group"); // Parent group
if (const char* s = lua_tostring(L, -1)) {
group = s;
}
lua_pop(L, 1);
// Add a new separator 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<ui::MenuSeparator>();
plugin->ext->addMenuSeparator(menuItem.get());
appMenus->addMenuItemIntoGroup(group, std::move(menuItem));
}
}
}
return 0;
}
int Plugin_get_name(lua_State* L)
{
auto* plugin = get_obj<Plugin>(L, 1);
lua_pushstring(L, plugin->ext->name().c_str());
return 1;
}
int Plugin_get_displayName(lua_State* L)
{
auto* plugin = get_obj<Plugin>(L, 1);
lua_pushstring(L, plugin->ext->displayName().c_str());
return 1;
}
int Plugin_get_path(lua_State* L)
{
auto* plugin = get_obj<Plugin>(L, 1);
lua_pushstring(L, plugin->ext->path().c_str());
return 1;
}
int Plugin_get_version(lua_State* L)
{
auto* plugin = get_obj<Plugin>(L, 1);
const base::Version version(plugin->ext->version());
push_version(L, version);
return 1;
}
int Plugin_get_preferences(lua_State* L)
{
if (!lua_getuservalue(L, 1)) {
lua_newtable(L);
lua_pushvalue(L, -1);
lua_setuservalue(L, 1);
}
return 1;
}
int Plugin_set_preferences(lua_State* L)
{
lua_pushvalue(L, 2);
lua_setuservalue(L, 1);
return 0;
}
const luaL_Reg Plugin_methods[] = {
{ "__gc", Plugin_gc },
{ "newCommand", Plugin_newCommand },
{ "deleteCommand", Plugin_deleteCommand },
{ "newMenuGroup", Plugin_newMenuGroup },
{ "deleteMenuGroup", Plugin_deleteMenuGroup },
{ "newMenuSeparator", Plugin_newMenuSeparator },
{ nullptr, nullptr }
};
const Property Plugin_properties[] = {
{ "name", Plugin_get_name, nullptr },
{ "displayName", Plugin_get_displayName, nullptr },
{ "path", Plugin_get_path, nullptr },
{ "version", Plugin_get_version, nullptr },
{ "preferences", Plugin_get_preferences, Plugin_set_preferences },
{ nullptr, nullptr, nullptr }
};
} // anonymous namespace
DEF_MTNAME(Plugin);
void register_plugin_class(lua_State* L)
{
REG_CLASS(L, Plugin);
REG_CLASS_PROPERTIES(L, Plugin);
}
void push_plugin(lua_State* L, Extension* ext)
{
push_new<Plugin>(L, ext);
}
}} // namespace app::script