1 | // Aseprite
|
2 | // Copyright (C) 2019-2022 Igara Studio S.A.
|
3 | // Copyright (C) 2001-2018 David Capello
|
4 | //
|
5 | // This program is distributed under the terms of
|
6 | // the End-User License Agreement for Aseprite.
|
7 |
|
8 | #ifdef HAVE_CONFIG_H
|
9 | #include "config.h"
|
10 | #endif
|
11 |
|
12 | #include "app/ui/pref_widget.h"
|
13 |
|
14 | #include "app/widget_loader.h"
|
15 |
|
16 | #include "app/app.h"
|
17 | #include "app/i18n/strings.h"
|
18 | #include "app/modules/gui.h"
|
19 | #include "app/resource_finder.h"
|
20 | #include "app/ui/button_set.h"
|
21 | #include "app/ui/color_button.h"
|
22 | #include "app/ui/drop_down_button.h"
|
23 | #include "app/ui/expr_entry.h"
|
24 | #include "app/ui/icon_button.h"
|
25 | #include "app/ui/search_entry.h"
|
26 | #include "app/ui/skin/skin_theme.h"
|
27 | #include "app/widget_not_found.h"
|
28 | #include "app/xml_document.h"
|
29 | #include "app/xml_exception.h"
|
30 | #include "base/exception.h"
|
31 | #include "base/fs.h"
|
32 | #include "base/memory.h"
|
33 | #include "os/system.h"
|
34 | #include "ui/ui.h"
|
35 |
|
36 | #include "tinyxml.h"
|
37 |
|
38 | #include <cstdio>
|
39 | #include <cstdlib>
|
40 | #include <cstring>
|
41 | #include <limits>
|
42 | #include <memory>
|
43 |
|
44 | namespace app {
|
45 |
|
46 | using namespace ui;
|
47 | using namespace app::skin;
|
48 |
|
49 | static int convert_align_value_to_flags(const char *value);
|
50 | static int int_attr(const TiXmlElement* elem, const char* attribute_name, int default_value);
|
51 |
|
52 | WidgetLoader::WidgetLoader()
|
53 | : m_tooltipManager(NULL)
|
54 | {
|
55 | }
|
56 |
|
57 | WidgetLoader::~WidgetLoader()
|
58 | {
|
59 | for (TypeCreatorsMap::iterator
|
60 | it=m_typeCreators.begin(), end=m_typeCreators.end(); it != end; ++it)
|
61 | it->second->dispose();
|
62 | }
|
63 |
|
64 | void WidgetLoader::addWidgetType(const char* tagName, IWidgetTypeCreator* creator)
|
65 | {
|
66 | m_typeCreators[tagName] = creator;
|
67 | }
|
68 |
|
69 | Widget* WidgetLoader::loadWidget(const char* fileName, const char* widgetId, ui::Widget* widget)
|
70 | {
|
71 | std::string buf;
|
72 |
|
73 | ResourceFinder rf;
|
74 | rf.addPath(fileName);
|
75 |
|
76 | buf = "widgets/" ;
|
77 | buf += fileName;
|
78 | rf.includeDataDir(buf.c_str());
|
79 |
|
80 | if (!rf.findFirst())
|
81 | throw WidgetNotFound(widgetId);
|
82 |
|
83 | widget = loadWidgetFromXmlFile(rf.filename(), widgetId, widget);
|
84 | if (!widget)
|
85 | throw WidgetNotFound(widgetId);
|
86 |
|
87 | return widget;
|
88 | }
|
89 |
|
90 | Widget* WidgetLoader::loadWidgetFromXmlFile(
|
91 | const std::string& xmlFilename,
|
92 | const std::string& widgetId,
|
93 | ui::Widget* widget)
|
94 | {
|
95 | m_tooltipManager = NULL;
|
96 | m_xmlTranslator.setStringIdPrefix(widgetId.c_str());
|
97 |
|
98 | XmlDocumentRef doc(open_xml(xmlFilename));
|
99 | TiXmlHandle handle(doc.get());
|
100 |
|
101 | // Search the requested widget.
|
102 | TiXmlElement* xmlElement = handle
|
103 | .FirstChild("gui" )
|
104 | .FirstChildElement().ToElement();
|
105 |
|
106 | while (xmlElement) {
|
107 | const char* nodename = xmlElement->Attribute("id" );
|
108 |
|
109 | if (nodename && nodename == widgetId) {
|
110 | widget = convertXmlElementToWidget(xmlElement, NULL, NULL, widget);
|
111 | break;
|
112 | }
|
113 |
|
114 | xmlElement = xmlElement->NextSiblingElement();
|
115 | }
|
116 |
|
117 | return widget;
|
118 | }
|
119 |
|
120 | Widget* WidgetLoader::convertXmlElementToWidget(const TiXmlElement* elem, Widget* root, Widget* parent, Widget* widget)
|
121 | {
|
122 | const std::string elem_name = elem->Value();
|
123 |
|
124 | // TODO error handling: add a message if the widget is bad specified
|
125 |
|
126 | // Try to use one of the creators.
|
127 | TypeCreatorsMap::iterator it = m_typeCreators.find(elem_name);
|
128 |
|
129 | if (it != m_typeCreators.end()) {
|
130 | if (!widget)
|
131 | widget = it->second->createWidgetFromXml(elem);
|
132 | }
|
133 | else if (elem_name == "panel" ) {
|
134 | if (!widget)
|
135 | widget = new Panel();
|
136 | }
|
137 | else if (elem_name == "box" ) {
|
138 | bool horizontal = bool_attr(elem, "horizontal" , false);
|
139 | bool vertical = bool_attr(elem, "vertical" , false);
|
140 | int align = (horizontal ? HORIZONTAL: vertical ? VERTICAL: 0);
|
141 |
|
142 | if (!widget)
|
143 | widget = new Box(align);
|
144 | else
|
145 | widget->setAlign(widget->align() | align);
|
146 | }
|
147 | else if (elem_name == "vbox" ) {
|
148 | if (!widget)
|
149 | widget = new VBox();
|
150 | }
|
151 | else if (elem_name == "hbox" ) {
|
152 | if (!widget)
|
153 | widget = new HBox();
|
154 | }
|
155 | else if (elem_name == "boxfiller" ) {
|
156 | if (!widget)
|
157 | widget = new BoxFiller();
|
158 | }
|
159 | else if (elem_name == "button" ) {
|
160 | const char* icon_name = elem->Attribute("icon" );
|
161 |
|
162 | if (!widget) {
|
163 | if (icon_name) {
|
164 | SkinPartPtr part = SkinTheme::instance()->getPartById(icon_name);
|
165 | if (!part)
|
166 | throw base::Exception("<button> element found with invalid 'icon' attribute '%s'" ,
|
167 | icon_name);
|
168 |
|
169 | widget = new IconButton(part);
|
170 | }
|
171 | else {
|
172 | widget = new Button("" );
|
173 | }
|
174 | }
|
175 |
|
176 | bool left = bool_attr(elem, "left" , false);
|
177 | bool right = bool_attr(elem, "right" , false);
|
178 | bool top = bool_attr(elem, "top" , false);
|
179 | bool bottom = bool_attr(elem, "bottom" , false);
|
180 | bool closewindow = bool_attr(elem, "closewindow" , false);
|
181 |
|
182 | widget->setAlign((left ? LEFT: (right ? RIGHT: CENTER)) |
|
183 | (top ? TOP: (bottom ? BOTTOM: MIDDLE)));
|
184 |
|
185 | if (closewindow) {
|
186 | static_cast<Button*>(widget)
|
187 | ->Click.connect([widget]{ widget->closeWindow(); });
|
188 | }
|
189 | }
|
190 | else if (elem_name == "check" ) {
|
191 | const char* looklike = elem->Attribute("looklike" );
|
192 | const char* pref = elem->Attribute("pref" );
|
193 |
|
194 | ASSERT(!widget || !pref); // widget && pref is not supported
|
195 |
|
196 | if (looklike != NULL && strcmp(looklike, "button" ) == 0) {
|
197 | ASSERT(!pref); // not supported yet
|
198 |
|
199 | if (!widget)
|
200 | widget = new CheckBox("" , kButtonWidget);
|
201 | }
|
202 | else {
|
203 | if (!widget) {
|
204 | // Automatic bind <check> widget with bool preference option
|
205 | if (pref) {
|
206 | std::unique_ptr<BoolPrefWidget<CheckBox>> prefWidget(
|
207 | new BoolPrefWidget<CheckBox>("" ));
|
208 | prefWidget->setPref(pref);
|
209 | widget = prefWidget.release();
|
210 | }
|
211 | else {
|
212 | widget = new CheckBox("" );
|
213 | }
|
214 | }
|
215 | }
|
216 |
|
217 | bool center = bool_attr(elem, "center" , false);
|
218 | bool right = bool_attr(elem, "right" , false);
|
219 | bool top = bool_attr(elem, "top" , false);
|
220 | bool bottom = bool_attr(elem, "bottom" , false);
|
221 |
|
222 | widget->setAlign((center ? CENTER:
|
223 | (right ? RIGHT: LEFT)) |
|
224 | (top ? TOP:
|
225 | (bottom ? BOTTOM: MIDDLE)));
|
226 | }
|
227 | else if (elem_name == "combobox" ) {
|
228 | if (!widget)
|
229 | widget = new ComboBox();
|
230 |
|
231 | bool editable = bool_attr(elem, "editable" , false);
|
232 | if (editable)
|
233 | ((ComboBox*)widget)->setEditable(true);
|
234 |
|
235 | const char* suffix = elem->Attribute("suffix" );
|
236 | if (suffix)
|
237 | ((ComboBox*)widget)->getEntryWidget()->setSuffix(suffix);
|
238 | }
|
239 | else if (elem_name == "entry" ||
|
240 | elem_name == "expr" ) {
|
241 | const char* maxsize = elem->Attribute("maxsize" );
|
242 | if (elem_name == "entry" && !maxsize)
|
243 | throw std::runtime_error("<entry> element found without 'maxsize' attribute" );
|
244 |
|
245 | const char* suffix = elem->Attribute("suffix" );
|
246 | const char* decimals = elem->Attribute("decimals" );
|
247 | const bool readonly = bool_attr(elem, "readonly" , false);
|
248 |
|
249 | widget = (elem_name == "expr" ?
|
250 | new ExprEntry:
|
251 | new Entry(strtol(maxsize, nullptr, 10), "" ));
|
252 |
|
253 | if (readonly)
|
254 | ((Entry*)widget)->setReadOnly(true);
|
255 |
|
256 | if (suffix)
|
257 | ((Entry*)widget)->setSuffix(suffix);
|
258 |
|
259 | if (elem_name == "expr" && decimals)
|
260 | ((ExprEntry*)widget)->setDecimals(strtol(decimals, nullptr, 10));
|
261 | }
|
262 | else if (elem_name == "grid" ) {
|
263 | const char *columns = elem->Attribute("columns" );
|
264 | bool same_width_columns = bool_attr(elem, "same_width_columns" , false);
|
265 |
|
266 | if (columns != NULL) {
|
267 | widget = new ui::Grid(strtol(columns, NULL, 10),
|
268 | same_width_columns);
|
269 | }
|
270 | }
|
271 | else if (elem_name == "label" ) {
|
272 | if (!widget)
|
273 | widget = new Label("" );
|
274 |
|
275 | bool center = bool_attr(elem, "center" , false);
|
276 | bool right = bool_attr(elem, "right" , false);
|
277 | bool top = bool_attr(elem, "top" , false);
|
278 | bool bottom = bool_attr(elem, "bottom" , false);
|
279 |
|
280 | widget->setAlign((center ? CENTER:
|
281 | (right ? RIGHT: LEFT)) |
|
282 | (top ? TOP:
|
283 | (bottom ? BOTTOM: MIDDLE)));
|
284 | }
|
285 | else if (elem_name == "link" ) {
|
286 | const char* url = elem->Attribute("url" );
|
287 |
|
288 | if (!widget)
|
289 | widget = new LinkLabel(url ? url: "" , "" );
|
290 | else {
|
291 | LinkLabel* link = dynamic_cast<LinkLabel*>(widget);
|
292 | ASSERT(link != NULL);
|
293 | if (link)
|
294 | link->setUrl(url);
|
295 | }
|
296 |
|
297 | bool center = bool_attr(elem, "center" , false);
|
298 | bool right = bool_attr(elem, "right" , false);
|
299 | bool top = bool_attr(elem, "top" , false);
|
300 | bool bottom = bool_attr(elem, "bottom" , false);
|
301 |
|
302 | widget->setAlign(
|
303 | (center ? CENTER: (right ? RIGHT: LEFT)) |
|
304 | (top ? TOP: (bottom ? BOTTOM: MIDDLE)));
|
305 | }
|
306 | else if (elem_name == "listbox" ) {
|
307 | if (!widget)
|
308 | widget = new ListBox();
|
309 |
|
310 | bool multiselect = bool_attr(elem, "multiselect" , false);
|
311 | if (multiselect)
|
312 | static_cast<ListBox*>(widget)->setMultiselect(multiselect);
|
313 | }
|
314 | else if (elem_name == "listitem" ) {
|
315 | ListItem* listitem;
|
316 | if (!widget) {
|
317 | listitem = new ListItem("" );
|
318 | widget = listitem;
|
319 | }
|
320 | else {
|
321 | listitem = dynamic_cast<ListItem*>(widget);
|
322 | ASSERT(listitem != NULL);
|
323 | }
|
324 |
|
325 | const char* value = elem->Attribute("value" );
|
326 | if (value)
|
327 | listitem->setValue(value);
|
328 | }
|
329 | else if (elem_name == "splitter" ) {
|
330 | bool horizontal = bool_attr(elem, "horizontal" , false);
|
331 | bool vertical = bool_attr(elem, "vertical" , false);
|
332 | const char* by = elem->Attribute("by" );
|
333 | const char* position = elem->Attribute("position" );
|
334 | Splitter::Type type = (by && strcmp(by, "pixel" ) == 0 ?
|
335 | Splitter::ByPixel:
|
336 | Splitter::ByPercentage);
|
337 |
|
338 | Splitter* splitter = new Splitter(type,
|
339 | horizontal ? HORIZONTAL:
|
340 | vertical ? VERTICAL: 0);
|
341 | if (position) {
|
342 | splitter->setPosition(strtod(position, NULL)
|
343 | * (type == Splitter::ByPixel ? guiscale(): 1));
|
344 | }
|
345 | widget = splitter;
|
346 | }
|
347 | else if (elem_name == "radio" ) {
|
348 | const char* group = elem->Attribute("group" );
|
349 | const char* looklike = elem->Attribute("looklike" );
|
350 |
|
351 | int radio_group = (group ? strtol(group, NULL, 10): 1);
|
352 |
|
353 | if (!widget) {
|
354 | if (looklike != NULL && strcmp(looklike, "button" ) == 0) {
|
355 | widget = new RadioButton("" , radio_group, kButtonWidget);
|
356 | }
|
357 | else {
|
358 | widget = new RadioButton("" , radio_group);
|
359 | }
|
360 | }
|
361 | else {
|
362 | RadioButton* radio = dynamic_cast<RadioButton*>(widget);
|
363 | ASSERT(radio != NULL);
|
364 | if (radio)
|
365 | radio->setRadioGroup(radio_group);
|
366 | }
|
367 |
|
368 | bool center = bool_attr(elem, "center" , false);
|
369 | bool right = bool_attr(elem, "right" , false);
|
370 | bool top = bool_attr(elem, "top" , false);
|
371 | bool bottom = bool_attr(elem, "bottom" , false);
|
372 |
|
373 | widget->setAlign(
|
374 | (center ? CENTER:
|
375 | (right ? RIGHT: LEFT)) |
|
376 | (top ? TOP:
|
377 | (bottom ? BOTTOM: MIDDLE)));
|
378 | }
|
379 | else if (elem_name == "separator" ) {
|
380 | bool center = bool_attr(elem, "center" , false);
|
381 | bool right = bool_attr(elem, "right" , false);
|
382 | bool middle = bool_attr(elem, "middle" , false);
|
383 | bool bottom = bool_attr(elem, "bottom" , false);
|
384 | bool horizontal = bool_attr(elem, "horizontal" , false);
|
385 | bool vertical = bool_attr(elem, "vertical" , false);
|
386 | int align =
|
387 | (horizontal ? HORIZONTAL: 0) |
|
388 | (vertical ? VERTICAL: 0) |
|
389 | (center ? CENTER: (right ? RIGHT: LEFT)) |
|
390 | (middle ? MIDDLE: (bottom ? BOTTOM: TOP));
|
391 |
|
392 | if (!widget) {
|
393 | widget = new Separator(m_xmlTranslator(elem, "text" ), align);
|
394 | }
|
395 | else
|
396 | widget->setAlign(widget->align() | align);
|
397 | }
|
398 | else if (elem_name == "slider" ) {
|
399 | const char *min = elem->Attribute("min" );
|
400 | const char *max = elem->Attribute("max" );
|
401 | const bool readonly = bool_attr(elem, "readonly" , false);
|
402 | int min_value = (min ? strtol(min, nullptr, 10): 0);
|
403 | int max_value = (max ? strtol(max, nullptr, 10): 0);
|
404 |
|
405 | widget = new Slider(min_value, max_value, min_value);
|
406 | static_cast<Slider*>(widget)->setReadOnly(readonly);
|
407 | }
|
408 | else if (elem_name == "textbox" ) {
|
409 | const char* text = (elem->GetText() ? elem->GetText(): "" );
|
410 | bool wordwrap = bool_attr(elem, "wordwrap" , false);
|
411 |
|
412 | if (!widget)
|
413 | widget = new TextBox(text, 0);
|
414 | else
|
415 | widget->setText(text);
|
416 |
|
417 | if (wordwrap)
|
418 | widget->setAlign(widget->align() | WORDWRAP);
|
419 | }
|
420 | else if (elem_name == "view" ) {
|
421 | if (!widget)
|
422 | widget = new View();
|
423 | }
|
424 | else if (elem_name == "window" ) {
|
425 | if (!widget) {
|
426 | bool desktop = bool_attr(elem, "desktop" , false);
|
427 |
|
428 | if (desktop)
|
429 | widget = new Window(Window::DesktopWindow);
|
430 | else if (elem->Attribute("text" ))
|
431 | widget = new Window(Window::WithTitleBar, m_xmlTranslator(elem, "text" ));
|
432 | else
|
433 | widget = new Window(Window::WithoutTitleBar);
|
434 | }
|
435 | }
|
436 | else if (elem_name == "colorpicker" ) {
|
437 | const bool rgba = bool_attr(elem, "rgba" , false);
|
438 | const bool simple = bool_attr(elem, "simple" , false);
|
439 |
|
440 | if (!widget) {
|
441 | ColorButtonOptions options;
|
442 | options.canPinSelector = false;
|
443 | options.showSimpleColors = simple;
|
444 | options.showIndexTab = true;
|
445 | widget = new ColorButton(Color::fromMask(),
|
446 | (rgba ? IMAGE_RGB:
|
447 | app_get_current_pixel_format()),
|
448 | options);
|
449 | }
|
450 | }
|
451 | else if (elem_name == "dropdownbutton" ) {
|
452 | if (!widget) {
|
453 | widget = new DropDownButton(m_xmlTranslator(elem, "text" ).c_str());
|
454 | }
|
455 | }
|
456 | else if (elem_name == "buttonset" ) {
|
457 | const char* columns = elem->Attribute("columns" );
|
458 |
|
459 | if (!widget && columns)
|
460 | widget = new ButtonSet(strtol(columns, NULL, 10));
|
461 |
|
462 | if (ButtonSet* buttonset = dynamic_cast<ButtonSet*>(widget)) {
|
463 | if (bool_attr(elem, "multiple" , false))
|
464 | buttonset->setMultiMode(ButtonSet::MultiMode::Set);
|
465 | if (bool_attr(elem, "oneormore" , false))
|
466 | buttonset->setMultiMode(ButtonSet::MultiMode::OneOrMore);
|
467 | }
|
468 | }
|
469 | else if (elem_name == "item" ) {
|
470 | if (!parent)
|
471 | throw std::runtime_error("<item> without parent" );
|
472 |
|
473 | if (ButtonSet* buttonset = dynamic_cast<ButtonSet*>(parent)) {
|
474 | const char* icon = elem->Attribute("icon" );
|
475 | const char* text = elem->Attribute("text" );
|
476 | int hspan = int_attr(elem, "hspan" , 1);
|
477 | int vspan = int_attr(elem, "vspan" , 1);
|
478 |
|
479 | ButtonSet::Item* item = new ButtonSet::Item();
|
480 |
|
481 | if (icon) {
|
482 | SkinPartPtr part = SkinTheme::instance()->getPartById(std::string(icon));
|
483 | if (part)
|
484 | item->setIcon(part);
|
485 | }
|
486 |
|
487 | if (text)
|
488 | item->setText(m_xmlTranslator(elem, "text" ));
|
489 |
|
490 | buttonset->addItem(item, hspan, vspan);
|
491 | fillWidgetWithXmlElementAttributes(elem, root, item);
|
492 | }
|
493 | }
|
494 | else if (elem_name == "image" ) {
|
495 | if (!widget) {
|
496 | const char* file = elem->Attribute("file" );
|
497 | const char* icon = elem->Attribute("icon" );
|
498 |
|
499 | if (file) {
|
500 | ResourceFinder rf;
|
501 | rf.includeDataDir(file);
|
502 | if (!rf.findFirst())
|
503 | throw base::Exception("File %s not found" , file);
|
504 |
|
505 | try {
|
506 | os::SurfaceRef sur = os::instance()->loadRgbaSurface(rf.filename().c_str());
|
507 | if (sur) {
|
508 | sur->setImmutable();
|
509 | widget = new ImageView(sur, 0);
|
510 | }
|
511 | }
|
512 | catch (...) {
|
513 | throw base::Exception("Error loading %s file" , file);
|
514 | }
|
515 | }
|
516 | else if (icon) {
|
517 | SkinPartPtr part = SkinTheme::instance()->getPartById(std::string(icon));
|
518 | if (part) {
|
519 | widget = new ImageView(part->bitmapRef(0), 0);
|
520 | }
|
521 | }
|
522 | }
|
523 | }
|
524 | else if (elem_name == "search" ) {
|
525 | if (!widget)
|
526 | widget = new SearchEntry;
|
527 | }
|
528 |
|
529 | // Was the widget created?
|
530 | if (widget) {
|
531 | fillWidgetWithXmlElementAttributesWithChildren(elem, root, widget);
|
532 | }
|
533 |
|
534 | return widget;
|
535 | }
|
536 |
|
537 | void WidgetLoader::fillWidgetWithXmlElementAttributes(const TiXmlElement* elem, Widget* root, Widget* widget)
|
538 | {
|
539 | const char* id = elem->Attribute("id" );
|
540 | const char* tooltip_dir = elem->Attribute("tooltip_dir" );
|
541 | bool selected = bool_attr(elem, "selected" , false);
|
542 | bool disabled = bool_attr(elem, "disabled" , false);
|
543 | bool expansive = bool_attr(elem, "expansive" , false);
|
544 | bool homogeneous = bool_attr(elem, "homogeneous" , false);
|
545 | bool magnet = bool_attr(elem, "magnet" , false);
|
546 | bool noborders = bool_attr(elem, "noborders" , false);
|
547 | bool visible = bool_attr(elem, "visible" , true);
|
548 | const char* width = elem->Attribute("width" );
|
549 | const char* height = elem->Attribute("height" );
|
550 | const char* minwidth = elem->Attribute("minwidth" );
|
551 | const char* minheight = elem->Attribute("minheight" );
|
552 | const char* maxwidth = elem->Attribute("maxwidth" );
|
553 | const char* maxheight = elem->Attribute("maxheight" );
|
554 | const char* border = elem->Attribute("border" );
|
555 | const char* styleid = elem->Attribute("style" );
|
556 | const char* childspacing = elem->Attribute("childspacing" );
|
557 |
|
558 | if (width) {
|
559 | if (!minwidth) minwidth = width;
|
560 | if (!maxwidth) maxwidth = width;
|
561 | }
|
562 |
|
563 | if (height) {
|
564 | if (!minheight) minheight = height;
|
565 | if (!maxheight) maxheight = height;
|
566 | }
|
567 |
|
568 | if (id)
|
569 | widget->setId(id);
|
570 |
|
571 | if (elem->Attribute("text" ))
|
572 | widget->setText(m_xmlTranslator(elem, "text" ));
|
573 |
|
574 | if (elem->Attribute("tooltip" ) && root) {
|
575 | if (!m_tooltipManager) {
|
576 | m_tooltipManager = new ui::TooltipManager();
|
577 | root->addChild(m_tooltipManager);
|
578 | }
|
579 |
|
580 | int dir = LEFT;
|
581 | if (tooltip_dir) {
|
582 | if (strcmp(tooltip_dir, "top" ) == 0) dir = TOP;
|
583 | else if (strcmp(tooltip_dir, "bottom" ) == 0) dir = BOTTOM;
|
584 | else if (strcmp(tooltip_dir, "left" ) == 0) dir = LEFT;
|
585 | else if (strcmp(tooltip_dir, "right" ) == 0) dir = RIGHT;
|
586 | }
|
587 |
|
588 | Widget* widgetWithTooltip;
|
589 | if (widget->type() == ui::kComboBoxWidget)
|
590 | widgetWithTooltip = static_cast<ComboBox*>(widget)->getEntryWidget();
|
591 | else
|
592 | widgetWithTooltip = widget;
|
593 | m_tooltipManager->addTooltipFor(widgetWithTooltip, m_xmlTranslator(elem, "tooltip" ), dir);
|
594 | }
|
595 |
|
596 | if (selected)
|
597 | widget->setSelected(selected);
|
598 |
|
599 | if (disabled)
|
600 | widget->setEnabled(false);
|
601 |
|
602 | if (expansive)
|
603 | widget->setExpansive(true);
|
604 |
|
605 | if (!visible)
|
606 | widget->setVisible(false);
|
607 |
|
608 | if (homogeneous)
|
609 | widget->setAlign(widget->align() | HOMOGENEOUS);
|
610 |
|
611 | if (magnet)
|
612 | widget->setFocusMagnet(true);
|
613 |
|
614 | if (noborders) {
|
615 | widget->InitTheme.connect(
|
616 | [widget]{
|
617 | widget->noBorderNoChildSpacing();
|
618 | });
|
619 | }
|
620 |
|
621 | if (border) {
|
622 | int v = strtol(border, nullptr, 10);
|
623 | widget->InitTheme.connect(
|
624 | [widget, v]{
|
625 | widget->setBorder(gfx::Border(v*guiscale()));
|
626 | });
|
627 | }
|
628 |
|
629 | if (childspacing) {
|
630 | int v = strtol(childspacing, nullptr, 10);
|
631 | widget->InitTheme.connect(
|
632 | [widget, v]{
|
633 | widget->setChildSpacing(v*guiscale());
|
634 | });
|
635 | }
|
636 |
|
637 | if (minwidth || minheight ||
|
638 | maxwidth || maxheight) {
|
639 | const int minw = (minwidth ? strtol(minwidth, NULL, 10): 0);
|
640 | const int minh = (minheight ? strtol(minheight, NULL, 10): 0);
|
641 | const int maxw = (maxwidth ? strtol(maxwidth, NULL, 10): 0);
|
642 | const int maxh = (maxheight ? strtol(maxheight, NULL, 10): 0);
|
643 | widget->InitTheme.connect(
|
644 | [widget, minw, minh, maxw, maxh]{
|
645 | widget->setMinSize(gfx::Size(0, 0));
|
646 | widget->setMaxSize(gfx::Size(std::numeric_limits<int>::max(),
|
647 | std::numeric_limits<int>::max()));
|
648 | const gfx::Size reqSize = widget->sizeHint();
|
649 | widget->setMinSize(
|
650 | gfx::Size((minw > 0 ? guiscale()*minw: reqSize.w),
|
651 | (minh > 0 ? guiscale()*minh: reqSize.h)));
|
652 | widget->setMaxSize(
|
653 | gfx::Size((maxw > 0 ? guiscale()*maxw: std::numeric_limits<int>::max()),
|
654 | (maxh > 0 ? guiscale()*maxh: std::numeric_limits<int>::max())));
|
655 | });
|
656 | }
|
657 |
|
658 | if (styleid) {
|
659 | std::string styleIdStr = styleid;
|
660 | widget->InitTheme.connect(
|
661 | [widget, styleIdStr]{
|
662 | auto theme = SkinTheme::get(widget);
|
663 | ui::Style* style = theme->getStyleById(styleIdStr);
|
664 | if (style)
|
665 | widget->setStyle(style);
|
666 | else
|
667 | throw base::Exception("Style %s not found" , styleIdStr.c_str());
|
668 | });
|
669 | }
|
670 |
|
671 | // Assign widget mnemonic from the character preceded by a '&'
|
672 | widget->processMnemonicFromText();
|
673 | widget->initTheme();
|
674 | }
|
675 |
|
676 | void WidgetLoader::fillWidgetWithXmlElementAttributesWithChildren(const TiXmlElement* elem, ui::Widget* root, ui::Widget* widget)
|
677 | {
|
678 | fillWidgetWithXmlElementAttributes(elem, root, widget);
|
679 |
|
680 | if (!root)
|
681 | root = widget;
|
682 |
|
683 | // Children
|
684 | const TiXmlElement* childElem = elem->FirstChildElement();
|
685 | while (childElem) {
|
686 | Widget* child = convertXmlElementToWidget(childElem, root, widget, NULL);
|
687 | if (child) {
|
688 | // Attach the child in the view
|
689 | if (widget->type() == kViewWidget) {
|
690 | static_cast<View*>(widget)->attachToView(child);
|
691 | break;
|
692 | }
|
693 | // Add the child in the grid
|
694 | else if (widget->type() == kGridWidget) {
|
695 | const char* cell_hspan = childElem->Attribute("cell_hspan" );
|
696 | const char* cell_vspan = childElem->Attribute("cell_vspan" );
|
697 | const char* cell_align = childElem->Attribute("cell_align" );
|
698 | int hspan = cell_hspan ? strtol(cell_hspan, NULL, 10): 1;
|
699 | int vspan = cell_vspan ? strtol(cell_vspan, NULL, 10): 1;
|
700 | int align = cell_align ? convert_align_value_to_flags(cell_align): 0;
|
701 | auto grid = dynamic_cast<ui::Grid*>(widget);
|
702 | ASSERT(grid != nullptr);
|
703 |
|
704 | grid->addChildInCell(child, hspan, vspan, align);
|
705 | }
|
706 | // Attach the child in the view
|
707 | else if (widget->type() == kComboBoxWidget &&
|
708 | child->type() == kListItemWidget) {
|
709 | auto combo = dynamic_cast<ComboBox*>(widget);
|
710 | ASSERT(combo != nullptr);
|
711 |
|
712 | combo->addItem(dynamic_cast<ListItem*>(child));
|
713 | }
|
714 | // Just add the child in any other kind of widget
|
715 | else
|
716 | widget->addChild(child);
|
717 | }
|
718 | childElem = childElem->NextSiblingElement();
|
719 | }
|
720 |
|
721 | if (widget->type() == kViewWidget) {
|
722 | bool maxsize = bool_attr(elem, "maxsize" , false);
|
723 | if (maxsize)
|
724 | static_cast<View*>(widget)->makeVisibleAllScrollableArea();
|
725 | }
|
726 | }
|
727 |
|
728 | static int convert_align_value_to_flags(const char *value)
|
729 | {
|
730 | char *tok, *ptr = base_strdup(value);
|
731 | int flags = 0;
|
732 |
|
733 | for (tok=strtok(ptr, " " );
|
734 | tok != NULL;
|
735 | tok=strtok(NULL, " " )) {
|
736 | if (strcmp(tok, "horizontal" ) == 0) {
|
737 | flags |= HORIZONTAL;
|
738 | }
|
739 | else if (strcmp(tok, "vertical" ) == 0) {
|
740 | flags |= VERTICAL;
|
741 | }
|
742 | else if (strcmp(tok, "left" ) == 0) {
|
743 | flags |= LEFT;
|
744 | }
|
745 | else if (strcmp(tok, "center" ) == 0) {
|
746 | flags |= CENTER;
|
747 | }
|
748 | else if (strcmp(tok, "right" ) == 0) {
|
749 | flags |= RIGHT;
|
750 | }
|
751 | else if (strcmp(tok, "top" ) == 0) {
|
752 | flags |= TOP;
|
753 | }
|
754 | else if (strcmp(tok, "middle" ) == 0) {
|
755 | flags |= MIDDLE;
|
756 | }
|
757 | else if (strcmp(tok, "bottom" ) == 0) {
|
758 | flags |= BOTTOM;
|
759 | }
|
760 | else if (strcmp(tok, "homogeneous" ) == 0) {
|
761 | flags |= HOMOGENEOUS;
|
762 | }
|
763 | }
|
764 |
|
765 | base_free(ptr);
|
766 | return flags;
|
767 | }
|
768 |
|
769 | static int int_attr(const TiXmlElement* elem, const char* attribute_name, int default_value)
|
770 | {
|
771 | const char* value = elem->Attribute(attribute_name);
|
772 |
|
773 | return (value ? strtol(value, NULL, 10): default_value);
|
774 | }
|
775 |
|
776 | } // namespace app
|
777 | |