commit bb2f57592b23a9f4ddab6313d1c3c424d9a7616f Author: Carl Kittelberger Date: Fri May 6 20:32:40 2016 +0200 Initial commit (ui2walk modified). diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..9951f85 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,24 @@ +# This is the official list of 'Walk' authors for copyright purposes. + +# Names should be added to this file as +# Name or Organization +# The email address is not required for organizations. + +# Please keep the list sorted. + +# Contributors +# ============ + +Alexander Neumann +Attila Tajti +Benny Siegert +Cary Cherng +Hill +Joseph Watson +ktye +llxwj +Mateusz Czapliński +Michael Teichgräber +Shawn Sun +Tim Dufrane +Vincent Vanackere diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6eadba4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2010 The Walk Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/ui2walk.go b/ui2walk.go new file mode 100644 index 0000000..89a2a36 --- /dev/null +++ b/ui2walk.go @@ -0,0 +1,1374 @@ +// 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") + +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( + `// This file was created by ui2walk and may be regenerated. + // DO NOT EDIT OR YOUR MODIFICATIONS WILL BE LOST! + + //+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)) + } + + 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)) +}