1 /* 2 * DQt - D bindings for the Qt Toolkit 3 * 4 * GNU Lesser General Public License Usage 5 * This file may be used under the terms of the GNU Lesser 6 * General Public License version 3 as published by the Free Software 7 * Foundation and appearing in the file LICENSE.LGPL3 included in the 8 * packaging of this file. Please review the following information to 9 * ensure the GNU Lesser General Public License version 3 requirements 10 * will be met: https://www.gnu.org/licenses/lgpl-3.0.html. 11 */ 12 module qt.widgets.ui; 13 14 import qt.helpers; 15 import std.array; 16 17 private void writeStringLiteral(R)(ref Appender!string code, R str) 18 { 19 code.put("\""); 20 foreach(dchar c; str) 21 { 22 if(c == '\"') 23 code.put("\\\""); 24 else if(c == '\n') 25 code.put("\\n\" ~\n\""); 26 else if(c == '\r') 27 code.put("\\r"); 28 else if(c == '\\') 29 code.put("\\\\"); 30 else 31 code.put(c); 32 } 33 code.put("\""); 34 } 35 36 private struct UICodeWriter() 37 { 38 import qt.widgets.internal.dxml.dom; 39 import qt.widgets.internal.dxml.util; 40 import std.ascii; 41 import std.uni; 42 import std.conv; 43 import std.algorithm; 44 import std.exception; 45 import std.string; 46 alias DOMEntity = qt.widgets.internal.dxml.dom.DOMEntity!string; 47 48 string customWidgetPackage; 49 Appender!string codeVars; 50 Appender!string codeSetup; 51 Appender!string codeSetupAddActions; 52 Appender!string codeSetupConnect; 53 Appender!string codeSetupDelayed; 54 Appender!string codeRetranslate; 55 size_t[string] tmpCountSetup; 56 size_t[string] tmpCountRetranslate; 57 string[string] widgetTypes; 58 59 string createTmpVar(ref size_t[string] tmpCount, string prefix) 60 { 61 string r; 62 if(prefix !in tmpCount) 63 { 64 r = prefix; 65 tmpCount[prefix] = 1; 66 } 67 else 68 { 69 r = text(prefix, tmpCount[prefix]); 70 tmpCount[prefix]++; 71 } 72 return r; 73 } 74 75 struct WidgetInfo 76 { 77 string xmlName; 78 string name; 79 string className; 80 } 81 82 WidgetInfo rootWidgetInfo; 83 84 WidgetInfo getWidgetInfo(DOMEntity widget) 85 { 86 WidgetInfo info; 87 info.xmlName = widget.name; 88 foreach(attr; widget.attributes) 89 { 90 if(attr.name == "class") 91 info.className = attr.value; 92 else if(attr.name == "name") 93 info.name = attr.value; 94 } 95 if(widget.name == "action") 96 info.className = "QAction"; 97 if(widget.name == "spacer") 98 info.className = "QSpacerItem"; 99 return info; 100 } 101 102 string getWidgetModule(string name) 103 { 104 if(name == "QSpacerItem") 105 return "qt.widgets.layoutitem"; 106 if(name == "QVBoxLayout") 107 return "qt.widgets.boxlayout"; 108 if(name == "QHBoxLayout") 109 return "qt.widgets.boxlayout"; 110 if(name == "QTimeEdit") 111 return "qt.widgets.datetimeedit"; 112 if(name == "QDateEdit") 113 return "qt.widgets.datetimeedit"; 114 if(name == "QDoubleSpinBox") 115 return "qt.widgets.spinbox"; 116 if(name == "QWebEngineView") 117 return "qt.webenginewidgets.webengineview"; 118 119 if(name.startsWith("Q")) 120 return "qt.widgets." ~ std.uni.toLower(name[1..$]); 121 122 if(customWidgetPackage.length) 123 return customWidgetPackage ~ "." ~ name.toLower; 124 else 125 return name.toLower; 126 } 127 128 void addProperty(DOMEntity property, string widgetName, string widgetType, bool isTopLevel, string extraArg) 129 { 130 string name; 131 foreach(attr; property.attributes) 132 { 133 if(attr.name == "name") 134 name = attr.value; 135 } 136 137 enforce(property.children.length == 1); 138 139 bool noTr; 140 string comment; 141 foreach(attr; property.children[0].attributes) 142 { 143 if(attr.name == "notr" && attr.value == "true") 144 noTr = true; 145 if(attr.name == "comment") 146 comment = attr.value; 147 } 148 149 Appender!string* code = &codeSetup; 150 size_t[string]* tmpCount = &tmpCountSetup; 151 if(property.children[0].name == "string" && !noTr) 152 { 153 code = &codeRetranslate; 154 tmpCount = &tmpCountRetranslate; 155 } 156 else if(name == "currentIndex") 157 code = &codeSetupDelayed; 158 159 if(widgetType == "Line" && name == "orientation") 160 { 161 enforce(property.children[0].name == "enum"); 162 enforce(property.children[0].children.length == 1); 163 enforce(property.children[0].children[0].type == EntityType.text); 164 string value = property.children[0].children[0].text; 165 166 code.put(" "); 167 code.put(widgetName); 168 code.put(".setFrameShape(dqtimported!q{qt.widgets.frame}.QFrame.Shape."); 169 if(value == "Qt::Horizontal") 170 code.put("HLine"); 171 else if(value == "Qt::Vertical") 172 code.put("VLine"); 173 else 174 enforce(false); 175 code.put(");\n"); 176 177 code.put(" "); 178 code.put(widgetName); 179 code.put("."); 180 code.put("setFrameShadow(dqtimported!q{qt.widgets.frame}.QFrame.Shadow.Sunken);\n"); 181 return; 182 } 183 184 Appender!string codeValue; 185 bool needTmp; 186 string methodName = "set" ~ std.ascii.toUpper(name[0]) ~ name[1..$]; 187 if(name == "showGroupSeparator") 188 methodName = "setGroupSeparatorShown"; 189 if(property.children[0].name == "rect") 190 { 191 if(isTopLevel) 192 { 193 methodName = "resize"; 194 foreach(i, c; property.children[0].children) 195 { 196 if(i < 2) 197 continue; 198 if(i > 2) 199 codeValue.put(", "); 200 enforce(c.children.length == 1); 201 enforce(c.children[0].type == EntityType.text); 202 codeValue.put(c.children[0].text); 203 } 204 } 205 else 206 { 207 codeValue.put("dqtimported!q{qt.core.rect}.QRect("); 208 foreach(i, c; property.children[0].children) 209 { 210 if(i) 211 codeValue.put(", "); 212 enforce(c.children.length == 1); 213 enforce(c.children[0].type == EntityType.text); 214 codeValue.put(c.children[0].text); 215 } 216 codeValue.put(")"); 217 needTmp = true; 218 } 219 } 220 else if(property.children[0].name == "color") 221 { 222 codeValue.put("dqtimported!q{qt.gui.color}.QColor("); 223 foreach(i, c; property.children[0].children) 224 { 225 if(i) 226 codeValue.put(", "); 227 enforce(c.children.length == 1); 228 enforce(c.children[0].type == EntityType.text); 229 codeValue.put(c.children[0].text); 230 } 231 codeValue.put(")"); 232 } 233 else if(property.children[0].name.among("bool", "number", "double")) 234 { 235 enforce(property.children[0].children.length == 1); 236 enforce(property.children[0].children[0].type == EntityType.text); 237 codeValue.put(property.children[0].children[0].text); 238 } 239 else if(property.children[0].name == "set") 240 { 241 enforce(property.children[0].children.length == 1); 242 enforce(property.children[0].children[0].type == EntityType.text); 243 codeValue.put("dqtimported!q{qt.core.flags}.flagsFromStaticString!(typeof("); 244 codeValue.put(widgetName); 245 codeValue.put("."); 246 codeValue.put(name); 247 codeValue.put("()), q{"); 248 codeValue.put(property.children[0].children[0].text); 249 codeValue.put("})"); 250 } 251 else if(property.children[0].name == "enum") 252 { 253 enforce(property.children[0].children.length == 1); 254 enforce(property.children[0].children[0].type == EntityType.text); 255 codeValue.put("dqtimported!q{qt.core.flags}.enumFromStaticString!(typeof("); 256 codeValue.put(widgetName); 257 codeValue.put("."); 258 codeValue.put(name); 259 codeValue.put("()), q{"); 260 codeValue.put(property.children[0].children[0].text); 261 codeValue.put("})"); 262 } 263 else if(property.children[0].name == "string") 264 { 265 string value; 266 if(property.children[0].children.length) 267 { 268 enforce(property.children[0].children.length == 1); 269 enforce(property.children[0].children[0].type == EntityType.text); 270 value = property.children[0].children[0].text; 271 } 272 if(name == "title" && property.name == "attribute") 273 methodName = "setTabText"; 274 if(name == "text" && widgetType == "QComboBox") 275 methodName = "setItemText"; 276 if(value.length == 0) 277 { 278 if(widgetType == "QTreeWidget") 279 return; 280 codeValue.put("dqtimported!q{qt.core.string}.QString.create()"); 281 } 282 else if(noTr) 283 { 284 codeValue.put("dqtimported!q{qt.core.string}.QString.fromUtf8("); 285 writeStringLiteral(codeValue, value.asDecodedXML); 286 codeValue.put(")"); 287 } 288 else 289 { 290 codeValue.put("dqtimported!q{qt.core.coreapplication}.QCoreApplication.translate(\""); 291 codeValue.put(rootWidgetInfo.name); 292 codeValue.put("\", "); 293 writeStringLiteral(codeValue, value.asDecodedXML); 294 codeValue.put(", "); 295 if(comment.length) 296 writeStringLiteral(codeValue, comment.asDecodedXML); 297 else 298 codeValue.put("null"); 299 codeValue.put(")"); 300 } 301 needTmp = true; 302 } 303 else if(property.children[0].name == "size") 304 { 305 string width, height; 306 foreach(i, c; property.children[0].children) 307 { 308 enforce(c.children.length == 1); 309 enforce(c.children[0].type == EntityType.text); 310 if(c.name == "width") 311 width = c.children[0].text; 312 else if(c.name == "height") 313 height = c.children[0].text; 314 else 315 enforce(false); 316 } 317 318 codeValue.put("dqtimported!q{qt.core.size}.QSize("); 319 codeValue.put(width); 320 codeValue.put(", "); 321 codeValue.put(height); 322 codeValue.put(")"); 323 needTmp = true; 324 } 325 else if(property.children[0].name == "sizepolicy") 326 { 327 string hsizetype="Expanding", vsizetype="Expanding"; 328 foreach(attr; property.children[0].attributes) 329 { 330 if(attr.name == "hsizetype") 331 hsizetype = attr.value; 332 if(attr.name == "vsizetype") 333 vsizetype = attr.value; 334 } 335 336 string sizePolicyVar = createTmpVar(*tmpCount, "sizePolicy"); 337 code.put(" auto "); 338 code.put(sizePolicyVar); 339 code.put(" = dqtimported!q{qt.widgets.sizepolicy}.QSizePolicy(dqtimported!q{qt.widgets.sizepolicy}.QSizePolicy.Policy."); 340 code.put(hsizetype); 341 code.put(", dqtimported!q{qt.widgets.sizepolicy}.QSizePolicy.Policy."); 342 code.put(vsizetype); 343 code.put(");\n"); 344 345 foreach(i, c; property.children[0].children) 346 { 347 enforce(c.children.length == 1); 348 enforce(c.children[0].type == EntityType.text); 349 code.put(" "); 350 code.put(sizePolicyVar); 351 code.put("."); 352 if(c.name == "horstretch") 353 code.put("setHorizontalStretch"); 354 else if(c.name == "verstretch") 355 code.put("setVerticalStretch"); 356 else 357 enforce(false); 358 code.put("("); 359 code.put(c.children[0].text); 360 code.put(");\n"); 361 } 362 363 code.put(" "); 364 code.put(sizePolicyVar); 365 code.put("."); 366 code.put("setHeightForWidth"); 367 code.put("("); 368 code.put(widgetName); 369 code.put(".sizePolicy().hasHeightForWidth());\n"); 370 371 codeValue.put(sizePolicyVar); 372 } 373 else if(property.children[0].name == "font") 374 { 375 string fontVar = createTmpVar(*tmpCount, "font"); 376 code.put(" auto "); 377 code.put(fontVar); 378 code.put(" = dqtimported!q{qt.gui.font}.QFont.create();\n"); 379 380 foreach(i, c; property.children[0].children) 381 { 382 enforce(c.children.length == 1); 383 enforce(c.children[0].type == EntityType.text); 384 code.put(" "); 385 code.put(fontVar); 386 code.put("."); 387 if(c.name == "pointsize") 388 code.put("setPointSize"); 389 else 390 { 391 code.put("set"); 392 code.put(c.name.capitalize); 393 } 394 code.put("("); 395 code.put(c.children[0].text); 396 code.put(");\n"); 397 } 398 399 codeValue.put(fontVar); 400 } 401 else if(property.children[0].name == "pixmap") 402 { 403 enforce(property.children[0].children.length == 1); 404 enforce(property.children[0].children[0].type == EntityType.text); 405 codeValue.put("dqtimported!q{qt.gui.pixmap}.QPixmap(dqtimported!q{qt.core.string}.QString("); 406 writeStringLiteral(codeValue, property.children[0].children[0].text); 407 codeValue.put("))"); 408 } 409 else if(property.children[0].name == "cursorShape") 410 { 411 enforce(property.children[0].children.length == 1); 412 enforce(property.children[0].children[0].type == EntityType.text); 413 codeValue.put("dqtimported!q{qt.gui.cursor}.QCursor(dqtimported!q{qt.core.namespace}.CursorShape."); 414 codeValue.put(property.children[0].children[0].text); 415 codeValue.put(")"); 416 needTmp = true; 417 } 418 else if(property.children[0].name == "datetime") 419 { 420 string year, month, day, hour, minute, second; 421 foreach(i, c; property.children[0].children) 422 { 423 enforce(c.children.length == 1); 424 enforce(c.children[0].type == EntityType.text); 425 if(c.name == "year") 426 year = c.children[0].text; 427 else if(c.name == "month") 428 month = c.children[0].text; 429 else if(c.name == "day") 430 day = c.children[0].text; 431 else if(c.name == "hour") 432 hour = c.children[0].text; 433 else if(c.name == "minute") 434 minute = c.children[0].text; 435 else if(c.name == "second") 436 second = c.children[0].text; 437 else 438 enforce(false); 439 } 440 441 string var = createTmpVar(*tmpCount, "datetime"); 442 code.put(" auto "); 443 code.put(var); 444 code.put(" = "); 445 code.put("dqtimported!q{qt.core.datetime}.QDateTime("); 446 code.put("dqtimported!q{qt.core.datetime}.QDate("); 447 code.put(year); 448 code.put(", "); 449 code.put(month); 450 code.put(", "); 451 code.put(day); 452 code.put("),"); 453 code.put("dqtimported!q{qt.core.datetime}.QTime("); 454 code.put(hour); 455 code.put(", "); 456 code.put(minute); 457 code.put(", "); 458 code.put(second); 459 code.put(")"); 460 code.put(");\n"); 461 codeValue.put(var); 462 } 463 else if(property.children[0].name == "date") 464 { 465 string year, month, day; 466 foreach(i, c; property.children[0].children) 467 { 468 enforce(c.children.length == 1); 469 enforce(c.children[0].type == EntityType.text); 470 if(c.name == "year") 471 year = c.children[0].text; 472 else if(c.name == "month") 473 month = c.children[0].text; 474 else if(c.name == "day") 475 day = c.children[0].text; 476 else 477 enforce(false); 478 } 479 480 string var = createTmpVar(*tmpCount, "date"); 481 code.put(" auto "); 482 code.put(var); 483 code.put(" = "); 484 code.put("dqtimported!q{qt.core.datetime}.QDate("); 485 code.put(year); 486 code.put(", "); 487 code.put(month); 488 code.put(", "); 489 code.put(day); 490 code.put(");\n"); 491 codeValue.put(var); 492 } 493 else if(property.children[0].name == "time") 494 { 495 string hour, minute, second; 496 foreach(i, c; property.children[0].children) 497 { 498 enforce(c.children.length == 1); 499 enforce(c.children[0].type == EntityType.text); 500 if(c.name == "hour") 501 hour = c.children[0].text; 502 else if(c.name == "minute") 503 minute = c.children[0].text; 504 else if(c.name == "second") 505 second = c.children[0].text; 506 else 507 enforce(false); 508 } 509 510 string var = createTmpVar(*tmpCount, "time"); 511 code.put(" auto "); 512 code.put(var); 513 code.put(" = "); 514 code.put("dqtimported!q{qt.core.datetime}.QTime("); 515 code.put(hour); 516 code.put(", "); 517 code.put(minute); 518 code.put(", "); 519 code.put(second); 520 code.put(");\n"); 521 codeValue.put(var); 522 } 523 else if(property.children[0].name == "iconset") 524 { 525 string iconVar = createTmpVar(*tmpCount, "iconVar"); 526 527 code.put(" dqtimported!q{qt.gui.icon}.QIcon "); 528 code.put(iconVar); 529 code.put(";\n"); 530 531 string theme; 532 foreach(attr; property.children[0].attributes) 533 { 534 if(attr.name == "theme") 535 theme = attr.value; 536 } 537 538 if(theme.length) 539 { 540 string iconNameVar = createTmpVar(*tmpCount, "iconThemeName"); 541 542 code.put(" auto "); 543 code.put(iconNameVar); 544 code.put(" = dqtimported!q{qt.core.string}.QString("); 545 writeStringLiteral(*code, theme); 546 code.put(");\n"); 547 code.put(" if (dqtimported!q{qt.gui.icon}.QIcon.hasThemeIcon("); 548 code.put(iconNameVar); 549 code.put(")) {\n"); 550 code.put(" "); 551 code.put(iconVar); 552 code.put(" = dqtimported!q{qt.gui.icon}.QIcon.fromTheme("); 553 code.put(iconNameVar); 554 code.put(");\n"); 555 code.put(" } else {\n"); 556 } 557 558 foreach(i, c; property.children[0].children) 559 { 560 if(c.type == EntityType.text) 561 continue; 562 enforce(c.children.length == 1); 563 enforce(c.children[0].type == EntityType.text); 564 565 string type = c.name; 566 string mode, state; 567 if(type.endsWith("on")) 568 { 569 mode = type[0..$-2]; 570 state = type[$-2..$]; 571 } 572 else if(type.endsWith("off")) 573 { 574 mode = type[0..$-3]; 575 state = type[$-3..$]; 576 } 577 else 578 enforce(false); 579 580 if(theme.length) 581 code.put(" "); 582 code.put(" "); 583 code.put(iconVar); 584 code.put(".addFile(dqtimported!q{qt.core.string}.QString("); 585 writeStringLiteral(*code, c.children[0].text); 586 code.put("), globalInitVar!(dqtimported!q{qt.core.size}.QSize), dqtimported!q{qt.gui.icon}.QIcon.Mode."); 587 code.put(mode.capitalize); 588 code.put(", dqtimported!q{qt.gui.icon}.QIcon.State."); 589 code.put(state.capitalize); 590 code.put(");\n"); 591 } 592 593 if(theme.length) 594 code.put(" }\n"); 595 codeValue.put(iconVar); 596 } 597 else 598 enforce(false, "Unsupported property type " ~ property.children[0].name); 599 600 code.put(" "); 601 string tmpVar; 602 if(needTmp) 603 { 604 tmpVar = createTmpVar(*tmpCount, "tmp"); 605 code.put("auto "); 606 code.put(tmpVar); 607 code.put(" = "); 608 code.put(codeValue.data); 609 code.put(";\n"); 610 code.put(" "); 611 } 612 code.put(widgetName); 613 code.put("."); 614 code.put(methodName); 615 code.put("("); 616 code.put(extraArg); 617 if(needTmp) 618 code.put(tmpVar); 619 else 620 code.put(codeValue.data); 621 code.put(");\n"); 622 } 623 624 WidgetInfo addItem(DOMEntity widget, string parentName, string parentType, bool isRoot, size_t itemIndex) 625 { 626 WidgetInfo info = getWidgetInfo(widget); 627 628 string extraArg; 629 string name; 630 if(parentType == "QComboBox") 631 { 632 codeSetup.put(" "); 633 codeSetup.put(parentName); 634 codeSetup.put(".addItem(dqtimported!q{qt.core.string}.QString.create());\n"); 635 extraArg = text(itemIndex, ", "); 636 name = parentName; 637 } 638 else if(parentType == "QTreeWidget") 639 { 640 name = createTmpVar(tmpCountSetup, "__qtreewidgetitem"); 641 codeSetup.put(" dqtimported!q{qt.widgets.treewidget}.QTreeWidgetItem "); 642 codeSetup.put(name); 643 codeSetup.put(" = cpp_new!(dqtimported!q{qt.widgets.treewidget}.QTreeWidgetItem)("); 644 codeSetup.put(parentName); 645 codeSetup.put(");\n"); 646 647 codeRetranslate.put(" dqtimported!q{qt.widgets.treewidget}.QTreeWidgetItem "); 648 codeRetranslate.put(name); 649 codeRetranslate.put(" = "); 650 codeRetranslate.put(parentName); 651 codeRetranslate.put(isRoot ? ".topLevelItem(" : ".child("); 652 codeRetranslate.put(text(itemIndex)); 653 codeRetranslate.put(");\n"); 654 } 655 else if(parentType == "column") 656 { 657 extraArg = text(itemIndex, ", "); 658 name = parentName; 659 } 660 size_t[string] columnByProperty; 661 foreach(property; widget.children) 662 { 663 if(parentType == "QTreeWidget") 664 { 665 string propName; 666 foreach(attr; property.attributes) 667 { 668 if(attr.name == "name") 669 propName = attr.value; 670 } 671 if(propName == "flags") 672 extraArg = ""; 673 else 674 { 675 size_t col = columnByProperty.get(propName, 0); 676 extraArg = text(col, ", "); 677 columnByProperty[propName] = col + 1; 678 } 679 } 680 if(property.name == "property") 681 addProperty(property, name, parentType, false, extraArg); 682 } 683 size_t childItemCount; 684 foreach(c; widget.children) 685 { 686 if(c.name == "item") 687 { 688 addItem(c, name, parentType, false, childItemCount); 689 childItemCount++; 690 } 691 } 692 return info; 693 } 694 695 WidgetInfo addWidget(DOMEntity widget, string parentName, WidgetInfo parentInfo, WidgetInfo parentParentInfo) 696 { 697 WidgetInfo info = getWidgetInfo(widget); 698 699 if(info.name.length) 700 { 701 widgetTypes[info.name] = info.className; 702 } 703 704 if(widget.name == "spacer") 705 { 706 string orientation; 707 string sizeType = "Expanding"; 708 string width = "0"; 709 string height = "0"; 710 711 foreach(property; widget.children) 712 { 713 if(property.name == "property") 714 { 715 string propName; 716 foreach(attr; property.attributes) 717 { 718 if(attr.name == "name") 719 propName = attr.value; 720 } 721 722 if(propName == "orientation") 723 { 724 enforce(property.children.length == 1); 725 enforce(property.children[0].name == "enum"); 726 enforce(property.children[0].children.length == 1); 727 orientation = property.children[0].children[0].text; 728 } 729 else if(propName == "sizeType") 730 { 731 enforce(property.children.length == 1); 732 enforce(property.children[0].name == "enum"); 733 enforce(property.children[0].children.length == 1); 734 sizeType = property.children[0].children[0].text; 735 enforce(sizeType.startsWith("QSizePolicy::")); 736 sizeType = sizeType["QSizePolicy::".length..$]; 737 } 738 else if(propName == "sizeHint") 739 { 740 enforce(property.children.length == 1); 741 enforce(property.children[0].name == "size"); 742 enforce(property.children[0].children.length == 2); 743 enforce(property.children[0].children[0].name == "width"); 744 enforce(property.children[0].children[1].name == "height"); 745 width = property.children[0].children[0].children[0].text; 746 height = property.children[0].children[1].children[0].text; 747 } 748 } 749 } 750 751 codeVars.put(" "); 752 codeVars.put("dqtimported!q{"); 753 codeVars.put(getWidgetModule(info.className)); 754 codeVars.put("}."); 755 codeVars.put(info.className); 756 codeVars.put(" "); 757 codeVars.put(info.name); 758 codeVars.put(";\n"); 759 760 codeSetup.put(" "); 761 codeSetup.put(info.name); 762 codeSetup.put(" = cpp_new!(dqtimported!q{"); 763 codeSetup.put(getWidgetModule(info.className)); 764 codeSetup.put("}."); 765 codeSetup.put(info.className); 766 codeSetup.put(")("); 767 codeSetup.put(width); 768 codeSetup.put(", "); 769 codeSetup.put(height); 770 codeSetup.put(", "); 771 if(orientation == "Qt::Horizontal") 772 { 773 codeSetup.put("dqtimported!q{qt.widgets.sizepolicy}.QSizePolicy.Policy."); 774 codeSetup.put(sizeType); 775 codeSetup.put(", "); 776 codeSetup.put("dqtimported!q{qt.widgets.sizepolicy}.QSizePolicy.Policy.Minimum"); 777 } 778 else if(orientation == "Qt::Vertical") 779 { 780 codeSetup.put("dqtimported!q{qt.widgets.sizepolicy}.QSizePolicy.Policy.Minimum"); 781 codeSetup.put(", "); 782 codeSetup.put("dqtimported!q{qt.widgets.sizepolicy}.QSizePolicy.Policy."); 783 codeSetup.put(sizeType); 784 } 785 else 786 enforce(false); 787 codeSetup.put(");\n"); 788 return info; 789 } 790 791 if(info.name.length) 792 { 793 codeVars.put(" "); 794 if(info.className == "Line") 795 { 796 codeVars.put("dqtimported!q{qt.widgets.frame}.QFrame"); 797 } 798 else 799 { 800 codeVars.put("dqtimported!q{"); 801 codeVars.put(getWidgetModule(info.className)); 802 codeVars.put("}."); 803 codeVars.put(info.className); 804 } 805 codeVars.put(" "); 806 codeVars.put(info.name); 807 codeVars.put(";\n"); 808 809 codeSetup.put(" "); 810 codeSetup.put(info.name); 811 codeSetup.put(" = cpp_new!("); 812 if(info.className == "Line") 813 { 814 codeSetup.put("dqtimported!q{qt.widgets.frame}.QFrame"); 815 } 816 else 817 { 818 codeSetup.put("dqtimported!q{"); 819 codeSetup.put(getWidgetModule(info.className)); 820 codeSetup.put("}."); 821 codeSetup.put(info.className); 822 } 823 codeSetup.put(")("); 824 if(!(parentInfo.xmlName == "layout" && widget.name == "layout")) 825 codeSetup.put(parentName); 826 codeSetup.put(");\n"); 827 } 828 string sortingEnabledVar; 829 if(info.className == "QTreeWidget") 830 { 831 string headerName = createTmpVar(tmpCountSetup, "__qtreewidgetitem"); 832 codeSetup.put(" dqtimported!q{qt.widgets.treewidget}.QTreeWidgetItem "); 833 codeSetup.put(headerName); 834 codeSetup.put(" = cpp_new!(dqtimported!q{qt.widgets.treewidget}.QTreeWidgetItem)("); 835 codeSetup.put(");\n"); 836 837 codeRetranslate.put(" dqtimported!q{qt.widgets.treewidget}.QTreeWidgetItem "); 838 codeRetranslate.put(headerName); 839 codeRetranslate.put(" = "); 840 codeRetranslate.put(info.name); 841 codeRetranslate.put(".headerItem();\n"); 842 843 size_t childItemCount; 844 foreach(c; widget.children) 845 { 846 if(c.name == "column") 847 { 848 WidgetInfo childInfo = addItem(c, headerName, "column", true, childItemCount); 849 childItemCount++; 850 } 851 } 852 853 codeSetup.put(" "); 854 codeSetup.put(info.name); 855 codeSetup.put(".setHeaderItem("); 856 codeSetup.put(headerName); 857 codeSetup.put(");\n"); 858 859 sortingEnabledVar = createTmpVar(tmpCountRetranslate, "__sortingEnabled"); 860 codeRetranslate.put(" const(bool) "); 861 codeRetranslate.put(sortingEnabledVar); 862 codeRetranslate.put(" = "); 863 codeRetranslate.put(info.name); 864 codeRetranslate.put(".isSortingEnabled();\n"); 865 866 codeRetranslate.put(" "); 867 codeRetranslate.put(info.name); 868 codeRetranslate.put(".setSortingEnabled(false);\n"); 869 } 870 size_t childItemCount; 871 foreach(c; widget.children) 872 { 873 if(c.name == "item" && widget.name != "layout") 874 { 875 WidgetInfo childInfo = addItem(c, info.name, info.className, true, childItemCount); 876 childItemCount++; 877 } 878 } 879 if(info.name.length) 880 { 881 codeSetup.put(" "); 882 codeSetup.put(info.name); 883 codeSetup.put(".setObjectName(\""); 884 codeSetup.put(info.name); 885 codeSetup.put("\");\n"); 886 } 887 888 string constructorArg = info.name; 889 if(info.className == "QTabWidget") 890 constructorArg = ""; 891 if(info.className == "QScrollArea") 892 constructorArg = ""; 893 if(widget.name == "layout") 894 constructorArg = parentName; 895 if(widget.name == "item") 896 constructorArg = parentName; 897 string[string] margins; 898 foreach(property; widget.children) 899 { 900 if(property.name == "property") 901 { 902 string name; 903 foreach(attr; property.attributes) 904 { 905 if(attr.name == "name") 906 name = attr.value; 907 } 908 if(name.among("leftMargin", "topMargin", "rightMargin", "bottomMargin")) 909 { 910 enforce(property.children[0].name == "number"); 911 enforce(property.children[0].children.length == 1); 912 enforce(property.children[0].children[0].type == EntityType.text); 913 margins[name] = property.children[0].children[0].text; 914 } 915 else 916 addProperty(property, info.name, info.className, false, ""); 917 } 918 } 919 bool inLayoutInSplitter = parentInfo.className == "QWidget" && parentParentInfo.className == "QSplitter" && info.xmlName == "layout"; 920 if(margins !is null || inLayoutInSplitter) 921 { 922 string defaultMargin = (parentInfo.xmlName == "layout") ? "0" : "-1"; 923 if(inLayoutInSplitter) 924 defaultMargin = "0"; 925 codeSetup.put(" "); 926 codeSetup.put(info.name); 927 codeSetup.put(".setContentsMargins("); 928 codeSetup.put(margins.get("leftMargin", defaultMargin)); 929 codeSetup.put(", "); 930 codeSetup.put(margins.get("topMargin", defaultMargin)); 931 codeSetup.put(", "); 932 codeSetup.put(margins.get("rightMargin", defaultMargin)); 933 codeSetup.put(", "); 934 codeSetup.put(margins.get("bottomMargin", defaultMargin)); 935 codeSetup.put(");\n"); 936 } 937 foreach(c; widget.children) 938 { 939 WidgetInfo childInfo = addChildWidget(widget, info, c, constructorArg, info, parentInfo); 940 if(info.className == "QTabWidget") 941 { 942 foreach(property; c.children) 943 { 944 if(property.name == "attribute") 945 addProperty(property, info.name, info.className, false, info.name ~ ".indexOf(" ~ childInfo.name ~ "), "); 946 } 947 } 948 } 949 if(info.className == "QTreeWidget") 950 { 951 codeRetranslate.put(" "); 952 codeRetranslate.put(info.name); 953 codeRetranslate.put(".setSortingEnabled("); 954 codeRetranslate.put(sortingEnabledVar); 955 codeRetranslate.put(");\n"); 956 } 957 return info; 958 } 959 960 WidgetInfo addChildWidget(DOMEntity widget, WidgetInfo info, DOMEntity c, string constructorArg, WidgetInfo parentInfo, WidgetInfo parentParentInfo) 961 { 962 if(c.name == "item" && widget.name == "layout") 963 { 964 enforce(c.children.length == 1); 965 WidgetInfo childInfo = addWidget(c.children[0], constructorArg, parentInfo, parentParentInfo); 966 967 string row, column, rowspan = "1", colspan = "1"; 968 foreach(attr; c.attributes) 969 { 970 if(attr.name == "row") 971 row = attr.value; 972 if(attr.name == "column") 973 column = attr.value; 974 if(attr.name == "rowspan") 975 rowspan = attr.value; 976 if(attr.name == "colspan") 977 colspan = attr.value; 978 } 979 980 codeSetup.put("\n "); 981 codeSetup.put(info.name); 982 if(info.className == "QFormLayout") 983 { 984 if(c.children[0].name == "layout") 985 codeSetup.put(".setLayout("); 986 else if(c.children[0].name == "spacer") 987 codeSetup.put(".setItem("); 988 else 989 codeSetup.put(".setWidget("); 990 991 codeSetup.put(row); 992 codeSetup.put(", dqtimported!q{qt.widgets.formlayout}.QFormLayout.ItemRole."); 993 codeSetup.put((column == "1") ? "FieldRole" : "LabelRole"); 994 codeSetup.put(", "); 995 } 996 else 997 { 998 if(c.children[0].name == "layout") 999 codeSetup.put(".addLayout("); 1000 else if(c.children[0].name == "spacer") 1001 codeSetup.put(".addItem("); 1002 else 1003 codeSetup.put(".addWidget("); 1004 } 1005 codeSetup.put(childInfo.name); 1006 if(info.className == "QGridLayout") 1007 { 1008 codeSetup.put(", "); 1009 codeSetup.put(row); 1010 codeSetup.put(", "); 1011 codeSetup.put(column); 1012 codeSetup.put(", "); 1013 codeSetup.put(rowspan); 1014 codeSetup.put(", "); 1015 codeSetup.put(colspan); 1016 } 1017 codeSetup.put(");\n\n"); 1018 return childInfo; 1019 } 1020 else if(c.name == "widget" || c.name == "layout") 1021 { 1022 WidgetInfo childInfo = addWidget(c, constructorArg, parentInfo, parentParentInfo); 1023 1024 if(info.className == "QTabWidget") 1025 { 1026 codeSetup.put(" "); 1027 codeSetup.put(info.name); 1028 codeSetup.put(".addTab("); 1029 codeSetup.put(childInfo.name); 1030 codeSetup.put(", "); 1031 codeSetup.put("dqtimported!q{qt.core.string}.QString.create()"); // TODO: not translateable strings 1032 codeSetup.put(");\n"); 1033 } 1034 else if(info.className == "QMainWindow") 1035 { 1036 if(childInfo.className != "QMenuBar" && childInfo.className != "QStatusBar") 1037 codeSetup.put("\n"); 1038 codeSetup.put(" "); 1039 codeSetup.put(info.name); 1040 if(childInfo.className == "QMenuBar") 1041 codeSetup.put(".setMenuBar("); 1042 else if(childInfo.className == "QStatusBar") 1043 codeSetup.put(".setStatusBar("); 1044 else 1045 codeSetup.put(".setCentralWidget("); 1046 codeSetup.put(childInfo.name); 1047 codeSetup.put(");\n"); 1048 } 1049 else if(info.className == "QSplitter") 1050 { 1051 codeSetup.put(" "); 1052 codeSetup.put(info.name); 1053 codeSetup.put(".addWidget("); 1054 codeSetup.put(childInfo.name); 1055 codeSetup.put(");\n"); 1056 } 1057 else if(info.className == "QScrollArea") 1058 { 1059 codeSetup.put(" "); 1060 codeSetup.put(info.name); 1061 codeSetup.put(".setWidget("); 1062 codeSetup.put(childInfo.name); 1063 codeSetup.put(");\n"); 1064 } 1065 return childInfo; 1066 } 1067 else if(c.name == "addaction") 1068 { 1069 string name; 1070 foreach(attr; c.attributes) 1071 { 1072 if(attr.name == "name") 1073 name = attr.value; 1074 } 1075 1076 codeSetupAddActions.put(" "); 1077 codeSetupAddActions.put(info.name); 1078 codeSetupAddActions.put(".addAction("); 1079 codeSetupAddActions.put(name); 1080 if(name in widgetTypes && widgetTypes[name].among("QMenu", "QMenuBar")) 1081 codeSetupAddActions.put(".menuAction()"); 1082 codeSetupAddActions.put(");\n"); 1083 } 1084 return WidgetInfo.init; 1085 } 1086 1087 void addConnection(DOMEntity connection) 1088 { 1089 string sender, signal, receiver, slot; 1090 foreach(ref c; connection.children) 1091 { 1092 if(c.name == "sender") 1093 { 1094 enforce(c.children.length == 1); 1095 enforce(c.children[0].type == EntityType.text); 1096 sender = c.children[0].text; 1097 } 1098 else if(c.name == "signal") 1099 { 1100 enforce(c.children.length == 1); 1101 enforce(c.children[0].type == EntityType.text); 1102 signal = c.children[0].text; 1103 } 1104 else if(c.name == "receiver") 1105 { 1106 enforce(c.children.length == 1); 1107 enforce(c.children[0].type == EntityType.text); 1108 receiver = c.children[0].text; 1109 } 1110 else if(c.name == "slot") 1111 { 1112 enforce(c.children.length == 1); 1113 enforce(c.children[0].type == EntityType.text); 1114 slot = c.children[0].text; 1115 } 1116 } 1117 1118 // Ignore parameter types for signal and slot for now. 1119 foreach(i, char c; signal) 1120 if(c == '(') 1121 { 1122 signal = signal[0..i]; 1123 break; 1124 } 1125 foreach(i, char c; slot) 1126 if(c == '(') 1127 { 1128 slot = slot[0..i]; 1129 break; 1130 } 1131 1132 codeSetupConnect.put(" dqtimported!q{qt.core.object}.QObject.connect("); 1133 codeSetupConnect.put(sender); 1134 codeSetupConnect.put(".signal!\""); 1135 codeSetupConnect.put(signal); 1136 codeSetupConnect.put("\", "); 1137 codeSetupConnect.put(receiver); 1138 codeSetupConnect.put(".slot!\""); 1139 codeSetupConnect.put(slot); 1140 codeSetupConnect.put("\");\n"); 1141 } 1142 1143 string convert(string xml, string customWidgetPackage) 1144 { 1145 this.customWidgetPackage = customWidgetPackage; 1146 auto dom = parseDOM!simpleXML(xml); 1147 assert(dom.type == EntityType.elementStart); 1148 1149 enforce(dom.children.length == 1); 1150 auto root = dom.children[0]; 1151 enforce(root.name == "ui"); 1152 1153 DOMEntity* rootWidget; 1154 foreach(ref c; root.children) 1155 { 1156 if(c.name == "widget") 1157 { 1158 enforce(rootWidget is null, "Multpile root widgets"); 1159 rootWidget = &c; 1160 } 1161 } 1162 enforce(rootWidget !is null); 1163 rootWidgetInfo = getWidgetInfo(*rootWidget); 1164 1165 codeSetup.put(" if ("); 1166 codeSetup.put(rootWidgetInfo.name); 1167 codeSetup.put(".objectName().isEmpty())\n"); 1168 codeSetup.put(" "); 1169 codeSetup.put(rootWidgetInfo.name); 1170 codeSetup.put(".setObjectName(\""); 1171 codeSetup.put(rootWidgetInfo.name); 1172 codeSetup.put("\");\n"); 1173 1174 foreach(c; rootWidget.children) 1175 { 1176 if(c.name == "property") 1177 addProperty(c, rootWidgetInfo.name, rootWidgetInfo.className, true, ""); 1178 } 1179 1180 foreach(c; rootWidget.children) 1181 { 1182 if(c.name == "action") 1183 { 1184 addWidget(c, rootWidgetInfo.name, WidgetInfo(), WidgetInfo()); 1185 } 1186 } 1187 1188 foreach(c; rootWidget.children) 1189 { 1190 addChildWidget(*rootWidget, rootWidgetInfo, c, rootWidgetInfo.name, WidgetInfo(), WidgetInfo()); 1191 } 1192 1193 foreach(c; root.children) 1194 { 1195 if(c.name == "connections") 1196 { 1197 foreach(c2; c.children) 1198 { 1199 if(c2.name == "connection") 1200 { 1201 addConnection(c2); 1202 } 1203 } 1204 } 1205 } 1206 1207 codeVars.put("\n"); 1208 codeVars.put(" void setupUi("); 1209 codeVars.put("dqtimported!q{"); 1210 codeVars.put(getWidgetModule(rootWidgetInfo.className)); 1211 codeVars.put("}."); 1212 codeVars.put(rootWidgetInfo.className); 1213 codeVars.put(" "); 1214 codeVars.put(rootWidgetInfo.name); 1215 codeVars.put(")\n"); 1216 codeVars.put(" {\n"); 1217 codeVars.put(" import core.stdcpp.new_: cpp_new;\n"); 1218 codeVars.put(codeSetup.data); 1219 codeVars.put(codeSetupAddActions.data); 1220 codeVars.put("\n retranslateUi("); 1221 codeVars.put(rootWidgetInfo.name); 1222 codeVars.put(");\n"); 1223 codeVars.put(codeSetupConnect.data); 1224 codeVars.put(codeSetupDelayed.data); 1225 codeVars.put("\n dqtimported!q{qt.core.objectdefs}.QMetaObject.connectSlotsByName("); 1226 codeVars.put(rootWidgetInfo.name); 1227 codeVars.put(");\n"); 1228 codeVars.put(" } // setupUi\n"); 1229 codeVars.put("\n"); 1230 codeVars.put(" void retranslateUi("); 1231 codeVars.put("dqtimported!q{"); 1232 codeVars.put(getWidgetModule(rootWidgetInfo.className)); 1233 codeVars.put("}."); 1234 codeVars.put(rootWidgetInfo.className); 1235 codeVars.put(" "); 1236 codeVars.put(rootWidgetInfo.name); 1237 codeVars.put(")\n"); 1238 codeVars.put(" {\n"); 1239 codeVars.put(codeRetranslate.data); 1240 codeVars.put(" } // retranslateUi\n"); 1241 return codeVars.data; 1242 } 1243 } 1244 1245 1246 /++ 1247 Generates code from *.ui file created with [Qt Designer](https://doc.qt.io/qt-6/qtdesigner-manual.html). 1248 1249 The generated code contains multiple declarations: 1250 Every widget in the *.ui file is declared as a variable. 1251 The function `setupUi` is created, which creates and configures all 1252 widgets. 1253 The function `retranslateUi` sets all translatable strings. 1254 1255 Params: 1256 xml = Content of *.ui file. 1257 customWidgetPackage = Package for custom widgets used in *.ui file. 1258 1259 Returns: 1260 D code for use in mixin. 1261 +/ 1262 string generateUICode()(string xml, string customWidgetPackage = "") 1263 { 1264 return UICodeWriter!()().convert(xml, customWidgetPackage); 1265 } 1266 1267 /++ 1268 Struct for using a *.ui file created with [Qt Designer](https://doc.qt.io/qt-6/qtdesigner-manual.html). 1269 1270 The *.ui file is read at compile time with the [import expression](https://dlang.org/spec/expression.html#import_expressions). 1271 Use the dmd option -J for setting the search path for those files. 1272 The members of the struct are created with `generateUICode`. 1273 1274 Params: 1275 filename = Filename of *.ui file. 1276 customWidgetPackage = Package for custom widgets used in *.ui file. 1277 +/ 1278 struct UIStruct(string filename, string customWidgetPackage = "") 1279 { 1280 mixin(generateUICode(import(filename), customWidgetPackage)); 1281 }