1 | // Aseprite Code Generator |
2 | // Copyright (c) 2021 Igara Studio S.A. |
3 | // Copyright (c) 2014-2018 David Capello |
4 | // |
5 | // This file is released under the terms of the MIT license. |
6 | // Read LICENSE.txt for more information. |
7 | |
8 | #include "gen/ui_class.h" |
9 | |
10 | #include "base/exception.h" |
11 | #include "base/file_handle.h" |
12 | #include "base/fs.h" |
13 | #include "base/string.h" |
14 | #include "gen/common.h" |
15 | |
16 | #include <iostream> |
17 | #include <set> |
18 | #include <vector> |
19 | |
20 | typedef std::vector<TiXmlElement*> XmlElements; |
21 | |
22 | namespace { |
23 | |
24 | struct Item { |
25 | std::string xmlid; |
26 | std::string cppid; |
27 | std::string type; |
28 | std::string incl; |
29 | const Item& typeIncl(const char* type, |
30 | const char* incl) { |
31 | this->type = type; |
32 | this->incl = incl; |
33 | return *this; |
34 | } |
35 | }; |
36 | |
37 | } |
38 | |
39 | static TiXmlElement* find_element_by_id(TiXmlElement* elem, const std::string& thisId) |
40 | { |
41 | const char* id = elem->Attribute("id" ); |
42 | if (id && id == thisId) |
43 | return elem; |
44 | |
45 | TiXmlElement* child = elem->FirstChildElement(); |
46 | while (child) { |
47 | TiXmlElement* match = find_element_by_id(child, thisId); |
48 | if (match) |
49 | return match; |
50 | |
51 | child = child->NextSiblingElement(); |
52 | } |
53 | |
54 | return NULL; |
55 | } |
56 | |
57 | static void collect_widgets_with_ids(TiXmlElement* elem, XmlElements& widgets) |
58 | { |
59 | TiXmlElement* child = elem->FirstChildElement(); |
60 | while (child) { |
61 | const char* id = child->Attribute("id" ); |
62 | if (id) |
63 | widgets.push_back(child); |
64 | collect_widgets_with_ids(child, widgets); |
65 | child = child->NextSiblingElement(); |
66 | } |
67 | } |
68 | |
69 | static Item convert_to_item(TiXmlElement* elem) |
70 | { |
71 | static std::string parent; |
72 | const std::string name = elem->Value(); |
73 | if (name != "item" ) |
74 | parent = name; |
75 | |
76 | Item item; |
77 | if (elem->Attribute("id" )) { |
78 | item.xmlid = elem->Attribute("id" ); |
79 | item.cppid = convert_xmlid_to_cppid(item.xmlid, false); |
80 | } |
81 | |
82 | if (name == "box" ) |
83 | return item.typeIncl("ui::Box" , |
84 | "ui/box.h" ); |
85 | if (name == "button" ) |
86 | return item.typeIncl("ui::Button" , |
87 | "ui/button.h" ); |
88 | if (name == "buttonset" ) |
89 | return item.typeIncl("app::ButtonSet" , |
90 | "app/ui/button_set.h" ); |
91 | if (name == "check" ) { |
92 | if (elem->Attribute("pref" ) != nullptr) |
93 | return item.typeIncl("BoolPrefWidget<ui::CheckBox>" , |
94 | "app/ui/pref_widget.h" ); |
95 | else |
96 | return item.typeIncl("ui::CheckBox" , |
97 | "ui/button.h" ); |
98 | } |
99 | if (name == "colorpicker" ) |
100 | return item.typeIncl("app::ColorButton" , |
101 | "app/ui/color_button.h" ); |
102 | if (name == "combobox" ) |
103 | return item.typeIncl("ui::ComboBox" , |
104 | "ui/combobox.h" ); |
105 | if (name == "dropdownbutton" ) |
106 | return item.typeIncl("app::DropDownButton" , |
107 | "app/ui/drop_down_button.h" ); |
108 | if (name == "entry" ) |
109 | return item.typeIncl("ui::Entry" , |
110 | "ui/entry.h" ); |
111 | if (name == "expr" ) |
112 | return item.typeIncl("app::ExprEntry" , |
113 | "app/ui/expr_entry.h" ); |
114 | if (name == "grid" ) |
115 | return item.typeIncl("ui::Grid" , |
116 | "ui/grid.h" ); |
117 | if (name == "hbox" ) |
118 | return item.typeIncl("ui::HBox" , |
119 | "ui/box.h" ); |
120 | if (name == "image" ) |
121 | return item.typeIncl("ui::ImageView" , |
122 | "ui/image_view.h" ); |
123 | if (name == "item" && |
124 | parent == "buttonset" ) |
125 | return item.typeIncl("app::ButtonSet::Item" , |
126 | "app/ui/button_set.h" ); |
127 | if (name == "label" ) |
128 | return item.typeIncl("ui::Label" , |
129 | "ui/label.h" ); |
130 | if (name == "link" ) |
131 | return item.typeIncl("ui::LinkLabel" , |
132 | "ui/link_label.h" ); |
133 | if (name == "listbox" ) |
134 | return item.typeIncl("ui::ListBox" , |
135 | "ui/listbox.h" ); |
136 | if (name == "listitem" ) |
137 | return item.typeIncl("ui::ListItem" , |
138 | "ui/listitem.h" ); |
139 | if (name == "panel" ) |
140 | return item.typeIncl("ui::Panel" , |
141 | "ui/panel.h" ); |
142 | if (name == "popupwindow" ) |
143 | return item.typeIncl("ui::PopupWindow" , |
144 | "ui/popup_window.h" ); |
145 | if (name == "radio" ) |
146 | return item.typeIncl("ui::RadioButton" , |
147 | "ui/button.h" ); |
148 | if (name == "search" ) |
149 | return item.typeIncl("app::SearchEntry" , |
150 | "app/ui/search_entry.h" ); |
151 | if (name == "separator" ) |
152 | return item.typeIncl("ui::Separator" , |
153 | "ui/separator.h" ); |
154 | if (name == "slider" ) |
155 | return item.typeIncl("ui::Slider" , |
156 | "ui/slider.h" ); |
157 | if (name == "splitter" ) |
158 | return item.typeIncl("ui::Splitter" , |
159 | "ui/splitter.h" ); |
160 | if (name == "textbox" ) |
161 | return item.typeIncl("ui::TextBox" , |
162 | "ui/textbox.h" ); |
163 | if (name == "tipwindow" ) |
164 | return item.typeIncl("ui::TipWindow" , |
165 | "ui/tooltips.h" ); |
166 | if (name == "vbox" ) |
167 | return item.typeIncl("ui::VBox" , |
168 | "ui/box.h" ); |
169 | if (name == "view" ) |
170 | return item.typeIncl("ui::View" , |
171 | "ui/view.h" ); |
172 | if (name == "window" ) |
173 | return item.typeIncl("ui::Window" , |
174 | "ui/window.h" ); |
175 | |
176 | throw base::Exception("Unknown widget name: " + name); |
177 | } |
178 | |
179 | void gen_ui_class(TiXmlDocument* doc, |
180 | const std::string& inputFn, |
181 | const std::string& widgetId) |
182 | { |
183 | std::cout |
184 | << "// Don't modify, generated file from " << inputFn << "\n" |
185 | << "\n" ; |
186 | |
187 | TiXmlHandle handle(doc); |
188 | TiXmlElement* elem = handle.FirstChild("gui" ).ToElement(); |
189 | elem = find_element_by_id(elem, widgetId); |
190 | if (!elem) { |
191 | std::cout << "#error Widget not found: " << widgetId << "\n" ; |
192 | return; |
193 | } |
194 | |
195 | std::vector<Item> items; |
196 | { |
197 | XmlElements xmlWidgets; |
198 | collect_widgets_with_ids(elem, xmlWidgets); |
199 | for (TiXmlElement* elem : xmlWidgets) { |
200 | const char* id = elem->Attribute("id" ); |
201 | if (!id) |
202 | continue; |
203 | items.push_back(convert_to_item(elem)); |
204 | } |
205 | } |
206 | |
207 | std::string className = convert_xmlid_to_cppid(widgetId, true); |
208 | std::string fnUpper = base::string_to_upper(base::get_file_title(inputFn)); |
209 | Item mainWidget = convert_to_item(elem); |
210 | |
211 | std::set<std::string> ; |
212 | headerFiles.insert("app/find_widget.h" ); |
213 | headerFiles.insert("app/load_widget.h" ); |
214 | headerFiles.insert(mainWidget.incl); |
215 | for (const Item& item : items) |
216 | headerFiles.insert(item.incl); |
217 | |
218 | std::cout |
219 | << "#ifndef GENERATED_" << fnUpper << "_H_INCLUDED\n" |
220 | << "#define GENERATED_" << fnUpper << "_H_INCLUDED\n" |
221 | << "#pragma once\n" |
222 | << "\n" ; |
223 | for (const auto& incl : headerFiles) |
224 | std::cout << "#include \"" << incl << "\"\n" ; |
225 | std::cout |
226 | << "\n" |
227 | << "namespace app {\n" |
228 | << "namespace gen {\n" |
229 | << "\n" |
230 | << " class " << className << " : public " << mainWidget.type << " {\n" |
231 | << " public:\n" |
232 | << " " << className << "()" ; |
233 | |
234 | // Special ctor for base class |
235 | if (mainWidget.type == "ui::Window" ) { |
236 | const char* desktop = elem->Attribute("desktop" ); |
237 | if (desktop && std::string(desktop) == "true" ) |
238 | std::cout |
239 | << " : ui::Window(ui::Window::DesktopWindow)" ; |
240 | else |
241 | std::cout |
242 | << " : ui::Window(ui::Window::WithTitleBar)" ; |
243 | } |
244 | |
245 | std::cout |
246 | << " {\n" |
247 | << " app::load_widget(\"" << base::get_file_name(inputFn) << "\", \"" << widgetId << "\", this);\n" |
248 | << " app::finder(this)\n" ; |
249 | |
250 | for (const Item& item : items) { |
251 | std::cout |
252 | << " >> \"" << item.xmlid << "\" >> m_" << item.cppid << "\n" ; |
253 | } |
254 | |
255 | std::cout |
256 | << " ;\n" |
257 | << " }\n" |
258 | << "\n" ; |
259 | |
260 | for (const Item& item : items) { |
261 | std::cout |
262 | << " " << item.type << "* " << item.cppid << "() const { return m_" << item.cppid << "; }\n" ; |
263 | } |
264 | |
265 | std::cout |
266 | << "\n" |
267 | << " private:\n" ; |
268 | |
269 | for (const Item& item : items) { |
270 | std::cout |
271 | << " " << item.type << "* m_" << item.cppid << ";\n" ; |
272 | } |
273 | |
274 | std::cout |
275 | << " };\n" |
276 | << "\n" |
277 | << "} // namespace gen\n" |
278 | << "} // namespace app\n" |
279 | << "\n" |
280 | << "#endif\n" ; |
281 | } |
282 | |