| 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 | |