ui2walk/ui2walk.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))
}