diff --git a/data/pref.xml b/data/pref.xml
index 8b87e550b..9a4212402 100644
--- a/data/pref.xml
+++ b/data/pref.xml
@@ -345,6 +345,7 @@
+
diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt
index e89017af1..31a562d46 100644
--- a/src/app/CMakeLists.txt
+++ b/src/app/CMakeLists.txt
@@ -170,6 +170,7 @@ if(ENABLE_SCRIPTING)
script/palettes_class.cpp
script/pixel_color_object.cpp
script/point_class.cpp
+ script/preferences_object.cpp
script/range_class.cpp
script/rectangle_class.cpp
script/security.cpp
@@ -183,6 +184,7 @@ if(ENABLE_SCRIPTING)
script/tag_class.cpp
script/tags_class.cpp
script/tool_class.cpp
+ script/values.cpp
script/version_class.cpp
shell.cpp
${scripting_files_ui})
diff --git a/src/app/pref/option.h b/src/app/pref/option.h
index b75751124..939355463 100644
--- a/src/app/pref/option.h
+++ b/src/app/pref/option.h
@@ -1,4 +1,5 @@
// Aseprite
+// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@@ -11,6 +12,10 @@
#include "obs/signal.h"
#include
+#ifdef ENABLE_SCRIPTING
+ #include "app/script/values.h"
+#endif
+
namespace app {
class OptionBase;
@@ -40,6 +45,12 @@ namespace app {
virtual ~OptionBase() { }
const char* section() const { return m_section->name(); }
const char* id() const { return m_id; }
+
+#ifdef ENABLE_SCRIPTING
+ virtual void pushLua(lua_State* L) = 0;
+ virtual void fromLua(lua_State* L, int index) = 0;
+#endif
+
protected:
Section* m_section;
const char* m_id;
@@ -104,6 +115,15 @@ namespace app {
m_section->AfterChange();
}
+#ifdef ENABLE_SCRIPTING
+ void pushLua(lua_State* L) override {
+ script::push_value_to_lua(L, m_value);
+ }
+ void fromLua(lua_State* L, int index) override {
+ setValue(script::get_value_from_lua(L, index));
+ }
+#endif
+
obs::signal BeforeChange;
obs::signal AfterChange;
diff --git a/src/app/pref/preferences.cpp b/src/app/pref/preferences.cpp
index 965fdb60b..e26de7c70 100644
--- a/src/app/pref/preferences.cpp
+++ b/src/app/pref/preferences.cpp
@@ -158,6 +158,12 @@ void Preferences::resetToolPreferences(tools::Tool* tool)
std::string section = std::string("tool.") + tool->getId();
del_config_section(section.c_str());
+
+ // TODO improve this, if we add new sections in pref.xml we have to
+ // update this manually :(
+ del_config_section((section + ".brush").c_str());
+ del_config_section((section + ".spray").c_str());
+ del_config_section((section + ".floodfill").c_str());
}
void Preferences::removeDocument(Doc* doc)
diff --git a/src/app/script/engine.cpp b/src/app/script/engine.cpp
index 5e810be80..a7491f5f0 100644
--- a/src/app/script/engine.cpp
+++ b/src/app/script/engine.cpp
@@ -135,6 +135,7 @@ int unsupported(lua_State* L)
void register_app_object(lua_State* L);
void register_app_pixel_color_object(lua_State* L);
void register_app_command_object(lua_State* L);
+void register_app_preferences_object(lua_State* L);
void register_brush_class(lua_State* L);
void register_cel_class(lua_State* L);
@@ -231,6 +232,7 @@ Engine::Engine()
register_app_object(L);
register_app_pixel_color_object(L);
register_app_command_object(L);
+ register_app_preferences_object(L);
// Register constants
lua_newtable(L);
diff --git a/src/app/script/preferences_object.cpp b/src/app/script/preferences_object.cpp
new file mode 100644
index 000000000..09e6614cb
--- /dev/null
+++ b/src/app/script/preferences_object.cpp
@@ -0,0 +1,152 @@
+// Aseprite
+// Copyright (C) 2019 Igara Studio S.A.
+// Copyright (C) 2017-2018 David Capello
+//
+// 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/doc.h"
+#include "app/pref/option.h"
+#include "app/pref/preferences.h"
+#include "app/script/docobj.h"
+#include "app/script/engine.h"
+#include "app/script/luacpp.h"
+#include "doc/sprite.h"
+
+#include
+
+namespace app {
+namespace script {
+
+namespace {
+
+struct AppPreferences { };
+
+int Section_index(lua_State* L)
+{
+ Section* section = get_ptr(L, 1);
+
+ const char* id = lua_tostring(L, 2);
+ if (!id)
+ return luaL_error(L, "optionName in 'app.preferences.%s.optionName' must be a string",
+ section->name());
+
+ OptionBase* option = section->option(id);
+ if (!option) {
+ Section* subSection = section->section(
+ (section->name() && *section->name() ? std::string(section->name()) + "." + id:
+ std::string(id)).c_str());
+ if (subSection) {
+ push_ptr(L, subSection);
+ return 1;
+ }
+ return luaL_error(L, "option '%s' in section '%s' doesn't exist", id, section->name());
+ }
+
+ option->pushLua(L);
+ return 1;
+}
+
+int Section_newindex(lua_State* L)
+{
+ Section* section = get_ptr(L, 1);
+
+ const char* id = lua_tostring(L, 2);
+ if (!id)
+ return luaL_error(L, "optionName in 'app.preferences.%s.optionName' must be a string",
+ section->name());
+
+ OptionBase* option = section->option(id);
+ if (!option)
+ return luaL_error(L, "option '%s' in section '%s' doesn't exist",
+ id, section->name());
+
+ option->fromLua(L, 3);
+ return 0;
+}
+
+int ToolPref_function(lua_State* L)
+{
+ auto tool = get_tool_from_arg(L, 1);
+ if (!tool)
+ return luaL_error(L, "tool preferences not found");
+
+ // If we don't have the UI available, we reset the tools
+ // preferences, so scripts that are executed in batch mode have a
+ // reproducible behavior.
+ if (!App::instance()->isGui())
+ Preferences::instance().resetToolPreferences(tool);
+
+ ToolPreferences& toolPref = Preferences::instance().tool(tool);
+ push_ptr(L, (Section*)&toolPref);
+ return 1;
+}
+
+int DocPref_function(lua_State* L)
+{
+ auto sprite = get_docobj(L, 1);
+ DocumentPreferences& docPref =
+ Preferences::instance().document(
+ static_cast(sprite->document()));
+ push_ptr(L, (Section*)&docPref);
+ return 1;
+}
+
+int AppPreferences_index(lua_State* L)
+{
+ const char* id = lua_tostring(L, 2);
+ if (!id)
+ return luaL_error(L, "id in 'app.preferences.id' must be a string");
+
+ if (std::strcmp(id, "tool") == 0) {
+ lua_pushcfunction(L, ToolPref_function);
+ return 1;
+ }
+ else if (std::strcmp(id, "document") == 0) {
+ lua_pushcfunction(L, DocPref_function);
+ return 1;
+ }
+
+ Section* section = Preferences::instance().section(id);
+ if (!section)
+ return luaL_error(L, "section '%s' in preferences doesn't exist", id);
+
+ push_ptr(L, section);
+ return 1;
+}
+
+const luaL_Reg Section_methods[] = {
+ { "__index", Section_index },
+ { "__newindex", Section_newindex },
+ { nullptr, nullptr }
+};
+
+const luaL_Reg AppPreferences_methods[] = {
+ { "__index", AppPreferences_index },
+ { nullptr, nullptr }
+};
+
+} // anonymous namespace
+
+DEF_MTNAME(Section);
+DEF_MTNAME(AppPreferences);
+
+void register_app_preferences_object(lua_State* L)
+{
+ REG_CLASS(L, Section);
+ REG_CLASS(L, AppPreferences);
+
+ lua_getglobal(L, "app");
+ lua_pushstring(L, "preferences");
+ push_new(L);
+ lua_rawset(L, -3);
+ lua_pop(L, 1);
+}
+
+} // namespace script
+} // namespace app
diff --git a/src/app/script/values.cpp b/src/app/script/values.cpp
new file mode 100644
index 000000000..a76836886
--- /dev/null
+++ b/src/app/script/values.cpp
@@ -0,0 +1,173 @@
+// Aseprite
+// Copyright (C) 2019 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/script/values.h"
+
+#include "app/pref/preferences.h"
+#include "app/script/engine.h"
+#include "app/script/luacpp.h"
+
+namespace app {
+namespace script {
+
+// TODO this is similar to app::Param<> specializations::fromLua() specializations
+
+// ----------------------------------------------------------------------
+// bool
+
+template<>
+void push_value_to_lua(lua_State* L, const bool& value) {
+ lua_pushboolean(L, value);
+}
+
+template<>
+bool get_value_from_lua(lua_State* L, int index) {
+ return lua_toboolean(L, index);
+}
+
+// ----------------------------------------------------------------------
+// int
+
+template<>
+void push_value_to_lua(lua_State* L, const int& value) {
+ lua_pushinteger(L, value);
+}
+
+template<>
+int get_value_from_lua(lua_State* L, int index) {
+ return lua_tointeger(L, index);
+}
+
+// ----------------------------------------------------------------------
+// double
+
+template<>
+void push_value_to_lua(lua_State* L, const double& value) {
+ lua_pushnumber(L, value);
+}
+
+template<>
+double get_value_from_lua(lua_State* L, int index) {
+ return lua_tonumber(L, index);
+}
+
+// ----------------------------------------------------------------------
+// std::string
+
+template<>
+void push_value_to_lua(lua_State* L, const std::string& value) {
+ lua_pushstring(L, value.c_str());
+}
+
+template<>
+std::string get_value_from_lua(lua_State* L, int index) {
+ if (const char* v = lua_tostring(L, index))
+ return std::string(v);
+ else
+ return std::string();
+}
+
+// ----------------------------------------------------------------------
+// Color
+
+template<>
+void push_value_to_lua(lua_State* L, const app::Color& value) {
+ push_obj(L, value);
+}
+
+template<>
+app::Color get_value_from_lua(lua_State* L, int index) {
+ return convert_args_into_color(L, index);
+}
+
+// ----------------------------------------------------------------------
+// Point
+
+template<>
+void push_value_to_lua(lua_State* L, const gfx::Point& value) {
+ push_obj(L, value);
+}
+
+template<>
+gfx::Point get_value_from_lua(lua_State* L, int index) {
+ return convert_args_into_point(L, index);
+}
+
+// ----------------------------------------------------------------------
+// Size
+
+template<>
+void push_value_to_lua(lua_State* L, const gfx::Size& value) {
+ push_obj(L, value);
+}
+
+template<>
+gfx::Size get_value_from_lua(lua_State* L, int index) {
+ return convert_args_into_size(L, index);
+}
+
+// ----------------------------------------------------------------------
+// Rect
+
+template<>
+void push_value_to_lua(lua_State* L, const gfx::Rect& value) {
+ push_obj(L, value);
+}
+
+template<>
+gfx::Rect get_value_from_lua(lua_State* L, int index) {
+ return convert_args_into_rect(L, index);
+}
+
+// ----------------------------------------------------------------------
+// enums
+
+#define FOR_ENUM(T) \
+ template<> \
+ void push_value_to_lua(lua_State* L, const T& value) { \
+ lua_pushinteger(L, static_cast(value)); \
+ } \
+ \
+ template<> \
+ T get_value_from_lua(lua_State* L, int index) { \
+ return static_cast(lua_tointeger(L, index)); \
+ }
+
+FOR_ENUM(app::CelsTarget)
+FOR_ENUM(app::ColorBar::ColorSelector)
+FOR_ENUM(app::DocExporter::DataFormat)
+FOR_ENUM(app::SpriteSheetType)
+FOR_ENUM(app::gen::BgType)
+FOR_ENUM(app::gen::BrushPreview)
+FOR_ENUM(app::gen::BrushType)
+FOR_ENUM(app::gen::ColorProfileBehavior)
+FOR_ENUM(app::gen::EyedropperChannel)
+FOR_ENUM(app::gen::EyedropperSample)
+FOR_ENUM(app::gen::FillReferTo)
+FOR_ENUM(app::gen::HueSaturationMode)
+FOR_ENUM(app::gen::OnionskinType)
+FOR_ENUM(app::gen::PaintingCursorType)
+FOR_ENUM(app::gen::PivotPosition)
+FOR_ENUM(app::gen::RightClickMode)
+FOR_ENUM(app::gen::SelectionMode)
+FOR_ENUM(app::gen::StopAtGrid)
+FOR_ENUM(app::gen::SymmetryMode)
+FOR_ENUM(app::gen::TimelinePosition)
+FOR_ENUM(app::tools::FreehandAlgorithm)
+FOR_ENUM(app::tools::InkType)
+FOR_ENUM(app::tools::RotationAlgorithm)
+FOR_ENUM(doc::AniDir)
+FOR_ENUM(doc::BrushPattern)
+FOR_ENUM(doc::ColorMode)
+FOR_ENUM(filters::TiledMode)
+FOR_ENUM(render::OnionskinPosition)
+
+} // namespace script
+} // namespace app
diff --git a/src/app/script/values.h b/src/app/script/values.h
new file mode 100644
index 000000000..7b5044a18
--- /dev/null
+++ b/src/app/script/values.h
@@ -0,0 +1,29 @@
+// Aseprite
+// Copyright (C) 2019 Igara Studio S.A.
+//
+// This program is distributed under the terms of
+// the End-User License Agreement for Aseprite.
+
+#ifndef APP_SCRIPT_VALUES_H_INCLUDED
+#define APP_SCRIPT_VALUES_H_INCLUDED
+#pragma once
+
+#ifdef ENABLE_SCRIPTING
+
+extern "C" struct lua_State;
+
+namespace app {
+namespace script {
+
+template
+T get_value_from_lua(lua_State* L, int index);
+
+template
+void push_value_to_lua(lua_State* L, const T& value);
+
+} // namespace script
+} // namespace app
+
+#endif // ENABLE_SCRIPTING
+
+#endif