From 31e565a2b042ad1bbbf342fd79ef34b1b0687fc0 Mon Sep 17 00:00:00 2001 From: David Capello Date: Sun, 13 Jul 2014 13:24:57 -0300 Subject: [PATCH] Add "gen" utility to generate UI widget wrappers from XML files --- CMakeLists.txt | 15 +- src/CMakeLists.txt | 31 ++- src/README.md | 1 + src/app/load_widget.h | 6 +- src/app/widget_loader.cpp | 559 +++++++++++++++++++------------------- src/app/widget_loader.h | 20 +- src/gen/CMakeLists.txt | 14 + src/gen/LICENSE.txt | 20 ++ src/gen/README.md | 23 ++ src/gen/gen.cpp | 50 ++++ src/gen/ui_class.cpp | 176 ++++++++++++ src/gen/ui_class.h | 16 ++ 12 files changed, 636 insertions(+), 295 deletions(-) create mode 100644 src/gen/CMakeLists.txt create mode 100644 src/gen/LICENSE.txt create mode 100644 src/gen/README.md create mode 100644 src/gen/gen.cpp create mode 100644 src/gen/ui_class.cpp create mode 100644 src/gen/ui_class.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a751ac63b..440c6cbb3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 922594da7..7ffe0aa3e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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 diff --git a/src/README.md b/src/README.md index c0567638c..9d0f96685 100644 --- a/src/README.md +++ b/src/README.md @@ -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. diff --git a/src/app/load_widget.h b/src/app/load_widget.h index 0b283a4a3..6f2a61ecf 100644 --- a/src/app/load_widget.h +++ b/src/app/load_widget.h @@ -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 - 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(fileName, widgetId); + return loader.loadWidgetT(fileName, widgetId, widget); } } // namespace app diff --git a/src/app/widget_loader.cpp b/src/app/widget_loader.cpp index 8f31836f4..91e272664 100644 --- a/src/app/widget_loader.cpp +++ b/src/app/widget_loader.cpp @@ -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 #include -#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(widget) - ->Click.connect(Bind(&Widget::closeWindow, widget)); - } + setup_bevels(widget, b[0], b[1], b[2], b[3]); + } + + if (closewindow) { + static_cast(widget) + ->Click.connect(Bind(&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(" 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(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(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(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(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(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(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(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(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(widget)->makeVisibleAllScrollableArea(); + } +} + static int convert_align_value_to_flags(const char *value) { char *tok, *ptr = base_strdup(value); diff --git a/src/app/widget_loader.h b/src/app/widget_loader.h index b700a9560..02b2a365f 100644 --- a/src/app/widget_loader.h +++ b/src/app/widget_loader.h @@ -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 - T* loadWidgetT(const char* fileName, const char* widgetId) { - ui::Widget* widget = loadWidget(fileName, widgetId); - - T* specificWidget = dynamic_cast(widget); + T* loadWidgetT(const char* fileName, const char* widgetId, T* widget = NULL) { + T* specificWidget = dynamic_cast(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 TypeCreatorsMap; diff --git a/src/gen/CMakeLists.txt b/src/gen/CMakeLists.txt new file mode 100644 index 000000000..90180b34d --- /dev/null +++ b/src/gen/CMakeLists.txt @@ -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() + diff --git a/src/gen/LICENSE.txt b/src/gen/LICENSE.txt new file mode 100644 index 000000000..aea0f6e8f --- /dev/null +++ b/src/gen/LICENSE.txt @@ -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. diff --git a/src/gen/README.md b/src/gen/README.md new file mode 100644 index 000000000..3cf272bad --- /dev/null +++ b/src/gen/README.md @@ -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. diff --git a/src/gen/gen.cpp b/src/gen/gen.cpp new file mode 100644 index 000000000..7399446fe --- /dev/null +++ b/src/gen/gen.cpp @@ -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 + +typedef base::ProgramOptions PO; + +static void run(int argc, const char* argv[]) +{ + PO po; + PO::Option& inputFn = po.add("input").requiresValue(""); + PO::Option& widgetId = po.add("widgetid").requiresValue(""); + 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; + } +} diff --git a/src/gen/ui_class.cpp b/src/gen/ui_class.cpp new file mode 100644 index 000000000..a4d8ebf09 --- /dev/null +++ b/src/gen/ui_class.cpp @@ -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 +#include + +typedef base::ProgramOptions PO; +typedef std::vector 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; iAttribute("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"; +} diff --git a/src/gen/ui_class.h b/src/gen/ui_class.h new file mode 100644 index 000000000..a4d407c94 --- /dev/null +++ b/src/gen/ui_class.h @@ -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 +#include "tinyxml.h" + +void gen_ui_class(TiXmlDocument* doc, const std::string& inputFn, const std::string& widgetId); + +#endif