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 }