Add setting/preference classes/enums generator

Changes:
* Add a class (Option<T>) to get/set/listen changes to one specific
  preference option
* Add data/pref.xml with the metadata to generate types/classes (not it's
  quite easy to add new preferences)
* Modify the generator to support generation of .h and .cpp files of
  preference types
* Add code to migrate old options to new ones (and functions to delete
  old values)
* Only a couple of options were migrated at the moment, it is a WIP, in the
  future we should completely remove ISettings and direct calls
  to set/get_config_*
This commit is contained in:
David Capello 2014-12-14 20:19:31 -03:00
parent 65da03d181
commit 73658399cc
21 changed files with 964 additions and 38 deletions

View File

@ -43,8 +43,6 @@
# Refactoring
* Merge everything related to configuration/settings in one class
(allow configuration per document). Use cfg.cpp and settings/ dir.
* Refactor src/file/ in several layers.
* Use streams instead of FILEs.
* Destroy modules/gui.h.

135
data/pref.xml Normal file
View File

@ -0,0 +1,135 @@
<?xml version="1.0" encoding="utf-8"?>
<preferences>
<types>
<enum id="InkType">
<value id="DEFAULT" value="0" />
<value id="SET_ALPHA" value="1" />
<value id="LOCK_ALPHA" value="2" />
</enum>
<enum id="RightClickMode">
<value id="PAINT_BGCOLOR" value="0" />
<value id="PICK_FGCOLOR" value="1" />
<value id="ERASE" value="2" />
</enum>
<enum id="TiledMode">
<value id="NONE" value="0" />
<value id="X_AXIS" value="1" />
<value id="Y_AXIS" value="2" />
<value id="BOTH" value="3" />
</enum>
<enum id="OnionskinType">
<value id="MERGE" value="0" />
<value id="RED_BLUE_TINT" value="1" />
</enum>
<enum id="AniDir">
<value id="FORWARD" value="0" />
<value id="REVERSE" value="1" />
<value id="PING_PONG" value="2" />
</enum>
<enum id="FreehandAlgorithm">
<value id="REGULAR" value="0" />
<value id="PIXEL_PERFECT" value="1" />
<value id="DOTS" value="2" />
</enum>
<enum id="BrushType">
<value id="CIRCLE" value="0" />
<value id="SQUARE" value="1" />
<value id="LINE" value="2" />
</enum>
<enum id="SelectionMode">
<value id="DEFAULT" value="0" />
<value id="ADD" value="1" />
<value id="SUBTRACT" value="2" />
</enum>
<enum id="RotationAlgorithm">
<value id="FAST" value="0" />
<value id="ROTSPRITE" value="1" />
</enum>
</types>
<global>
<section id="general">
<option id="autoshow_timeline" type="bool" default="true" migrate="Options.AutoShowTimeline" />
<option id="expand_menubar_on_mouseover" type="bool" default="false" migrate="Options.ExpandMenuBarOnMouseover" />
</section>
<section id="undo" text="Undo">
<option id="size_limit" type="int" default="0" />
<option id="goto_modified" type="bool" default="false" />
</section>
<section id="editor" text="Editor">
<option id="zoom_with_scroll_wheel" type="bool" default="false" form="" />
<option id="center_on_zoom" type="bool" default="false" />
<option id="show_scrollbars" type="bool" default="false" />
<option id="right_click_mode" type="RightClickMode" default="RightClickMode::PAINT_BGCOLOR" />
<option id="grab_alpha" type="bool" default="false" />
<option id="auto_select_layer" type="bool" default="false" />
<option id="fg_color" type="app::Color" />
<option id="bg_color" type="app::Color" />
<option id="current_tool" type="std::string" default="&quot;pencil&quot;" />
</section>
<section id="experimental" text="Experimental">
<option id="use_native_cursor" type="bool" default="false" />
<option id="flash_layer" type="bool" default="false" />
</section>
</global>
<tool>
<option id="opacity" type="int" default="255" />
<option id="tolerance" type="int" default="0" />
<option id="contiguous" type="bool" default="true" />
<option id="filled" type="bool" default="false" />
<option id="filled_preview" type="bool" default="false" />
<option id="ink" type="InkType" />
<option id="freehand_algorithm" type="FreehandAlgorithm" />
<section id="brush">
<option id="type" type="BrushType" />
<option id="size" type="int" />
<option id="angle" type="int" />
</section>
<section id="spray">
<option id="width" type="int" default="16" />
<option id="speed" type="int" default="32" />
</section>
<section id="selection">
<option id="mode" type="SelectionMode" />
<option id="transparent_color" type="app::Color" />
<option id="rotation_algorithm" type="RotationAlgorithm" />
</section>
</tool>
<document>
<section id="tiled">
<option id="tiled_mode" type="TiledMode" default="TiledMode::NONE" />
</section>
<section id="grid">
<option id="snap" type="bool" default="false" />
<option id="visible" type="bool" default="false" />
<option id="bounds" type="gfx::Rect" default="gfx::Rect(0, 0, 16, 16)" />
<option id="color" type="app::Color" default="app::Color::fromRgb(0, 0, 255)" />
<option id="opacity" type="int" default="160" />
<option id="auto_opacity" type="bool" default="true" />
</section>
<section id="pixel_grid">
<option id="visible" type="bool" default="false" />
<option id="color" type="app::Color" default="app::Color::fromRgb(200, 200, 200)" />
<option id="opacity" type="int" default="160" />
<option id="auto_opacity" type="bool" default="true" />
</section>
<section id="onionskin">
<option id="active" type="bool" default="false" />
<option id="prev_frames" type="int" default="1" />
<option id="next_frames" type="int" default="1" />
<option id="opacity_base" type="int" default="68" />
<option id="opacity_step" type="int" default="28" />
<option id="type" type="OnionskinType" default="OnionskinType::MERGE" />
</section>
<section id="loop">
<option id="visible" type="bool" default="false" />
<option id="from" type="doc::FrameNumber" default="doc::FrameNumber(0)" />
<option id="to" type="doc::FrameNumber" default="doc::FrameNumber(1)" />
<option id="ani_dir" type="AniDir" default="AniDir::FORWARD" />
</section>
</document>
</preferences>

View File

@ -7,7 +7,6 @@
file(GLOB widget_files ${CMAKE_SOURCE_DIR}/data/widgets/*.xml)
foreach(widget_file ${widget_files})
get_filename_component(widget_name ${widget_file} NAME_WE)
set(target_name generated_${widget_name})
set(output_fn ${CMAKE_CURRENT_BINARY_DIR}/generated_${widget_name}.h)
add_custom_command(
@ -17,9 +16,30 @@ foreach(widget_file ${widget_files})
MAIN_DEPENDENCY ${widget_file}
DEPENDS gen)
list(APPEND generated_header_files ${output_fn})
list(APPEND generated_files ${output_fn})
endforeach()
# pref.h and pref.cpp
set(pref_xml ${CMAKE_SOURCE_DIR}/data/pref.xml)
set(output_fn ${CMAKE_CURRENT_BINARY_DIR}/generated_pref_types.h)
add_custom_command(
OUTPUT ${output_fn}
COMMAND ${CMAKE_BINARY_DIR}/bin/gen --input ${pref_xml} --pref-h > ${output_fn}
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
MAIN_DEPENDENCY ${pref_xml}
DEPENDS gen)
list(APPEND generated_files ${output_fn})
set(output_fn ${CMAKE_CURRENT_BINARY_DIR}/generated_pref_types.cpp)
add_custom_command(
OUTPUT ${output_fn}
COMMAND ${CMAKE_BINARY_DIR}/bin/gen --input ${pref_xml} --pref-cpp > ${output_fn}
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
MAIN_DEPENDENCY ${pref_xml}
DEPENDS gen)
list(APPEND generated_files ${output_fn})
# Directory where generated files by "gen" utility will stay.
include_directories(${CMAKE_CURRENT_BINARY_DIR})
@ -169,6 +189,7 @@ add_library(app-lib
modules/gui.cpp
modules/palettes.cpp
objects_container_impl.cpp
pref/preferences.cpp
project.cpp
recent_files.cpp
res/palettes_loader_delegate.cpp
@ -288,4 +309,4 @@ add_library(app-lib
xml_document.cpp
xml_exception.cpp
zoom.cpp
${generated_header_files})
${generated_files})

View File

@ -46,6 +46,7 @@
#include "app/modules/gfx.h"
#include "app/modules/gui.h"
#include "app/modules/palettes.h"
#include "app/pref/preferences.h"
#include "app/recent_files.h"
#include "app/resource_finder.h"
#include "app/send_crash.h"
@ -93,9 +94,14 @@ namespace app {
using namespace ui;
class App::Modules {
class App::CoreModules {
public:
ConfigModule m_configModule;
Preferences m_preferences;
};
class App::Modules {
public:
LoggerModule m_loggerModule;
FileSystemModule m_file_system_module;
tools::ToolBox m_toolbox;
@ -114,7 +120,8 @@ public:
App* App::m_instance = NULL;
App::App()
: m_modules(NULL)
: m_coreModules(NULL)
, m_modules(NULL)
, m_legacy(NULL)
, m_isGui(false)
, m_isShell(false)
@ -131,6 +138,7 @@ void App::initialize(const AppOptions& options)
// Initializes the application loading the modules, setting the
// graphics mode, loading the configuration and resources, etc.
m_coreModules = new CoreModules;
m_modules = new Modules(!options.startUI(), options.verbose());
m_isGui = options.startUI();
m_isShell = options.startShell();
@ -476,6 +484,7 @@ App::~App()
delete m_legacy;
delete m_modules;
delete m_coreModules;
// Destroy the loaded gui.xml data.
delete KeyboardShortcuts::instance();
@ -515,6 +524,11 @@ RecentFiles* App::getRecentFiles() const
return &m_modules->m_recent_files;
}
Preferences& App::preferences() const
{
return m_coreModules->m_preferences;
}
void App::showNotification(INotificationDelegate* del)
{
m_mainWindow->showNotification(del);

View File

@ -45,6 +45,7 @@ namespace app {
class LegacyModules;
class LoggerModule;
class MainWindow;
class Preferences;
class RecentFiles;
namespace tools {
@ -75,6 +76,7 @@ namespace app {
tools::ToolBox* getToolBox() const;
RecentFiles* getRecentFiles() const;
MainWindow* getMainWindow() const { return m_mainWindow; }
Preferences& preferences() const;
void showNotification(INotificationDelegate* del);
void updateDisplayTitleBar();
@ -90,11 +92,13 @@ namespace app {
private:
typedef std::vector<std::string> FileList;
class CoreModules;
class Modules;
static App* m_instance;
base::UniquePtr<ui::GuiSystem> m_guiSystem;
CoreModules* m_coreModules;
Modules* m_modules;
LegacyModules* m_legacy;
bool m_isGui;

View File

@ -29,6 +29,7 @@
#include "app/load_widget.h"
#include "app/modules/editors.h"
#include "app/modules/gui.h"
#include "app/pref/preferences.h"
#include "app/resource_finder.h"
#include "app/send_crash.h"
#include "app/settings/document_settings.h"
@ -59,6 +60,8 @@ public:
, m_gridColor(new ColorButton(m_docSettings->getGridColor(), IMAGE_RGB))
, m_cursorColor(new ColorButton(Editor::get_cursor_color(), IMAGE_RGB))
{
Preferences& preferences = App::instance()->preferences();
sectionListbox()->ChangeSelectedItem.connect(Bind<void>(&OptionsWindow::onChangeSection, this));
cursorColorBox()->addChild(m_cursorColor);
@ -75,11 +78,10 @@ public:
pixelGridAutoOpacity()->setSelected(m_docSettings->getPixelGridAutoOpacity());
// Others
if (get_config_bool("Options", "AutoShowTimeline", true))
if (preferences.general.autoshowTimeline())
autotimeline()->setSelected(true);
if (get_config_bool("Options", "ExpandMenuBarOnMouseover",
ui::MenuBar::expandOnMouseover()))
if (preferences.general.expandMenubarOnMouseover())
expandMenubarOnMouseover()->setSelected(true);
if (m_settings->getCenterOnZoom())
@ -151,6 +153,8 @@ public:
}
void saveConfig() {
Preferences& preferences = App::instance()->preferences();
Editor::set_cursor_color(m_cursorColor->getColor());
m_docSettings->setGridColor(m_gridColor->getColor());
m_docSettings->setGridOpacity(gridOpacity()->getValue());
@ -159,10 +163,10 @@ public:
m_docSettings->setPixelGridOpacity(pixelGridOpacity()->getValue());
m_docSettings->setPixelGridAutoOpacity(pixelGridAutoOpacity()->isSelected());
set_config_bool("Options", "AutoShowTimeline", autotimeline()->isSelected());
preferences.general.autoshowTimeline(autotimeline()->isSelected());
bool expandOnMouseover = expandMenubarOnMouseover()->isSelected();
set_config_bool("Options", "ExpandMenuBarOnMouseover", expandOnMouseover);
preferences.general.expandMenubarOnMouseover(expandOnMouseover);
ui::MenuBar::setExpandOnMouseover(expandOnMouseover);
m_settings->setCenterOnZoom(centerOnZoom()->isSelected());
@ -259,9 +263,10 @@ OptionsCommand::OptionsCommand()
"Options",
CmdUIOnlyFlag)
{
Preferences& preferences = App::instance()->preferences();
ui::MenuBar::setExpandOnMouseover(
get_config_bool("Options", "ExpandMenuBarOnMouseover",
ui::MenuBar::expandOnMouseover()));
preferences.general.expandMenubarOnMouseover());
}
void OptionsCommand::onExecute(Context* context)

View File

@ -179,4 +179,9 @@ void set_config_color(const char* section, const char* name, const app::Color& v
set_config_string(section, name, value.toString().c_str());
}
void del_config_value(const char* section, const char* name)
{
g_configs.back()->deleteValue(section, name);
}
} // namespace app

View File

@ -56,6 +56,76 @@ namespace app {
app::Color get_config_color(const char* section, const char* name, const app::Color& value);
void set_config_color(const char* section, const char* name, const app::Color& value);
void del_config_value(const char* section, const char* name);
// Generic get/set_config_value functions
inline const char* get_config_value(const char* section, const char* name, const char* value) {
return get_config_string(section, name, value);
}
inline std::string get_config_value(const char* section, const char* name, const std::string& value) {
return get_config_string(section, name, value.c_str());
}
inline bool get_config_value(const char* section, const char* name, bool value) {
return get_config_bool(section, name, value);
}
template<typename T>
inline T get_config_value(const char* section, const char* name, const T& value) {
return static_cast<T>(get_config_int(section, name, static_cast<int>(value)));
}
inline float get_config_value(const char* section, const char* name, float value) {
return get_config_float(section, name, value);
}
inline double get_config_value(const char* section, const char* name, double value) {
return get_config_float(section, name, value);
}
inline gfx::Rect get_config_value(const char* section, const char* name, const gfx::Rect& value) {
return get_config_rect(section, name, value);
}
inline app::Color get_config_value(const char* section, const char* name, const app::Color& value) {
return get_config_color(section, name, value);
}
inline void set_config_value(const char* section, const char* name, const char* value) {
set_config_string(section, name, value);
}
inline void set_config_value(const char* section, const char* name, const std::string& value) {
set_config_string(section, name, value.c_str());
}
inline void set_config_value(const char* section, const char* name, bool value) {
set_config_bool(section, name, value);
}
template<typename T>
inline void set_config_value(const char* section, const char* name, const T& value) {
set_config_int(section, name, static_cast<int>(value));
}
inline void set_config_value(const char* section, const char* name, float value) {
set_config_float(section, name, value);
}
inline void set_config_value(const char* section, const char* name, double value) {
set_config_float(section, name, value);
}
inline void set_config_value(const char* section, const char* name, const gfx::Rect& value) {
set_config_rect(section, name, value);
}
inline void set_config_value(const char* section, const char* name, const app::Color& value) {
set_config_color(section, name, value);
}
} // namespace app
#endif

81
src/app/pref/option.h Normal file
View File

@ -0,0 +1,81 @@
/* Aseprite
* Copyright (C) 2001-2014 David Capello
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef APP_PREF_OPTION_H_INCLUDED
#define APP_PREF_OPTION_H_INCLUDED
#pragma once
#include "base/signal.h"
#include "base/disable_copying.h"
namespace app {
template<typename T>
class Option {
public:
Option(const char* section, const char* id, const T& defaultValue = T())
: m_section(section)
, m_id(id)
, m_default(defaultValue)
, m_value(defaultValue)
, m_dirty(false) {
}
const char* section() const { return m_section; }
const char* id() const { return m_id; }
const T& defaultValue() const { return m_default; }
bool isDirty() const { return m_dirty; }
void forceDirtyFlag() { m_dirty = true; }
void cleanDirtyFlag() { m_dirty = false; }
const T& operator()() const {
return m_value;
}
const T& operator()(const T& newValue) {
if (m_value == newValue)
return m_value;
BeforeChange(*this, newValue);
T oldValue = m_value;
m_value = newValue;
m_dirty = true;
AfterChange(*this, oldValue);
return m_value;
}
Signal2<void, Option&, const T&> BeforeChange;
Signal2<void, Option&, const T&> AfterChange;
private:
const char* m_section;
const char* m_id;
T m_default;
T m_value;
bool m_dirty;
Option();
DISABLE_COPYING(Option);
};
} // namespace app
#endif

60
src/app/pref/option_io.h Normal file
View File

@ -0,0 +1,60 @@
/* Aseprite
* Copyright (C) 2001-2014 David Capello
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef APP_PREF_OPTION_IO_H_INCLUDED
#define APP_PREF_OPTION_IO_H_INCLUDED
#pragma once
#include "app/ini_file.h"
#include "app/pref/option.h"
namespace app {
// Load
template<typename T>
void load_option(Option<T>& opt) {
opt(get_config_value(opt.section(), opt.id(), opt.defaultValue()));
}
template<typename T>
void load_option_with_migration(Option<T>& opt, const char* oldSection, const char* oldName) {
if (get_config_string(oldSection, oldName, NULL)) {
opt(get_config_value(oldSection, oldName, opt.defaultValue()));
del_config_value(oldSection, oldName);
opt.forceDirtyFlag();
}
else
opt(get_config_value(opt.section(), opt.id(), opt.defaultValue()));
}
// Save
template<typename T>
void save_option(Option<T>& opt) {
if (!opt.isDirty())
return;
set_config_value(opt.section(), opt.id(), opt());
opt.cleanDirtyFlag();
}
} // namespace app
#endif

View File

@ -0,0 +1,130 @@
/* Aseprite
* Copyright (C) 2001-2014 David Capello
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/document.h"
#include "app/ini_file.h"
#include "app/pref/preferences.h"
#include "app/resource_finder.h"
#include "app/tools/tool.h"
namespace app {
Preferences::Preferences()
: app::gen::GlobalPref("")
{
load();
}
Preferences::~Preferences()
{
save();
for (auto& pair : m_tools)
delete pair.second;
for (auto& pair : m_docs)
delete pair.second;
}
void Preferences::load()
{
app::gen::GlobalPref::load();
}
void Preferences::save()
{
app::gen::GlobalPref::save();
for (auto& pair : m_tools)
pair.second->save();
for (auto& pair : m_docs) {
app::Document* doc = pair.first;
bool specific_file = false;
if (doc && doc->isAssociatedToFile()) {
push_config_state();
set_config_file(docConfigFileName(doc).c_str());
specific_file = true;
}
pair.second->save();
if (specific_file) {
flush_config_file();
pop_config_state();
}
}
flush_config_file();
}
ToolPreferences& Preferences::tool(tools::Tool* tool)
{
ASSERT(tool != NULL);
auto it = m_tools.find(tool->getId());
if (it != m_tools.end()) {
return *it->second;
}
else {
std::string section = "tool.";
section += tool->getId();
ToolPreferences* toolPref = new ToolPreferences(section);
m_tools[tool->getId()] = toolPref;
return *toolPref;
}
}
DocumentPreferences& Preferences::document(app::Document* document)
{
ASSERT(document != NULL);
auto it = m_docs.find(document);
if (it != m_docs.end()) {
return *it->second;
}
else {
DocumentPreferences* docPref = new DocumentPreferences("");
m_docs[document] = docPref;
return *docPref;
}
}
std::string Preferences::docConfigFileName(app::Document* doc)
{
if (!doc)
return "";
ResourceFinder rf;
std::string fn = doc->filename();
for (size_t i=0; i<fn.size(); ++i) {
if (fn[i] == ' ' || fn[i] == '/' || fn[i] == '\\' || fn[i] == ':' || fn[i] == '.') {
fn[i] = '-';
}
}
rf.includeUserDir(("files/" + fn + ".ini").c_str());
return rf.getFirstOrCreateDefault();
}
} // namespace app

View File

@ -0,0 +1,61 @@
/* Aseprite
* Copyright (C) 2001-2014 David Capello
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef APP_PREF_PREFERENCES_H_INCLUDED
#define APP_PREF_PREFERENCES_H_INCLUDED
#pragma once
#include "app/pref/option.h"
#include "generated_pref_types.h"
#include <map>
#include <string>
#include <vector>
namespace app {
namespace tools {
class Tool;
}
class Document;
typedef app::gen::ToolPref ToolPreferences;
typedef app::gen::DocPref DocumentPreferences;
class Preferences : public app::gen::GlobalPref {
public:
Preferences();
~Preferences();
void load();
void save();
ToolPreferences& tool(tools::Tool* tool);
DocumentPreferences& document(app::Document* document);
private:
std::string docConfigFileName(app::Document* doc);
std::map<std::string, app::ToolPreferences*> m_tools;
std::map<app::Document*, app::DocumentPreferences*> m_docs;
};
} // namespace app
#endif

View File

@ -28,6 +28,7 @@
#include "app/ini_file.h"
#include "app/load_widget.h"
#include "app/modules/editors.h"
#include "app/pref/preferences.h"
#include "app/settings/settings.h"
#include "app/ui/color_bar.h"
#include "app/ui/context_bar.h"
@ -218,7 +219,9 @@ void MainWindow::setTimelineVisibility(bool visible)
void MainWindow::popTimeline()
{
if (!get_config_bool("Options", "AutoShowTimeline", true))
Preferences& preferences = App::instance()->preferences();
if (!preferences.general.autoshowTimeline())
return;
if (!getTimelineVisibility())

View File

@ -55,6 +55,10 @@ public:
m_ini.SetDoubleValue(section, name, value);
}
void deleteValue(const char* section, const char* name) {
m_ini.Delete(section, name, true);
}
void load(const std::string& filename) {
m_filename = filename;
@ -135,6 +139,11 @@ void CfgFile::setDoubleValue(const char* section, const char* name, double value
m_impl->setDoubleValue(section, name, value);
}
void CfgFile::deleteValue(const char* section, const char* name)
{
m_impl->deleteValue(section, name);
}
void CfgFile::load(const std::string& filename)
{
m_impl->load(filename);

View File

@ -29,6 +29,8 @@ namespace cfg {
void setIntValue(const char* section, const char* name, int value);
void setDoubleValue(const char* section, const char* name, double value);
void deleteValue(const char* section, const char* name);
void load(const std::string& filename);
void save();

View File

@ -3,6 +3,7 @@
add_executable(gen
gen.cpp
pref_types.cpp
ui_class.cpp)
if(MSVC)

32
src/gen/common.h Normal file
View File

@ -0,0 +1,32 @@
// Aseprite Code Generator
// Copyright (c) 2014 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef GEN_COMMON_H_INCLUDED
#define GEN_COMMON_H_INCLUDED
#pragma once
#include <cctype>
#include <string>
inline std::string convert_xmlid_to_cppid(const std::string& xmlid, bool firstLetterUpperCase)
{
bool firstLetter = firstLetterUpperCase;
std::string cppid;
for (size_t i=0; i<xmlid.size(); ++i) {
if (xmlid[i] == '_') {
firstLetter = true;
}
else if (firstLetter) {
firstLetter = false;
cppid += std::toupper(xmlid[i]);
}
else
cppid += xmlid[i];
}
return cppid;
}
#endif

View File

@ -8,6 +8,7 @@
#include "base/path.h"
#include "base/program_options.h"
#include "base/string.h"
#include "gen/pref_types.h"
#include "gen/ui_class.h"
#include "tinyxml.h"
@ -20,6 +21,8 @@ static void run(int argc, const char* argv[])
PO po;
PO::Option& inputOpt = po.add("input").requiresValue("<filename>");
PO::Option& widgetId = po.add("widgetid").requiresValue("<filename>");
PO::Option& prefH = po.add("pref-h");
PO::Option& prefCpp = po.add("pref-cpp");
po.parse(argc, argv);
// Try to load the XML file
@ -34,8 +37,14 @@ static void run(int argc, const char* argv[])
throw std::runtime_error("invalid input file");
}
if (doc && po.enabled(widgetId))
gen_ui_class(doc, inputFilename, po.value_of(widgetId));
if (doc) {
if (po.enabled(widgetId))
gen_ui_class(doc, inputFilename, po.value_of(widgetId));
else if (po.enabled(prefH))
gen_pref_header(doc, inputFilename);
else if (po.enabled(prefCpp))
gen_pref_impl(doc, inputFilename);
}
}
int main(int argc, const char* argv[])

288
src/gen/pref_types.cpp Normal file
View File

@ -0,0 +1,288 @@
// Aseprite Code Generator
// Copyright (c) 2014 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#include "base/exception.h"
#include "base/file_handle.h"
#include "base/path.h"
#include "base/split_string.h"
#include "base/string.h"
#include "gen/common.h"
#include "gen/pref_types.h"
#include <iostream>
#include <vector>
typedef std::vector<TiXmlElement*> XmlElements;
static void print_pref_class_def(TiXmlElement* elem, const std::string& className, const char* section, int indentSpaces)
{
std::string indent(indentSpaces, ' ');
std::cout
<< "\n"
<< indent << "class " << className << " {\n"
<< indent << " std::string m_section;\n"
<< indent << "public:\n"
<< indent << " " << className << "(const std::string& section);\n";
std::cout
<< indent << " void load();\n"
<< indent << " void save();\n";
TiXmlElement* child = (elem->FirstChild() ? elem->FirstChild()->ToElement(): NULL);
while (child) {
if (child->Value()) {
std::string name = child->Value();
const char* childId = child->Attribute("id");
if (name == "option") {
if (!child->Attribute("type")) throw std::runtime_error("missing 'type' attr in <option>");
if (!childId) throw std::runtime_error("missing 'id' attr in <option>");
std::string memberName = convert_xmlid_to_cppid(childId, false);
std::cout
<< indent << " Option<" << child->Attribute("type") << "> " << memberName << ";\n";
}
else if (name == "section") {
if (!childId) throw std::runtime_error("missing 'id' attr in <section>");
std::string childClassName = convert_xmlid_to_cppid(childId, true);
std::string memberName = convert_xmlid_to_cppid(childId, false);
print_pref_class_def(child, childClassName, childId, indentSpaces+2);
std::cout
<< indent << " " << childClassName << " " << memberName << ";\n";
}
}
child = child->NextSiblingElement();
}
std::cout
<< indent << "};\n";
}
static void print_pref_class_impl(TiXmlElement* elem, const std::string& prefix, const std::string& className, const char* section)
{
std::cout
<< "\n"
<< prefix << className << "::" << className << "(const std::string& section)\n";
if (section)
std::cout << " : m_section((!section.empty() ? section + \".\": section) + \"" << section << "\")\n";
else
std::cout << " : m_section(section)\n";
TiXmlElement* child = (elem->FirstChild() ? elem->FirstChild()->ToElement(): NULL);
while (child) {
if (child->Value()) {
std::string name = child->Value();
const char* childId = child->Attribute("id");
if (name == "option") {
if (!child->Attribute("type")) throw std::runtime_error("missing 'type' attr in <option>");
if (!childId) throw std::runtime_error("missing 'id' attr in <option>");
std::string memberName = convert_xmlid_to_cppid(childId, false);
std::cout << " , "
<< memberName << "(m_section.c_str(), \"" << childId << "\"";
if (child->Attribute("default"))
std::cout << ", " << child->Attribute("default");
std::cout << ")\n";
}
else if (name == "section") {
if (!childId) throw std::runtime_error("missing 'id' attr in <option>");
std::string memberName = convert_xmlid_to_cppid(childId, false);
std::cout << " , " << memberName << "(m_section)\n";
}
}
child = child->NextSiblingElement();
}
std::cout
<< "{\n"
<< "}\n";
std::cout
<< "\n"
<< "void " << prefix << className << "::load()\n"
<< "{\n";
child = (elem->FirstChild() ? elem->FirstChild()->ToElement(): NULL);
while (child) {
if (child->Value()) {
std::string name = child->Value();
const char* childId = child->Attribute("id");
if (name == "option") {
std::string memberName = convert_xmlid_to_cppid(childId, false);
const char* migrate = child->Attribute("migrate");
if (migrate) {
std::vector<std::string> parts;
base::split_string(migrate, parts, ".");
std::cout << " load_option_with_migration(" << memberName
<< ", \"" << parts[0]
<< "\", \"" << parts[1] << "\");\n";
}
else
std::cout << " load_option(" << memberName << ");\n";
}
else if (name == "section") {
std::string memberName = convert_xmlid_to_cppid(childId, false);
std::cout << " " << memberName << ".load();\n";
}
}
child = child->NextSiblingElement();
}
std::cout
<< "}\n"
<< "\n"
<< "void " << prefix << className << "::save()\n"
<< "{\n";
child = (elem->FirstChild() ? elem->FirstChild()->ToElement(): NULL);
while (child) {
if (child->Value()) {
std::string name = child->Value();
if (name == "option") {
std::string memberName = convert_xmlid_to_cppid(child->Attribute("id"), false);
std::cout << " save_option(" << memberName << ");\n";
}
else if (name == "section") {
std::string memberName = convert_xmlid_to_cppid(child->Attribute("id"), false);
std::cout << " " << memberName << ".save();\n";
}
}
child = child->NextSiblingElement();
}
std::cout
<< "}\n";
child = (elem->FirstChild() ? elem->FirstChild()->ToElement(): NULL);
while (child) {
if (child->Value()) {
std::string name = child->Value();
if (name == "section") {
std::string childClassName = convert_xmlid_to_cppid(child->Attribute("id"), true);
print_pref_class_impl(child, className + "::", childClassName, child->Attribute("id"));
}
}
child = child->NextSiblingElement();
}
}
void gen_pref_header(TiXmlDocument* doc, const std::string& inputFn)
{
std::cout
<< "// Don't modify, generated file from " << inputFn << "\n"
<< "\n";
std::cout
<< "#ifndef GENERATED_PREF_TYPES_H_INCLUDED\n"
<< "#define GENERATED_PREF_TYPES_H_INCLUDED\n"
<< "#pragma once\n"
<< "\n"
<< "#include \"app/color.h\"\n"
<< "#include \"app/pref/option.h\"\n"
<< "#include \"doc/frame_number.h\"\n"
<< "#include \"gfx/rect.h\"\n"
<< "\n"
<< "#include <string>\n"
<< "\n"
<< "namespace app {\n"
<< "namespace gen {\n";
TiXmlHandle handle(doc);
TiXmlElement* elem = handle
.FirstChild("preferences")
.FirstChild("types")
.FirstChild("enum").ToElement();
while (elem) {
if (!elem->Attribute("id")) throw std::runtime_error("missing 'id' attr in <enum>");
std::cout
<< "\n"
<< " enum class " << elem->Attribute("id") << " {\n";
TiXmlElement* child = elem->FirstChildElement("value");
while (child) {
if (!child->Attribute("id")) throw std::runtime_error("missing 'id' attr in <value>");
if (!child->Attribute("value")) throw std::runtime_error("missing 'value' attr in <value>");
std::cout << " " << child->Attribute("id") << " = "
<< child->Attribute("value") << ",\n";
child = child->NextSiblingElement("value");
}
std::cout
<< " };\n";
elem = elem->NextSiblingElement("enum");
}
elem = handle
.FirstChild("preferences")
.FirstChild("global").ToElement();
if (elem)
print_pref_class_def(elem, "GlobalPref", NULL, 2);
elem = handle
.FirstChild("preferences")
.FirstChild("tool").ToElement();
if (elem)
print_pref_class_def(elem, "ToolPref", NULL, 2);
elem = handle
.FirstChild("preferences")
.FirstChild("document").ToElement();
if (elem)
print_pref_class_def(elem, "DocPref", NULL, 2);
std::cout
<< "\n"
<< "} // namespace gen\n"
<< "} // namespace app\n"
<< "\n"
<< "#endif\n";
}
void gen_pref_impl(TiXmlDocument* doc, const std::string& inputFn)
{
std::cout
<< "// Don't modify, generated file from " << inputFn << "\n"
<< "\n"
<< "#ifdef HAVE_CONFIG_H\n"
<< "#include \"config.h\"\n"
<< "#endif\n"
<< "\n"
<< "#include \"generated_pref_types.h\"\n"
<< "\n"
<< "#include \"app/pref/option_io.h\"\n"
<< "\n"
<< "namespace app {\n"
<< "namespace gen {\n";
TiXmlHandle handle(doc);
TiXmlElement* elem = handle
.FirstChild("preferences")
.FirstChild("global").ToElement();
if (elem)
print_pref_class_impl(elem, "", "GlobalPref", NULL);
elem = handle
.FirstChild("preferences")
.FirstChild("tool").ToElement();
if (elem)
print_pref_class_impl(elem, "", "ToolPref", NULL);
elem = handle
.FirstChild("preferences")
.FirstChild("document").ToElement();
if (elem)
print_pref_class_impl(elem, "", "DocPref", NULL);
std::cout
<< "\n"
<< "} // namespace gen\n"
<< "} // namespace app\n";
}

17
src/gen/pref_types.h Normal file
View File

@ -0,0 +1,17 @@
// Aseprite Code Generator
// Copyright (c) 2014 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef GEN_PREF_CLASS_H_INCLUDED
#define GEN_PREF_CLASS_H_INCLUDED
#pragma once
#include <string>
#include "tinyxml.h"
void gen_pref_header(TiXmlDocument* doc, const std::string& inputFn);
void gen_pref_impl(TiXmlDocument* doc, const std::string& inputFn);
#endif

View File

@ -7,34 +7,15 @@
#include "base/exception.h"
#include "base/file_handle.h"
#include "base/path.h"
#include "base/program_options.h"
#include "base/string.h"
#include "gen/common.h"
#include "gen/ui_class.h"
#include <cctype>
#include <iostream>
#include <vector>
typedef base::ProgramOptions PO;
typedef std::vector<TiXmlElement*> XmlElements;
static std::string convert_xmlid_to_cppid(const std::string& xmlid, bool firstLetterUpperCase)
{
bool firstLetter = firstLetterUpperCase;
std::string cppid;
for (size_t i=0; i<xmlid.size(); ++i) {
if (xmlid[i] == '_') {
firstLetter = true;
}
else if (firstLetter) {
firstLetter = false;
cppid += std::toupper(xmlid[i]);
}
else
cppid += xmlid[i];
}
return cppid;
}
static TiXmlElement* find_element_by_id(TiXmlElement* elem, const std::string& thisId)
{
const char* id = elem->Attribute("id");