Add "gen" utility to generate UI widget wrappers from XML files

This commit is contained in:
David Capello 2014-07-13 13:24:57 -03:00
parent 958d8f922f
commit 31e565a2b0
12 changed files with 636 additions and 295 deletions

View File

@ -1,15 +1,18 @@
# ASEPRITE
# Copyright (C) 2001-2013 David Capello
# Aseprite
# Copyright (C) 2001-2014 David Capello
#
# Parts of this file come from the Allegro 4.4 CMakeLists.txt
# CMake setup
cmake_minimum_required(VERSION 2.6 FATAL_ERROR)
# CMP0003: Libraries linked via full path no longer produce linker search paths.
#if(COMMAND cmake_policy)
# cmake_policy(SET CMP0003 NEW)
#endif(COMMAND cmake_policy)
if(COMMAND cmake_policy)
# CMP0003: Libraries linked via full path no longer produce linker search paths.
#cmake_policy(SET CMP0003 NEW)
# CMP0046: Old behavior to silently ignore non-existent dependencies.
cmake_policy(SET CMP0046 OLD)
endif(COMMAND cmake_policy)
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING

View File

@ -1,5 +1,5 @@
# Aseprite
# Copyright (C) 2001-2013 David Capello
# Copyright (C) 2001-2014 David Capello
add_definitions(-DHAVE_CONFIG_H)
@ -20,6 +20,9 @@ set(aseprite_libraries app-lib css-lib doc-lib raster-lib
# Directories where .h files can be found
include_directories(. .. ../third_party)
# Directory where generated files by "gen" utility will stay.
include_directories(${CMAKE_CURRENT_BINARY_DIR})
# Third-party libraries
if(USE_SHARED_JPEGLIB)
@ -161,10 +164,11 @@ endif()
# Aseprite Libraries (in preferred order to be built)
add_subdirectory(base)
add_subdirectory(css)
add_subdirectory(doc)
add_subdirectory(filters)
add_subdirectory(gen)
add_subdirectory(gfx)
add_subdirectory(css)
add_subdirectory(raster)
add_subdirectory(scripting)
add_subdirectory(she)
@ -209,6 +213,29 @@ if(EXISTS ../docs/quickref.pdf)
DESTINATION share/aseprite/docs/quickref.pdf)
endif()
######################################################################
# Generate source files from widget XML files
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(
OUTPUT ${output_fn}
COMMAND gen/gen --input ${widget_file} --widgetid ${widget_name} > ${output_fn}
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
MAIN_DEPENDENCY ${widget_file}
DEPENDS gen)
add_custom_target(${target_name} DEPENDS ${output_fn})
set_source_files_properties(${target_name} PROPERTIES GENERATED TRUE)
add_dependencies(app-lib ${target_name})
endforeach()
######################################################################
# Tests

View File

@ -24,6 +24,7 @@ because they don't depend on any other component.
* [cfg](cfg/) (base, allegro): Library to handle configuration/settings/user preferences.
* [doc](doc/) (base, gfx): Document model library (business layer, replacement of `raster` library).
* [gen](gen/) (base): Helper utility to generate C++ files from different XMLs.
* [net](net/) (base): Networking library to send HTTP requests.
* [raster](raster/) (base, gfx): Library to handle graphics entities like sprites, images, frames.
* [she](she/) (base, gfx, allegro): A wrapper for the Allegro library.

View File

@ -1,5 +1,5 @@
/* Aseprite
* Copyright (C) 2001-2013 David Capello
* 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
@ -26,9 +26,9 @@
namespace app {
template<class T>
inline T* load_widget(const char* fileName, const char* widgetId) {
inline T* load_widget(const char* fileName, const char* widgetId, T* widget = NULL) {
WidgetLoader loader;
return loader.loadWidgetT<T>(fileName, widgetId);
return loader.loadWidgetT<T>(fileName, widgetId, widget);
}
} // namespace app

View File

@ -1,5 +1,5 @@
/* Aseprite
* Copyright (C) 2001-2013 David Capello
* 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
@ -42,8 +42,6 @@
#include <cstdlib>
#include <cstring>
#define TRANSLATE_ATTR(a) a
namespace app {
using namespace ui;
@ -68,9 +66,8 @@ void WidgetLoader::addWidgetType(const char* tagName, IWidgetTypeCreator* creato
m_typeCreators[tagName] = creator;
}
Widget* WidgetLoader::loadWidget(const char* fileName, const char* widgetId)
Widget* WidgetLoader::loadWidget(const char* fileName, const char* widgetId, ui::Widget* widget)
{
Widget* widget;
std::string buf;
ResourceFinder rf;
@ -83,17 +80,18 @@ Widget* WidgetLoader::loadWidget(const char* fileName, const char* widgetId)
if (!rf.findFirst())
throw WidgetNotFound(widgetId);
widget = loadWidgetFromXmlFile(rf.filename(), widgetId);
widget = loadWidgetFromXmlFile(rf.filename(), widgetId, widget);
if (!widget)
throw WidgetNotFound(widgetId);
return widget;
}
Widget* WidgetLoader::loadWidgetFromXmlFile(const std::string& xmlFilename,
const std::string& widgetId)
Widget* WidgetLoader::loadWidgetFromXmlFile(
const std::string& xmlFilename,
const std::string& widgetId,
ui::Widget* widget)
{
Widget* widget = NULL;
m_tooltipManager = NULL;
XmlDocumentRef doc(open_xml(xmlFilename));
@ -108,7 +106,7 @@ Widget* WidgetLoader::loadWidgetFromXmlFile(const std::string& xmlFilename,
const char* nodename = xmlElement->Attribute("id");
if (nodename && nodename == widgetId) {
widget = convertXmlElementToWidget(xmlElement, NULL);
widget = convertXmlElementToWidget(xmlElement, NULL, widget);
break;
}
@ -118,11 +116,9 @@ Widget* WidgetLoader::loadWidgetFromXmlFile(const std::string& xmlFilename,
return widget;
}
Widget* WidgetLoader::convertXmlElementToWidget(const TiXmlElement* elem, Widget* root)
Widget* WidgetLoader::convertXmlElementToWidget(const TiXmlElement* elem, Widget* root, Widget* widget)
{
const std::string elem_name = elem->Value();
Widget* widget = NULL;
Widget* child;
// TODO error handling: add a message if the widget is bad specified
@ -130,121 +126,107 @@ Widget* WidgetLoader::convertXmlElementToWidget(const TiXmlElement* elem, Widget
TypeCreatorsMap::iterator it = m_typeCreators.find(elem_name);
if (it != m_typeCreators.end()) {
widget = it->second->createWidgetFromXml(elem);
if (!widget)
widget = it->second->createWidgetFromXml(elem);
}
// Oneof
else if (elem_name == "panel") {
widget = new Panel();
if (!widget)
widget = new Panel();
}
// Boxes
else if (elem_name == "box") {
bool horizontal = bool_attr_is_true(elem, "horizontal");
bool vertical = bool_attr_is_true(elem, "vertical");
bool homogeneous = bool_attr_is_true(elem, "homogeneous");
int align = (horizontal ? JI_HORIZONTAL: vertical ? JI_VERTICAL: 0);
widget = new Box((horizontal ? JI_HORIZONTAL:
vertical ? JI_VERTICAL: 0) |
(homogeneous ? JI_HOMOGENEOUS: 0));
if (!widget)
widget = new Box(align);
else
widget->setAlign(widget->getAlign() | align);
}
else if (elem_name == "vbox") {
bool homogeneous = bool_attr_is_true(elem, "homogeneous");
widget = new VBox();
if (homogeneous)
widget->setAlign(widget->getAlign() | JI_HOMOGENEOUS);
if (!widget)
widget = new VBox();
}
else if (elem_name == "hbox") {
bool homogeneous = bool_attr_is_true(elem, "homogeneous");
widget = new HBox();
if (homogeneous)
widget->setAlign(widget->getAlign() | JI_HOMOGENEOUS);
if (!widget)
widget = new HBox();
}
else if (elem_name == "boxfiller") {
widget = new BoxFiller();
if (!widget)
widget = new BoxFiller();
}
// Button
else if (elem_name == "button") {
const char *text = elem->Attribute("text");
if (!widget)
widget = new Button("");
widget = new Button(text ? TRANSLATE_ATTR(text): "");
if (widget) {
bool left = bool_attr_is_true(elem, "left");
bool right = bool_attr_is_true(elem, "right");
bool top = bool_attr_is_true(elem, "top");
bool bottom = bool_attr_is_true(elem, "bottom");
bool closewindow = bool_attr_is_true(elem, "closewindow");
const char *_bevel = elem->Attribute("bevel");
bool left = bool_attr_is_true(elem, "left");
bool right = bool_attr_is_true(elem, "right");
bool top = bool_attr_is_true(elem, "top");
bool bottom = bool_attr_is_true(elem, "bottom");
bool closewindow = bool_attr_is_true(elem, "closewindow");
const char *_bevel = elem->Attribute("bevel");
widget->setAlign((left ? JI_LEFT: (right ? JI_RIGHT: JI_CENTER)) |
(top ? JI_TOP: (bottom ? JI_BOTTOM: JI_MIDDLE)));
widget->setAlign((left ? JI_LEFT: (right ? JI_RIGHT: JI_CENTER)) |
(top ? JI_TOP: (bottom ? JI_BOTTOM: JI_MIDDLE)));
if (_bevel != NULL) {
char* bevel = base_strdup(_bevel);
int c, b[4];
char *tok;
if (_bevel != NULL) {
char* bevel = base_strdup(_bevel);
int c, b[4];
char *tok;
for (c=0; c<4; ++c)
b[c] = 0;
for (c=0; c<4; ++c)
b[c] = 0;
for (tok=ustrtok(bevel, " "), c=0;
tok;
tok=ustrtok(NULL, " "), ++c) {
if (c < 4)
b[c] = ustrtol(tok, NULL, 10);
}
base_free(bevel);
setup_bevels(widget, b[0], b[1], b[2], b[3]);
for (tok=ustrtok(bevel, " "), c=0;
tok;
tok=ustrtok(NULL, " "), ++c) {
if (c < 4)
b[c] = ustrtol(tok, NULL, 10);
}
base_free(bevel);
if (closewindow) {
static_cast<Button*>(widget)
->Click.connect(Bind<void>(&Widget::closeWindow, widget));
}
setup_bevels(widget, b[0], b[1], b[2], b[3]);
}
if (closewindow) {
static_cast<Button*>(widget)
->Click.connect(Bind<void>(&Widget::closeWindow, widget));
}
}
// Check
else if (elem_name == "check") {
const char *text = elem->Attribute("text");
const char *looklike = elem->Attribute("looklike");
text = (text ? TRANSLATE_ATTR(text): "");
if (looklike != NULL && strcmp(looklike, "button") == 0) {
widget = new CheckBox(text, kButtonWidget);
if (!widget)
widget = new CheckBox("", kButtonWidget);
}
else {
widget = new CheckBox(text);
if (!widget)
widget = new CheckBox("");
}
if (widget) {
bool center = bool_attr_is_true(elem, "center");
bool right = bool_attr_is_true(elem, "right");
bool top = bool_attr_is_true(elem, "top");
bool bottom = bool_attr_is_true(elem, "bottom");
bool center = bool_attr_is_true(elem, "center");
bool right = bool_attr_is_true(elem, "right");
bool top = bool_attr_is_true(elem, "top");
bool bottom = bool_attr_is_true(elem, "bottom");
widget->setAlign((center ? JI_CENTER:
(right ? JI_RIGHT: JI_LEFT)) |
(top ? JI_TOP:
(bottom ? JI_BOTTOM: JI_MIDDLE)));
}
widget->setAlign((center ? JI_CENTER:
(right ? JI_RIGHT: JI_LEFT)) |
(top ? JI_TOP:
(bottom ? JI_BOTTOM: JI_MIDDLE)));
}
/* combobox */
else if (elem_name == "combobox") {
widget = new ComboBox();
if (!widget)
widget = new ComboBox();
}
/* entry */
else if (elem_name == "entry") {
const char* maxsize = elem->Attribute("maxsize");
const char* text = elem->Attribute("text");
const char* suffix = elem->Attribute("suffix");
if (maxsize != NULL) {
bool readonly = bool_attr_is_true(elem, "readonly");
widget = new Entry(strtol(maxsize, NULL, 10),
text ? TRANSLATE_ATTR(text): "");
widget = new Entry(strtol(maxsize, NULL, 10), "");
if (readonly)
((Entry*)widget)->setReadOnly(true);
@ -255,7 +237,6 @@ Widget* WidgetLoader::convertXmlElementToWidget(const TiXmlElement* elem, Widget
else
throw std::runtime_error("<entry> element found without 'maxsize' attribute");
}
/* grid */
else if (elem_name == "grid") {
const char *columns = elem->Attribute("columns");
bool same_width_columns = bool_attr_is_true(elem, "same_width_columns");
@ -265,56 +246,60 @@ Widget* WidgetLoader::convertXmlElementToWidget(const TiXmlElement* elem, Widget
same_width_columns);
}
}
/* label */
else if (elem_name == "label") {
const char *text = elem->Attribute("text");
if (!widget)
widget = new Label("");
widget = new Label(text ? TRANSLATE_ATTR(text): "");
if (widget) {
bool center = bool_attr_is_true(elem, "center");
bool right = bool_attr_is_true(elem, "right");
bool top = bool_attr_is_true(elem, "top");
bool bottom = bool_attr_is_true(elem, "bottom");
bool center = bool_attr_is_true(elem, "center");
bool right = bool_attr_is_true(elem, "right");
bool top = bool_attr_is_true(elem, "top");
bool bottom = bool_attr_is_true(elem, "bottom");
widget->setAlign((center ? JI_CENTER:
(right ? JI_RIGHT: JI_LEFT)) |
(top ? JI_TOP:
(bottom ? JI_BOTTOM: JI_MIDDLE)));
}
widget->setAlign((center ? JI_CENTER:
(right ? JI_RIGHT: JI_LEFT)) |
(top ? JI_TOP:
(bottom ? JI_BOTTOM: JI_MIDDLE)));
}
/* link */
else if (elem_name == "link") {
const char* text = elem->Attribute("text");
const char* url = elem->Attribute("url");
widget = new LinkLabel(url ? url: "", text ? TRANSLATE_ATTR(text): "");
if (widget) {
bool center = bool_attr_is_true(elem, "center");
bool right = bool_attr_is_true(elem, "right");
bool top = bool_attr_is_true(elem, "top");
bool bottom = bool_attr_is_true(elem, "bottom");
widget->setAlign(
(center ? JI_CENTER: (right ? JI_RIGHT: JI_LEFT)) |
(top ? JI_TOP: (bottom ? JI_BOTTOM: JI_MIDDLE)));
if (!widget)
widget = new LinkLabel(url ? url: "", "");
else {
LinkLabel* link = dynamic_cast<LinkLabel*>(widget);
ASSERT(link != NULL);
if (link)
link->setUrl(url);
}
bool center = bool_attr_is_true(elem, "center");
bool right = bool_attr_is_true(elem, "right");
bool top = bool_attr_is_true(elem, "top");
bool bottom = bool_attr_is_true(elem, "bottom");
widget->setAlign(
(center ? JI_CENTER: (right ? JI_RIGHT: JI_LEFT)) |
(top ? JI_TOP: (bottom ? JI_BOTTOM: JI_MIDDLE)));
}
/* listbox */
else if (elem_name == "listbox") {
widget = new ListBox();
if (!widget)
widget = new ListBox();
}
/* listitem */
else if (elem_name == "listitem") {
const char* text = elem->Attribute("text");
const char* value = elem->Attribute("value");
ListItem* listitem = new ListItem(text ? TRANSLATE_ATTR(text): "");
if (value) {
listitem->setValue(value);
ListItem* listitem;
if (!widget) {
listitem = new ListItem("");
widget = listitem;
}
widget = listitem;
else {
listitem = dynamic_cast<ListItem*>(widget);
ASSERT(listitem != NULL);
}
const char* value = elem->Attribute("value");
if (value)
listitem->setValue(value);
}
/* splitter */
else if (elem_name == "splitter") {
bool horizontal = bool_attr_is_true(elem, "horizontal");
bool vertical = bool_attr_is_true(elem, "vertical");
@ -333,53 +318,58 @@ Widget* WidgetLoader::convertXmlElementToWidget(const TiXmlElement* elem, Widget
}
widget = splitter;
}
/* radio */
else if (elem_name == "radio") {
const char* text = elem->Attribute("text");
const char* group = elem->Attribute("group");
const char *looklike = elem->Attribute("looklike");
const char* looklike = elem->Attribute("looklike");
text = (text ? TRANSLATE_ATTR(text): "");
int radio_group = (group ? strtol(group, NULL, 10): 1);
if (looklike != NULL && strcmp(looklike, "button") == 0) {
widget = new RadioButton(text, radio_group, kButtonWidget);
if (!widget) {
if (looklike != NULL && strcmp(looklike, "button") == 0) {
widget = new RadioButton("", radio_group, kButtonWidget);
}
else {
widget = new RadioButton("", radio_group);
}
}
else {
widget = new RadioButton(text, radio_group);
RadioButton* radio = dynamic_cast<RadioButton*>(widget);
ASSERT(radio != NULL);
if (radio)
radio->setRadioGroup(radio_group);
}
if (widget) {
bool center = bool_attr_is_true(elem, "center");
bool right = bool_attr_is_true(elem, "right");
bool top = bool_attr_is_true(elem, "top");
bool bottom = bool_attr_is_true(elem, "bottom");
bool center = bool_attr_is_true(elem, "center");
bool right = bool_attr_is_true(elem, "right");
bool top = bool_attr_is_true(elem, "top");
bool bottom = bool_attr_is_true(elem, "bottom");
widget->setAlign((center ? JI_CENTER:
(right ? JI_RIGHT: JI_LEFT)) |
(top ? JI_TOP:
(bottom ? JI_BOTTOM: JI_MIDDLE)));
}
widget->setAlign(
(center ? JI_CENTER:
(right ? JI_RIGHT: JI_LEFT)) |
(top ? JI_TOP:
(bottom ? JI_BOTTOM: JI_MIDDLE)));
}
/* separator */
else if (elem_name == "separator") {
const char *text = elem->Attribute("text");
bool center = bool_attr_is_true(elem, "center");
bool right = bool_attr_is_true(elem, "right");
bool middle = bool_attr_is_true(elem, "middle");
bool bottom = bool_attr_is_true(elem, "bottom");
bool horizontal = bool_attr_is_true(elem, "horizontal");
bool vertical = bool_attr_is_true(elem, "vertical");
int align =
(horizontal ? JI_HORIZONTAL: 0) |
(vertical ? JI_VERTICAL: 0) |
(center ? JI_CENTER: (right ? JI_RIGHT: JI_LEFT)) |
(middle ? JI_MIDDLE: (bottom ? JI_BOTTOM: JI_TOP));
widget = new Separator(text ? TRANSLATE_ATTR(text): "",
(horizontal ? JI_HORIZONTAL: 0) |
(vertical ? JI_VERTICAL: 0) |
(center ? JI_CENTER:
(right ? JI_RIGHT: JI_LEFT)) |
(middle ? JI_MIDDLE:
(bottom ? JI_BOTTOM: JI_TOP)));
if (!widget) {
const char* text = elem->Attribute("text");
widget = new Separator(text ? text: "", align);
}
else
widget->setAlign(widget->getAlign() | align);
}
/* slider */
else if (elem_name == "slider") {
const char *min = elem->Attribute("min");
const char *max = elem->Attribute("max");
@ -388,145 +378,164 @@ Widget* WidgetLoader::convertXmlElementToWidget(const TiXmlElement* elem, Widget
widget = new Slider(min_value, max_value, min_value);
}
/* textbox */
else if (elem_name == "textbox") {
bool wordwrap = bool_attr_is_true(elem, "wordwrap");
widget = new TextBox(elem->GetText(), wordwrap ? JI_WORDWRAP: 0);
}
/* view */
else if (elem_name == "view") {
widget = new View();
}
/* window */
else if (elem_name == "window") {
const char *text = elem->Attribute("text");
bool desktop = bool_attr_is_true(elem, "desktop");
if (desktop)
widget = new Window(Window::DesktopWindow);
else if (text)
widget = new Window(Window::WithTitleBar, TRANSLATE_ATTR(text));
if (!widget)
widget = new TextBox(elem->GetText(), 0);
else
widget = new Window(Window::WithoutTitleBar);
widget->setText(elem->GetText());
if (wordwrap)
widget->setAlign(widget->getAlign() | JI_WORDWRAP);
}
else if (elem_name == "view") {
if (!widget)
widget = new View();
}
else if (elem_name == "window") {
if (!widget) {
const char* text = elem->Attribute("text");
bool desktop = bool_attr_is_true(elem, "desktop");
if (desktop)
widget = new Window(Window::DesktopWindow);
else if (text)
widget = new Window(Window::WithTitleBar, text);
else
widget = new Window(Window::WithoutTitleBar);
}
}
/* colorpicker */
else if (elem_name == "colorpicker") {
widget = new ColorButton(Color::fromMask(), app_get_current_pixel_format());
if (!widget)
widget = new ColorButton(Color::fromMask(), app_get_current_pixel_format());
}
// Was the widget created?
if (widget) {
const char* id = elem->Attribute("id");
const char* tooltip = elem->Attribute("tooltip");
bool selected = bool_attr_is_true(elem, "selected");
bool disabled = bool_attr_is_true(elem, "disabled");
bool expansive = bool_attr_is_true(elem, "expansive");
bool magnet = bool_attr_is_true(elem, "magnet");
bool noborders = bool_attr_is_true(elem, "noborders");
const char* width = elem->Attribute("width");
const char* height = elem->Attribute("height");
const char* minwidth = elem->Attribute("minwidth");
const char* minheight = elem->Attribute("minheight");
const char* maxwidth = elem->Attribute("maxwidth");
const char* maxheight = elem->Attribute("maxheight");
const char* childspacing = elem->Attribute("childspacing");
if (width) {
if (!minwidth) minwidth = width;
if (!maxwidth) maxwidth = width;
}
if (height) {
if (!minheight) minheight = height;
if (!maxheight) maxheight = height;
}
if (id != NULL)
widget->setId(id);
if (tooltip != NULL && root != NULL) {
if (!m_tooltipManager) {
m_tooltipManager = new ui::TooltipManager();
root->addChild(m_tooltipManager);
}
m_tooltipManager->addTooltipFor(widget, tooltip, JI_LEFT);
}
if (selected)
widget->setSelected(selected);
if (disabled)
widget->setEnabled(false);
if (expansive)
widget->setExpansive(true);
if (magnet)
widget->setFocusMagnet(true);
if (noborders)
widget->noBorderNoChildSpacing();
if (childspacing)
widget->child_spacing = strtol(childspacing, NULL, 10);
gfx::Size reqSize = widget->getPreferredSize();
if (minwidth || minheight) {
int w = (minwidth ? jguiscale()*strtol(minwidth, NULL, 10): reqSize.w);
int h = (minheight ? jguiscale()*strtol(minheight, NULL, 10): reqSize.h);
widget->setMinSize(gfx::Size(w, h));
}
if (maxwidth || maxheight) {
int w = (maxwidth ? jguiscale()*strtol(maxwidth, NULL, 10): INT_MAX);
int h = (maxheight ? jguiscale()*strtol(maxheight, NULL, 10): INT_MAX);
widget->setMaxSize(gfx::Size(w, h));
}
if (!root)
root = widget;
// Children
const TiXmlElement* childElem = elem->FirstChildElement();
while (childElem) {
child = convertXmlElementToWidget(childElem, root);
if (child) {
// Attach the child in the view
if (widget->type == kViewWidget) {
static_cast<View*>(widget)->attachToView(child);
break;
}
// Add the child in the grid
else if (widget->type == kGridWidget) {
const char* cell_hspan = childElem->Attribute("cell_hspan");
const char* cell_vspan = childElem->Attribute("cell_vspan");
const char* cell_align = childElem->Attribute("cell_align");
int hspan = cell_hspan ? strtol(cell_hspan, NULL, 10): 1;
int vspan = cell_vspan ? strtol(cell_vspan, NULL, 10): 1;
int align = cell_align ? convert_align_value_to_flags(cell_align): 0;
Grid* grid = dynamic_cast<Grid*>(widget);
ASSERT(grid != NULL);
grid->addChildInCell(child, hspan, vspan, align);
}
// Just add the child in any other kind of widget
else
widget->addChild(child);
}
childElem = childElem->NextSiblingElement();
}
if (widget->type == kViewWidget) {
bool maxsize = bool_attr_is_true(elem, "maxsize");
if (maxsize)
static_cast<View*>(widget)->makeVisibleAllScrollableArea();
}
}
if (widget)
fillWidgetWithXmlElementAttributes(elem, root, widget);
return widget;
}
void WidgetLoader::fillWidgetWithXmlElementAttributes(const TiXmlElement* elem, ui::Widget* root, ui::Widget* widget)
{
const char* id = elem->Attribute("id");
const char* text = elem->Attribute("text");
const char* tooltip = elem->Attribute("tooltip");
bool selected = bool_attr_is_true(elem, "selected");
bool disabled = bool_attr_is_true(elem, "disabled");
bool expansive = bool_attr_is_true(elem, "expansive");
bool homogeneous = bool_attr_is_true(elem, "homogeneous");
bool magnet = bool_attr_is_true(elem, "magnet");
bool noborders = bool_attr_is_true(elem, "noborders");
const char* width = elem->Attribute("width");
const char* height = elem->Attribute("height");
const char* minwidth = elem->Attribute("minwidth");
const char* minheight = elem->Attribute("minheight");
const char* maxwidth = elem->Attribute("maxwidth");
const char* maxheight = elem->Attribute("maxheight");
const char* childspacing = elem->Attribute("childspacing");
if (width) {
if (!minwidth) minwidth = width;
if (!maxwidth) maxwidth = width;
}
if (height) {
if (!minheight) minheight = height;
if (!maxheight) maxheight = height;
}
if (id != NULL)
widget->setId(id);
if (text)
widget->setText(text);
if (tooltip != NULL && root != NULL) {
if (!m_tooltipManager) {
m_tooltipManager = new ui::TooltipManager();
root->addChild(m_tooltipManager);
}
m_tooltipManager->addTooltipFor(widget, tooltip, JI_LEFT);
}
if (selected)
widget->setSelected(selected);
if (disabled)
widget->setEnabled(false);
if (expansive)
widget->setExpansive(true);
if (homogeneous)
widget->setAlign(widget->getAlign() | JI_HOMOGENEOUS);
if (magnet)
widget->setFocusMagnet(true);
if (noborders)
widget->noBorderNoChildSpacing();
if (childspacing)
widget->child_spacing = strtol(childspacing, NULL, 10);
gfx::Size reqSize = widget->getPreferredSize();
if (minwidth || minheight) {
int w = (minwidth ? jguiscale()*strtol(minwidth, NULL, 10): reqSize.w);
int h = (minheight ? jguiscale()*strtol(minheight, NULL, 10): reqSize.h);
widget->setMinSize(gfx::Size(w, h));
}
if (maxwidth || maxheight) {
int w = (maxwidth ? jguiscale()*strtol(maxwidth, NULL, 10): INT_MAX);
int h = (maxheight ? jguiscale()*strtol(maxheight, NULL, 10): INT_MAX);
widget->setMaxSize(gfx::Size(w, h));
}
if (!root)
root = widget;
// Children
const TiXmlElement* childElem = elem->FirstChildElement();
while (childElem) {
Widget* child = convertXmlElementToWidget(childElem, root, NULL);
if (child) {
// Attach the child in the view
if (widget->type == kViewWidget) {
static_cast<View*>(widget)->attachToView(child);
break;
}
// Add the child in the grid
else if (widget->type == kGridWidget) {
const char* cell_hspan = childElem->Attribute("cell_hspan");
const char* cell_vspan = childElem->Attribute("cell_vspan");
const char* cell_align = childElem->Attribute("cell_align");
int hspan = cell_hspan ? strtol(cell_hspan, NULL, 10): 1;
int vspan = cell_vspan ? strtol(cell_vspan, NULL, 10): 1;
int align = cell_align ? convert_align_value_to_flags(cell_align): 0;
Grid* grid = dynamic_cast<Grid*>(widget);
ASSERT(grid != NULL);
grid->addChildInCell(child, hspan, vspan, align);
}
// Just add the child in any other kind of widget
else
widget->addChild(child);
}
childElem = childElem->NextSiblingElement();
}
if (widget->type == kViewWidget) {
bool maxsize = bool_attr_is_true(elem, "maxsize");
if (maxsize)
static_cast<View*>(widget)->makeVisibleAllScrollableArea();
}
}
static int convert_align_value_to_flags(const char *value)
{
char *tok, *ptr = base_strdup(value);

View File

@ -1,5 +1,5 @@
/* Aseprite
* Copyright (C) 2001-2013 David Capello
* 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
@ -56,13 +56,11 @@ namespace app {
void addWidgetType(const char* tagName, IWidgetTypeCreator* creator);
// Loads the specified widget from an .xml file.
ui::Widget* loadWidget(const char* fileName, const char* widgetId);
ui::Widget* loadWidget(const char* fileName, const char* widgetId, ui::Widget* widget = NULL);
template<class T>
T* loadWidgetT(const char* fileName, const char* widgetId) {
ui::Widget* widget = loadWidget(fileName, widgetId);
T* specificWidget = dynamic_cast<T*>(widget);
T* loadWidgetT(const char* fileName, const char* widgetId, T* widget = NULL) {
T* specificWidget = dynamic_cast<T*>(loadWidget(fileName, widgetId, widget));
if (!specificWidget)
throw WidgetTypeMismatch(widgetId);
@ -70,9 +68,13 @@ namespace app {
}
private:
ui::Widget* loadWidgetFromXmlFile(const std::string& xmlFilename,
const std::string& widgetId);
ui::Widget* convertXmlElementToWidget(const TiXmlElement* elem, ui::Widget* root);
ui::Widget* loadWidgetFromXmlFile(
const std::string& xmlFilename,
const std::string& widgetId,
ui::Widget* widget);
ui::Widget* convertXmlElementToWidget(const TiXmlElement* elem, ui::Widget* root, ui::Widget* widget);
void fillWidgetWithXmlElementAttributes(const TiXmlElement* elem, ui::Widget* root, ui::Widget* widget);
typedef std::map<std::string, IWidgetTypeCreator*> TypeCreatorsMap;

14
src/gen/CMakeLists.txt Normal file
View File

@ -0,0 +1,14 @@
# Aseprite Code Generator
# Copyright (C) 2014 David Capello
add_executable(gen
gen.cpp
ui_class.cpp)
target_link_libraries(gen base-lib)
if(USE_SHARED_TINYXML)
target_link_libraries(gen ${LIBTINYXML_LIBRARY})
else()
target_link_libraries(gen tinyxml)
endif()

20
src/gen/LICENSE.txt Normal file
View File

@ -0,0 +1,20 @@
Copyright (c) 2014 David Capello
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

23
src/gen/README.md Normal file
View File

@ -0,0 +1,23 @@
# Aseprite Code Generator
*Copyright (C) 2014 David Capello*
> Distributed under [MIT license](LICENSE.txt)
This utility generates source code from XML files. Its aim is to
convert XML files (dynamic data) to C++ files (static structures) that
can be checked in compile-time. There are three areas of interest:
1. To create `ui::Widget`s subclasses from
[data/widgets/*.xml](../../data/widgets/)
files. In this way we can create wrappers that can access to each
XML file directly in a easier way (e.g. one member for each widget
with an `id` parameter on it).
2. To create configuration wrappers from a special
`config-metadata.xml` file (so we can replace
`get/set_config_int/bool/string()` function calls). There is an
ongoing `cfg` module to replace the whole reading/writing
operations of user's settings/preferences.
3. To create a wrapper class for theme data access. From
[data/skins/default/](../../data/skins/default/)
we can create a C++ class with a member function to access
each theme slice, color, style, etc.

50
src/gen/gen.cpp Normal file
View File

@ -0,0 +1,50 @@
// 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/file_handle.h"
#include "base/path.h"
#include "base/program_options.h"
#include "base/string.h"
#include "gen/ui_class.h"
#include "tinyxml.h"
#include <iostream>
typedef base::ProgramOptions PO;
static void run(int argc, const char* argv[])
{
PO po;
PO::Option& inputFn = po.add("input").requiresValue("<filename>");
PO::Option& widgetId = po.add("widgetid").requiresValue("<filename>");
po.parse(argc, argv);
// Try to load the XML file
TiXmlDocument* doc = NULL;
if (inputFn.enabled()) {
base::FileHandle inputFile(base::open_file(inputFn.value(), "rb"));
doc = new TiXmlDocument();
doc->SetValue(inputFn.value().c_str());
if (!doc->LoadFile(inputFile))
throw std::runtime_error("invalid input file");
}
if (doc && widgetId.enabled())
gen_ui_class(doc, inputFn.value(), widgetId.value());
}
int main(int argc, const char* argv[])
{
try {
run(argc, argv);
return 0;
}
catch (const std::exception& e) {
std::cerr << e.what() << "\n";
return 1;
}
}

176
src/gen/ui_class.cpp Normal file
View File

@ -0,0 +1,176 @@
// 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/program_options.h"
#include "base/string.h"
#include "gen/ui_class.h"
#include <cctype>
#include <iostream>
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");
if (id && id == thisId)
return elem;
TiXmlElement* child = elem->FirstChildElement();
while (child) {
TiXmlElement* match = find_element_by_id(child, thisId);
if (match)
return match;
child = child->NextSiblingElement();
}
return NULL;
}
static void collect_widgets_with_ids(TiXmlElement* elem, XmlElements& widgets)
{
TiXmlElement* child = elem->FirstChildElement();
while (child) {
const char* id = child->Attribute("id");
if (id)
widgets.push_back(child);
collect_widgets_with_ids(child, widgets);
child = child->NextSiblingElement();
}
}
static std::string convert_type(const std::string& name)
{
if (name == "box") return "ui::Box";
if (name == "button") return "ui::Button";
if (name == "check") return "ui::CheckBox";
if (name == "combobox") return "ui::ComboBox";
if (name == "entry") return "ui::Entry";
if (name == "hbox") return "ui::HBox";
if (name == "label") return "ui::Label";
if (name == "listbox") return "ui::ListBox";
if (name == "radio") return "ui::RadioButton";
if (name == "slider") return "ui::Slider";
if (name == "vbox") return "ui::VBox";
if (name == "view") return "ui::View";
if (name == "window") return "ui::Window";
throw base::Exception("unknown widget name: " + name);
}
void gen_ui_class(TiXmlDocument* doc, const std::string& inputFn, const std::string& widgetId)
{
std::cout
<< "// Don't modify, generated file from " << inputFn << "\n"
<< "\n";
TiXmlHandle handle(doc);
TiXmlElement* elem = handle.FirstChild("gui").ToElement();
elem = find_element_by_id(elem, widgetId);
if (!elem) {
std::cout << "#error Widget not found: " << widgetId << "\n";
return;
}
XmlElements widgets;
collect_widgets_with_ids(elem, widgets);
std::string className = convert_xmlid_to_cppid(widgetId, true);
std::string fnUpper = base::string_to_upper(base::get_file_title(inputFn));
std::string widgetType = convert_type(elem->Value());
std::cout
<< "#ifndef GENERATED_" << fnUpper << "_H_INCLUDED\n"
<< "#define GENERATED_" << fnUpper << "_H_INCLUDED\n"
<< "#pragma once\n"
<< "\n"
<< "#include \"app/find_widget.h\"\n"
<< "#include \"app/load_widget.h\"\n"
<< "#include \"ui/ui.h\"\n"
<< "\n"
<< "namespace app {\n"
<< "namespace gen {\n"
<< "\n"
<< " class " << className << " : public " << widgetType << " {\n"
<< " public:\n"
<< " " << className << "()";
// Special ctor for base class
if (widgetType == "ui::Window") {
std::cout
<< " : ui::Window(ui::Window::WithTitleBar)";
}
std::cout
<< " {\n"
<< " app::load_widget(\"" << base::get_file_name(inputFn) << "\", \"" << widgetId << "\", this);\n"
<< " app::finder(this)\n";
for (XmlElements::iterator it=widgets.begin(), end=widgets.end();
it != end; ++it) {
const char* id = (*it)->Attribute("id");
std::string cppid = convert_xmlid_to_cppid(id, false);
std::cout
<< " >> \"" << id << "\" >> m_" << cppid << "\n";
}
std::cout
<< " ;\n"
<< " }\n"
<< "\n";
for (XmlElements::iterator it=widgets.begin(), end=widgets.end();
it != end; ++it) {
std::string childType = convert_type((*it)->Value());
const char* id = (*it)->Attribute("id");
std::string cppid = convert_xmlid_to_cppid(id, false);
std::cout
<< " " << childType << "* " << cppid << "() { return m_" << cppid << "; }\n";
}
std::cout
<< "\n"
<< " private:\n";
for (XmlElements::iterator it=widgets.begin(), end=widgets.end();
it != end; ++it) {
std::string childType = convert_type((*it)->Value());
const char* id = (*it)->Attribute("id");
std::string cppid = convert_xmlid_to_cppid(id, false);
std::cout
<< " " << childType << "* m_" << cppid << ";\n";
}
std::cout
<< " };\n"
<< "\n"
<< "} // namespace gen\n"
<< "} // namespace app\n"
<< "\n"
<< "#endif\n";
}

16
src/gen/ui_class.h Normal file
View File

@ -0,0 +1,16 @@
// 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_UI_CLASS_H_INCLUDED
#define GEN_UI_CLASS_H_INCLUDED
#pragma once
#include <string>
#include "tinyxml.h"
void gen_ui_class(TiXmlDocument* doc, const std::string& inputFn, const std::string& widgetId);
#endif