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