mirror of https://github.com/icedream/ui2walk.git
1376 lines
29 KiB
Go
1376 lines
29 KiB
Go
// Copyright 2011 The Walk Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"encoding/xml"
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
)
|
|
|
|
var forceUpdate *bool = flag.Bool("force", false, "forces code generation for up-to-date files")
|
|
var translatable *bool = flag.Bool("tr", false, "adds calls to a user provided 'func tr(source string, context ...string) string' that returns a translation of the source argument, using provided context args for disambiguation")
|
|
var packageName *string = flag.String("pkg", "main", "defines the package name for which to generate the code")
|
|
|
|
type String struct {
|
|
Text string `xml:"string"`
|
|
Comment string `xml:"comment,attr"`
|
|
ExtraComment string `xml:"extracomment,attr"`
|
|
}
|
|
|
|
type UI struct {
|
|
Class string `xml:"class"`
|
|
Widget Widget `xml:"widget"`
|
|
CustomWidgets CustomWidgets `xml:"customwidgets"`
|
|
TabStops []string `xml:"tabstops>tabstop"`
|
|
}
|
|
|
|
type Widget struct {
|
|
Class string `xml:"class,attr"`
|
|
Name string `xml:"name,attr"`
|
|
Attribute []*Attribute `xml:"attribute"`
|
|
Property []*Property `xml:"property"`
|
|
Layout *Layout `xml:"layout"`
|
|
Widget []*Widget `xml:"widget"`
|
|
AddAction []*AddAction `xml:"addaction"`
|
|
Action []*Action `xml:"action"`
|
|
ignored bool
|
|
}
|
|
|
|
type Layout struct {
|
|
Class string `xml:"class,attr"`
|
|
Name string `xml:"name,attr"`
|
|
Stretch string `xml:"stretch,attr"`
|
|
Property []*Property `xml:"property"`
|
|
Item []*Item `xml:"item"`
|
|
ignored bool
|
|
}
|
|
|
|
type Item struct {
|
|
Row string `xml:"row,attr"`
|
|
Column string `xml:"column,attr"`
|
|
RowSpan string `xml:"rowspan,attr"`
|
|
ColSpan string `xml:"colspan,attr"`
|
|
Widget *Widget `xml:"widget"`
|
|
Spacer *Spacer `xml:"spacer"`
|
|
}
|
|
|
|
type Spacer struct {
|
|
Name string `xml:"name,attr"`
|
|
Property []*Property `xml:"property"`
|
|
}
|
|
|
|
type AddAction struct {
|
|
Name string `xml:"name,attr"`
|
|
}
|
|
|
|
type Action struct {
|
|
Name string `xml:"name,attr"`
|
|
Property []*Property `xml:"property"`
|
|
}
|
|
|
|
type Attribute struct {
|
|
Name string `xml:"name,attr"`
|
|
String
|
|
}
|
|
|
|
type Property struct {
|
|
Name string `xml:"name,attr"`
|
|
Bool bool `xml:"bool"`
|
|
Enum string `xml:"enum"`
|
|
Font *Font `xml:"font"`
|
|
Number float64 `xml:"number"`
|
|
Rect Rectangle `xml:"rect"`
|
|
Set string `xml:"set"`
|
|
Size Size `xml:"size"`
|
|
String
|
|
}
|
|
|
|
type Font struct {
|
|
Family string `xml:"family"`
|
|
PointSize int `xml:"pointsize"`
|
|
Italic bool `xml:"italic"`
|
|
Bold bool `xml:"bold"`
|
|
Underline bool `xml:"underline"`
|
|
StrikeOut bool `xml:"strikeout"`
|
|
}
|
|
|
|
type Rectangle struct {
|
|
X int `xml:"x"`
|
|
Y int `xml:"y"`
|
|
Width int `xml:"width"`
|
|
Height int `xml:"height"`
|
|
}
|
|
|
|
type Size struct {
|
|
Width int `xml:"width"`
|
|
Height int `xml:"height"`
|
|
}
|
|
|
|
type CustomWidgets struct {
|
|
CustomWidget []*CustomWidget `xml:"customwidget"`
|
|
}
|
|
|
|
type CustomWidget struct {
|
|
Class string `xml:"class"`
|
|
Extends string `xml:"extends"`
|
|
}
|
|
|
|
func trString(str *String) string {
|
|
if str == nil {
|
|
return ""
|
|
}
|
|
|
|
if !*translatable {
|
|
return fmt.Sprintf("`%s`", str.Text)
|
|
}
|
|
|
|
buf := new(bytes.Buffer)
|
|
buf.WriteString("tr(`")
|
|
buf.WriteString(str.Text)
|
|
buf.WriteString("`")
|
|
|
|
if str.Comment != "" {
|
|
buf.WriteString(", `")
|
|
buf.WriteString(str.Comment)
|
|
buf.WriteString("`")
|
|
}
|
|
|
|
if str.ExtraComment != "" {
|
|
buf.WriteString(", `")
|
|
buf.WriteString(str.ExtraComment)
|
|
buf.WriteString("`")
|
|
}
|
|
|
|
buf.WriteString(")")
|
|
|
|
return buf.String()
|
|
}
|
|
|
|
func logFatal(err error) {
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func parseUI(reader io.Reader) (*UI, error) {
|
|
ui := &UI{}
|
|
|
|
if err := xml.NewDecoder(reader).Decode(ui); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return ui, nil
|
|
}
|
|
|
|
func writeAttribute(buf *bytes.Buffer, attr *Attribute, qualifiedReceiver string) (err error) {
|
|
switch attr.Name {
|
|
case "title":
|
|
buf.WriteString(fmt.Sprintf(
|
|
"if err := %s.SetTitle(%s); err != nil {\nreturn err\n}\n",
|
|
qualifiedReceiver, trString(&attr.String)))
|
|
|
|
default:
|
|
fmt.Printf("Ignoring unsupported attribute: '%s'\n", attr.Name)
|
|
return nil
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func writeAttributes(buf *bytes.Buffer, attrs []*Attribute, qualifiedReceiver string) error {
|
|
for _, attr := range attrs {
|
|
if err := writeAttribute(buf, attr, qualifiedReceiver); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func writeProperty(buf *bytes.Buffer, prop *Property, qualifiedReceiver string, widget *Widget) (err error) {
|
|
if prop.Name == "windowTitle" && widget != nil && widget.Class == "QWidget" {
|
|
return
|
|
}
|
|
|
|
switch prop.Name {
|
|
case "decimals":
|
|
buf.WriteString(fmt.Sprintf("if err := %s.SetDecimals(%d); err != nil {\nreturn err\n}\n", qualifiedReceiver, int(prop.Number)))
|
|
|
|
case "echoMode":
|
|
switch prop.Enum {
|
|
case "QLineEdit::Normal":
|
|
// nop
|
|
|
|
case "QLineEdit::Password":
|
|
buf.WriteString(fmt.Sprintf("%s.SetPasswordMode(true)\n", qualifiedReceiver))
|
|
|
|
default:
|
|
fmt.Printf("Ignoring unsupported echoMode: '%s'\n", prop.Enum)
|
|
return nil
|
|
}
|
|
|
|
case "enabled":
|
|
buf.WriteString(fmt.Sprintf("%s.SetEnabled(%t)\n", qualifiedReceiver, prop.Bool))
|
|
|
|
case "font":
|
|
f := prop.Font
|
|
family := f.Family
|
|
if family == "" {
|
|
family = "MS Shell Dlg 2"
|
|
}
|
|
pointSize := f.PointSize
|
|
if pointSize == 0 {
|
|
pointSize = 8
|
|
}
|
|
buf.WriteString(fmt.Sprintf("if font, err = walk.NewFont(\"%s\", %d, ",
|
|
family, pointSize))
|
|
included := []bool{f.Bold, f.Italic, f.StrikeOut, f.Underline}
|
|
flags := []string{"walk.FontBold", "walk.FontItalic", "walk.FontStrikeOut", "walk.FontUnderline"}
|
|
var includedFlags []string
|
|
for i := 0; i < len(included); i++ {
|
|
if included[i] {
|
|
includedFlags = append(includedFlags, flags[i])
|
|
}
|
|
}
|
|
if len(includedFlags) == 0 {
|
|
buf.WriteString("0")
|
|
} else {
|
|
buf.WriteString(strings.Join(includedFlags, "|"))
|
|
}
|
|
buf.WriteString(`); err != nil {
|
|
return err
|
|
}
|
|
`)
|
|
buf.WriteString(fmt.Sprintf("%s.SetFont(font)\n", qualifiedReceiver))
|
|
|
|
case "geometry":
|
|
if qualifiedReceiver == "w" {
|
|
// Only set client size for top level
|
|
buf.WriteString(fmt.Sprintf(
|
|
`if err := %s.SetClientSize(walk.Size{%d, %d}); err != nil {
|
|
return err
|
|
}
|
|
`,
|
|
qualifiedReceiver, prop.Rect.Width, prop.Rect.Height))
|
|
} else {
|
|
buf.WriteString(fmt.Sprintf(
|
|
`if err := %s.SetBounds(walk.Rectangle{%d, %d, %d, %d}); err != nil {
|
|
return err
|
|
}
|
|
`,
|
|
qualifiedReceiver, prop.Rect.X, prop.Rect.Y, prop.Rect.Width, prop.Rect.Height))
|
|
}
|
|
|
|
case "maximumSize", "minimumSize":
|
|
// We do these two guys in writeProperties, because we want to map them
|
|
// to a single method call, if both are present.
|
|
|
|
case "maxLength":
|
|
buf.WriteString(fmt.Sprintf("%s.SetMaxLength(%d)\n", qualifiedReceiver, int(prop.Number)))
|
|
|
|
case "readOnly":
|
|
buf.WriteString(fmt.Sprintf("%s.SetReadOnly(%t)\n", qualifiedReceiver, prop.Bool))
|
|
|
|
case "text":
|
|
buf.WriteString(fmt.Sprintf(
|
|
"if err := %s.SetText(%s); err != nil {\nreturn err\n}\n",
|
|
qualifiedReceiver, trString(&prop.String)))
|
|
|
|
case "title", "windowTitle":
|
|
buf.WriteString(fmt.Sprintf(
|
|
"if err := %s.SetTitle(%s); err != nil {\nreturn err\n}\n",
|
|
qualifiedReceiver, trString(&prop.String)))
|
|
|
|
case "orientation":
|
|
var orientation string
|
|
switch prop.Enum {
|
|
case "Qt::Horizontal":
|
|
orientation = "walk.Horizontal"
|
|
|
|
case "Qt::Vertical":
|
|
orientation = "walk.Vertical"
|
|
|
|
default:
|
|
return errors.New(fmt.Sprintf("unknown orientation: '%s'", prop.Enum))
|
|
}
|
|
|
|
buf.WriteString(fmt.Sprintf(
|
|
`if err := %s.SetOrientation(%s); err != nil {
|
|
return err
|
|
}
|
|
`,
|
|
qualifiedReceiver, orientation))
|
|
|
|
default:
|
|
fmt.Printf("Ignoring unsupported property: '%s'\n", prop.Name)
|
|
return nil
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func writeProperties(buf *bytes.Buffer, props []*Property, qualifiedReceiver string, widget *Widget) error {
|
|
var minSize, maxSize Size
|
|
var hasMinOrMaxSize bool
|
|
|
|
for _, prop := range props {
|
|
if err := writeProperty(buf, prop, qualifiedReceiver, widget); err != nil {
|
|
return err
|
|
}
|
|
|
|
if prop.Name == "minimumSize" {
|
|
minSize = prop.Size
|
|
hasMinOrMaxSize = true
|
|
}
|
|
if prop.Name == "maximumSize" {
|
|
maxSize = prop.Size
|
|
hasMinOrMaxSize = true
|
|
}
|
|
}
|
|
|
|
if hasMinOrMaxSize {
|
|
buf.WriteString(fmt.Sprintf(
|
|
`if err := %s.SetMinMaxSize(walk.Size{%d, %d}, walk.Size{%d, %d}); err != nil {
|
|
return err
|
|
}
|
|
`,
|
|
qualifiedReceiver, minSize.Width, minSize.Height, maxSize.Width, maxSize.Height))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func writeItemInitializations(buf *bytes.Buffer, items []*Item, parent *Widget, qualifiedParent string, layout string) error {
|
|
for _, item := range items {
|
|
var itemName string
|
|
|
|
if item.Spacer != nil {
|
|
itemName = item.Spacer.Name
|
|
name2Prop := make(map[string]*Property)
|
|
|
|
for _, prop := range item.Spacer.Property {
|
|
name2Prop[prop.Name] = prop
|
|
}
|
|
|
|
orientation := name2Prop["orientation"]
|
|
sizeType := name2Prop["sizeType"]
|
|
sizeHint := name2Prop["sizeHint"]
|
|
|
|
var orientStr string
|
|
var fixedStr string
|
|
var secondParamStr string
|
|
|
|
if orientation.Enum == "Qt::Horizontal" {
|
|
orientStr = "H"
|
|
|
|
if sizeType != nil && sizeType.Enum == "QSizePolicy::Fixed" {
|
|
fixedStr = "Fixed"
|
|
secondParamStr = fmt.Sprintf(", %d", sizeHint.Size.Width)
|
|
}
|
|
} else {
|
|
orientStr = "V"
|
|
|
|
if sizeType != nil && sizeType.Enum == "QSizePolicy::Fixed" {
|
|
fixedStr = "Fixed"
|
|
secondParamStr = fmt.Sprintf(", %d", sizeHint.Size.Height)
|
|
}
|
|
}
|
|
|
|
if layout == "" {
|
|
buf.WriteString(fmt.Sprintf(
|
|
`
|
|
// anonymous spacer
|
|
if _, err := walk.New%sSpacer%s(%s%s); err != nil {
|
|
return err
|
|
}
|
|
`,
|
|
orientStr, fixedStr, qualifiedParent, secondParamStr))
|
|
} else {
|
|
buf.WriteString(fmt.Sprintf(
|
|
`
|
|
// %s
|
|
%s, err := walk.New%sSpacer%s(%s%s)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
`,
|
|
itemName, itemName, orientStr, fixedStr, qualifiedParent, secondParamStr))
|
|
}
|
|
}
|
|
|
|
if item.Widget != nil && !item.Widget.ignored {
|
|
itemName = fmt.Sprintf("w.ui.%s", item.Widget.Name)
|
|
if err := writeWidgetInitialization(buf, item.Widget, parent, qualifiedParent); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if layout != "" && itemName != "" && item.Row != "" && item.Column != "" {
|
|
if item.ColSpan == "" {
|
|
item.ColSpan = "1"
|
|
}
|
|
if item.RowSpan == "" {
|
|
item.RowSpan = "1"
|
|
}
|
|
buf.WriteString(fmt.Sprintf(
|
|
` if err := %s.SetRange(%s, walk.Rectangle{%s, %s, %s, %s}); err != nil {
|
|
return err
|
|
}
|
|
`,
|
|
layout, itemName, item.Column, item.Row, item.ColSpan, item.RowSpan))
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func writeLayoutInitialization(buf *bytes.Buffer, layout *Layout, parent *Widget, qualifiedParent string) error {
|
|
var typ string
|
|
switch layout.Class {
|
|
case "QGridLayout":
|
|
typ = "GridLayout"
|
|
|
|
case "QHBoxLayout":
|
|
typ = "HBoxLayout"
|
|
|
|
case "QVBoxLayout":
|
|
typ = "VBoxLayout"
|
|
|
|
default:
|
|
return errors.New(fmt.Sprintf("unsupported layout type: '%s'", layout.Class))
|
|
}
|
|
|
|
buf.WriteString(fmt.Sprintf("%s := walk.New%s()\n",
|
|
layout.Name, typ))
|
|
|
|
buf.WriteString(fmt.Sprintf(
|
|
`if err := %s.SetLayout(%s); err != nil {
|
|
return err
|
|
}
|
|
`,
|
|
qualifiedParent, layout.Name))
|
|
|
|
spacing := 6
|
|
margL, margT, margR, margB := 9, 9, 9, 9
|
|
|
|
for _, prop := range layout.Property {
|
|
switch prop.Name {
|
|
case "spacing":
|
|
spacing = int(prop.Number)
|
|
|
|
case "leftMargin":
|
|
margL = int(prop.Number)
|
|
|
|
case "topMargin":
|
|
margT = int(prop.Number)
|
|
|
|
case "rightMargin":
|
|
margR = int(prop.Number)
|
|
|
|
case "bottomMargin":
|
|
margB = int(prop.Number)
|
|
|
|
case "margin":
|
|
m := int(prop.Number)
|
|
margL, margT, margR, margB = m, m, m, m
|
|
}
|
|
}
|
|
|
|
if margL != 0 || margT != 0 || margR != 0 || margB != 0 {
|
|
buf.WriteString(fmt.Sprintf(
|
|
`if err := %s.SetMargins(walk.Margins{%d, %d, %d, %d}); err != nil {
|
|
return err
|
|
}
|
|
`,
|
|
layout.Name, margL, margT, margR, margB))
|
|
}
|
|
|
|
if spacing != 0 {
|
|
buf.WriteString(fmt.Sprintf(
|
|
`if err := %s.SetSpacing(%d); err != nil {
|
|
return err
|
|
}
|
|
`,
|
|
layout.Name, spacing))
|
|
}
|
|
|
|
var layoutName string
|
|
if typ == "GridLayout" {
|
|
layoutName = layout.Name
|
|
}
|
|
|
|
if err := writeItemInitializations(buf, layout.Item, parent, qualifiedParent, layoutName); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func writeWidgetInitialization(buf *bytes.Buffer, widget *Widget, parent *Widget, qualifiedParent string) error {
|
|
receiver := fmt.Sprintf("w.ui.%s", widget.Name)
|
|
|
|
var typ string
|
|
var custom bool
|
|
switch widget.Class {
|
|
case "QCheckBox":
|
|
typ = "CheckBox"
|
|
|
|
case "QComboBox":
|
|
typ = "ComboBox"
|
|
|
|
case "QDateEdit":
|
|
typ = "DateEdit"
|
|
|
|
case "QDoubleSpinBox", "QSpinBox":
|
|
typ = "NumberEdit"
|
|
|
|
case "QFrame":
|
|
typ = "Composite"
|
|
|
|
case "QGroupBox":
|
|
typ = "GroupBox"
|
|
|
|
case "QLabel":
|
|
typ = "Label"
|
|
|
|
case "QLineEdit":
|
|
typ = "LineEdit"
|
|
|
|
case "QPlainTextEdit", "QTextEdit":
|
|
typ = "TextEdit"
|
|
|
|
case "QProgressBar":
|
|
typ = "ProgressBar"
|
|
|
|
case "QPushButton":
|
|
typ = "PushButton"
|
|
|
|
case "QRadioButton":
|
|
typ = "RadioButton"
|
|
|
|
case "QSplitter":
|
|
typ = "Splitter"
|
|
|
|
case "QTabWidget":
|
|
typ = "TabWidget"
|
|
|
|
case "QTableView", "QTableWidget":
|
|
typ = "TableView"
|
|
|
|
case "QToolButton":
|
|
typ = "ToolButton"
|
|
|
|
case "QTreeView", "QTreeWidget":
|
|
typ = "TreeView"
|
|
|
|
case "QWebView":
|
|
typ = "WebView"
|
|
|
|
case "QWidget":
|
|
if parent != nil && parent.Class == "QTabWidget" {
|
|
typ = "TabPage"
|
|
} else {
|
|
typ = "Composite"
|
|
}
|
|
|
|
default:
|
|
// FIXME: We assume this is a custom widget in the same package.
|
|
// We also require a func NewFoo(parent) (*Foo, error).
|
|
typ = widget.Class
|
|
custom = true
|
|
}
|
|
|
|
if custom {
|
|
buf.WriteString(fmt.Sprintf(
|
|
`
|
|
// %s
|
|
if %s, err = New%s(%s); err != nil {
|
|
return err
|
|
}
|
|
`,
|
|
widget.Name, receiver, typ, qualifiedParent))
|
|
} else {
|
|
if typ == "TabPage" {
|
|
buf.WriteString(fmt.Sprintf(
|
|
`
|
|
// %s
|
|
if %s, err = walk.NewTabPage(); err != nil {
|
|
return err
|
|
}
|
|
`,
|
|
widget.Name, receiver))
|
|
} else {
|
|
buf.WriteString(fmt.Sprintf(
|
|
`
|
|
// %s
|
|
if %s, err = walk.New%s(%s); err != nil {
|
|
return err
|
|
}
|
|
`,
|
|
widget.Name, receiver, typ, qualifiedParent))
|
|
}
|
|
}
|
|
|
|
buf.WriteString(fmt.Sprintf("%s.SetName(\"%s\")\n",
|
|
receiver, widget.Name))
|
|
|
|
if err := writeAttributes(buf, widget.Attribute, receiver); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := writeProperties(buf, widget.Property, receiver, widget); err != nil {
|
|
return err
|
|
}
|
|
|
|
if widget.Layout != nil && !widget.Layout.ignored {
|
|
if err := writeLayoutInitialization(buf, widget.Layout, widget, receiver); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if typ == "TabPage" {
|
|
buf.WriteString(fmt.Sprintf(
|
|
`if err := %s.Pages().Add(%s); err != nil {
|
|
return err
|
|
}
|
|
`,
|
|
qualifiedParent, receiver))
|
|
}
|
|
|
|
return writeWidgetInitializations(buf, widget.Widget, widget, receiver)
|
|
}
|
|
|
|
func writeWidgetInitializations(buf *bytes.Buffer, widgets []*Widget, parent *Widget, qualifiedParent string) error {
|
|
for _, widget := range widgets {
|
|
if widget.ignored || widget.Class == "QMenuBar" || widget.Class == "QStatusBar" {
|
|
continue
|
|
}
|
|
|
|
if err := writeWidgetInitialization(buf, widget, parent, qualifiedParent); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func writeWidgetDecl(buf *bytes.Buffer, widget *Widget, parent *Widget) error {
|
|
var typ string
|
|
switch widget.Class {
|
|
case "QCheckBox":
|
|
typ = "walk.CheckBox"
|
|
|
|
case "QComboBox":
|
|
typ = "walk.ComboBox"
|
|
|
|
case "QDateEdit":
|
|
typ = "walk.DateEdit"
|
|
|
|
case "QDoubleSpinBox", "QSpinBox":
|
|
typ = "walk.NumberEdit"
|
|
|
|
case "QFrame":
|
|
typ = "walk.Composite"
|
|
|
|
case "QGroupBox":
|
|
typ = "walk.GroupBox"
|
|
|
|
case "QLabel":
|
|
typ = "walk.Label"
|
|
|
|
case "QLineEdit":
|
|
typ = "walk.LineEdit"
|
|
|
|
case "QPlainTextEdit", "QTextEdit":
|
|
typ = "walk.TextEdit"
|
|
|
|
case "QProgressBar":
|
|
typ = "walk.ProgressBar"
|
|
|
|
case "QPushButton":
|
|
typ = "walk.PushButton"
|
|
|
|
case "QRadioButton":
|
|
typ = "walk.RadioButton"
|
|
|
|
case "QSplitter":
|
|
typ = "walk.Splitter"
|
|
|
|
case "QTabWidget":
|
|
typ = "walk.TabWidget"
|
|
|
|
case "QTableView", "QTableWidget":
|
|
typ = "walk.TableView"
|
|
|
|
case "QToolButton":
|
|
typ = "walk.ToolButton"
|
|
|
|
case "QTreeView", "QTreeWidget":
|
|
typ = "walk.TreeView"
|
|
|
|
case "QWebView":
|
|
typ = "walk.WebView"
|
|
|
|
case "QWidget":
|
|
if parent != nil && parent.Class == "QTabWidget" {
|
|
typ = "walk.TabPage"
|
|
} else {
|
|
typ = "walk.Composite"
|
|
}
|
|
|
|
default:
|
|
// FIXME: For now, we assume this is a custom widget in the same package
|
|
typ = widget.Class
|
|
}
|
|
|
|
buf.WriteString(fmt.Sprintf("%s *%s\n", widget.Name, typ))
|
|
|
|
if widget.Layout != nil {
|
|
return writeItemDecls(buf, widget.Layout.Item, widget)
|
|
}
|
|
|
|
return writeWidgetDecls(buf, widget.Widget, widget)
|
|
}
|
|
|
|
func writeWidgetDecls(buf *bytes.Buffer, widgets []*Widget, parent *Widget) error {
|
|
for _, widget := range widgets {
|
|
switch widget.Class {
|
|
case "QMenuBar", "QStatusBar":
|
|
continue
|
|
}
|
|
|
|
if err := writeWidgetDecl(buf, widget, parent); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func writeItemDecls(buf *bytes.Buffer, items []*Item, parent *Widget) error {
|
|
for _, item := range items {
|
|
if item.Widget == nil {
|
|
continue
|
|
}
|
|
|
|
if err := writeWidgetDecl(buf, item.Widget, parent); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func writeActionDecl(buf *bytes.Buffer, action *Action) error {
|
|
buf.WriteString(action.Name)
|
|
buf.WriteString(" *walk.Action\n")
|
|
return nil
|
|
}
|
|
|
|
func writeActionDecls(buf *bytes.Buffer, actions []*Action) error {
|
|
for _, action := range actions {
|
|
if err := writeActionDecl(buf, action); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func writeMenuInitialization(buf *bytes.Buffer, menu *Widget, realActions map[string]bool) error {
|
|
var qualifiedParentMenu string
|
|
|
|
if menu.Class == "QMenuBar" {
|
|
buf.WriteString("// Menus\n\n")
|
|
|
|
qualifiedParentMenu = "w.Menu()"
|
|
} else {
|
|
qualifiedParentMenu = menu.Name
|
|
}
|
|
|
|
for _, addAction := range menu.AddAction {
|
|
if realActions[addAction.Name] {
|
|
buf.WriteString("if err := ")
|
|
buf.WriteString(qualifiedParentMenu)
|
|
buf.WriteString(".Actions().Add(w.ui.actions.")
|
|
buf.WriteString(addAction.Name)
|
|
buf.WriteString(`); err != nil {
|
|
return err
|
|
}
|
|
|
|
`)
|
|
} else {
|
|
for _, submenu := range menu.Widget {
|
|
if submenu.Name != addAction.Name {
|
|
continue
|
|
}
|
|
|
|
buf.WriteString("// ")
|
|
buf.WriteString(submenu.Name)
|
|
buf.WriteString("\n")
|
|
|
|
buf.WriteString(submenu.Name)
|
|
buf.WriteString(`, err := walk.NewMenu()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
`)
|
|
submenuActionName := submenu.Name + "Action"
|
|
|
|
buf.WriteString(submenuActionName)
|
|
buf.WriteString(", err := ")
|
|
buf.WriteString(qualifiedParentMenu)
|
|
buf.WriteString(".Actions().AddMenu(")
|
|
buf.WriteString(submenu.Name)
|
|
buf.WriteString(`)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
`)
|
|
|
|
for _, prop := range submenu.Property {
|
|
if prop.Name == "title" {
|
|
buf.WriteString("if err := ")
|
|
buf.WriteString(submenuActionName)
|
|
buf.WriteString(".SetText(")
|
|
buf.WriteString(trString(&prop.String))
|
|
buf.WriteString(`); err != nil {
|
|
return err
|
|
}
|
|
|
|
`)
|
|
break
|
|
}
|
|
}
|
|
|
|
if err := writeMenuInitialization(buf, submenu, realActions); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func writeActionInitializations(buf *bytes.Buffer, actions []*Action) error {
|
|
buf.WriteString("\n// Actions\n\n")
|
|
|
|
for _, action := range actions {
|
|
qualifiedReceiver := "w.ui.actions." + action.Name
|
|
|
|
buf.WriteString("// ")
|
|
buf.WriteString(qualifiedReceiver)
|
|
buf.WriteString("\n")
|
|
|
|
buf.WriteString(qualifiedReceiver)
|
|
buf.WriteString(" = walk.NewAction()\n")
|
|
|
|
if err := writeProperties(buf, action.Property, qualifiedReceiver, nil); err != nil {
|
|
return err
|
|
}
|
|
|
|
buf.WriteString("\n")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func generateUICode(buf *bytes.Buffer, ui *UI) error {
|
|
// Comment, package decl, imports
|
|
buf.WriteString(fmt.Sprintf(
|
|
`// This file was created by ui2walk and may be regenerated.
|
|
// DO NOT EDIT OR YOUR MODIFICATIONS WILL BE LOST!
|
|
|
|
//+build windows
|
|
|
|
package %s
|
|
|
|
import (
|
|
"github.com/lxn/walk"
|
|
)
|
|
|
|
`, *packageName))
|
|
|
|
// Embed the corresponding Walk type.
|
|
var embeddedType string
|
|
switch ui.Widget.Class {
|
|
case "QMainWindow":
|
|
embeddedType = "MainWindow"
|
|
|
|
case "QDialog":
|
|
embeddedType = "Dialog"
|
|
|
|
case "QWidget":
|
|
embeddedType = "Composite"
|
|
|
|
default:
|
|
return errors.New(fmt.Sprintf("Top level '%s' currently not supported.", ui.Widget.Class))
|
|
}
|
|
|
|
genTypeBaseName := strings.ToLower(ui.Class[:1]) + ui.Class[1:]
|
|
|
|
if len(ui.Widget.Action) > 0 {
|
|
// This struct will contain actions.
|
|
buf.WriteString(fmt.Sprintf("type %sActions struct {\n", genTypeBaseName))
|
|
|
|
writeActionDecls(buf, ui.Widget.Action)
|
|
|
|
buf.WriteString("}\n\n")
|
|
}
|
|
|
|
// Struct containing all descendant widgets.
|
|
buf.WriteString(fmt.Sprintf("type %sUI struct {\n", genTypeBaseName))
|
|
|
|
if len(ui.Widget.Action) > 0 {
|
|
buf.WriteString(fmt.Sprintf("actions %sActions\n", genTypeBaseName))
|
|
}
|
|
|
|
// Descendant widget decls
|
|
if ui.Widget.Widget != nil {
|
|
if err := writeWidgetDecls(buf, ui.Widget.Widget, &ui.Widget); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if ui.Widget.Layout != nil {
|
|
if err := writeItemDecls(buf, ui.Widget.Layout.Item, &ui.Widget); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// end struct
|
|
buf.WriteString("}\n\n")
|
|
|
|
// init func
|
|
switch embeddedType {
|
|
case "MainWindow":
|
|
buf.WriteString(fmt.Sprintf(
|
|
`func (w *%s) init() (err error) {
|
|
if w.MainWindow, err = walk.NewMainWindow()`,
|
|
ui.Widget.Name))
|
|
|
|
case "Dialog":
|
|
buf.WriteString(fmt.Sprintf(
|
|
`func (w *%s) init(owner walk.Form) (err error) {
|
|
if w.Dialog, err = walk.NewDialog(owner)`,
|
|
ui.Widget.Name))
|
|
|
|
case "Composite":
|
|
buf.WriteString(fmt.Sprintf(
|
|
`func (w *%s) init(parent walk.Container) (err error) {
|
|
if w.Composite, err = walk.NewComposite(parent)`,
|
|
ui.Widget.Name))
|
|
}
|
|
|
|
buf.WriteString(fmt.Sprintf(`; err != nil {
|
|
return err
|
|
}
|
|
|
|
succeeded := false
|
|
defer func(){
|
|
if !succeeded {
|
|
w.Dispose()
|
|
}
|
|
}()
|
|
|
|
var font *walk.Font
|
|
if font == nil {
|
|
font = nil
|
|
}
|
|
|
|
w.SetName("%s")
|
|
`,
|
|
ui.Widget.Name))
|
|
|
|
if embeddedType == "MainWindow" {
|
|
buf.WriteString(fmt.Sprintf(
|
|
`l := walk.NewVBoxLayout()
|
|
if err := l.SetMargins(walk.Margins{0, 0, 0, 0}); err != nil {
|
|
return err
|
|
}
|
|
if err := w.SetLayout(l); err != nil {
|
|
return err
|
|
}
|
|
`))
|
|
}
|
|
|
|
if err := writeProperties(buf, ui.Widget.Property, "w", &ui.Widget); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Let's see if we find a QMenuBar widget.
|
|
var menuBar *Widget
|
|
for _, widget := range ui.Widget.Widget {
|
|
if widget.Class == "QMenuBar" {
|
|
menuBar = widget
|
|
break
|
|
}
|
|
}
|
|
|
|
if len(ui.Widget.Action) > 0 {
|
|
writeActionInitializations(buf, ui.Widget.Action)
|
|
|
|
if menuBar != nil {
|
|
realActions := make(map[string]bool)
|
|
|
|
for _, action := range ui.Widget.Action {
|
|
realActions[action.Name] = true
|
|
}
|
|
|
|
writeMenuInitialization(buf, menuBar, realActions)
|
|
}
|
|
}
|
|
|
|
if ui.Widget.Widget != nil {
|
|
if err := writeWidgetInitializations(buf, ui.Widget.Widget, &ui.Widget, "w"); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if ui.Widget.Layout != nil {
|
|
if err := writeLayoutInitialization(buf, ui.Widget.Layout, &ui.Widget, "w"); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
buf.WriteString("\n// Tab order\n")
|
|
for i := len(ui.TabStops) - 1; i >= 0; i-- {
|
|
buf.WriteString(fmt.Sprintf(`if err = w.ui.%s.BringToTop(); err != nil {
|
|
return err
|
|
}
|
|
`,
|
|
ui.TabStops[i]))
|
|
}
|
|
|
|
// end func
|
|
buf.WriteString(`
|
|
succeeded = true
|
|
|
|
return nil
|
|
}`)
|
|
|
|
return nil
|
|
}
|
|
|
|
func generateLogicCode(buf *bytes.Buffer, ui *UI) error {
|
|
// Comment, package decl, imports
|
|
buf.WriteString(
|
|
`//+build windows
|
|
|
|
package main
|
|
|
|
import (
|
|
"github.com/lxn/walk"
|
|
)
|
|
|
|
`)
|
|
|
|
// Embed the corresponding Walk type.
|
|
var embeddedType string
|
|
switch ui.Widget.Class {
|
|
case "QMainWindow":
|
|
embeddedType = "MainWindow"
|
|
|
|
case "QDialog":
|
|
embeddedType = "Dialog"
|
|
|
|
case "QWidget":
|
|
embeddedType = "Composite"
|
|
|
|
default:
|
|
return errors.New(fmt.Sprintf("Top level '%s' currently not supported.", ui.Widget.Class))
|
|
}
|
|
|
|
buf.WriteString("type ")
|
|
buf.WriteString(ui.Widget.Name)
|
|
buf.WriteString(" struct {\n*walk.")
|
|
buf.WriteString(embeddedType)
|
|
buf.WriteString("\nui ")
|
|
buf.WriteString(strings.ToLower(ui.Class[:1]) + ui.Class[1:])
|
|
buf.WriteString(`UI
|
|
}
|
|
|
|
`)
|
|
|
|
switch embeddedType {
|
|
case "MainWindow":
|
|
buf.WriteString("func run")
|
|
buf.WriteString(ui.Widget.Name)
|
|
buf.WriteString(`() (int, error) {
|
|
mw := new(`)
|
|
buf.WriteString(ui.Widget.Name)
|
|
buf.WriteString(`)
|
|
if err := mw.init(); err != nil {
|
|
return 0, err
|
|
}
|
|
defer mw.Dispose()
|
|
|
|
// TODO: Do further required setup, e.g. for event handling, here.
|
|
|
|
mw.Show()
|
|
|
|
return mw.Run(), nil
|
|
}
|
|
`)
|
|
|
|
case "Dialog":
|
|
buf.WriteString("func run")
|
|
buf.WriteString(ui.Widget.Name)
|
|
buf.WriteString(`(owner walk.Form) (int, error) {
|
|
dlg := new(`)
|
|
buf.WriteString(ui.Widget.Name)
|
|
buf.WriteString(`)
|
|
if err := dlg.init(owner); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
`)
|
|
|
|
if b := findWidget(&ui.Widget, "QPushButton", []string{"accept", "ok"}); b != nil {
|
|
buf.WriteString("if err := dlg.SetDefaultButton(dlg.ui.")
|
|
buf.WriteString(b.Name)
|
|
buf.WriteString(`); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
dlg.ui.`)
|
|
buf.WriteString(b.Name)
|
|
buf.WriteString(`.Clicked().Attach(func(){
|
|
dlg.Accept()
|
|
})
|
|
|
|
`)
|
|
}
|
|
|
|
if b := findWidget(&ui.Widget, "QPushButton", []string{"cancel"}); b != nil {
|
|
buf.WriteString("if err := dlg.SetCancelButton(dlg.ui.")
|
|
buf.WriteString(b.Name)
|
|
buf.WriteString(`); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
dlg.ui.`)
|
|
buf.WriteString(b.Name)
|
|
buf.WriteString(`.Clicked().Attach(func(){
|
|
dlg.Cancel()
|
|
})
|
|
|
|
`)
|
|
}
|
|
|
|
buf.WriteString(`// TODO: Do further required setup, e.g. for event handling, here.
|
|
|
|
return dlg.Run(), nil
|
|
}
|
|
`)
|
|
|
|
case "Composite":
|
|
buf.WriteString("func new")
|
|
buf.WriteString(ui.Widget.Name)
|
|
buf.WriteString("(parent walk.Container) (*")
|
|
buf.WriteString(ui.Widget.Name)
|
|
buf.WriteString(`, error) {
|
|
c := new(`)
|
|
buf.WriteString(ui.Widget.Name)
|
|
buf.WriteString(`)
|
|
if err := c.init(parent); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// TODO: Do further required setup, e.g. for event handling, here.
|
|
|
|
return c, nil
|
|
}
|
|
`)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func findWidget(parent *Widget, class string, nameSubstrs []string) *Widget {
|
|
find := func(widget *Widget) *Widget {
|
|
if widget.Class == class {
|
|
for _, substr := range nameSubstrs {
|
|
if strings.Contains(widget.Name, substr) {
|
|
return widget
|
|
}
|
|
}
|
|
}
|
|
|
|
if w := findWidget(widget, class, nameSubstrs); w != nil {
|
|
return w
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
for _, widget := range parent.Widget {
|
|
if w := find(widget); w != nil {
|
|
return w
|
|
}
|
|
}
|
|
|
|
if parent.Layout != nil {
|
|
for _, item := range parent.Layout.Item {
|
|
if item.Widget != nil {
|
|
if w := find(item.Widget); w != nil {
|
|
return w
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func processFile(uiFilePath string) error {
|
|
goLogicFilePath := uiFilePath[:len(uiFilePath)-3] + ".go"
|
|
goUIFilePath := uiFilePath[:len(uiFilePath)-3] + "_ui.go"
|
|
|
|
uiFileInfo, err := os.Stat(uiFilePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
goUIFileInfo, err := os.Stat(goUIFilePath)
|
|
if !*forceUpdate && err == nil && !uiFileInfo.ModTime().After(goUIFileInfo.ModTime()) {
|
|
// The go file should be up-to-date
|
|
return nil
|
|
}
|
|
|
|
fmt.Printf("Processing '%s'\n", uiFilePath)
|
|
defer fmt.Println("")
|
|
|
|
uiFile, err := os.Open(uiFilePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer uiFile.Close()
|
|
|
|
reader := bufio.NewReader(uiFile)
|
|
|
|
ui, err := parseUI(reader)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
goLogicFile, err := os.OpenFile(goLogicFilePath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0666)
|
|
if err == nil {
|
|
defer goLogicFile.Close()
|
|
|
|
buf := new(bytes.Buffer)
|
|
|
|
if err := generateLogicCode(buf, ui); err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err := io.Copy(goLogicFile, buf); err != nil {
|
|
return err
|
|
}
|
|
if err := goLogicFile.Close(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
goUIFile, err := os.Create(goUIFilePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer goUIFile.Close()
|
|
|
|
buf := new(bytes.Buffer)
|
|
|
|
if err := generateUICode(buf, ui); err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err := io.Copy(goUIFile, buf); err != nil {
|
|
return err
|
|
}
|
|
if err := goUIFile.Close(); err != nil {
|
|
return err
|
|
}
|
|
|
|
dirPath := os.Getenv("GOBIN")
|
|
if dirPath == "" {
|
|
dirPath = filepath.Join(runtime.GOROOT(), "bin")
|
|
}
|
|
|
|
gofmtPath := filepath.Join(dirPath, "gofmt")
|
|
|
|
if runtime.GOOS == "windows" {
|
|
gofmtPath += ".exe"
|
|
}
|
|
|
|
args := []string{gofmtPath, "-w", goUIFilePath}
|
|
if goLogicFile != nil {
|
|
args = append(args, goLogicFilePath)
|
|
}
|
|
|
|
gofmt, err := os.StartProcess(gofmtPath, args, &os.ProcAttr{Files: []*os.File{nil, nil, os.Stderr}})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer gofmt.Release()
|
|
|
|
return nil
|
|
}
|
|
|
|
func processDirectory(dirPath string) error {
|
|
dir, err := os.Open(dirPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer dir.Close()
|
|
|
|
names, err := dir.Readdirnames(-1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, name := range names {
|
|
fullPath := path.Join(dirPath, name)
|
|
|
|
fi, err := os.Stat(fullPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if fi.IsDir() {
|
|
if err := processDirectory(fullPath); err != nil {
|
|
return err
|
|
}
|
|
} else if !fi.IsDir() && strings.HasSuffix(name, ".ui") {
|
|
if err := processFile(fullPath); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func main() {
|
|
flag.Parse()
|
|
|
|
cwd, err := os.Getwd()
|
|
logFatal(err)
|
|
|
|
logFatal(processDirectory(cwd))
|
|
}
|