From d47f1e31698dede9a2604eda01fd881dfe1bb26c Mon Sep 17 00:00:00 2001 From: icza Date: Tue, 8 Mar 2016 17:42:24 +0100 Subject: [PATCH] Gowut 1.0.0 Release. --- README.md | 36 +- examples/doc.go | 6 +- examples/login/login_demo.go | 105 +-- examples/showcase/app.yaml | 8 + examples/showcase/showcase.go | 835 +---------------- .../showcase/showcasecore/showcase_gae.go | 36 + .../showcase/showcasecore/showcasecore.go | 867 ++++++++++++++++++ examples/simple/simple_demo.go | 38 +- gwu/button.go | 24 +- gwu/comp.go | 32 +- gwu/comp_addons.go | 102 +-- gwu/css.go | 20 +- gwu/doc.go | 54 +- gwu/event.go | 226 ++--- gwu/examples_test.go | 32 +- gwu/expander.go | 34 +- gwu/html.go | 16 +- gwu/id.go | 22 +- gwu/image.go | 22 +- gwu/js.go | 46 +- gwu/label.go | 16 +- gwu/link.go | 20 +- gwu/listbox.go | 50 +- gwu/panel.go | 102 +-- gwu/server.go | 250 ++--- gwu/server_start.go | 78 ++ gwu/server_start_gae.go | 44 + gwu/session.go | 46 +- gwu/state_buttons.go | 106 +-- gwu/style.go | 290 +++--- gwu/table.go | 73 +- gwu/tabpanel.go | 106 +-- gwu/textbox_pwbox.go | 96 +- gwu/timer.go | 44 +- gwu/window.go | 30 +- gwu/writer.go | 138 +-- 36 files changed, 2157 insertions(+), 1893 deletions(-) create mode 100644 examples/showcase/app.yaml create mode 100644 examples/showcase/showcasecore/showcase_gae.go create mode 100644 examples/showcase/showcasecore/showcasecore.go create mode 100644 gwu/server_start.go create mode 100644 gwu/server_start_gae.go diff --git a/README.md b/README.md index ab5d92e..5fd9424 100644 --- a/README.md +++ b/README.md @@ -2,44 +2,46 @@ Gowut (Go Web UI Toolkit) is a full-featured, easy to use, platform independent Web UI Toolkit written in pure Go, no platform dependent native code is linked or called. -This site is for the Gowut source code and download hosting and for issue tracking. +This project is for public releases and for issue tracking of Gowut. Development takes place in the [gowut.dev](https://github.com/icza/gowut.dev) project. For News, documentation and examples please visit the **Gowut Home Page** here: **https://sites.google.com/site/gowebuitoolkit/** -This project is for public releases of Gowut. Development takes place in the [gowut.dev project](https://github.com/icza/gowut.dev). - ## Quick install ## To quickly install (or update to) the **latest** version, type: -``` -go get -u github.com/icza/gowut/gwu -``` + + go get -u github.com/icza/gowut/gwu ## Quick test ## To quickly test it and see it in action, run the following example applications (assuming you're in the root of your GOPATH): -**1. Showcase of Features.** This one auto-opens itself in your default browser. -``` -go run src/github.com/icza/gowut/examples/showcase/showcase.go -``` +**1. Showcase of Features.** + +This one auto-opens itself in your default browser. + + go run src/github.com/icza/gowut/examples/showcase/showcase.go + +The Showcase of Features is also available live: https://gowut-demo.appspot.com/show -**2. A single window example.** This one auto-opens itself in your default browser. -``` -go run src/github.com/icza/gowut/examples/simple/simple_demo.go -``` +**2. A single window example.** + +This one auto-opens itself in your default browser. + + go run src/github.com/icza/gowut/examples/simple/simple_demo.go **3. Login window example with session management.** -``` -go run src/github.com/icza/gowut/examples/login/login_demo.go -``` + + go run src/github.com/icza/gowut/examples/login/login_demo.go + Open the page http://localhost:3434/guitest/ in your browser to see it. ## Godoc of Gowut ## You can read the godoc of Gowut online here: + http://godoc.org/github.com/icza/gowut/gwu ## +1 / Star Gowut! ## diff --git a/examples/doc.go b/examples/doc.go index 871f270..e32da4b 100644 --- a/examples/doc.go +++ b/examples/doc.go @@ -1,15 +1,15 @@ // Copyright (C) 2013 Andras Belicza. All rights reserved. -// +// // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// +// // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program. If not, see . diff --git a/examples/login/login_demo.go b/examples/login/login_demo.go index 98e8660..eab44b9 100644 --- a/examples/login/login_demo.go +++ b/examples/login/login_demo.go @@ -1,15 +1,15 @@ // Copyright (C) 2013 Andras Belicza. All rights reserved. -// +// // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// +// // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program. If not, see . @@ -18,8 +18,8 @@ package main import ( - "github.com/icza/gowut/gwu" "fmt" + "github.com/icza/gowut/gwu" "log" "math/rand" "os" @@ -37,7 +37,7 @@ func (h *MyButtonHandler) HandleEvent(e gwu.Event) { if b, isButton := e.Src().(gwu.Button); isButton { b.SetText(b.Text() + h.text) h.counter++ - b.SetToolTip("You've clicked " + strconv.Itoa(h.counter) + " times!") + b.SetToolTip(fmt.Sprintf("You've clicked %d times!", h.counter)) e.MarkDirty(b) } } @@ -56,7 +56,7 @@ func (h *GreenHandler) HandleEvent(e gwu.Event) { } if state { - src.Style().SetBackground(gwu.CLR_GREEN) + src.Style().SetBackground(gwu.ClrGreen) } else { src.Style().SetBackground("") } @@ -73,7 +73,7 @@ func buildPrivateWins(s gwu.Session) { win.SetCellPadding(2) p := gwu.NewPanel() - p.SetLayout(gwu.LAYOUT_HORIZONTAL) + p.SetLayout(gwu.LayoutHorizontal) p.SetCellPadding(2) p.Add(gwu.NewLabel("I'm a label! Try clicking on the button=>")) p.Add(gwu.NewLink("Google Home", "https://google.com")) @@ -82,32 +82,32 @@ func buildPrivateWins(s gwu.Session) { p.Add(img) win.Add(p) button := gwu.NewButton("Click me") - button.AddEHandler(&MyButtonHandler{text: ":-)"}, gwu.ETYPE_CLICK) + button.AddEHandler(&MyButtonHandler{text: ":-)"}, gwu.ETypeClick) win.Add(button) extraBtns := gwu.NewPanel() - extraBtns.SetLayout(gwu.LAYOUT_NATURAL) + extraBtns.SetLayout(gwu.LayoutNatural) button.AddEHandlerFunc(func(e gwu.Event) { - extraBtn := gwu.NewButton("Extra #" + strconv.Itoa(extraBtns.CompsCount())) + extraBtn := gwu.NewButton(fmt.Sprintf("Extra #%d", extraBtns.CompsCount())) extraBtn.AddEHandlerFunc(func(e gwu.Event) { extraBtn.Parent().Remove(extraBtn) e.MarkDirty(extraBtns) - }, gwu.ETYPE_CLICK) + }, gwu.ETypeClick) extraBtns.Insert(extraBtn, 0) e.MarkDirty(extraBtns) - }, gwu.ETYPE_CLICK) + }, gwu.ETypeClick) win.Add(extraBtns) p = gwu.NewPanel() - p.SetLayout(gwu.LAYOUT_HORIZONTAL) + p.SetLayout(gwu.LayoutHorizontal) p.SetCellPadding(2) - p.Style().SetBorder2(1, gwu.BRD_STYLE_SOLID, gwu.CLR_BLACK) + p.Style().SetBorder2(1, gwu.BrdStyleSolid, gwu.ClrBlack) p.Add(gwu.NewLabel("A drop-down list being")) wideListBox := gwu.NewListBox([]string{"50", "100", "150", "200", "250"}) wideListBox.Style().SetWidth("50") wideListBox.AddEHandlerFunc(func(e gwu.Event) { wideListBox.Style().SetWidth(wideListBox.SelectedValue() + "px") e.MarkDirty(wideListBox) - }, gwu.ETYPE_CHANGE) + }, gwu.ETypeChange) p.Add(wideListBox) p.Add(gwu.NewLabel("pixel wide. And a multi-select list:")) listBox := gwu.NewListBox([]string{"First", "Second", "Third", "Forth", "Fifth", "Sixth"}) @@ -117,27 +117,27 @@ func buildPrivateWins(s gwu.Session) { countLabel := gwu.NewLabel("Selected count: 0") listBox.AddEHandlerFunc(func(e gwu.Event) { selCount := len(listBox.SelectedIndices()) - countLabel.SetText("Selected count: " + strconv.Itoa(selCount)) + countLabel.SetText(fmt.Sprintf("Selected count: %d", selCount)) e.MarkDirty(countLabel) - }, gwu.ETYPE_CHANGE) + }, gwu.ETypeChange) p.Add(countLabel) win.Add(p) greenCheckBox := gwu.NewCheckBox("I'm a check box. When checked, I'm green!") greenCheckBox.AddEHandlerFunc(func(e gwu.Event) { if greenCheckBox.State() { - greenCheckBox.Style().SetBackground(gwu.CLR_GREEN) + greenCheckBox.Style().SetBackground(gwu.ClrGreen) } else { greenCheckBox.Style().SetBackground("") } e.MarkDirty(greenCheckBox) - }, gwu.ETYPE_CLICK) - greenCheckBox.AddEHandler(greenHandler, gwu.ETYPE_CLICK) + }, gwu.ETypeClick) + greenCheckBox.AddEHandler(greenHandler, gwu.ETypeClick) win.Add(greenCheckBox) table := gwu.NewTable() table.SetCellPadding(2) - table.Style().SetBorder2(1, gwu.BRD_STYLE_SOLID, gwu.CLR_BLACK) + table.Style().SetBorder2(1, gwu.BrdStyleSolid, gwu.ClrBlack) table.EnsureSize(2, 4) table.Add(gwu.NewLabel("TAB-"), 0, 0) table.Add(gwu.NewLabel("LE"), 0, 1) @@ -145,69 +145,69 @@ func buildPrivateWins(s gwu.Session) { table.Add(gwu.NewLabel("MO"), 0, 3) table.Add(gwu.NewLabel("Enter your name:"), 1, 0) tb := gwu.NewTextBox("") - tb.AddSyncOnETypes(gwu.ETYPE_KEY_UP) + tb.AddSyncOnETypes(gwu.ETypeKeyUp) table.Add(tb, 1, 1) table.Add(gwu.NewLabel("You entered:"), 1, 2) nameLabel := gwu.NewLabel("") - nameLabel.Style().SetColor(gwu.CLR_RED) + nameLabel.Style().SetColor(gwu.ClrRed) tb.AddEHandlerFunc(func(e gwu.Event) { nameLabel.SetText(tb.Text()) e.MarkDirty(nameLabel) - }, gwu.ETYPE_CHANGE, gwu.ETYPE_KEY_UP) + }, gwu.ETypeChange, gwu.ETypeKeyUp) table.Add(nameLabel, 1, 3) win.Add(table) table = gwu.NewTable() - table.Style().SetBorder2(1, gwu.BRD_STYLE_SOLID, gwu.CLR_BLACK) - table.SetAlign(gwu.HA_RIGHT, gwu.VA_TOP) + table.Style().SetBorder2(1, gwu.BrdStyleSolid, gwu.ClrBlack) + table.SetAlign(gwu.HARight, gwu.VATop) table.EnsureSize(5, 5) for row := 0; row < 5; row++ { group := gwu.NewRadioGroup(strconv.Itoa(row)) for col := 0; col < 5; col++ { - radio := gwu.NewRadioButton("= "+strconv.Itoa(col)+" =", group) + radio := gwu.NewRadioButton(fmt.Sprintf("= %d =", col), group) radio.AddEHandlerFunc(func(e gwu.Event) { radios := []gwu.RadioButton{radio, radio.Group().PrevSelected()} for _, radio := range radios { if radio != nil { if radio.State() { - radio.Style().SetBackground(gwu.CLR_GREEN) + radio.Style().SetBackground(gwu.ClrGreen) } else { radio.Style().SetBackground("") } e.MarkDirty(radio) } } - }, gwu.ETYPE_CLICK) + }, gwu.ETypeClick) table.Add(radio, row, col) } } table.SetColSpan(2, 1, 2) table.SetRowSpan(3, 1, 2) table.CellFmt(2, 2).Style().SetSizePx(150, 80) - table.CellFmt(2, 2).SetAlign(gwu.HA_RIGHT, gwu.VA_BOTTOM) + table.CellFmt(2, 2).SetAlign(gwu.HARight, gwu.VABottom) table.RowFmt(2).Style().SetBackground("#808080") - table.RowFmt(2).SetAlign(gwu.HA_DEFAULT, gwu.VA_MIDDLE) + table.RowFmt(2).SetAlign(gwu.HADefault, gwu.VAMiddle) table.RowFmt(3).Style().SetBackground("#d0d0d0") table.RowFmt(4).Style().SetBackground("#b0b0b0") win.Add(table) tabPanel := gwu.NewTabPanel() - tabPanel.SetTabBarPlacement(gwu.TB_PLACEMENT_TOP) + tabPanel.SetTabBarPlacement(gwu.TbPlacementTop) for i := 0; i < 6; i++ { if i == 3 { img := gwu.NewImage("", "https://www.google.com/images/srpr/logo3w.png") img.Style().SetWidthPx(100) - tabPanel.Add(img, gwu.NewLabel("This is some long content, random="+strconv.Itoa(rand.Int()))) + tabPanel.Add(img, gwu.NewLabel(fmt.Sprintf("This is some long content, random=%d", rand.Int()))) continue } - tabPanel.AddString(strconv.Itoa(i)+". tab", gwu.NewLabel("This is some long content, random="+strconv.Itoa(rand.Int()))) + tabPanel.AddString(fmt.Sprintf("%d. tab", i), gwu.NewLabel(fmt.Sprintf("This is some long content, random=%d", rand.Int()))) } win.Add(tabPanel) tabPanel = gwu.NewTabPanel() - tabPanel.SetTabBarPlacement(gwu.TB_PLACEMENT_LEFT) - tabPanel.TabBarFmt().SetVAlign(gwu.VA_BOTTOM) + tabPanel.SetTabBarPlacement(gwu.TbPlacementLeft) + tabPanel.TabBarFmt().SetVAlign(gwu.VABottom) for i := 7; i < 11; i++ { - l := gwu.NewLabel("This is some long content, random=" + strconv.Itoa(rand.Int())) + l := gwu.NewLabel(fmt.Sprintf("This is some long content, random=%d", rand.Int())) if i == 9 { img := gwu.NewImage("", "https://www.google.com/images/srpr/logo3w.png") img.Style().SetWidthPx(100) @@ -215,7 +215,7 @@ func buildPrivateWins(s gwu.Session) { tabPanel.CellFmt(l).Style().SetSizePx(400, 400) continue } - tabPanel.AddString(strconv.Itoa(i)+". tab", l) + tabPanel.AddString(fmt.Sprintf("%d. tab", i), l) tabPanel.CellFmt(l).Style().SetSizePx(400, 400) } win.Add(tabPanel) @@ -226,7 +226,7 @@ func buildPrivateWins(s gwu.Session) { back := gwu.NewButton("Back") back.AddEHandlerFunc(func(e gwu.Event) { e.ReloadWin(win.Name()) - }, gwu.ETYPE_CLICK) + }, gwu.ETypeClick) win2.Add(back) s.AddWin(win2) } @@ -234,25 +234,25 @@ func buildPrivateWins(s gwu.Session) { func buildLoginWin(s gwu.Session) { win := gwu.NewWindow("login", "Login Window") win.Style().SetFullSize() - win.SetAlign(gwu.HA_CENTER, gwu.VA_MIDDLE) + win.SetAlign(gwu.HACenter, gwu.VAMiddle) p := gwu.NewPanel() - p.SetHAlign(gwu.HA_CENTER) + p.SetHAlign(gwu.HACenter) p.SetCellPadding(2) l := gwu.NewLabel("Test GUI Login Window") - l.Style().SetFontWeight(gwu.FONT_WEIGHT_BOLD).SetFontSize("150%") + l.Style().SetFontWeight(gwu.FontWeightBold).SetFontSize("150%") p.Add(l) l = gwu.NewLabel("Login") - l.Style().SetFontWeight(gwu.FONT_WEIGHT_BOLD).SetFontSize("130%") + l.Style().SetFontWeight(gwu.FontWeightBold).SetFontSize("130%") p.Add(l) - p.CellFmt(l).Style().SetBorder2(1, gwu.BRD_STYLE_DASHED, gwu.CLR_NAVY) + p.CellFmt(l).Style().SetBorder2(1, gwu.BrdStyleDashed, gwu.ClrNavy) l = gwu.NewLabel("user/pass: admin/a") - l.Style().SetFontSize("80%").SetFontStyle(gwu.FONT_STYLE_ITALIC) + l.Style().SetFontSize("80%").SetFontStyle(gwu.FontStyleItalic) p.Add(l) errL := gwu.NewLabel("") - errL.Style().SetColor(gwu.CLR_RED) + errL.Style().SetColor(gwu.ClrRed) p.Add(errL) table := gwu.NewTable() @@ -278,7 +278,7 @@ func buildLoginWin(s gwu.Session) { errL.SetText("Invalid user name or password!") e.MarkDirty(errL) } - }, gwu.ETYPE_CLICK) + }, gwu.ETypeClick) p.Add(b) l = gwu.NewLabel("") p.Add(l) @@ -288,7 +288,7 @@ func buildLoginWin(s gwu.Session) { win.SetFocusedCompId(tb.Id()) p = gwu.NewPanel() - p.SetLayout(gwu.LAYOUT_HORIZONTAL) + p.SetLayout(gwu.LayoutHorizontal) p.SetCellPadding(2) p.Add(gwu.NewLabel("Here's an ON/OFF switch which enables/disables the other one:")) sw := gwu.NewSwitchButton() @@ -303,7 +303,7 @@ func buildLoginWin(s gwu.Session) { sw.AddEHandlerFunc(func(e gwu.Event) { sw2.SetEnabled(sw.State()) e.MarkDirty(sw2) - }, gwu.ETYPE_CLICK) + }, gwu.ETypeClick) win.Add(p) s.AddWin(win) @@ -323,7 +323,8 @@ func (h SessHandler) Removed(s gwu.Session) { func main() { // Create GUI server //server := gwu.NewServer("guitest", "") - server := gwu.NewServerTLS("guitest", "", "test_tls/cert.pem", "test_tls/key.pem") + folder := "../../../../../../test_tls/" + server := gwu.NewServerTLS("guitest", "", folder+"cert.pem", folder+"key.pem") server.SetText("Test GUI Application") server.AddSessCreatorName("login", "Login Window") @@ -331,13 +332,13 @@ func main() { win := gwu.NewWindow("home", "Home Window") l := gwu.NewLabel("Home, sweet home of " + server.Text()) - l.Style().SetFontWeight(gwu.FONT_WEIGHT_BOLD).SetFontSize("130%") + l.Style().SetFontWeight(gwu.FontWeightBold).SetFontSize("130%") win.Add(l) win.Add(gwu.NewLabel("Click on the button to login:")) b := gwu.NewButton("Login") b.AddEHandlerFunc(func(e gwu.Event) { e.ReloadWin("login") - }, gwu.ETYPE_CLICK) + }, gwu.ETypeClick) win.Add(b) server.AddWin(win) diff --git a/examples/showcase/app.yaml b/examples/showcase/app.yaml new file mode 100644 index 0000000..a0e69fd --- /dev/null +++ b/examples/showcase/app.yaml @@ -0,0 +1,8 @@ +application: gowut-demo +version: 1 +runtime: go +api_version: go1 + +handlers: +- url: /.* + script: _go_app diff --git a/examples/showcase/showcase.go b/examples/showcase/showcase.go index a51b1e9..9ec3962 100644 --- a/examples/showcase/showcase.go +++ b/examples/showcase/showcase.go @@ -1,837 +1,35 @@ +// +build !appengine + // Copyright (C) 2013 Andras Belicza. All rights reserved. -// +// // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// +// // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program. If not, see . -// A Gowut "Showcase of Features" application. +// Contains the main function of the Gowut "Showcase of Features" demo. +// Separated because main() can't be defined on AppEngine. package main import ( - "github.com/icza/gowut/gwu" "fmt" + "github.com/icza/gowut/examples/showcase/showcasecore" + "log" "os" - "strconv" - "time" ) -// plural returns an empty string if i is equal to 1, -// "s" otherwise. -func plural(i int) string { - if i == 1 { - return "" - } - return "s" -} - -func buildHomeDemo(event gwu.Event) gwu.Comp { - p := gwu.NewPanel() - - p.Add(gwu.NewLabel("This app is written in and showcases Gowut version " + gwu.GOWUT_VERSION + ".")) - p.AddVSpace(20) - p.Add(gwu.NewLabel("Select components on the left side to see them in action.")) - - return p -} - -func buildExpanderDemo(event gwu.Event) gwu.Comp { - p := gwu.NewPanel() - - l := gwu.NewLabel("Click on the Expander's header.") - l.Style().SetColor(gwu.CLR_GREEN) - p.Add(l) - p.AddVSpace(5) - e := gwu.NewExpander() - e.SetHeader(gwu.NewLabel("I'm an Expander.")) - e.SetContent(gwu.NewLabel("I'm the content of the Expander.")) - p.Add(e) - e.AddEHandlerFunc(func(ev gwu.Event) { - if e.Expanded() { - l.SetText("You expanded it.") - } else { - l.SetText("You collapsed it.") - } - ev.MarkDirty(l) - }, gwu.ETYPE_STATE_CHANGE) - - p.AddVSpace(20) - var ee gwu.Expander - for i := 4; i >= 0; i-- { - e2 := gwu.NewExpander() - e2.SetHeader(gwu.NewLabel("I hide embedded expanders. #" + strconv.Itoa(i))) - if i == 4 { - e2.SetContent(gwu.NewLabel("No more.")) - } else { - e2.SetContent(ee) - } - ee = e2 - } - p.Add(ee) - - return p -} - -func buildLinkContainerDemo(event gwu.Event) gwu.Comp { - p := gwu.NewPanel() - - link := gwu.NewLink("An obvious link, to Google Home", "https://google.com/") - inside := gwu.NewPanel() - inside.Style().SetBorder2(1, gwu.BRD_STYLE_SOLID, gwu.CLR_GRAY) - inside.Add(gwu.NewLabel("Everything inside this box also links to Google!")) - inside.Add(gwu.NewButton("Me too!")) - link.SetComp(inside) - p.Add(link) - - return p -} - -func buildPanelDemo(event gwu.Event) gwu.Comp { - p := gwu.NewPanel() - - p.Add(gwu.NewLabel("Panel with horizontal layout:")) - h := gwu.NewHorizontalPanel() - for i := 1; i <= 5; i++ { - h.Add(gwu.NewButton("Button " + strconv.Itoa(i))) - } - p.Add(h) - - p.AddVSpace(20) - p.Add(gwu.NewLabel("Panel with vertical layout:")) - v := gwu.NewVerticalPanel() - for i := 1; i <= 5; i++ { - v.Add(gwu.NewButton("Button " + strconv.Itoa(i))) - } - p.Add(v) - - p.AddVSpace(20) - p.Add(gwu.NewLabel("Panel with natural layout:")) - n := gwu.NewNaturalPanel() - for i := 1; i <= 20; i++ { - n.Add(gwu.NewButton("LONG BUTTON " + strconv.Itoa(i))) - } - p.Add(n) - - return p -} - -func buildTableDemo(event gwu.Event) gwu.Comp { - p := gwu.NewPanel() - - l := gwu.NewLabel("Tip: Switch to the 'debug' theme (top right) to see cell borders.") - l.Style().SetColor(gwu.CLR_RED).SetFontStyle(gwu.FONT_STYLE_ITALIC) - p.Add(l) - - p.AddVSpace(20) - p.Add(gwu.NewLabel("A simple form aligned with a table:")) - p.AddVSpace(10) - t := gwu.NewTable() - t.SetCellPadding(2) - t.EnsureSize(2, 2) - var c gwu.Comp - t.Add(gwu.NewLabel("User name:"), 0, 0) - c = gwu.NewTextBox("") - c.Style().SetWidthPx(160) - t.Add(c, 0, 1) - t.Add(gwu.NewLabel("Password:"), 1, 0) - c = gwu.NewPasswBox("") - c.Style().SetWidthPx(160) - t.Add(c, 1, 1) - t.Add(gwu.NewLabel("Go to:"), 2, 0) - c = gwu.NewListBox([]string{"Inbox", "User preferences", "Last visited page"}) - c.Style().SetWidthPx(160) - t.Add(c, 2, 1) - p.Add(t) - - p.AddVSpace(30) - p.Add(gwu.NewLabel("Advanced table structure with modified alignment, row and col spans:")) - p.AddVSpace(10) - t = gwu.NewTable() - t.Style().SetBorder2(1, gwu.BRD_STYLE_SOLID, gwu.CLR_GREY) - t.SetAlign(gwu.HA_RIGHT, gwu.VA_TOP) - t.EnsureSize(5, 5) - for row := 0; row < 5; row++ { - for col := 0; col < 5; col++ { - t.Add(gwu.NewButton("Button "+strconv.Itoa(row)+strconv.Itoa(col)), row, col) - } - } - t.SetColSpan(2, 1, 2) - t.SetRowSpan(3, 1, 2) - t.CellFmt(2, 2).Style().SetSizePx(150, 80) - t.CellFmt(2, 2).SetAlign(gwu.HA_RIGHT, gwu.VA_BOTTOM) - t.RowFmt(2).SetAlign(gwu.HA_DEFAULT, gwu.VA_MIDDLE) - t.CompAt(2, 1).Style().SetFullSize() - t.CompAt(4, 2).Style().SetFullWidth() - t.RowFmt(0).Style().SetBackground(gwu.CLR_RED) - t.RowFmt(1).Style().SetBackground(gwu.CLR_GREEN) - t.RowFmt(2).Style().SetBackground(gwu.CLR_BLUE) - t.RowFmt(3).Style().SetBackground(gwu.CLR_GREY) - t.RowFmt(4).Style().SetBackground(gwu.CLR_TEAL) - p.Add(t) - - return p -} - -func buildTabPanelDemo(event gwu.Event) gwu.Comp { - p := gwu.NewPanel() - - t := gwu.NewTabPanel() - t.Style().SetSizePx(500, 300) - - table := gwu.NewTable() - table.SetCellPadding(2) - table.EnsureSize(3, 2) - table.Add(gwu.NewLabel("Change tab bar placement:"), 0, 0) - table.Add(gwu.NewLabel("Tab bar horizontal align:"), 1, 0) - table.Add(gwu.NewLabel("Tab bar vertical align:"), 2, 0) - - placemslb := gwu.NewListBox([]string{"Top", "Right", "Bottom", "Left"}) - placems := []gwu.TabBarPlacement{gwu.TB_PLACEMENT_TOP, gwu.TB_PLACEMENT_RIGHT, gwu.TB_PLACEMENT_BOTTOM, gwu.TB_PLACEMENT_LEFT} - halignslb := gwu.NewListBox([]string{"Left", "Center", "Right"}) - haligns := []gwu.HAlign{gwu.HA_LEFT, gwu.HA_CENTER, gwu.HA_RIGHT} - valignslb := gwu.NewListBox([]string{"Top", "Middle", "Bottom"}) - valigns := []gwu.VAlign{gwu.VA_TOP, gwu.VA_MIDDLE, gwu.VA_BOTTOM} - placemslb.Style().SetFullWidth() - halignslb.Style().SetFullWidth() - valignslb.Style().SetFullWidth() - table.Add(placemslb, 0, 1) - table.Add(halignslb, 1, 1) - table.Add(valignslb, 2, 1) - - placemslb.AddEHandlerFunc(func(e gwu.Event) { - t.SetTabBarPlacement(placems[placemslb.SelectedIdx()]) - e.MarkDirty(t) - }, gwu.ETYPE_CHANGE) - halignslb.AddEHandlerFunc(func(e gwu.Event) { - t.TabBarFmt().SetHAlign(haligns[halignslb.SelectedIdx()]) - e.MarkDirty(t) - }, gwu.ETYPE_CHANGE) - valignslb.AddEHandlerFunc(func(e gwu.Event) { - t.TabBarFmt().SetVAlign(valigns[valignslb.SelectedIdx()]) - e.MarkDirty(t) - }, gwu.ETYPE_CHANGE) - - p.Add(table) - - fix := gwu.NewCheckBox("Fixed size") - fix.SetState(true) - fix.AddEHandlerFunc(func(e gwu.Event) { - if fix.State() { - t.Style().SetSizePx(500, 300) - } else { - t.Style().SetSize("", "") - } - e.MarkDirty(t) - }, gwu.ETYPE_CLICK) - p.Add(fix) - - p.AddVSpace(10) - l := gwu.NewLabel("Click on tabs...") - l.Style().SetColor(gwu.CLR_GREEN) - p.Add(l) - t.AddEHandlerFunc(func(e gwu.Event) { - l.SetText("Clicked on tab: " + strconv.Itoa(t.Selected())) - e.MarkDirty(l) - }, gwu.ETYPE_STATE_CHANGE) - p.AddVSpace(10) - c := gwu.NewPanel() - c.Add(gwu.NewLabel("This is a TabPanel.")) - c.Add(gwu.NewLabel("Click on other tabs to see their content.")) - c.AddVSpace(15) - c.Add(gwu.NewLabel("Or click here to see what's in the Hollow:")) - b := gwu.NewButton("Take me to the Hollow!") - b.AddEHandlerFunc(func(e gwu.Event) { - t.SetSelected(3) - e.MarkDirty(t) - }, gwu.ETYPE_CLICK) - c.Add(b) - t.AddString("Home", c) - c = gwu.NewPanel() - c.Add(gwu.NewLabel("You have no new messages.")) - t.AddString("Inbox", c) - c = gwu.NewPanel() - c.Add(gwu.NewLabel("You have no sent messages.")) - t.AddString("Sent", c) - c = gwu.NewPanel() - c.Add(gwu.NewLabel("There is nothing in the hollow.")) - t.AddString("Hollow", c) - c = gwu.NewPanel() - tb := gwu.NewTextBox("Click to edit this comment.") - tb.SetRows(10) - tb.SetCols(40) - c.Add(tb) - t.AddString("Comment", c) - p.Add(t) - - return p -} - -func buildWindowDemo(event gwu.Event) gwu.Comp { - p := gwu.NewPanel() - - p.Add(gwu.NewLabel("The Window represents the whole window, the page inside the browser.")) - p.AddVSpace(5) - p.Add(gwu.NewLabel("The Window is the top of the component hierarchy. It is an extension of the Panel.")) - - return p -} - -func buildCheckBoxDemo(event gwu.Event) gwu.Comp { - p := gwu.NewPanel() - - suml := gwu.NewLabel("") - - p.Add(gwu.NewLabel("Check the days you want to work on:")) - - cbs := []gwu.CheckBox{gwu.NewCheckBox("Monday"), gwu.NewCheckBox("Tuesday"), gwu.NewCheckBox("Wednesday"), - gwu.NewCheckBox("Thursday"), gwu.NewCheckBox("Friday"), gwu.NewCheckBox("Saturday"), gwu.NewCheckBox("Sunday")} - cbs[5].SetEnabled(false) - cbs[6].SetEnabled(false) - - for _, cb := range cbs { - p.Add(cb) - cb.AddEHandlerFunc(func(e gwu.Event) { - sum := 0 - for _, cb2 := range cbs { - if cb2.State() { - sum++ - } - } - suml.SetText(fmt.Sprintf("%d day%s is a total of %d hours a week.", sum, plural(sum), sum*8)) - e.MarkDirty(suml) - }, gwu.ETYPE_CLICK) - } - - p.Add(suml) - - return p -} - -func buildListBoxDemo(event gwu.Event) gwu.Comp { - p := gwu.NewPanel() - - row := gwu.NewHorizontalPanel() - l := gwu.NewLabel("Select a background color:") - row.Add(l) - lb := gwu.NewListBox([]string{"", "Black", "Red", "Green", "Blue", "White"}) - lb.AddEHandlerFunc(func(e gwu.Event) { - l.Style().SetBackground(lb.SelectedValue()) - e.MarkDirty(l) - }, gwu.ETYPE_CHANGE) - row.Add(lb) - p.Add(row) - - p.AddVSpace(10) - p.Add(gwu.NewLabel("Select numbers that add up to 89:")) - sumLabel := gwu.NewLabel("") - lb2 := gwu.NewListBox([]string{"1", "2", "4", "8", "16", "32", "64", "128"}) - lb2.SetMulti(true) - lb2.SetRows(10) - lb2.AddEHandlerFunc(func(e gwu.Event) { - sum := 0 - for _, idx := range lb2.SelectedIndices() { - sum += 1 << uint(idx) - } - if sum == 89 { - sumLabel.SetText("Hooray! You did it!") - } else { - sumLabel.SetText(fmt.Sprintf("Now quite there... (sum = %d)", sum)) - } - e.MarkDirty(sumLabel) - }, gwu.ETYPE_CHANGE) - p.Add(lb2) - p.Add(sumLabel) - - return p -} - -func buildTextBoxDemo(event gwu.Event) gwu.Comp { - p := gwu.NewPanel() - - p.Add(gwu.NewLabel("Enter your name (max 15 characters):")) - row := gwu.NewHorizontalPanel() - tb := gwu.NewTextBox("") - tb.SetMaxLength(15) - tb.AddSyncOnETypes(gwu.ETYPE_KEY_UP) - length := gwu.NewLabel("") - length.Style().SetFontSize("80%").SetFontStyle(gwu.FONT_STYLE_ITALIC) - tb.AddEHandlerFunc(func(e gwu.Event) { - rem := 15 - len(tb.Text()) - length.SetText(fmt.Sprintf("(%d character%s left...)", rem, plural(rem))) - e.MarkDirty(length) - }, gwu.ETYPE_CHANGE, gwu.ETYPE_KEY_UP) - row.Add(tb) - row.Add(length) - p.Add(row) - - p.AddVSpace(10) - p.Add(gwu.NewLabel("Short biography:")) - bio := gwu.NewTextBox("") - bio.SetRows(5) - bio.SetCols(40) - p.Add(bio) - - p.AddVSpace(10) - rtb := gwu.NewTextBox("This is just a read-only text box...") - rtb.SetReadOnly(true) - p.Add(rtb) - - p.AddVSpace(10) - dtb := gwu.NewTextBox("...and a disabled one.") - dtb.SetEnabled(false) - p.Add(dtb) - - return p -} - -func buildPasswBoxDemo(event gwu.Event) gwu.Comp { - p := gwu.NewPanel() - - p.Add(gwu.NewLabel("Enter your password:")) - p.Add(gwu.NewPasswBox("")) - - return p -} - -func buildRadioButtonDemo(event gwu.Event) gwu.Comp { - p := gwu.NewPanel() - - p.Add(gwu.NewLabel("Select your favorite programming language:")) - - group := gwu.NewRadioGroup("lang") - rbs := []gwu.RadioButton{gwu.NewRadioButton("Go", group), gwu.NewRadioButton("Java", group), gwu.NewRadioButton("C / C++", group), - gwu.NewRadioButton("Python", group), gwu.NewRadioButton("QBasic (nah this can't be your favorite)", group)} - rbs[4].SetEnabled(false) - - for _, rb := range rbs { - p.Add(rb) - } - - p.AddVSpace(20) - p.Add(gwu.NewLabel("Select your favorite computer game:")) - - group = gwu.NewRadioGroup("game") - rbs = []gwu.RadioButton{gwu.NewRadioButton("StarCraft II", group), gwu.NewRadioButton("Minecraft", group), - gwu.NewRadioButton("Other", group)} - - for _, rb := range rbs { - p.Add(rb) - } - - return p -} - -func buildSwitchButtonDemo(event gwu.Event) gwu.Comp { - p := gwu.NewPanel() - p.SetCellPadding(1) - - row := gwu.NewHorizontalPanel() - row.Add(gwu.NewLabel("Here's an ON/OFF switch which enables/disables the other one:")) - sw := gwu.NewSwitchButton() - sw.SetOnOff("ENB", "DISB") - sw.SetState(true) - row.Add(sw) - p.Add(row) - - p.AddVSpace(10) - row = gwu.NewHorizontalPanel() - row.Add(gwu.NewLabel("And the other one:")) - sw2 := gwu.NewSwitchButton() - sw2.SetEnabled(true) - sw2.Style().SetWidthPx(100) - row.Add(sw2) - sw.AddEHandlerFunc(func(e gwu.Event) { - sw2.SetEnabled(sw.State()) - e.MarkDirty(sw2) - }, gwu.ETYPE_CLICK) - p.Add(row) - - return p -} - -func buildButtonDemo(event gwu.Event) gwu.Comp { - p := gwu.NewPanel() - - l := gwu.NewLabel("") - - btnp := gwu.NewHorizontalPanel() - b := gwu.NewButton("Normal Button") - b.AddEHandlerFunc(func(e gwu.Event) { - switch e.Type() { - case gwu.ETYPE_MOUSE_OVER: - l.SetText("Mouse is over...") - case gwu.ETYPE_MOUSE_OUT: - l.SetText("Mouse is out.") - case gwu.ETYPE_CLICK: - x, y := e.Mouse() - l.SetText(fmt.Sprintf("Clicked at x=%d, y=%d", x, y)) - } - e.MarkDirty(l) - }, gwu.ETYPE_CLICK, gwu.ETYPE_MOUSE_OVER, gwu.ETYPE_MOUSE_OUT) - btnp.Add(b) - - b = gwu.NewButton("Disabled Button") - b.SetEnabled(false) - btnp.Add(b) - - p.Add(btnp) - - p.Add(l) - - return p -} - -func buildHtmlDemo(event gwu.Event) gwu.Comp { - p := gwu.NewPanel() - - html := `Hi! I'm inserted as HTML. Click on me!` - - p.Add(gwu.NewLabel("The following HTML code is inserted after the text box as an Html component:")) - ta := gwu.NewTextBox(html) - ta.SetReadOnly(true) - ta.Style().SetWidthPx(500) - ta.SetRows(4) - p.Add(ta) - - p.AddVSpace(20) - h := gwu.NewHtml(html) - p.Add(h) - - return p -} - -func buildImageDemo(event gwu.Event) gwu.Comp { - p := gwu.NewPanel() - - p.Add(gwu.NewLabel("Google's logo:")) - img := gwu.NewImage("Google's logo", "https://www.google.com/images/srpr/logo3w.png") - img.Style().SetSizePx(275, 95) - p.Add(img) - - p.AddVSpace(20) - p.Add(gwu.NewLabel("Go's Gopher:")) - img = gwu.NewImage("Go's Gopher", "http://golang.org/doc/gopher/frontpage.png") - img.Style().SetSizePx(250, 340) - p.Add(img) - - return p -} - -func buildLabelDemo(event gwu.Event) gwu.Comp { - p := gwu.NewPanel() - - p.Add(gwu.NewLabel("This is a Label.")) - p.Add(gwu.NewLabel("世界 And another one. ㅈㅈ")) - p.Add(gwu.NewLabel("Nothing special about them, but they may be the mostly used components.")) - - p.AddVSpace(20) - p.Add(gwu.NewLabel("You can change their text:")) - b := gwu.NewButton("Change!") - b.AddEHandlerFunc(func(e gwu.Event) { - for i := 0; i < p.CompsCount(); i++ { - if l, ok := p.CompAt(i).(gwu.Label); ok && l != b { - reversed := []rune(l.Text()) - for i, j := 0, len(reversed)-1; i < j; i, j = i+1, j-1 { - reversed[i], reversed[j] = reversed[j], reversed[i] - } - l.SetText(string(reversed)) - } - } - e.MarkDirty(p) - }, gwu.ETYPE_CLICK) - p.Add(b) - - return p -} - -func buildLinkDemo(event gwu.Event) gwu.Comp { - p := gwu.NewPanel() - p.SetCellPadding(3) - - p.Add(gwu.NewLink("Visit Gowut Home page", "https://sites.google.com/site/gowebuitoolkit/")) - p.Add(gwu.NewLink("Visit Gowut Project page", "https://github.com/icza/gowut")) - - row := gwu.NewHorizontalPanel() - row.SetCellPadding(3) - row.Add(gwu.NewLabel("Discussion forum:")) - row.Add(gwu.NewLink("https://groups.google.com/d/forum/gowebuitoolkit", "https://groups.google.com/d/forum/gowebuitoolkit")) - p.Add(row) - - row = gwu.NewHorizontalPanel() - row.SetCellPadding(3) - row.Add(gwu.NewLabel("Send e-mail to the Gowut author:")) - email := "iczaaa" + "@" + "gmail.com" - row.Add(gwu.NewLink("András Belicza <"+email+">", "mailto:"+email)) - p.Add(row) - - return p -} - -func buildTimerDemo(event gwu.Event) gwu.Comp { - p := gwu.NewPanel() - p.SetCellPadding(3) - - // Add timers to a panel which is always attached instead of our panel - // because the user can switch to another component demo causing this panel to be removed - // and that way timer events would address components that are not part of the window (returning error). - hiddenPan := event.Session().Attr("hiddenPan").(gwu.Panel) - - p.Add(gwu.NewLabel("A Timer is used to detonate a bomb after 3 seconds.")) - p.AddVSpace(10) - defText := "You can defuse the bomb with the button below. Tick... Tack..." - l := gwu.NewLabel(defText) - p.Add(l) - t := gwu.NewTimer(3 * time.Second) - b := gwu.NewButton("Defuse!") - t.AddEHandlerFunc(func(e gwu.Event) { - l.SetText("BOOOOM! You were too slow!") - l.Style().SetColor(gwu.CLR_RED) - b.SetEnabled(false) - e.MarkDirty(l, b) - }, gwu.ETYPE_STATE_CHANGE) - hiddenPan.Add(t) - row := gwu.NewHorizontalPanel() - b.AddEHandlerFunc(func(e gwu.Event) { - t.SetActive(false) - l.SetText("Bomb defused! Phew! Good Job!") - l.Style().SetColor(gwu.CLR_GREEN) - b.SetEnabled(false) - e.MarkDirty(t, l, b) - }, gwu.ETYPE_CLICK) - row.Add(b) - b2 := gwu.NewButton("Plant a new Bomb!") - b2.AddEHandlerFunc(func(e gwu.Event) { - t.SetActive(true) - t.Reset() - l.SetText(defText) - l.Style().SetColor("") - b.SetEnabled(true) - e.MarkDirty(t, l, b) - }, gwu.ETYPE_CLICK) - row.Add(b2) - p.Add(row) - - p.AddVSpace(20) - p.Add(gwu.NewLabel("A Timer is used to refresh the time below repeatedly in every second for half a minute.")) - tl := gwu.NewLabel("") - p.Add(tl) - t2 := gwu.NewTimer(time.Second) - t2.SetRepeat(true) - counter := 30 - t2.AddEHandlerFunc(func(e gwu.Event) { - counter-- - tl.SetText(fmt.Sprintf("%s (%d remaining)", time.Now().Format("2006-01-02 15:04:05"), counter)) - e.MarkDirty(tl) - if counter <= 0 { - t2.SetActive(false) - e.MarkDirty(t2) - } - }, gwu.ETYPE_STATE_CHANGE) - hiddenPan.Add(t2) - b3 := gwu.NewButton("Restart") - b3.AddEHandlerFunc(func(e gwu.Event) { - counter = 30 - t2.SetActive(true) - e.MarkDirty(t2) - }, gwu.ETYPE_CLICK) - p.Add(b3) - - event.MarkDirty(hiddenPan) - - return p -} - -type demo struct { - link gwu.Label - buildFunc func(gwu.Event) gwu.Comp - comp gwu.Comp // Lazily initialized demo comp -} -type pdemo *demo - -func buildShowcaseWin(sess gwu.Session) { - win := gwu.NewWindow("show", "Showcase of Features - Gowut") - win.Style().SetFullSize() - win.AddEHandlerFunc(func(e gwu.Event) { - switch e.Type() { - case gwu.ETYPE_WIN_LOAD: - fmt.Println("LOADING window:", e.Src().Id()) - case gwu.ETYPE_WIN_UNLOAD: - fmt.Println("UNLOADING window:", e.Src().Id()) - } - }, gwu.ETYPE_WIN_LOAD, gwu.ETYPE_WIN_UNLOAD) - - hiddenPan := gwu.NewNaturalPanel() - sess.SetAttr("hiddenPan", hiddenPan) - - header := gwu.NewHorizontalPanel() - header.Style().SetFullWidth().SetBorderBottom2(2, gwu.BRD_STYLE_SOLID, "#777777") - l := gwu.NewLabel("Gowut - Showcase of Features") - l.Style().SetFontWeight(gwu.FONT_WEIGHT_BOLD).SetFontSize("120%") - header.Add(l) - header.AddHConsumer() - header.Add(gwu.NewLabel("Theme:")) - themes := gwu.NewListBox([]string{"default", "debug"}) - themes.AddEHandlerFunc(func(e gwu.Event) { - win.SetTheme(themes.SelectedValue()) - e.ReloadWin("show") - }, gwu.ETYPE_CHANGE) - header.Add(themes) - header.AddHSpace(10) - reset := gwu.NewLink("Reset", "#") - reset.SetTarget("") - reset.AddEHandlerFunc(func(e gwu.Event) { - e.RemoveSess() - e.ReloadWin("show") - }, gwu.ETYPE_CLICK) - header.Add(reset) - setNoWrap(header) - win.Add(header) - - content := gwu.NewHorizontalPanel() - content.SetCellPadding(1) - content.SetVAlign(gwu.VA_TOP) - content.Style().SetFullSize() - - demoWrapper := gwu.NewPanel() - demoWrapper.Style().SetPaddingLeftPx(5) - demoWrapper.AddVSpace(10) - demoTitle := gwu.NewLabel("") - demoTitle.Style().SetFontWeight(gwu.FONT_WEIGHT_BOLD).SetFontSize("110%") - demoWrapper.Add(demoTitle) - demoWrapper.AddVSpace(10) - - links := gwu.NewPanel() - links.SetCellPadding(1) - links.Style().SetPaddingRightPx(5) - - demos := make(map[string]pdemo) - var selDemo pdemo - - selectDemo := func(d pdemo, e gwu.Event) { - if selDemo != nil { - selDemo.link.Style().SetBackground("") - if e != nil { - e.MarkDirty(selDemo.link) - } - demoWrapper.Remove(selDemo.comp) - } - selDemo = d - d.link.Style().SetBackground("#88ff88") - demoTitle.SetText(d.link.Text()) - if d.comp == nil { - d.comp = d.buildFunc(e) - } - demoWrapper.Add(d.comp) - if e != nil { - e.MarkDirty(d.link, demoWrapper) - } - } - - createDemo := func(name string, buildFunc func(gwu.Event) gwu.Comp) pdemo { - link := gwu.NewLabel(name) - link.Style().SetFullWidth().SetCursor(gwu.CURSOR_POINTER).SetDisplay(gwu.DISPLAY_BLOCK).SetColor(gwu.CLR_BLUE) - demo := &demo{link: link, buildFunc: buildFunc} - link.AddEHandlerFunc(func(e gwu.Event) { - selectDemo(demo, e) - }, gwu.ETYPE_CLICK) - links.Add(link) - demos[name] = demo - return demo - } - - links.Style().SetFullHeight().SetBorderRight2(2, gwu.BRD_STYLE_SOLID, "#777777") - links.AddVSpace(5) - homeDemo := createDemo("Home", buildHomeDemo) - selectDemo(homeDemo, nil) - links.AddVSpace(5) - l = gwu.NewLabel("Component Palette") - l.Style().SetFontWeight(gwu.FONT_WEIGHT_BOLD).SetFontSize("110%") - links.Add(l) - links.AddVSpace(5) - l = gwu.NewLabel("Containers") - l.Style().SetFontWeight(gwu.FONT_WEIGHT_BOLD) - links.Add(l) - createDemo("Expander", buildExpanderDemo) - createDemo("Link (as Container)", buildLinkContainerDemo) - createDemo("Panel", buildPanelDemo) - createDemo("Table", buildTableDemo) - createDemo("TabPanel", buildTabPanelDemo) - createDemo("Window", buildWindowDemo) - links.AddVSpace(5) - l = gwu.NewLabel("Input components") - l.Style().SetFontWeight(gwu.FONT_WEIGHT_BOLD).SetDisplay(gwu.DISPLAY_BLOCK) - links.Add(l) - createDemo("CheckBox", buildCheckBoxDemo) - createDemo("ListBox", buildListBoxDemo) - createDemo("TextBox", buildTextBoxDemo) - createDemo("PasswBox", buildPasswBoxDemo) - createDemo("RadioButton", buildRadioButtonDemo) - createDemo("SwitchButton", buildSwitchButtonDemo) - links.AddVSpace(5) - l = gwu.NewLabel("Other components") - l.Style().SetFontWeight(gwu.FONT_WEIGHT_BOLD) - links.Add(l) - createDemo("Button", buildButtonDemo) - createDemo("Html", buildHtmlDemo) - createDemo("Image", buildImageDemo) - createDemo("Label", buildLabelDemo) - createDemo("Link", buildLinkDemo) - createDemo("Timer", buildTimerDemo) - links.AddVConsumer() - setNoWrap(links) - content.Add(links) - content.Add(demoWrapper) - content.CellFmt(demoWrapper).Style().SetFullWidth() - - win.Add(content) - win.CellFmt(content).Style().SetFullSize() - - footer := gwu.NewHorizontalPanel() - footer.Style().SetFullWidth().SetBorderTop2(2, gwu.BRD_STYLE_SOLID, "#777777") - footer.Add(hiddenPan) - footer.AddHConsumer() - l = gwu.NewLabel("Copyright © 2013-2015 András Belicza. All rights reserved.") - l.Style().SetFontStyle(gwu.FONT_STYLE_ITALIC).SetFontSize("95%") - footer.Add(l) - footer.AddHSpace(10) - link := gwu.NewLink("Visit Gowut Home page", "https://sites.google.com/site/gowebuitoolkit/") - link.Style().SetFontStyle(gwu.FONT_STYLE_ITALIC).SetFontSize("95%") - footer.Add(link) - setNoWrap(footer) - win.Add(footer) - - sess.AddWin(win) -} - -// setNoWrap sets WHITE_SPACE_NOWRAP to all children of the specified panel. -func setNoWrap(panel gwu.Panel) { - count := panel.CompsCount() - for i := count - 1; i >= 0; i-- { - panel.CompAt(i).Style().SetWhiteSpace(gwu.WHITE_SPACE_NOWRAP) - } -} - -// SessHandler is our session handler to build the showcases window. -type SessHandler struct{} - -func (h SessHandler) Created(s gwu.Session) { - buildShowcaseWin(s) -} - -func (h SessHandler) Removed(s gwu.Session) {} - func main() { // Allow app control from command line (in co-operation with the starter script): - fmt.Println("Type 'r' to restart, 'e' to exit.") + log.Println("Type 'r' to restart, 'e' to exit.") go func() { var cmd string for { @@ -845,16 +43,5 @@ func main() { } }() - // Create GUI server - server := gwu.NewServer("showcase", "") - server.SetText("Gowut - Showcase of Features") - - server.AddSessCreatorName("show", "Showcase of Features - Gowut") - server.AddSHandler(SessHandler{}) - - // Start GUI server - if err := server.Start("show"); err != nil { - fmt.Println("Error: Cound not start GUI server:", err) - return - } + showcasecore.StartServer("showcase") } diff --git a/examples/showcase/showcasecore/showcase_gae.go b/examples/showcase/showcasecore/showcase_gae.go new file mode 100644 index 0000000..200a176 --- /dev/null +++ b/examples/showcase/showcasecore/showcase_gae.go @@ -0,0 +1,36 @@ +// +build appengine + +// Copyright (C) 2013 Andras Belicza. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// Contains a package init function which starts the server of the Gowut "Showcase of Features" demo. + +package showcasecore + +func init() { + extraHeadHtmls = []string{analyticsTrackingCode} + StartServer("") +} + +const analyticsTrackingCode = `` diff --git a/examples/showcase/showcasecore/showcasecore.go b/examples/showcase/showcasecore/showcasecore.go new file mode 100644 index 0000000..a8c9f82 --- /dev/null +++ b/examples/showcase/showcasecore/showcasecore.go @@ -0,0 +1,867 @@ +// Copyright (C) 2013 Andras Belicza. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// A Gowut "Showcase of Features" application. + +package showcasecore + +import ( + "fmt" + "github.com/icza/gowut/gwu" + "log" + "time" +) + +// plural returns an empty string if i is equal to 1, +// "s" otherwise. +func plural(i int) string { + if i == 1 { + return "" + } + return "s" +} + +func buildHomeDemo(event gwu.Event) gwu.Comp { + p := gwu.NewPanel() + + content := []string{ + "This app is written in and showcases Gowut " + gwu.GowutVersion + ".", + "Everything you see here is modeled and represented in Go (server side). Everything is created with Go code only (no HTML).", + "When you make modifications to components (e.g. enter text into a TextBox), that is automatically trasmitted to the server side via AJAX calls.", + "If you close the window and reopen the URL, you will see everything as you left it.", + "What you see and do here is also bound to You (using a Session), others don't see it and they can't interfere with it.", + "You may notice a small delay between your actions and the result (response). This is due to the network latency: your action needs to be transmitted to the Gowut server, and changed (dirty) components need to be refreshed (re-rendered). Gowut apps that run locally do not have such delays.", + "Select components on the left side to see them in action.", + } + + for _, s := range content { + p.Add(gwu.NewLabel(s)) + p.AddVSpace(20) + } + + return p +} + +func buildExpanderDemo(event gwu.Event) gwu.Comp { + p := gwu.NewPanel() + + l := gwu.NewLabel("Click on the Expander's header.") + l.Style().SetColor(gwu.ClrGreen) + p.Add(l) + p.AddVSpace(5) + e := gwu.NewExpander() + e.SetHeader(gwu.NewLabel("I'm an Expander.")) + e.SetContent(gwu.NewLabel("And I'm the content of the Expander.")) + p.Add(e) + e.AddEHandlerFunc(func(ev gwu.Event) { + if e.Expanded() { + l.SetText("You expanded it.") + } else { + l.SetText("You collapsed it.") + } + ev.MarkDirty(l) + }, gwu.ETypeStateChange) + + p.AddVSpace(20) + var ee gwu.Expander + for i := 5; i >= 0; i-- { + e2 := gwu.NewExpander() + e2.SetHeader(gwu.NewLabel(fmt.Sprintf("I hide embedded expanders. #%d", i))) + if i == 5 { + e2.SetContent(gwu.NewLabel("No more.")) + } else { + e2.SetContent(ee) + } + ee = e2 + } + p.Add(ee) + + return p +} + +func buildLinkContainerDemo(event gwu.Event) gwu.Comp { + p := gwu.NewPanel() + + link := gwu.NewLink("An obvious link, to Google Home", "https://google.com/") + inside := gwu.NewPanel() + inside.Style().SetPadding("5px").SetMarginTopPx(2).SetBorder2(1, gwu.BrdStyleSolid, gwu.ClrGray) + inside.Add(gwu.NewLabel("Everything inside this box also links to Google!")) + inside.Add(gwu.NewButton("Me too!")) + link.SetComp(inside) + p.Add(link) + + return p +} + +func buildPanelDemo(event gwu.Event) gwu.Comp { + p := gwu.NewPanel() + + p.Add(gwu.NewLabel("Panel with horizontal layout:")) + h := gwu.NewHorizontalPanel() + for i := 1; i <= 5; i++ { + h.Add(gwu.NewButton(fmt.Sprintf("Button %d", i))) + } + p.Add(h) + + p.AddVSpace(20) + p.Add(gwu.NewLabel("Panel with vertical layout:")) + v := gwu.NewVerticalPanel() + for i := 1; i <= 5; i++ { + v.Add(gwu.NewButton(fmt.Sprintf("Button %d", i))) + } + p.Add(v) + + p.AddVSpace(20) + p.Add(gwu.NewLabel("Panel with natural layout:")) + n := gwu.NewNaturalPanel() + for i := 1; i <= 40; i++ { + n.Add(gwu.NewButton(fmt.Sprintf("LONG BUTTON %d", i))) + } + p.Add(n) + + return p +} + +func buildTableDemo(event gwu.Event) gwu.Comp { + p := gwu.NewPanel() + + l := gwu.NewLabel("Tip: Switch to the 'debug' theme (top right) to see cell borders.") + l.Style().SetColor(gwu.ClrRed).SetFontStyle(gwu.FontStyleItalic) + p.Add(l) + + p.AddVSpace(20) + p.Add(gwu.NewLabel("A simple form aligned with a table:")) + p.AddVSpace(10) + t := gwu.NewTable() + t.SetCellPadding(2) + t.EnsureSize(2, 2) + var c gwu.Comp + t.Add(gwu.NewLabel("User name:"), 0, 0) + c = gwu.NewTextBox("") + c.Style().SetWidthPx(160) + t.Add(c, 0, 1) + t.Add(gwu.NewLabel("Password:"), 1, 0) + c = gwu.NewPasswBox("") + c.Style().SetWidthPx(160) + t.Add(c, 1, 1) + t.Add(gwu.NewLabel("Go to:"), 2, 0) + c = gwu.NewListBox([]string{"Inbox", "User preferences", "Last visited page"}) + c.Style().SetWidthPx(160) + t.Add(c, 2, 1) + p.Add(t) + + p.AddVSpace(30) + p.Add(gwu.NewLabel("Advanced table structure with modified alignment, row- and col spans:")) + p.AddVSpace(10) + t = gwu.NewTable() + t.Style().SetBorder2(1, gwu.BrdStyleSolid, gwu.ClrGrey) + t.SetAlign(gwu.HARight, gwu.VATop) + t.EnsureSize(5, 5) + for row := 0; row < 5; row++ { + for col := 0; col < 5; col++ { + t.Add(gwu.NewButton(fmt.Sprintf("Button %d%d", row, col)), row, col) + } + } + t.SetColSpan(2, 1, 2) + t.SetRowSpan(3, 1, 2) + t.CellFmt(2, 2).Style().SetSizePx(150, 80) + t.CellFmt(2, 2).SetAlign(gwu.HARight, gwu.VABottom) + t.RowFmt(2).SetAlign(gwu.HADefault, gwu.VAMiddle) + t.CompAt(2, 1).Style().SetFullSize() + t.CompAt(4, 2).Style().SetFullWidth() + t.RowFmt(0).Style().SetBackground(gwu.ClrRed) + t.RowFmt(1).Style().SetBackground(gwu.ClrGreen) + t.RowFmt(2).Style().SetBackground(gwu.ClrBlue) + t.RowFmt(3).Style().SetBackground(gwu.ClrGrey) + t.RowFmt(4).Style().SetBackground(gwu.ClrTeal) + p.Add(t) + + return p +} + +func buildTabPanelDemo(event gwu.Event) gwu.Comp { + p := gwu.NewPanel() + + t := gwu.NewTabPanel() + t.Style().SetSizePx(500, 300) + + table := gwu.NewTable() + table.SetCellPadding(2) + table.EnsureSize(3, 2) + table.Add(gwu.NewLabel("Change tab bar placement:"), 0, 0) + table.Add(gwu.NewLabel("Tab bar horizontal align:"), 1, 0) + table.Add(gwu.NewLabel("Tab bar vertical align:"), 2, 0) + + placemslb := gwu.NewListBox([]string{"Top", "Right", "Bottom", "Left"}) + placems := []gwu.TabBarPlacement{gwu.TbPlacementTop, gwu.TbPlacementRight, gwu.TbPlacementBottom, gwu.TbPlacementLeft} + halignslb := gwu.NewListBox([]string{"Left", "Center", "Right"}) + haligns := []gwu.HAlign{gwu.HALeft, gwu.HACenter, gwu.HARight} + valignslb := gwu.NewListBox([]string{"Top", "Middle", "Bottom"}) + valigns := []gwu.VAlign{gwu.VATop, gwu.VAMiddle, gwu.VABottom} + placemslb.Style().SetFullWidth() + halignslb.Style().SetFullWidth() + valignslb.Style().SetFullWidth() + table.Add(placemslb, 0, 1) + table.Add(halignslb, 1, 1) + table.Add(valignslb, 2, 1) + + placemslb.AddEHandlerFunc(func(e gwu.Event) { + t.SetTabBarPlacement(placems[placemslb.SelectedIdx()]) + e.MarkDirty(t) + }, gwu.ETypeChange) + halignslb.AddEHandlerFunc(func(e gwu.Event) { + t.TabBarFmt().SetHAlign(haligns[halignslb.SelectedIdx()]) + e.MarkDirty(t) + }, gwu.ETypeChange) + valignslb.AddEHandlerFunc(func(e gwu.Event) { + t.TabBarFmt().SetVAlign(valigns[valignslb.SelectedIdx()]) + e.MarkDirty(t) + }, gwu.ETypeChange) + + p.Add(table) + + fix := gwu.NewCheckBox("Fixed size") + fix.SetState(true) + fix.AddEHandlerFunc(func(e gwu.Event) { + if fix.State() { + t.Style().SetSizePx(500, 300) + } else { + t.Style().SetSize("", "") + } + e.MarkDirty(t) + }, gwu.ETypeClick) + p.Add(fix) + + p.AddVSpace(10) + l := gwu.NewLabel("Click on tabs...") + l.Style().SetColor(gwu.ClrGreen) + p.Add(l) + t.AddEHandlerFunc(func(e gwu.Event) { + l.SetText(fmt.Sprintf("Clicked on tab: %d", t.Selected())) + e.MarkDirty(l) + }, gwu.ETypeStateChange) + p.AddVSpace(10) + c := gwu.NewPanel() + c.Add(gwu.NewLabel("This is a TabPanel.")) + c.Add(gwu.NewLabel("Click on other tabs to see their content.")) + c.AddVSpace(15) + c.Add(gwu.NewLabel("Or click here to see what's in the Hollow:")) + b := gwu.NewButton("Take me to the Hollow!") + b.AddEHandlerFunc(func(e gwu.Event) { + t.SetSelected(3) + e.MarkDirty(t) + }, gwu.ETypeClick) + c.Add(b) + t.AddString("Home", c) + c = gwu.NewPanel() + c.Add(gwu.NewLabel("You have no new messages.")) + t.AddString("Inbox", c) + c = gwu.NewPanel() + c.Add(gwu.NewLabel("You have no sent messages.")) + t.AddString("Sent", c) + c = gwu.NewPanel() + c.Add(gwu.NewLabel("There is nothing in the hollow.")) + t.AddString("Hollow", c) + c = gwu.NewPanel() + tb := gwu.NewTextBox("Click to edit this comment.") + tb.SetRows(10) + tb.SetCols(40) + c.Add(tb) + t.AddString("Comment", c) + p.Add(t) + + return p +} + +func buildWindowDemo(event gwu.Event) gwu.Comp { + p := gwu.NewPanel() + + p.Add(gwu.NewLabel("The Window represents the whole window, the page inside the browser.")) + p.AddVSpace(5) + p.Add(gwu.NewLabel("The Window is the top of the component hierarchy. It is an extension of the Panel.")) + + return p +} + +func buildCheckBoxDemo(event gwu.Event) gwu.Comp { + p := gwu.NewPanel() + + suml := gwu.NewLabel("") + + p.Add(gwu.NewLabel("Check the days you want to work on:")) + + cbs := []gwu.CheckBox{gwu.NewCheckBox("Monday"), gwu.NewCheckBox("Tuesday"), gwu.NewCheckBox("Wednesday"), + gwu.NewCheckBox("Thursday"), gwu.NewCheckBox("Friday"), gwu.NewCheckBox("Saturday"), gwu.NewCheckBox("Sunday")} + cbs[5].SetEnabled(false) + cbs[6].SetEnabled(false) + + for _, cb := range cbs { + p.Add(cb) + cb.AddEHandlerFunc(func(e gwu.Event) { + sum := 0 + for _, cb2 := range cbs { + if cb2.State() { + sum++ + } + } + suml.SetText(fmt.Sprintf("%d day%s is a total of %d hours a week.", sum, plural(sum), sum*8)) + e.MarkDirty(suml) + }, gwu.ETypeClick) + } + + p.Add(suml) + + return p +} + +func buildListBoxDemo(event gwu.Event) gwu.Comp { + p := gwu.NewPanel() + + row := gwu.NewHorizontalPanel() + l := gwu.NewLabel("Select a background color:") + row.Add(l) + lb := gwu.NewListBox([]string{"", "Black", "Red", "Green", "Blue", "White"}) + lb.AddEHandlerFunc(func(e gwu.Event) { + l.Style().SetBackground(lb.SelectedValue()) + e.MarkDirty(l) + }, gwu.ETypeChange) + row.Add(lb) + p.Add(row) + + p.AddVSpace(10) + p.Add(gwu.NewLabel("Select numbers that add up to 89: (hold down the CTRL while selecting)")) + sumLabel := gwu.NewLabel("") + lb2 := gwu.NewListBox([]string{"1", "2", "4", "8", "16", "32", "64", "128"}) + lb2.SetMulti(true) + lb2.SetRows(10) + lb2.AddEHandlerFunc(func(e gwu.Event) { + sum := 0 + for _, idx := range lb2.SelectedIndices() { + sum += 1 << uint(idx) + } + if sum == 89 { + sumLabel.SetText("Hooray! You did it!") + } else { + sumLabel.SetText(fmt.Sprintf("Now quite there... (sum = %d)", sum)) + } + e.MarkDirty(sumLabel) + }, gwu.ETypeChange) + p.Add(lb2) + p.Add(sumLabel) + + return p +} + +func buildTextBoxDemo(event gwu.Event) gwu.Comp { + p := gwu.NewPanel() + + p.Add(gwu.NewLabel("Enter your name (max 15 characters):")) + row := gwu.NewHorizontalPanel() + tb := gwu.NewTextBox("") + tb.SetMaxLength(15) + tb.AddSyncOnETypes(gwu.ETypeKeyUp) + length := gwu.NewLabel("") + length.Style().SetFontSize("80%").SetFontStyle(gwu.FontStyleItalic) + tb.AddEHandlerFunc(func(e gwu.Event) { + rem := 15 - len(tb.Text()) + length.SetText(fmt.Sprintf("(%d character%s left...)", rem, plural(rem))) + e.MarkDirty(length) + }, gwu.ETypeChange, gwu.ETypeKeyUp) + row.Add(tb) + row.Add(length) + p.Add(row) + + p.AddVSpace(10) + p.Add(gwu.NewLabel("Short biography:")) + bio := gwu.NewTextBox("") + bio.SetRows(5) + bio.SetCols(40) + p.Add(bio) + + p.AddVSpace(10) + rtb := gwu.NewTextBox("This is just a read-only text box...") + rtb.SetReadOnly(true) + p.Add(rtb) + + p.AddVSpace(10) + dtb := gwu.NewTextBox("...and a disabled one.") + dtb.SetEnabled(false) + p.Add(dtb) + + return p +} + +func buildPasswBoxDemo(event gwu.Event) gwu.Comp { + p := gwu.NewPanel() + + p.Add(gwu.NewLabel("Enter your password:")) + p.Add(gwu.NewPasswBox("")) + + return p +} + +func buildRadioButtonDemo(event gwu.Event) gwu.Comp { + p := gwu.NewPanel() + + p.Add(gwu.NewLabel("Select your favorite programming language:")) + + group := gwu.NewRadioGroup("lang") + rbs := []gwu.RadioButton{gwu.NewRadioButton("Go", group), gwu.NewRadioButton("Java", group), gwu.NewRadioButton("C / C++", group), + gwu.NewRadioButton("Python", group), gwu.NewRadioButton("QBasic (nah this can't be your favorite)", group)} + rbs[4].SetEnabled(false) + + for _, rb := range rbs { + p.Add(rb) + } + + p.AddVSpace(20) + p.Add(gwu.NewLabel("Select your favorite computer game:")) + + group = gwu.NewRadioGroup("game") + rbs = []gwu.RadioButton{gwu.NewRadioButton("StarCraft II", group), gwu.NewRadioButton("Minecraft", group), + gwu.NewRadioButton("Other", group)} + + for _, rb := range rbs { + p.Add(rb) + } + + return p +} + +func buildSwitchButtonDemo(event gwu.Event) gwu.Comp { + p := gwu.NewPanel() + p.SetCellPadding(1) + + row := gwu.NewHorizontalPanel() + row.Add(gwu.NewLabel("Here's an ON/OFF switch which enables/disables the other one:")) + sw := gwu.NewSwitchButton() + sw.SetOnOff("ENB", "DISB") + sw.SetState(true) + row.Add(sw) + p.Add(row) + + p.AddVSpace(10) + row = gwu.NewHorizontalPanel() + row.Add(gwu.NewLabel("And the other one:")) + sw2 := gwu.NewSwitchButton() + sw2.SetEnabled(true) + sw2.Style().SetWidthPx(100) + row.Add(sw2) + sw.AddEHandlerFunc(func(e gwu.Event) { + sw2.SetEnabled(sw.State()) + e.MarkDirty(sw2) + }, gwu.ETypeClick) + p.Add(row) + + return p +} + +func buildButtonDemo(event gwu.Event) gwu.Comp { + p := gwu.NewPanel() + + l := gwu.NewLabel("") + + btnp := gwu.NewHorizontalPanel() + b := gwu.NewButton("Normal Button") + b.AddEHandlerFunc(func(e gwu.Event) { + switch e.Type() { + case gwu.ETypeMouseOver: + l.SetText("Mouse is over...") + case gwu.ETypeMouseOut: + l.SetText("Mouse is out.") + case gwu.ETypeClick: + x, y := e.Mouse() + l.SetText(fmt.Sprintf("Clicked at x=%d, y=%d", x, y)) + } + e.MarkDirty(l) + }, gwu.ETypeClick, gwu.ETypeMouseOver, gwu.ETypeMouseOut) + btnp.Add(b) + + b = gwu.NewButton("Disabled Button") + b.SetEnabled(false) + btnp.Add(b) + + p.Add(btnp) + + p.Add(l) + + return p +} + +func buildHtmlDemo(event gwu.Event) gwu.Comp { + p := gwu.NewPanel() + + html := `Hi! I'm inserted as HTML. Click on me!` + + p.Add(gwu.NewLabel("The following HTML code is inserted after the text box as an Html component:")) + ta := gwu.NewTextBox(html) + ta.SetReadOnly(true) + ta.Style().SetWidthPx(500) + ta.SetRows(4) + p.Add(ta) + + p.AddVSpace(20) + h := gwu.NewHtml(html) + p.Add(h) + + return p +} + +func buildImageDemo(event gwu.Event) gwu.Comp { + p := gwu.NewPanel() + + p.Add(gwu.NewLabel("Google's logo:")) + img := gwu.NewImage("Google's logo", "https://www.google.com/images/srpr/logo3w.png") + img.Style().SetSizePx(275, 95) + p.Add(img) + + p.AddVSpace(20) + p.Add(gwu.NewLabel("Go's Gopher:")) + img = gwu.NewImage("Go's Gopher", "https://golang.org/doc/gopher/frontpage.png") + img.Style().SetSizePx(250, 340) + p.Add(img) + + return p +} + +func buildLabelDemo(event gwu.Event) gwu.Comp { + p := gwu.NewPanel() + + p.Add(gwu.NewLabel("This is a Label.")) + p.Add(gwu.NewLabel("世界 And another one. ㅈㅈ")) + p.Add(gwu.NewLabel("Nothing special about them, but they may be the mostly used components.")) + + p.AddVSpace(20) + p.Add(gwu.NewLabel("You can change their text:")) + b := gwu.NewButton("Change!") + b.AddEHandlerFunc(func(e gwu.Event) { + for i := 0; i < p.CompsCount(); i++ { + if l, ok := p.CompAt(i).(gwu.Label); ok && l != b { + reversed := []rune(l.Text()) + for i, j := 0, len(reversed)-1; i < j; i, j = i+1, j-1 { + reversed[i], reversed[j] = reversed[j], reversed[i] + } + l.SetText(string(reversed)) + } + } + e.MarkDirty(p) + }, gwu.ETypeClick) + p.Add(b) + + return p +} + +func buildLinkDemo(event gwu.Event) gwu.Comp { + p := gwu.NewPanel() + p.SetCellPadding(3) + + p.Add(gwu.NewLink("Visit Gowut Home page", "https://sites.google.com/site/gowebuitoolkit/")) + p.Add(gwu.NewLink("Visit Gowut Project page", "https://github.com/icza/gowut")) + + row := gwu.NewHorizontalPanel() + row.SetCellPadding(3) + row.Add(gwu.NewLabel("Discussion forum:")) + row.Add(gwu.NewLink("https://groups.google.com/d/forum/gowebuitoolkit", "https://groups.google.com/d/forum/gowebuitoolkit")) + p.Add(row) + + row = gwu.NewHorizontalPanel() + row.SetCellPadding(3) + row.Add(gwu.NewLabel("Send e-mail to the Gowut author:")) + email := "iczaaa" + "@" + "gmail.com" + row.Add(gwu.NewLink("András Belicza <"+email+">", "mailto:"+email)) + p.Add(row) + + return p +} + +func buildTimerDemo(event gwu.Event) gwu.Comp { + p := gwu.NewPanel() + p.SetCellPadding(3) + + // Add timers to a panel which is always attached instead of our panel + // because the user can switch to another component demo causing this panel to be removed + // and that way timer events would address components that are not part of the window (returning error). + hiddenPan := event.Session().Attr("hiddenPan").(gwu.Panel) + + p.Add(gwu.NewLabel("A Timer is used to detonate a bomb after 3 seconds.")) + p.AddVSpace(10) + defText := "You can defuse the bomb with the button below. Tick... Tack..." + l := gwu.NewLabel(defText) + p.Add(l) + t := gwu.NewTimer(3 * time.Second) + b := gwu.NewButton("Defuse!") + t.AddEHandlerFunc(func(e gwu.Event) { + l.SetText("BOOOOM! You were too slow!") + l.Style().SetColor(gwu.ClrRed) + b.SetEnabled(false) + e.MarkDirty(l, b) + }, gwu.ETypeStateChange) + hiddenPan.Add(t) + row := gwu.NewHorizontalPanel() + b.AddEHandlerFunc(func(e gwu.Event) { + t.SetActive(false) + l.SetText("Bomb defused! Phew! Good Job!") + l.Style().SetColor(gwu.ClrGreen) + b.SetEnabled(false) + e.MarkDirty(t, l, b) + }, gwu.ETypeClick) + row.Add(b) + b2 := gwu.NewButton("Plant a new Bomb!") + b2.AddEHandlerFunc(func(e gwu.Event) { + t.SetActive(true) + t.Reset() + l.SetText(defText) + l.Style().SetColor("") + b.SetEnabled(true) + e.MarkDirty(t, l, b) + }, gwu.ETypeClick) + row.Add(b2) + p.Add(row) + + p.AddVSpace(20) + p.Add(gwu.NewLabel("A Timer is used to refresh the time below repeatedly in every second for half a minute.")) + tl := gwu.NewLabel("") + p.Add(tl) + t2 := gwu.NewTimer(time.Second) + t2.SetRepeat(true) + counter := 30 + t2.AddEHandlerFunc(func(e gwu.Event) { + counter-- + tl.SetText(fmt.Sprintf("%s (%d remaining)", time.Now().Format("2006-01-02 15:04:05"), counter)) + e.MarkDirty(tl) + if counter <= 0 { + t2.SetActive(false) + e.MarkDirty(t2) + } + }, gwu.ETypeStateChange) + hiddenPan.Add(t2) + b3 := gwu.NewButton("Restart") + b3.AddEHandlerFunc(func(e gwu.Event) { + counter = 30 + t2.SetActive(true) + e.MarkDirty(t2) + }, gwu.ETypeClick) + p.Add(b3) + + event.MarkDirty(hiddenPan) + + return p +} + +type demo struct { + link gwu.Label + buildFunc func(gwu.Event) gwu.Comp + comp gwu.Comp // Lazily initialized demo comp +} +type pdemo *demo + +var extraHeadHtmls []string + +func buildShowcaseWin(sess gwu.Session) { + win := gwu.NewWindow("show", "Showcase of Features - Gowut") + for _, headHtml := range extraHeadHtmls { + win.AddHeadHtml(headHtml) + } + + win.Style().SetFullSize() + win.AddEHandlerFunc(func(e gwu.Event) { + switch e.Type() { + case gwu.ETypeWinLoad: + log.Println("LOADING window:", e.Src().Id()) + case gwu.ETypeWinUnload: + log.Println("UNLOADING window:", e.Src().Id()) + } + }, gwu.ETypeWinLoad, gwu.ETypeWinUnload) + + hiddenPan := gwu.NewNaturalPanel() + sess.SetAttr("hiddenPan", hiddenPan) + + header := gwu.NewHorizontalPanel() + header.Style().SetFullWidth().SetBorderBottom2(2, gwu.BrdStyleSolid, "#777777") + l := gwu.NewLabel("Gowut - Showcase of Features") + l.Style().SetFontWeight(gwu.FontWeightBold).SetFontSize("120%") + header.Add(l) + header.AddHConsumer() + header.Add(gwu.NewLabel("Theme:")) + themes := gwu.NewListBox([]string{"default", "debug"}) + themes.AddEHandlerFunc(func(e gwu.Event) { + win.SetTheme(themes.SelectedValue()) + e.ReloadWin("show") + }, gwu.ETypeChange) + header.Add(themes) + header.AddHSpace(10) + reset := gwu.NewLink("Reset", "#") + reset.SetTarget("") + reset.AddEHandlerFunc(func(e gwu.Event) { + e.RemoveSess() + e.ReloadWin("show") + }, gwu.ETypeClick) + header.Add(reset) + setNoWrap(header) + win.Add(header) + + content := gwu.NewHorizontalPanel() + content.SetCellPadding(1) + content.SetVAlign(gwu.VATop) + content.Style().SetFullSize() + + demoWrapper := gwu.NewPanel() + demoWrapper.Style().SetPaddingLeftPx(5) + demoWrapper.AddVSpace(10) + demoTitle := gwu.NewLabel("") + demoTitle.Style().SetFontWeight(gwu.FontWeightBold).SetFontSize("110%") + demoWrapper.Add(demoTitle) + demoWrapper.AddVSpace(10) + + links := gwu.NewPanel() + links.SetCellPadding(1) + links.Style().SetPaddingRightPx(5) + + demos := make(map[string]pdemo) + var selDemo pdemo + + selectDemo := func(d pdemo, e gwu.Event) { + if selDemo != nil { + selDemo.link.Style().SetBackground("") + if e != nil { + e.MarkDirty(selDemo.link) + } + demoWrapper.Remove(selDemo.comp) + } + selDemo = d + d.link.Style().SetBackground("#88ff88") + demoTitle.SetText(d.link.Text()) + if d.comp == nil { + d.comp = d.buildFunc(e) + } + demoWrapper.Add(d.comp) + if e != nil { + e.MarkDirty(d.link, demoWrapper) + } + } + + createDemo := func(name string, buildFunc func(gwu.Event) gwu.Comp) pdemo { + link := gwu.NewLabel(name) + link.Style().SetFullWidth().SetCursor(gwu.CursorPointer).SetDisplay(gwu.DisplayBlock).SetColor(gwu.ClrBlue) + demo := &demo{link: link, buildFunc: buildFunc} + link.AddEHandlerFunc(func(e gwu.Event) { + selectDemo(demo, e) + }, gwu.ETypeClick) + links.Add(link) + demos[name] = demo + return demo + } + + links.Style().SetFullHeight().SetBorderRight2(2, gwu.BrdStyleSolid, "#777777") + links.AddVSpace(5) + homeDemo := createDemo("Home", buildHomeDemo) + selectDemo(homeDemo, nil) + links.AddVSpace(5) + l = gwu.NewLabel("Component Palette") + l.Style().SetFontWeight(gwu.FontWeightBold).SetFontSize("110%") + links.Add(l) + links.AddVSpace(5) + l = gwu.NewLabel("Containers") + l.Style().SetFontWeight(gwu.FontWeightBold) + links.Add(l) + createDemo("Expander", buildExpanderDemo) + createDemo("Link (as Container)", buildLinkContainerDemo) + createDemo("Panel", buildPanelDemo) + createDemo("Table", buildTableDemo) + createDemo("TabPanel", buildTabPanelDemo) + createDemo("Window", buildWindowDemo) + links.AddVSpace(5) + l = gwu.NewLabel("Input components") + l.Style().SetFontWeight(gwu.FontWeightBold).SetDisplay(gwu.DisplayBlock) + links.Add(l) + createDemo("CheckBox", buildCheckBoxDemo) + createDemo("ListBox", buildListBoxDemo) + createDemo("TextBox", buildTextBoxDemo) + createDemo("PasswBox", buildPasswBoxDemo) + createDemo("RadioButton", buildRadioButtonDemo) + createDemo("SwitchButton", buildSwitchButtonDemo) + links.AddVSpace(5) + l = gwu.NewLabel("Other components") + l.Style().SetFontWeight(gwu.FontWeightBold) + links.Add(l) + createDemo("Button", buildButtonDemo) + createDemo("Html", buildHtmlDemo) + createDemo("Image", buildImageDemo) + createDemo("Label", buildLabelDemo) + createDemo("Link", buildLinkDemo) + createDemo("Timer", buildTimerDemo) + links.AddVConsumer() + setNoWrap(links) + content.Add(links) + content.Add(demoWrapper) + content.CellFmt(demoWrapper).Style().SetFullWidth() + + win.Add(content) + win.CellFmt(content).Style().SetFullSize() + + footer := gwu.NewHorizontalPanel() + footer.Style().SetFullWidth().SetBorderTop2(2, gwu.BrdStyleSolid, "#777777") + footer.Add(hiddenPan) + footer.AddHConsumer() + l = gwu.NewLabel("Copyright © 2013-2016 András Belicza. All rights reserved.") + l.Style().SetFontStyle(gwu.FontStyleItalic).SetFontSize("95%") + footer.Add(l) + footer.AddHSpace(10) + link := gwu.NewLink("Visit Gowut Home page", "https://sites.google.com/site/gowebuitoolkit/") + link.Style().SetFontStyle(gwu.FontStyleItalic).SetFontSize("95%") + footer.Add(link) + setNoWrap(footer) + win.Add(footer) + + sess.AddWin(win) +} + +// setNoWrap sets WhiteSpaceNowrap to all children of the specified panel. +func setNoWrap(panel gwu.Panel) { + count := panel.CompsCount() + for i := count - 1; i >= 0; i-- { + panel.CompAt(i).Style().SetWhiteSpace(gwu.WhiteSpaceNowrap) + } +} + +// SessHandler is our session handler to build the showcases window. +type SessHandler struct{} + +func (h SessHandler) Created(s gwu.Session) { + buildShowcaseWin(s) +} + +func (h SessHandler) Removed(s gwu.Session) {} + +// StartServer creates and starts the Gowut GUI server. +func StartServer(appName string) { + // Create GUI server + server := gwu.NewServer(appName, "") + server.AddStaticDir("/asdf", "w:/") + server.SetText("Gowut - Showcase of Features") + + server.AddSessCreatorName("show", "Showcase of Features - Gowut") + server.AddSHandler(SessHandler{}) + // Just for the demo: Add an extra "Gowut-Server" header to all responses holding the Gowut version + server.SetHeaders(map[string][]string{ + "Gowut-Server": {gwu.GowutVersion}, + }) + + // Start GUI server + if err := server.Start("show"); err != nil { + log.Println("Error: Cound not start GUI server:", err) + return + } +} diff --git a/examples/simple/simple_demo.go b/examples/simple/simple_demo.go index 8b738ff..1b74034 100644 --- a/examples/simple/simple_demo.go +++ b/examples/simple/simple_demo.go @@ -1,15 +1,15 @@ // Copyright (C) 2013 Andras Belicza. All rights reserved. -// +// // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// +// // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program. If not, see . @@ -18,8 +18,8 @@ package main import ( + "fmt" "github.com/icza/gowut/gwu" - "strconv" ) type MyButtonHandler struct { @@ -31,7 +31,7 @@ func (h *MyButtonHandler) HandleEvent(e gwu.Event) { if b, isButton := e.Src().(gwu.Button); isButton { b.SetText(b.Text() + h.text) h.counter++ - b.SetToolTip("You've clicked " + strconv.Itoa(h.counter) + " times!") + b.SetToolTip(fmt.Sprintf("You've clicked %d times!", h.counter)) e.MarkDirty(b) } } @@ -40,30 +40,30 @@ func main() { // Create and build a window win := gwu.NewWindow("main", "Test GUI Window") win.Style().SetFullWidth() - win.SetHAlign(gwu.HA_CENTER) + win.SetHAlign(gwu.HACenter) win.SetCellPadding(2) // Button which changes window content win.Add(gwu.NewLabel("I'm a label! Try clicking on the button=>")) btn := gwu.NewButton("Click me") - btn.AddEHandler(&MyButtonHandler{text: ":-)"}, gwu.ETYPE_CLICK) + btn.AddEHandler(&MyButtonHandler{text: ":-)"}, gwu.ETypeClick) win.Add(btn) btnsPanel := gwu.NewNaturalPanel() btn.AddEHandlerFunc(func(e gwu.Event) { // Create and add a new button... - newbtn := gwu.NewButton("Extra #" + strconv.Itoa(btnsPanel.CompsCount())) + newbtn := gwu.NewButton(fmt.Sprintf("Extra #%d", btnsPanel.CompsCount())) newbtn.AddEHandlerFunc(func(e gwu.Event) { btnsPanel.Remove(newbtn) // ...which removes itself when clicked e.MarkDirty(btnsPanel) - }, gwu.ETYPE_CLICK) + }, gwu.ETypeClick) btnsPanel.Insert(newbtn, 0) e.MarkDirty(btnsPanel) - }, gwu.ETYPE_CLICK) + }, gwu.ETypeClick) win.Add(btnsPanel) // ListBox examples p := gwu.NewHorizontalPanel() - p.Style().SetBorder2(1, gwu.BRD_STYLE_SOLID, gwu.CLR_BLACK) + p.Style().SetBorder2(1, gwu.BrdStyleSolid, gwu.ClrBlack) p.SetCellPadding(2) p.Add(gwu.NewLabel("A drop-down list being")) widelb := gwu.NewListBox([]string{"50", "100", "150", "200", "250"}) @@ -71,7 +71,7 @@ func main() { widelb.AddEHandlerFunc(func(e gwu.Event) { widelb.Style().SetWidth(widelb.SelectedValue() + "px") e.MarkDirty(widelb) - }, gwu.ETYPE_CHANGE) + }, gwu.ETypeChange) p.Add(widelb) p.Add(gwu.NewLabel("pixel wide. And a multi-select list:")) listBox := gwu.NewListBox([]string{"First", "Second", "Third", "Forth", "Fifth", "Sixth"}) @@ -80,9 +80,9 @@ func main() { p.Add(listBox) countLabel := gwu.NewLabel("Selected count: 0") listBox.AddEHandlerFunc(func(e gwu.Event) { - countLabel.SetText("Selected count: " + strconv.Itoa(len(listBox.SelectedIndices()))) + countLabel.SetText(fmt.Sprintf("Selected count: %d", len(listBox.SelectedIndices()))) e.MarkDirty(countLabel) - }, gwu.ETYPE_CHANGE) + }, gwu.ETypeChange) p.Add(countLabel) win.Add(p) @@ -90,27 +90,27 @@ func main() { greencb := gwu.NewCheckBox("I'm a check box. When checked, I'm green!") greencb.AddEHandlerFunc(func(e gwu.Event) { if greencb.State() { - greencb.Style().SetBackground(gwu.CLR_GREEN) + greencb.Style().SetBackground(gwu.ClrGreen) } else { greencb.Style().SetBackground("") } e.MarkDirty(greencb) - }, gwu.ETYPE_CLICK) + }, gwu.ETypeClick) win.Add(greencb) // TextBox with echo p = gwu.NewHorizontalPanel() p.Add(gwu.NewLabel("Enter your name:")) tb := gwu.NewTextBox("") - tb.AddSyncOnETypes(gwu.ETYPE_KEY_UP) + tb.AddSyncOnETypes(gwu.ETypeKeyUp) p.Add(tb) p.Add(gwu.NewLabel("You entered:")) nameLabel := gwu.NewLabel("") - nameLabel.Style().SetColor(gwu.CLR_RED) + nameLabel.Style().SetColor(gwu.ClrRed) tb.AddEHandlerFunc(func(e gwu.Event) { nameLabel.SetText(tb.Text()) e.MarkDirty(nameLabel) - }, gwu.ETYPE_CHANGE, gwu.ETYPE_KEY_UP) + }, gwu.ETypeChange, gwu.ETypeKeyUp) p.Add(nameLabel) win.Add(p) diff --git a/gwu/button.go b/gwu/button.go index 8c3fdda..4b38f3e 100644 --- a/gwu/button.go +++ b/gwu/button.go @@ -1,15 +1,15 @@ // Copyright (C) 2013 Andras Belicza. All rights reserved. -// +// // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// +// // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program. If not, see . @@ -18,9 +18,9 @@ package gwu // Button interface defines a clickable button. -// -// Suggested event type to handle actions: ETYPE_CLICK -// +// +// Suggested event type to handle actions: ETypeClick +// // Default style class: "gwu-Button" type Button interface { // Button is a component. @@ -53,18 +53,18 @@ func newButtonImpl(valueProviderJs []byte, text string) buttonImpl { } var ( - _STR_BUTTON_OP = []byte(`" + strButtonOp = []byte(`" ) -func (c *buttonImpl) Render(w writer) { - w.Write(_STR_BUTTON_OP) +func (c *buttonImpl) Render(w Writer) { + w.Write(strButtonOp) c.renderAttrsAndStyle(w) c.renderEHandlers(w) c.renderEnabled(w) - w.Write(_STR_GT) + w.Write(strGT) c.renderText(w) - w.Write(_STR_BUTTON_CL) + w.Write(strButtonCl) } diff --git a/gwu/comp.go b/gwu/comp.go index f711502..bf67b67 100644 --- a/gwu/comp.go +++ b/gwu/comp.go @@ -1,15 +1,15 @@ // Copyright (C) 2013 Andras Belicza. All rights reserved. -// +// // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// +// // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program. If not, see . @@ -120,7 +120,7 @@ type Comp interface { dispatchEvent(e Event) // Render renders the component (as HTML code). - Render(w writer) + Render(w Writer) } // Comp implementation. @@ -132,7 +132,7 @@ type compImpl struct { styleImpl *styleImpl // Style builder. handlers map[EventType][]EventHandler // Event handlers mapped from event type. Lazily initialized. - valueProviderJs []byte // If the HTML representation of the component has a value, this JavaScript code code must provide it. It will be automatically sent as the PARAM_COMP_ID parameter. + valueProviderJs []byte // If the HTML representation of the component has a value, this JavaScript code code must provide it. It will be automatically sent as the paramCompId parameter. syncOnETypes map[EventType]bool // Tells on which event types should comp value sync happen. } @@ -217,7 +217,7 @@ func (c *compImpl) DescendantOf(c2 Comp) bool { } // renderAttrs renders the explicitly set attributes and styles. -func (c *compImpl) renderAttrsAndStyle(w writer) { +func (c *compImpl) renderAttrsAndStyle(w Writer) { for name, value := range c.attrs { w.WriteAttr(name, value) } @@ -263,18 +263,18 @@ func (c *compImpl) AddSyncOnETypes(etypes ...EventType) { for _, etype := range etypes { if !c.syncOnETypes[etype] { // If not yet synced... c.syncOnETypes[etype] = true - c.AddEHandler(EMPTY_EHANDLER, etype) + c.AddEHandler(EmptyEHandler, etype) } } } var ( - _STR_SE_PREFIX = []byte(`="se(event,`) // `="se(event,` - _STR_SE_SUFFIX = []byte(`)"`) // `)"` + strSePrefix = []byte(`="se(event,`) // `="se(event,` + strSeSuffix = []byte(`)"`) // `)"` ) // rendrenderEventHandlers renders the event handlers as attributes. -func (c *compImpl) renderEHandlers(w writer) { +func (c *compImpl) renderEHandlers(w Writer) { for etype, _ := range c.handlers { etypeAttr := etypeAttrs[etype] if len(etypeAttr) == 0 { // Only general events are added to the etypeAttrs map @@ -283,17 +283,17 @@ func (c *compImpl) renderEHandlers(w writer) { // To render : ` ="se(event,etype,compId,value)"` // Example (checkbox onclick): ` onclick="se(event,0,4327,this.checked)"` - w.Write(_STR_SPACE) + w.Write(strSpace) w.Write(etypeAttr) - w.Write(_STR_SE_PREFIX) + w.Write(strSePrefix) w.Writev(int(etype)) - w.Write(_STR_COMMA) + w.Write(strComma) w.Writev(int(c.id)) if len(c.valueProviderJs) > 0 && c.syncOnETypes != nil && c.syncOnETypes[etype] { - w.Write(_STR_COMMA) + w.Write(strComma) w.Write(c.valueProviderJs) } - w.Write(_STR_SE_SUFFIX) + w.Write(strSeSuffix) } } @@ -310,5 +310,5 @@ func (c *compImpl) dispatchEvent(e Event) { // THIS IS AN EMPTY IMPLEMENTATION. // ALL COMPONENTS SHOULD DEFINE THEIR OWN -func (c *compImpl) Render(w writer) { +func (c *compImpl) Render(w Writer) { } diff --git a/gwu/comp_addons.go b/gwu/comp_addons.go index a415b5a..e9fac57 100644 --- a/gwu/comp_addons.go +++ b/gwu/comp_addons.go @@ -1,15 +1,15 @@ // Copyright (C) 2013 Andras Belicza. All rights reserved. -// +// // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// +// // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program. If not, see . @@ -52,7 +52,7 @@ func (c *hasTextImpl) SetText(text string) { } // renderText renders the text. -func (c *hasTextImpl) renderText(w writer) { +func (c *hasTextImpl) renderText(w Writer) { w.Writees(c.text) } @@ -83,12 +83,12 @@ func (c *hasEnabledImpl) SetEnabled(enabled bool) { c.enabled = enabled } -var _STR_DISABLED = []byte(` disabled="disabled"`) // ` disabled="disabled"` +var strDisabled = []byte(` disabled="disabled"`) // ` disabled="disabled"` // renderEnabled renders the enabled attribute. -func (c *hasEnabledImpl) renderEnabled(w writer) { +func (c *hasEnabledImpl) renderEnabled(w Writer) { if !c.enabled { - w.Write(_STR_DISABLED) + w.Write(strDisabled) } } @@ -120,7 +120,7 @@ func (c *hasUrlImpl) SetUrl(url string) { } // renderUrl renders the URL string. -func (c *hasUrlImpl) renderUrl(attr string, w writer) { +func (c *hasUrlImpl) renderUrl(attr string, w Writer) { w.WriteAttr(attr, c.url) } @@ -129,11 +129,11 @@ type HAlign string // Horizontal alignment constants. const ( - HA_LEFT HAlign = "left" // Horizontal left alignment - HA_CENTER HAlign = "center" // Horizontal center alignment - HA_RIGHT HAlign = "right" // Horizontal right alignment + HALeft HAlign = "left" // Horizontal left alignment + HACenter = "center" // Horizontal center alignment + HARight = "right" // Horizontal right alignment - HA_DEFAULT HAlign = "" // Browser default (or inherited) horizontal alignment + HADefault = "" // Browser default (or inherited) horizontal alignment ) // Vertical alignment type. @@ -141,11 +141,11 @@ type VAlign string // Vertical alignment constants. const ( - VA_TOP VAlign = "top" // Vertical top alignment - VA_MIDDLE VAlign = "middle" // Vertical center alignment - VA_BOTTOM VAlign = "bottom" // Vertical bottom alignment + VATop VAlign = "top" // Vertical top alignment + VAMiddle = "middle" // Vertical center alignment + VABottom = "bottom" // Vertical bottom alignment - VA_DEFAULT VAlign = "" // Browser default (or inherited) vertical alignment + VADefault = "" // Browser default (or inherited) vertical alignment ) // HasHVAlign interfaces defines a horizontal and a vertical @@ -234,13 +234,13 @@ type cellFmtImpl struct { } // newCellFmtImpl creates a new cellFmtImpl. -// Default horizontal alignment is HA_DEFAULT, -// default vertical alignment is VA_DEFAULT. +// Default horizontal alignment is HADefult, +// default vertical alignment is VADefault. func newCellFmtImpl() *cellFmtImpl { - // Initialize hasHVAlignImpl with HA_DEFAULT and VA_DEFAULT + // Initialize hasHVAlignImpl with HADefault and VADefault // so if aligns are not changed, they will not be rendered => // they will be inherited (from TR). - return &cellFmtImpl{hasHVAlignImpl: newHasHVAlignImpl(HA_DEFAULT, VA_DEFAULT)} + return &cellFmtImpl{hasHVAlignImpl: newHasHVAlignImpl(HADefault, VADefault)} } func (c *cellFmtImpl) Style() Style { @@ -278,46 +278,46 @@ func (c *cellFmtImpl) setIAttr(name string, value int) { // render renders the formatted HTML tag for the specified tag name. // tag must start with a less than sign, e.g. " // they will be inherited (from TR). - c := tableViewImpl{compImpl: newCompImpl(nil), hasHVAlignImpl: newHasHVAlignImpl(HA_DEFAULT, VA_DEFAULT)} + c := tableViewImpl{compImpl: newCompImpl(nil), hasHVAlignImpl: newHasHVAlignImpl(HADefault, VADefault)} c.SetCellSpacing(0) c.SetCellPadding(0) return c @@ -394,21 +394,21 @@ func (c *tableViewImpl) SetCellPadding(padding int) { c.SetIAttr("cellpadding", padding) } -var _STR_ST_VALIGN = []byte(` style="vertical-align:`) // ` style="vertical-align:` +var strStVAlign = []byte(` style="vertical-align:`) // ` style="vertical-align:` // renderTr renders an HTML TR tag with horizontal and vertical -// alignment info included. -func (c *tableViewImpl) renderTr(w writer) { - w.Write(_STR_TR_OP) - if c.halign != HA_DEFAULT { - w.Write(_STR_ALIGN) +// alignment info included. +func (c *tableViewImpl) renderTr(w Writer) { + w.Write(strTROp) + if c.halign != HADefault { + w.Write(strAlign) w.Writes(string(c.halign)) - w.Write(_STR_QUOTE) + w.Write(strQuote) } - if c.valign != VA_DEFAULT { - w.Write(_STR_ST_VALIGN) + if c.valign != VADefault { + w.Write(strStVAlign) w.Writes(string(c.valign)) - w.Write(_STR_QUOTE) + w.Write(strQuote) } - w.Write(_STR_GT) + w.Write(strGT) } diff --git a/gwu/css.go b/gwu/css.go index cd0f58b..20369d4 100644 --- a/gwu/css.go +++ b/gwu/css.go @@ -1,15 +1,15 @@ // Copyright (C) 2013 Andras Belicza. All rights reserved. -// +// // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// +// // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program. If not, see . @@ -19,21 +19,21 @@ package gwu // Built-in CSS themes. const ( - THEME_DEFAULT = "default" // Default CSS theme - THEME_DEBUG = "debug" // Debug CSS theme, useful for developing/debugging purposes. + ThemeDefault = "default" // Default CSS theme + ThemeDebug = "debug" // Debug CSS theme, useful for developing/debugging purposes. ) // resNameStaticCss returns the CSS resource name // for the specified CSS theme. func resNameStaticCss(theme string) string { // E.g. "gowut-default-0.8.0.css" - return "gowut-" + theme + "-" + GOWUT_VERSION + ".css" + return "gowut-" + theme + "-" + GowutVersion + ".css" } var staticCss map[string][]byte = make(map[string][]byte) func init() { - staticCss[resNameStaticCss(THEME_DEFAULT)] = []byte("" + + staticCss[resNameStaticCss(ThemeDefault)] = []byte("" + ` .gwuimg-collapsed {background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAATUlEQVQ4y83RsQkAMAhEURNc+iZw7KQNgnjGRlv5D0SRMQPgADjVbr3AuzCz1QJYKAUyiAYiqAx4aHe/p9XAn6C/IQ1kb9TfMATYcM5cL5cg3qDaS5UAAAAASUVORK5CYII=)} .gwuimg-expanded {background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAATElEQVQ4y2NgGGjACGNUVlb+J0Vje3s7IwMDAwMT1VxAiitgtlPfBcS4Atl22rgAnyvQbaedC7C5ApvtVHEBXlBZWfmfUKwwMQx5AADNQhjmAryM3wAAAABJRU5ErkJggg==)} @@ -79,8 +79,8 @@ body {font-family:Arial} .gwu-SwitchButton-On-Active:disabled, .gwu-SwitchButton-Off-Active:disabled, .gwu-SwitchButton-On-Inactive:disabled, .gwu-SwitchButton-Off-Inactive:disabled {color:black} .gwu-Expander {} -.gwu-Expander-Header, .gwu-Expander-Header-Expanded {padding-left:19px; cursor:pointer} -.gwu-Expander-Content {padding-left:19px} +.gwu-Expander-Header, .gwu-Expander-Header-Expanded {cursor:pointer} +.gwu-Expander-Header, .gwu-Expander-Header-Expanded, .gwu-Expander-Content {padding-left:19px} .gwu-TabBar {} .gwu-TabBar-Top {padding:0px 5px 0px 5px; border-bottom:5px solid #8080f8} @@ -93,7 +93,7 @@ body {font-family:Arial} .gwu-TabPanel-Content {border:1px solid #8080f8; width:100%; height:100%} `) - staticCss[resNameStaticCss(THEME_DEBUG)] = []byte(string(staticCss[resNameStaticCss(THEME_DEFAULT)]) + + staticCss[resNameStaticCss(ThemeDebug)] = []byte(string(staticCss[resNameStaticCss(ThemeDefault)]) + ` .gwu-Window td, .gwu-Table td, .gwu-Panel td, .gwu-TabPanel td {border:1px solid black} `) diff --git a/gwu/doc.go b/gwu/doc.go index 22b737d..bfff859 100644 --- a/gwu/doc.go +++ b/gwu/doc.go @@ -1,15 +1,15 @@ // Copyright (C) 2013 Andras Belicza. All rights reserved. -// +// // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// +// // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program. If not, see . @@ -111,8 +111,8 @@ Event handling is possible via event handlers. An event handler is an implementation of the EventHandler interface. Event handlers have to be attached to the components which will be the source of the event. Event handlers are registered to event types or kinds (EventType) such as click -event (ETYPE_CLICK), value change event (ETYPE_CHANGE), key up event -(ETYPE_KEY_UP) etc. +event (ETypeClick), value change event (ETypeChange), key up event +(ETypeKeyUp) etc. The HandleEvent method of an event handler gets an Event value which has multiple purposes and functions. 1) The event contains the parameters @@ -254,7 +254,7 @@ https://github.com/icza/gowut/blob/master/examples/simple/simple_demo.go if b, isButton := e.Src().(gwu.Button); isButton { b.SetText(b.Text() + h.text) h.counter++ - b.SetToolTip("You've clicked " + strconv.Itoa(h.counter) + " times!") + b.SetToolTip(fmt.Sprintf("You've clicked %d times!", h.counter)) e.MarkDirty(b) } } @@ -263,30 +263,30 @@ https://github.com/icza/gowut/blob/master/examples/simple/simple_demo.go // Create and build a window win := gwu.NewWindow("main", "Test GUI Window") win.Style().SetFullWidth() - win.SetHAlign(gwu.HA_CENTER) + win.SetHAlign(gwu.HaCenter) win.SetCellPadding(2) // Button which changes window content win.Add(gwu.NewLabel("I'm a label! Try clicking on the button=>")) btn := gwu.NewButton("Click me") - btn.AddEHandler(&MyButtonHandler{text: ":-)"}, gwu.ETYPE_CLICK) + btn.AddEHandler(&MyButtonHandler{text: ":-)"}, gwu.ETypeClick) win.Add(btn) btnsPanel := gwu.NewNaturalPanel() btn.AddEHandlerFunc(func(e gwu.Event) { // Create and add a new button... - newbtn := gwu.NewButton("Extra #" + strconv.Itoa(btnsPanel.CompsCount())) + newbtn := gwu.NewButton(fmt.Sprintf("Extra #%d", btnsPanel.CompsCount())) newbtn.AddEHandlerFunc(func(e gwu.Event) { btnsPanel.Remove(newbtn) // ...which removes itself when clicked e.MarkDirty(btnsPanel) - }, gwu.ETYPE_CLICK) + }, gwu.ETypeClick) btnsPanel.Insert(newbtn, 0) e.MarkDirty(btnsPanel) - }, gwu.ETYPE_CLICK) + }, gwu.ETypeClick) win.Add(btnsPanel) // ListBox examples p := gwu.NewHorizontalPanel() - p.Style().SetBorder2(1, gwu.BRD_STYLE_SOLID, gwu.CLR_BLACK) + p.Style().SetBorder2(1, gwu.BrdStyleSolid, gwu.ClrBlack) p.SetCellPadding(2) p.Add(gwu.NewLabel("A drop-down list being")) widelb := gwu.NewListBox([]string{"50", "100", "150", "200", "250"}) @@ -294,7 +294,7 @@ https://github.com/icza/gowut/blob/master/examples/simple/simple_demo.go widelb.AddEHandlerFunc(func(e gwu.Event) { widelb.Style().SetWidth(widelb.SelectedValue() + "px") e.MarkDirty(widelb) - }, gwu.ETYPE_CHANGE) + }, gwu.ETypeChange) p.Add(widelb) p.Add(gwu.NewLabel("pixel wide. And a multi-select list:")) listBox := gwu.NewListBox([]string{"First", "Second", "Third", "Forth", "Fifth", "Sixth"}) @@ -303,9 +303,9 @@ https://github.com/icza/gowut/blob/master/examples/simple/simple_demo.go p.Add(listBox) countLabel := gwu.NewLabel("Selected count: 0") listBox.AddEHandlerFunc(func(e gwu.Event) { - countLabel.SetText("Selected count: " + strconv.Itoa(len(listBox.SelectedIndices()))) + countLabel.SetText(fmt.Sprintf("Selected count: %d", len(listBox.SelectedIndices()))) e.MarkDirty(countLabel) - }, gwu.ETYPE_CHANGE) + }, gwu.ETypeChange) p.Add(countLabel) win.Add(p) @@ -313,27 +313,27 @@ https://github.com/icza/gowut/blob/master/examples/simple/simple_demo.go greencb := gwu.NewCheckBox("I'm a check box. When checked, I'm green!") greencb.AddEHandlerFunc(func(e gwu.Event) { if greencb.State() { - greencb.Style().SetBackground(gwu.CLR_GREEN) + greencb.Style().SetBackground(gwu.ClrGreen) } else { greencb.Style().SetBackground("") } e.MarkDirty(greencb) - }, gwu.ETYPE_CLICK) + }, gwu.ETypeClick) win.Add(greencb) // TextBox with echo p = gwu.NewHorizontalPanel() p.Add(gwu.NewLabel("Enter your name:")) tb := gwu.NewTextBox("") - tb.AddSyncOnETypes(gwu.ETYPE_KEY_UP) + tb.AddSyncOnETypes(gwu.ETypeKeyUp) p.Add(tb) p.Add(gwu.NewLabel("You entered:")) nameLabel := gwu.NewLabel("") - nameLabel.Style().SetColor(gwu.CLR_RED) + nameLabel.Style().SetColor(gwu.ClrRed) tb.AddEHandlerFunc(func(e gwu.Event) { nameLabel.SetText(tb.Text()) e.MarkDirty(nameLabel) - }, gwu.ETYPE_CHANGE, gwu.ETYPE_KEY_UP) + }, gwu.ETypeChange, gwu.ETypeKeyUp) p.Add(nameLabel) win.Add(p) @@ -359,7 +359,7 @@ generate multiple mouseover and mouseout events because the same HTML node is re under the mouse cursor). 2) Attaching onmousedown and onmouseup event handlers to a check box and re-rendering it -prevents ETYPE_CHANGE handlers being called when clicking on it. +prevents ETypeChange handlers being called when clicking on it. Closing @@ -383,11 +383,13 @@ Author email: gmail.com, user name: iczaaa Home page: https://sites.google.com/site/gowebuitoolkit/ -Source code: https://github.com/icza/gowut +Source code (public releases): https://github.com/icza/gowut + +Source code (development): https://github.com/icza/gowut.dev Discussion forum: https://groups.google.com/d/forum/gowebuitoolkit -Live demo: Coming soon... +Live demo: https://gowut-demo.appspot.com/show */ @@ -395,7 +397,7 @@ package gwu // Gowut version information. const ( - GOWUT_VERSION = "0.9.0" // Gowut version (major.minor.maintenance) - GOWUT_RELEASE_DATE = "2015-07-08 CET" // Gowut release date - GOWUT_REL_DATE_LAYOUT = "2006-01-02 MST" // Gowut release date layout (for time.Parse()) + GowutVersion = "1.0.0" // Gowut version (major.minor.maintenance[dev]) + GowutReleaseDate = "2016-03-08 CET" // Gowut release date + GowutRelDateLayout = "2006-01-02 MST" // Gowut release date layout (for time.Parse()) ) diff --git a/gwu/event.go b/gwu/event.go index 06944b0..be89009 100644 --- a/gwu/event.go +++ b/gwu/event.go @@ -1,15 +1,15 @@ // Copyright (C) 2013 Andras Belicza. All rights reserved. -// +// // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// +// // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program. If not, see . @@ -32,26 +32,26 @@ func (etype EventType) String() string { // Event types. const ( // General events for all components - ETYPE_CLICK EventType = iota // Mouse click event - ETYPE_DBL_CLICK // Mouse double click event - ETYPE_MOUSE_DOWN // Mouse down event - ETYPE_MOUSE_MOVE // Mouse move event - ETYPE_MOUSE_OVER // Mouse over event - ETYPE_MOUSE_OUT // Mouse out event - ETYPE_MOUSE_UP // Mouse up event - ETYPE_KEY_DOWN // Key down event - ETYPE_KEY_PRESS // Key press event - ETYPE_KEY_UP // Key up event - ETYPE_BLUR // Blur event (component loses focus) - ETYPE_CHANGE // Change event (value change) - ETYPE_FOCUS // Focus event (component gains focus) + ETypeClick EventType = iota // Mouse click event + ETypeDblClick // Mouse double click event + ETypeMousedown // Mouse down event + ETypeMouseMove // Mouse move event + ETypeMouseOver // Mouse over event + ETypeMouseOut // Mouse out event + ETypeMouseUp // Mouse up event + ETypeKeyDown // Key down event + ETypeKeyPress // Key press event + ETypeKeyUp // Key up event + ETypeBlur // Blur event (component loses focus) + ETypeChange // Change event (value change) + ETypeFocus // Focus event (component gains focus) // Window events (for Window only) - ETYPE_WIN_LOAD // Window load event - ETYPE_WIN_UNLOAD // Window unload event + ETypeWinLoad // Window load event + ETypeWinUnload // Window unload event // Internal events, generated and dispatched internally while processing another event - ETYPE_STATE_CHANGE // State change + ETypeStateChange // State change ) // Event type category. @@ -59,57 +59,57 @@ type EventCategory int // Event type categories. const ( - ECAT_GENERAL EventCategory = iota // General event type for all components - ECAT_WINDOW // Window event type for Window only - ECAT_INTERNAL // Internal event generated and dispatched internally while processing another event + ECatGeneral EventCategory = iota // General event type for all components + ECatWindow // Window event type for Window only + ECatInternal // Internal event generated and dispatched internally while processing another event - ECAT_UNKNOWN EventCategory = -1 // Unknown event category + ECatUnknown EventCategory = -1 // Unknown event category ) // Category returns the event type category. func (etype EventType) Category() EventCategory { switch { - case etype >= ETYPE_CLICK && etype <= ETYPE_FOCUS: - return ECAT_GENERAL - case etype >= ETYPE_WIN_LOAD && etype <= ETYPE_WIN_UNLOAD: - return ECAT_WINDOW - case etype >= ETYPE_STATE_CHANGE && etype <= ETYPE_STATE_CHANGE: - return ECAT_INTERNAL + case etype >= ETypeClick && etype <= ETypeFocus: + return ECatGeneral + case etype >= ETypeWinLoad && etype <= ETypeWinUnload: + return ECatWindow + case etype >= ETypeStateChange && etype <= ETypeStateChange: + return ECatInternal } - return ECAT_UNKNOWN + return ECatUnknown } // Attribute names for the general event types; only for the general event types. var etypeAttrs map[EventType][]byte = map[EventType][]byte{ - ETYPE_CLICK: []byte("onclick"), - ETYPE_DBL_CLICK: []byte("ondblclick"), - ETYPE_MOUSE_DOWN: []byte("onmousedown"), - ETYPE_MOUSE_MOVE: []byte("onmousemove"), - ETYPE_MOUSE_OVER: []byte("onmouseover"), - ETYPE_MOUSE_OUT: []byte("onmouseout"), - ETYPE_MOUSE_UP: []byte("onmouseup"), - ETYPE_KEY_DOWN: []byte("onkeydown"), - ETYPE_KEY_PRESS: []byte("onkeypress"), - ETYPE_KEY_UP: []byte("onkeyup"), - ETYPE_BLUR: []byte("onblur"), - ETYPE_CHANGE: []byte("onchange"), - ETYPE_FOCUS: []byte("onfocus")} + ETypeClick: []byte("onclick"), + ETypeDblClick: []byte("ondblclick"), + ETypeMousedown: []byte("onmousedown"), + ETypeMouseMove: []byte("onmousemove"), + ETypeMouseOver: []byte("onmouseover"), + ETypeMouseOut: []byte("onmouseout"), + ETypeMouseUp: []byte("onmouseup"), + ETypeKeyDown: []byte("onkeydown"), + ETypeKeyPress: []byte("onkeypress"), + ETypeKeyUp: []byte("onkeyup"), + ETypeBlur: []byte("onblur"), + ETypeChange: []byte("onchange"), + ETypeFocus: []byte("onfocus")} // Function names for window event types. var etypeFuncs map[EventType][]byte = map[EventType][]byte{ - ETYPE_WIN_LOAD: []byte("onload"), - ETYPE_WIN_UNLOAD: []byte("onbeforeunload")} // Bind it to onbeforeunload (instead of onunload) for several reasons (onunload might cause trouble for AJAX; onunload is not called in IE if page is just refreshed...) + ETypeWinLoad: []byte("onload"), + ETypeWinUnload: []byte("onbeforeunload")} // Bind it to onbeforeunload (instead of onunload) for several reasons (onunload might cause trouble for AJAX; onunload is not called in IE if page is just refreshed...) // Mouse button type. type MouseBtn int // Mouse buttons const ( - MOUSE_BTN_UNKNOWN MouseBtn = -1 // Unknown mouse button (info not available) - MOUSE_BTN_LEFT = 0 // Left mouse button - MOUSE_BTN_MIDDLE = 1 // Middle mouse button - MOUSE_BTN_RIGHT = 2 // Right mouse button + MouseBtnUnknown MouseBtn = -1 // Unknown mouse button (info not available) + MouseBtnLeft = 0 // Left mouse button + MouseBtnMiddle = 1 // Middle mouse button + MouseBtnRight = 2 // Right mouse button ) // Modifier key type. @@ -117,10 +117,10 @@ type ModKey int // Modifier key masks. const ( - MOD_KEY_ALT ModKey = 1 << iota // Alt key - MOD_KEY_CTRL // Control key - MOD_KEY_META // Meta key - MOD_KEY_SHIFT // Shift key + ModKeyAlt ModKey = 1 << iota // Alt key + ModKeyCtrl // Control key + ModKeyMeta // Meta key + ModKeyShift // Shift key ) // Key (keyboard key) type. @@ -128,70 +128,70 @@ type Key int // Some key codes. const ( - KEY_BACKSPACE Key = 8 - KEY_ENTER Key = 13 - KEY_SHIFT Key = 16 - KEY_CTRL Key = 17 - KEY_ALT Key = 18 - KEY_CAPS_LOCK Key = 20 - KEY_ESCAPE Key = 27 - KEY_SPACE Key = 32 - KEY_PG_UP Key = 33 - KEY_PG_DOWN Key = 34 - KEY_END Key = 35 - KEY_HOME Key = 36 - KEY_LEFT Key = 37 - KEY_UP Key = 38 - KEY_RIGHT Key = 39 - KEY_DOWN Key = 40 - KEY_PRINT_SCRN Key = 44 - KEY_INSERT Key = 45 - KEY_DEL Key = 46 - - KEY_0 Key = 48 - KEY_9 Key = 57 - - KEY_A Key = 65 - KEY_Z Key = 90 - - KEY_WIN Key = 91 - - KEY_NUMPAD_0 Key = 96 - KEY_NUMPAD_9 Key = 105 - KEY_NUMPAD_MUL Key = 106 - KEY_NUMPAD_PLUS Key = 107 - KEY_NUMPAD_MINUS Key = 109 - KEY_NUMPAD_DOT Key = 110 - KEY_NUMPAD_DIV Key = 111 - - KEY_F1 Key = 112 - KEY_F2 Key = 113 - KEY_F3 Key = 114 - KEY_F4 Key = 115 - KEY_F5 Key = 116 - KEY_F6 Key = 117 - KEY_F7 Key = 118 - KEY_F8 Key = 119 - KEY_F9 Key = 120 - KEY_F10 Key = 121 - KEY_F11 Key = 122 - KEY_F12 Key = 123 - - KEY_NUM_LOCK Key = 144 - KEY_SCROLL_LOCK Key = 145 + KeyBackspace Key = 8 + KeyEnter = 13 + KeyShift = 16 + KeyCtrl = 17 + KeyAlt = 18 + KeyCapsLock = 20 + KeyEscape = 27 + KeySpace = 32 + KeyPgUp = 33 + KeyPgDown = 34 + KeyEnd = 35 + KeyHome = 36 + KeyLeft = 37 + KeyUp = 38 + KeyRight = 39 + KeyDown = 40 + KeyPrintScrn = 44 + KeyInsert = 45 + KeyDel = 46 + + Key0 = 48 + Key9 = 57 + + KeyA = 65 + KeyZ = 90 + + KeyWin = 91 + + KeyNumpad0 = 96 + KeyNumPad9 = 105 + KeyNumpadMul = 106 + KeyNumpadPlus = 107 + KeyNumpadMinus = 109 + KeyNumpadDot = 110 + KeyNumpadDiv = 111 + + KeyF1 = 112 + KeyF2 = 113 + KeyF3 = 114 + KeyF4 = 115 + KeyF5 = 116 + KeyF6 = 117 + KeyF7 = 118 + KeyF8 = 119 + KeyF9 = 120 + KeyF10 = 121 + KeyF11 = 122 + KeyF12 = 123 + + KeyNumLock = 144 + KeyScrollLock = 145 ) // Empty event handler which does nothing. -const EMPTY_EHANDLER emptyEventHandler = 0 +const EmptyEHandler emptyEventHandler = 0 // EventHandler interface defines a handler capable of handling events. type EventHandler interface { // Handles the event. - // + // // If components are modified in a way that their view changes, // these components must be marked dirty in the event object // (so the client will see up-to-date state). - // + // // If the component tree is modified (new component added // or removed for example), then the Container whose structure // was modified has to be marked dirty. @@ -222,7 +222,7 @@ type Event interface { MouseWin() (x, y int) // MouseBtn returns the mouse button. - // If no mouse button info is available, MOUSE_BTN_UNKNOWN is returned. + // If no mouse button info is available, MouseBtnUnknown is returned. MouseBtn() MouseBtn // ModKeys returns the states of the modifier keys. @@ -245,15 +245,15 @@ type Event interface { // MarkDirty marks components dirty, // causing them to be re-rendered after processing the current event. // Component re-rendering happens without page reload in the browser. - // + // // Note: the Window itself (which is a Comp) can also be marked dirty // causing the whole window content to be re-rendered without page reload! - // + // // Marking a component dirty also marks all of its decendants dirty, recursively. - // + // // Also note that components will not be re-rendered multiple times. // For example if a child component and its parent component are both - // marked dirty, the child component will only be re-rendered once. + // marked dirty, the child component will only be re-rendered once. MarkDirty(comps ...Comp) // SetFocusedComp sets the component to be focused after processing @@ -387,7 +387,7 @@ func (e *eventImpl) MarkDirty(comps ...Comp) { // dirty returns true if the specified component is already marked dirty. // Note that a component being dirty makes all of its descendants dirty, recursively. -// +// // Also note that the "dirty" flag might change during the event dispatching // because if a "clean" component is moved from a dirty parent to a clean parent, // its inherited dirty flag changes from true to false. diff --git a/gwu/examples_test.go b/gwu/examples_test.go index 1f0240c..d474c70 100644 --- a/gwu/examples_test.go +++ b/gwu/examples_test.go @@ -1,51 +1,51 @@ // Copyright (C) 2013 Andras Belicza. All rights reserved. -// +// // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// +// // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program. If not, see . -package gwu_test +package gwu import ( "code/google/com/p/gowut/gwu" ) -// Example code determining which button was clicked. +// Example code determining which button was clicked. func ExampleButton() { b := gwu.NewButton("Click me") b.AddEHandlerFunc(func(e gwu.Event) { - if e.MouseBtn() == gwu.MOUSE_BTN_MIDDLE { + if e.MouseBtn() == gwu.MouseBtnMiddle { // Middle click } - }, gwu.ETYPE_CLICK) + }, gwu.ETypeClick) } -// Example code determining what kind of key is involved. +// Example code determining what kind of key is involved. func ExampleTextBox() { b := gwu.NewTextBox("") - tb.AddSyncOnETypes(gwu.ETYPE_KEY_UP) // This is here so we will see up-to-date value in the event handler + tb.AddSyncOnETypes(gwu.ETypeKeyUp) // This is here so we will see up-to-date value in the event handler b.AddEHandlerFunc(func(e gwu.Event) { - if e.ModKey(gwu.MOD_KEY_SHIFT) { + if e.ModKey(gwu.ModKeyShift) { // SHIFT is pressed } c := e.KeyCode() switch { - case c == gwu.KEY_ENTER: // Enter - case c >= gwu.KEY_0 && c <= gwu.KEY_9: + case c == gwu.KeyEnter: // Enter + case c >= gwu.Key0 && c <= gwu.Key9: fallthrough - case c >= gwu.KEY_NUMPAD_0 && c <= gwuKEY_NUMPAD_9: // Number - case c >= gwu.KEY_A && c <= gwu.KEY_Z: // Letter - case c >= gwu.KEY_F1 && c <= gwu.KEY_F12: // Function key + case c >= gwu.KeyNumpad0 && c <= gwu.KeyNumpad9: // Number + case c >= gwu.KeyA && c <= gwu.KeyZ: // Letter + case c >= gwu.KeyF1 && c <= gwu.KeyF12: // Function key } - }, gwu.ETYPE_KEY_UP) + }, gwu.ETypeKeyUp) } diff --git a/gwu/expander.go b/gwu/expander.go index 8c0c5af..e625fcb 100644 --- a/gwu/expander.go +++ b/gwu/expander.go @@ -1,15 +1,15 @@ // Copyright (C) 2013 Andras Belicza. All rights reserved. -// +// // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// +// // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program. If not, see . @@ -19,12 +19,12 @@ package gwu // Expander interface defines a component which can show and hide // another component when clicked on the header. -// -// You can register ETYPE_STATE_CHANGE event handlers which will be called when the user +// +// You can register ETypeStateChange event handlers which will be called when the user // expands or collapses the expander by clicking on the header. The event source will be // the expander. The event will have a parent event whose source will be the clicked // header component and will contain the mouse coordinates. -// +// // Default style classes: "gwu-Expander", "gwu-Expander-Header", // "gwuimg-collapsed", "gwu-Expander-Header-Expanded", "gwuimg-expanded", // "gwu-Expander-Content" @@ -73,8 +73,8 @@ type expanderImpl struct { // By default expanders are collapsed. func NewExpander() Expander { c := &expanderImpl{tableViewImpl: newTableViewImpl(), expanded: true, headerFmt: newCellFmtImpl(), contentFmt: newCellFmtImpl()} - c.headerFmt.SetAlign(HA_LEFT, VA_MIDDLE) - c.contentFmt.SetAlign(HA_LEFT, VA_TOP) + c.headerFmt.SetAlign(HALeft, VAMiddle) + c.contentFmt.SetAlign(HALeft, VATop) c.Style().AddClass("gwu-Expander") // Init styles by changing expanded state, to the default value. c.SetExpanded(false) @@ -151,10 +151,10 @@ func (c *expanderImpl) SetHeader(header Comp) { header.AddEHandlerFunc(func(e Event) { c.SetExpanded(!c.expanded) e.MarkDirty(c) - if c.handlers[ETYPE_STATE_CHANGE] != nil { - c.dispatchEvent(e.forkEvent(ETYPE_STATE_CHANGE, c)) + if c.handlers[ETypeStateChange] != nil { + c.dispatchEvent(e.forkEvent(ETypeStateChange, c)) } - }, ETYPE_CLICK) + }, ETypeClick) } func (c *expanderImpl) Content() Comp { @@ -202,23 +202,23 @@ func (c *expanderImpl) ContentFmt() CellFmt { return c.contentFmt } -func (c *expanderImpl) Render(w writer) { - w.Write(_STR_TABLE_OP) +func (c *expanderImpl) Render(w Writer) { + w.Write(strTableOp) c.renderAttrsAndStyle(w) c.renderEHandlers(w) - w.Write(_STR_GT) + w.Write(strGT) if c.header != nil { c.renderTr(w) - c.headerFmt.render(_STR_TD_OP, w) + c.headerFmt.render(strTDOp, w) c.header.Render(w) } if c.expanded && c.content != nil { c.renderTr(w) - c.contentFmt.render(_STR_TD_OP, w) + c.contentFmt.render(strTDOp, w) c.content.Render(w) } - w.Write(_STR_TABLE_CL) + w.Write(strTableCl) } diff --git a/gwu/html.go b/gwu/html.go index 4db2c18..c45aaaa 100644 --- a/gwu/html.go +++ b/gwu/html.go @@ -1,15 +1,15 @@ // Copyright (C) 2013 Andras Belicza. All rights reserved. -// +// // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// +// // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program. If not, see . @@ -18,7 +18,7 @@ package gwu // Html interface defines a component which wraps an HTML text into a component. -// +// // Default style class: "gwu-Html" type Html interface { // Html is a component. @@ -53,13 +53,13 @@ func (c *htmlImpl) SetHtml(html string) { c.html = html } -func (c *htmlImpl) Render(w writer) { - w.Write(_STR_SPAN_OP) +func (c *htmlImpl) Render(w Writer) { + w.Write(strSpanOp) c.renderAttrsAndStyle(w) c.renderEHandlers(w) - w.Write(_STR_GT) + w.Write(strGT) w.Writes(c.html) - w.Write(_STR_SPAN_CL) + w.Write(strSpanCl) } diff --git a/gwu/id.go b/gwu/id.go index 6a098cd..963b316 100644 --- a/gwu/id.go +++ b/gwu/id.go @@ -1,15 +1,15 @@ // Copyright (C) 2013 Andras Belicza. All rights reserved. -// +// // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// +// // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program. If not, see . @@ -19,6 +19,7 @@ package gwu import ( "strconv" + "sync/atomic" ) // The type of the ids of the components. @@ -41,19 +42,10 @@ func AtoID(s string) (ID, error) { // Component id generation and provider -// A channel used to generate unique ids -var idChan chan ID = make(chan ID) - -// init stats a new go routine to generate unique ids -func init() { - go func() { - for i := 0; ; i++ { - idChan <- ID(i) - } - }() -} +// Last used value for ID +var lastId = new(int64) // nextCompId returns a unique component id func nextCompId() ID { - return <-idChan + return ID(atomic.AddInt64(lastId, 1)) } diff --git a/gwu/image.go b/gwu/image.go index b6d0da8..178739a 100644 --- a/gwu/image.go +++ b/gwu/image.go @@ -1,15 +1,15 @@ // Copyright (C) 2013 Andras Belicza. All rights reserved. -// +// // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// +// // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program. If not, see . @@ -18,7 +18,7 @@ package gwu // Image interface defines an image. -// +// // Default style class: "gwu-Image" type Image interface { // Image is a component. @@ -47,17 +47,17 @@ func NewImage(text, url string) Image { } var ( - _STR_IMG_OP = []byte("`) // `">` + strImgOp = []byte("`) // `">` ) -func (c *imageImpl) Render(w writer) { - w.Write(_STR_IMG_OP) +func (c *imageImpl) Render(w Writer) { + w.Write(strImgOp) c.renderUrl("src", w) c.renderAttrsAndStyle(w) c.renderEHandlers(w) - w.Write(_STR_ALT) + w.Write(strAlt) c.renderText(w) - w.Write(_STR_IMG_CL) + w.Write(strImgCl) } diff --git a/gwu/js.go b/gwu/js.go index 1b48eba..8762d57 100644 --- a/gwu/js.go +++ b/gwu/js.go @@ -1,15 +1,15 @@ // Copyright (C) 2013 Andras Belicza. All rights reserved. -// +// // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// +// // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program. If not, see . @@ -22,7 +22,7 @@ import ( ) // Static JavaScript resource name -const _RES_NAME_STATIC_JS = "gowut-" + GOWUT_VERSION + ".js" +const resNameStaticJs = "gowut-" + GowutVersion + ".js" // Static javascript code var staticJs []byte @@ -31,29 +31,29 @@ func init() { // Init staticJs staticJs = []byte("" + // Param consts - "var _pEventType='" + _PARAM_EVENT_TYPE + - "',_pCompId='" + _PARAM_COMP_ID + - "',_pCompValue='" + _PARAM_COMP_VALUE + - "',_pFocCompId='" + _PARAM_FOCUSED_COMP_ID + - "',_pMouseWX='" + _PARAM_MOUSE_WX + - "',_pMouseWY='" + _PARAM_MOUSE_WY + - "',_pMouseX='" + _PARAM_MOUSE_X + - "',_pMouseY='" + _PARAM_MOUSE_Y + - "',_pMouseBtn='" + _PARAM_MOUSE_BTN + - "',_pModKeys='" + _PARAM_MOD_KEYS + - "',_pKeyCode='" + _PARAM_KEY_CODE + + "var _pEventType='" + paramEventType + + "',_pCompId='" + paramCompId + + "',_pCompValue='" + paramCompValue + + "',_pFocCompId='" + paramFocusedCompId + + "',_pMouseWX='" + paramMouseWX + + "',_pMouseWY='" + paramMouseWY + + "',_pMouseX='" + paramMouseX + + "',_pMouseY='" + paramMouseY + + "',_pMouseBtn='" + paramMouseBtn + + "',_pModKeys='" + paramModKeys + + "',_pKeyCode='" + paramKeyCode + "';\n" + // Modifier key masks - "var _modKeyAlt=" + strconv.Itoa(int(MOD_KEY_ALT)) + - ",_modKeyCtlr=" + strconv.Itoa(int(MOD_KEY_CTRL)) + - ",_modKeyMeta=" + strconv.Itoa(int(MOD_KEY_META)) + - ",_modKeyShift=" + strconv.Itoa(int(MOD_KEY_SHIFT)) + + "var _modKeyAlt=" + strconv.Itoa(int(ModKeyAlt)) + + ",_modKeyCtlr=" + strconv.Itoa(int(ModKeyCtrl)) + + ",_modKeyMeta=" + strconv.Itoa(int(ModKeyMeta)) + + ",_modKeyShift=" + strconv.Itoa(int(ModKeyShift)) + ";\n" + // Event response action consts - "var _eraNoAction=" + strconv.Itoa(_ERA_NO_ACTION) + - ",_eraReloadWin=" + strconv.Itoa(_ERA_RELOAD_WIN) + - ",_eraDirtyComps=" + strconv.Itoa(_ERA_DIRTY_COMPS) + - ",_eraFocusComp=" + strconv.Itoa(_ERA_FOCUS_COMP) + + "var _eraNoAction=" + strconv.Itoa(eraNoAction) + + ",_eraReloadWin=" + strconv.Itoa(eraReloadWin) + + ",_eraDirtyComps=" + strconv.Itoa(eraDirtyComps) + + ",_eraFocusComp=" + strconv.Itoa(eraFocusComp) + ";" + ` diff --git a/gwu/label.go b/gwu/label.go index 085d4a1..c7b151e 100644 --- a/gwu/label.go +++ b/gwu/label.go @@ -1,15 +1,15 @@ // Copyright (C) 2013 Andras Belicza. All rights reserved. -// +// // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// +// // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program. If not, see . @@ -18,7 +18,7 @@ package gwu // Label interface defines a component which wraps a text into a component. -// +// // Default style class: "gwu-Label" type Label interface { // Label is a component. @@ -41,13 +41,13 @@ func NewLabel(text string) Label { return c } -func (c *labelImpl) Render(w writer) { - w.Write(_STR_SPAN_OP) +func (c *labelImpl) Render(w Writer) { + w.Write(strSpanOp) c.renderAttrsAndStyle(w) c.renderEHandlers(w) - w.Write(_STR_GT) + w.Write(strGT) c.renderText(w) - w.Write(_STR_SPAN_CL) + w.Write(strSpanCl) } diff --git a/gwu/link.go b/gwu/link.go index d8769e6..a79e438 100644 --- a/gwu/link.go +++ b/gwu/link.go @@ -1,15 +1,15 @@ // Copyright (C) 2013 Andras Belicza. All rights reserved. -// +// // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// +// // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program. If not, see . @@ -21,7 +21,7 @@ package gwu // Links are usually used with a text, although Link is a // container, and allows to set a child component // which if set will also be a part of the clickable link. -// +// // Default style class: "gwu-Link" type Link interface { // Link is a Container. @@ -127,16 +127,16 @@ func (c *linkImpl) SetComp(c2 Comp) { } var ( - _STR_A_OP = []byte("") // "" + strAOp = []byte("") // "" ) -func (c *linkImpl) Render(w writer) { - w.Write(_STR_A_OP) +func (c *linkImpl) Render(w Writer) { + w.Write(strAOp) c.renderUrl("href", w) c.renderAttrsAndStyle(w) c.renderEHandlers(w) - w.Write(_STR_GT) + w.Write(strGT) c.renderText(w) @@ -144,5 +144,5 @@ func (c *linkImpl) Render(w writer) { c.comp.Render(w) } - w.Write(_STR_A_CL) + w.Write(strACl) } diff --git a/gwu/listbox.go b/gwu/listbox.go index ab5c224..d76a25c 100644 --- a/gwu/listbox.go +++ b/gwu/listbox.go @@ -1,15 +1,15 @@ // Copyright (C) 2013 Andras Belicza. All rights reserved. -// +// // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// +// // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program. If not, see . @@ -25,9 +25,9 @@ import ( // ListBox interface defines a component which allows selecting one or multiple values // from a predefined list. -// -// Suggested event type to handle changes: ETYPE_CHANGE -// +// +// Suggested event type to handle changes: ETypeChange +// // Default style class: "gwu-ListBox" type ListBox interface { // ListBox is a component @@ -81,7 +81,7 @@ type ListBox interface { // ListBox implementation. type listBoxImpl struct { - compImpl // Component implementation + compImpl // Component implementation hasEnabledImpl // Has enabled implementation values []string // Values to choose from @@ -91,13 +91,13 @@ type listBoxImpl struct { } var ( - _STR_SELIDXS = []byte("selIdxs(this)") // "selIdxs(this)" + strSelidx = []byte("selIdxs(this)") // "selIdxs(this)" ) // NewListBox creates a new ListBox. func NewListBox(values []string) ListBox { - c := &listBoxImpl{newCompImpl(_STR_SELIDXS), newHasEnabledImpl(), values, false, make([]bool, len(values)), 1} - c.AddSyncOnETypes(ETYPE_CHANGE) + c := &listBoxImpl{newCompImpl(strSelidx), newHasEnabledImpl(), values, false, make([]bool, len(values)), 1} + c.AddSyncOnETypes(ETypeChange) c.Style().AddClass("gwu-ListBox") return c } @@ -180,7 +180,7 @@ func (c *listBoxImpl) ClearSelected() { } func (c *listBoxImpl) preprocessEvent(event Event, r *http.Request) { - value := r.FormValue(_PARAM_COMP_VALUE) + value := r.FormValue(paramCompValue) if len(value) == 0 { return } @@ -195,34 +195,34 @@ func (c *listBoxImpl) preprocessEvent(event Event, r *http.Request) { } var ( - _STR_SELECT_OP = []byte("`) // `") // "" - _STR_SELECT_CL = []byte("") // "" + strSelectOp = []byte("`) // `") // "" + strSelectCl = []byte("") // "" ) -func (c *listBoxImpl) Render(w writer) { - w.Write(_STR_SELECT_OP) +func (c *listBoxImpl) Render(w Writer) { + w.Write(strSelectOp) if c.multi { - w.Write(_STR_MULTIPLE) + w.Write(strMultiple) } w.WriteAttr("size", strconv.Itoa(c.rows)) c.renderAttrsAndStyle(w) c.renderEnabled(w) c.renderEHandlers(w) - w.Write(_STR_GT) + w.Write(strGT) for i, value := range c.values { if c.selected[i] { - w.Write(_STR_OPTION_OP_SEL) + w.Write(strOptionOpSel) } else { - w.Write(_STR_OPTION_OP) + w.Write(strOptionOp) } w.Writees(value) - w.Write(_STR_OPTION_CL) + w.Write(strOptionCl) } - w.Write(_STR_SELECT_CL) + w.Write(strSelectCl) } diff --git a/gwu/panel.go b/gwu/panel.go index 3939c32..dae3349 100644 --- a/gwu/panel.go +++ b/gwu/panel.go @@ -1,15 +1,15 @@ // Copyright (C) 2013 Andras Belicza. All rights reserved. -// +// // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// +// // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program. If not, see . @@ -26,16 +26,16 @@ type Layout int // Layout strategies. const ( - LAYOUT_NATURAL Layout = iota // Natural layout: elements are displayed in their natural order. - LAYOUT_VERTICAL // Vertical layout: elements are layed out vertically. - LAYOUT_HORIZONTAL // Horizontal layout: elements are layed out horizontally. + LayoutNatural Layout = iota // Natural layout: elements are displayed in their natural order. + LayoutVertical // Vertical layout: elements are layed out vertically. + LayoutHorizontal // Horizontal layout: elements are layed out horizontally. ) // PanelView interface defines a container which stores child components // sequentially (one dimensional, associated with an index), and lays out // its children in a row or column using TableView based on a layout strategy, // but does not define the way how child components can be added. -// +// // Default style class: "gwu-Panel" type PanelView interface { // PanelView is a TableView. @@ -60,7 +60,7 @@ type PanelView interface { // CellFmt returns the cell formatter of the specified child component. // If the specified component is not a child, nil is returned. - // Cell formatting has no effect if layout is LAYOUT_NATURAL. + // Cell formatting has no effect if layout is LayoutNatural. CellFmt(c Comp) CellFmt } @@ -82,26 +82,26 @@ type Panel interface { Insert(c Comp, idx int) bool // AddHSpace adds and returns a fixed-width horizontal space consumer. - // Useful when layout is LAYOUT_HORIZONTAL. + // Useful when layout is LayoutHorizontal. AddHSpace(width int) Comp // AddVSpace adds and returns a fixed-height vertical space consumer. - // Useful when layout is LAYOUT_VERTICAL. + // Useful when layout is LayoutVertical. AddVSpace(height int) Comp // AddSpace adds and returns a fixed-size space consumer. AddSpace(width, height int) Comp // AddHConsumer adds and returns a horizontal (free) space consumer. - // Useful when layout is LAYOUT_HORIZONTAL. - // + // Useful when layout is LayoutHorizontal. + // // Tip: When adding a horizontal space consumer, you may set the // white space style attribute of other components in the the panel - // to WHITE_SPACE_NOWRAP to avoid texts getting wrapped to multiple lines. + // to WhiteSpaceNowrap to avoid texts getting wrapped to multiple lines. AddHConsumer() Comp // AddVConsumer adds and returns a vertical (free) space consumer. - // Useful when layout is LAYOUT_VERTICAL. + // Useful when layout is LayoutVertical. AddVConsumer() Comp } @@ -115,9 +115,9 @@ type panelImpl struct { } // NewPanel creates a new Panel. -// Default layout strategy is LAYOUT_VERTICAL, -// default horizontal alignment is HA_DEFAULT, -// default vertical alignment is VA_DEFAULT. +// Default layout strategy is LayoutVertical, +// default horizontal alignment is HADefault, +// default vertical alignment is VADefault. func NewPanel() Panel { c := newPanelImpl() c.Style().AddClass("gwu-Panel") @@ -125,36 +125,36 @@ func NewPanel() Panel { } // NewNaturalPanel creates a new Panel initialized with -// LAYOUT_NATURAL layout. -// Default horizontal alignment is HA_DEFAULT, -// default vertical alignment is VA_DEFAULT. +// LayoutNatural layout. +// Default horizontal alignment is HADefault, +// default vertical alignment is VADefault. func NewNaturalPanel() Panel { p := NewPanel() - p.SetLayout(LAYOUT_NATURAL) + p.SetLayout(LayoutNatural) return p } // NewHorizontalPanel creates a new Panel initialized with -// LAYOUT_HORIZONTAL layout. -// Default horizontal alignment is HA_DEFAULT, -// default vertical alignment is VA_DEFAULT. +// LayoutHorizontal layout. +// Default horizontal alignment is HADefault, +// default vertical alignment is VADefault. func NewHorizontalPanel() Panel { p := NewPanel() - p.SetLayout(LAYOUT_HORIZONTAL) + p.SetLayout(LayoutHorizontal) return p } // NewVerticalPanel creates a new Panel initialized with -// LAYOUT_VERTICAL layout. -// Default horizontal alignment is HA_DEFAULT, -// default vertical alignment is VA_DEFAULT. +// LayoutVertical layout. +// Default horizontal alignment is HADefault, +// default vertical alignment is VADefault. func NewVerticalPanel() Panel { return NewPanel() } // newPanelImpl creates a new panelImpl. func newPanelImpl() panelImpl { - return panelImpl{tableViewImpl: newTableViewImpl(), layout: LAYOUT_VERTICAL, comps: make([]Comp, 0, 2)} + return panelImpl{tableViewImpl: newTableViewImpl(), layout: LayoutVertical, comps: make([]Comp, 0, 2)} } func (c *panelImpl) Remove(c2 Comp) bool { @@ -280,21 +280,21 @@ func (c *panelImpl) Insert(c2 Comp, idx int) bool { func (c *panelImpl) AddHSpace(width int) Comp { l := NewLabel("") - l.Style().SetDisplay(DISPLAY_BLOCK).SetWidthPx(width) + l.Style().SetDisplay(DisplayBlock).SetWidthPx(width) c.Add(l) return l } func (c *panelImpl) AddVSpace(height int) Comp { l := NewLabel("") - l.Style().SetDisplay(DISPLAY_BLOCK).SetHeightPx(height) + l.Style().SetDisplay(DisplayBlock).SetHeightPx(height) c.Add(l) return l } func (c *panelImpl) AddSpace(width, height int) Comp { l := NewLabel("") - l.Style().SetDisplay(DISPLAY_BLOCK).SetSizePx(width, height) + l.Style().SetDisplay(DisplayBlock).SetSizePx(width, height) c.Add(l) return l } @@ -313,40 +313,40 @@ func (c *panelImpl) AddVConsumer() Comp { return l } -func (c *panelImpl) Render(w writer) { +func (c *panelImpl) Render(w Writer) { switch c.layout { - case LAYOUT_NATURAL: + case LayoutNatural: c.layoutNatural(w) - case LAYOUT_HORIZONTAL: + case LayoutHorizontal: c.layoutHorizontal(w) - case LAYOUT_VERTICAL: + case LayoutVertical: c.layoutVertical(w) } } // layoutNatural renders the panel and the child components // using the natural layout strategy. -func (c *panelImpl) layoutNatural(w writer) { +func (c *panelImpl) layoutNatural(w Writer) { // No wrapper table but we still need a wrapper tag for attributes... - w.Write(_STR_SPAN_OP) + w.Write(strSpanOp) c.renderAttrsAndStyle(w) c.renderEHandlers(w) - w.Write(_STR_GT) + w.Write(strGT) for _, c2 := range c.comps { c2.Render(w) } - w.Write(_STR_SPAN_CL) + w.Write(strSpanCl) } // layoutHorizontal renders the panel and the child components // using the horizontal layout strategy. -func (c *panelImpl) layoutHorizontal(w writer) { - w.Write(_STR_TABLE_OP) +func (c *panelImpl) layoutHorizontal(w Writer) { + w.Write(strTableOp) c.renderAttrsAndStyle(w) c.renderEHandlers(w) - w.Write(_STR_GT) + w.Write(strGT) c.renderTr(w) @@ -355,16 +355,16 @@ func (c *panelImpl) layoutHorizontal(w writer) { c2.Render(w) } - w.Write(_STR_TABLE_CL) + w.Write(strTableCl) } // layoutVertical renders the panel and the child components // using the vertical layout strategy. -func (c *panelImpl) layoutVertical(w writer) { - w.Write(_STR_TABLE_OP) +func (c *panelImpl) layoutVertical(w Writer) { + w.Write(strTableOp) c.renderAttrsAndStyle(w) c.renderEHandlers(w) - w.Write(_STR_GT) + w.Write(strGT) // There is the same TR tag for each cell: trWriter := bytes.NewBuffer(nil) @@ -377,14 +377,14 @@ func (c *panelImpl) layoutVertical(w writer) { c2.Render(w) } - w.Write(_STR_TABLE_CL) + w.Write(strTableCl) } // renderTd renders the formatted HTML TD tag for the specified child component. -func (c *panelImpl) renderTd(c2 Comp, w writer) { +func (c *panelImpl) renderTd(c2 Comp, w Writer) { if cf := c.cellFmts[c2.Id()]; cf == nil { - w.Write(_STR_TD) + w.Write(strTD) } else { - cf.render(_STR_TD_OP, w) + cf.render(strTDOp, w) } } diff --git a/gwu/server.go b/gwu/server.go index 4992767..8f072f9 100644 --- a/gwu/server.go +++ b/gwu/server.go @@ -1,15 +1,15 @@ // Copyright (C) 2013 Andras Belicza. All rights reserved. -// +// // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// +// // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program. If not, see . @@ -23,8 +23,6 @@ import ( "fmt" "log" "net/http" - "os/exec" - "runtime" "strconv" "strings" "time" @@ -32,36 +30,36 @@ import ( // Internal path constants. const ( - _PATH_STATIC = "_gwu_static/" // App path-relative path for GWU static contents. - _PATH_EVENT = "e" // Window-relative path for sending events - _PATH_RENDER_COMP = "rc" // Window-relative path for rendering a component + pathStatic = "_gwu_static/" // App path-relative path for GWU static contents. + pathEvent = "e" // Window-relative path for sending events + pathRenderComp = "rc" // Window-relative path for rendering a component ) // Parameters passed between the browser and the server. const ( - _PARAM_EVENT_TYPE = "et" // Event type parameter name - _PARAM_COMP_ID = "cid" // Component id parameter name - _PARAM_COMP_VALUE = "cval" // Component value parameter name - _PARAM_FOCUSED_COMP_ID = "fcid" // Focused component id parameter name - _PARAM_MOUSE_WX = "mwx" // Mouse x pixel coordinate (inside window) - _PARAM_MOUSE_WY = "mwy" // Mouse y pixel coordinate (inside window) - _PARAM_MOUSE_X = "mx" // Mouse x pixel coordinate (relative to source component) - _PARAM_MOUSE_Y = "my" // Mouse y pixel coordinate (relative to source component) - _PARAM_MOUSE_BTN = "mb" // Mouse button - _PARAM_MOD_KEYS = "mk" // Modifier key states - _PARAM_KEY_CODE = "kc" // Key code + paramEventType = "et" // Event type parameter name + paramCompId = "cid" // Component id parameter name + paramCompValue = "cval" // Component value parameter name + paramFocusedCompId = "fcid" // Focused component id parameter name + paramMouseWX = "mwx" // Mouse x pixel coordinate (inside window) + paramMouseWY = "mwy" // Mouse y pixel coordinate (inside window) + paramMouseX = "mx" // Mouse x pixel coordinate (relative to source component) + paramMouseY = "my" // Mouse y pixel coordinate (relative to source component) + paramMouseBtn = "mb" // Mouse button + paramModKeys = "mk" // Modifier key states + paramKeyCode = "kc" // Key code ) // Event response actions (client actions to take after processing an event). const ( - _ERA_NO_ACTION = iota // Event processing OK and no action required - _ERA_RELOAD_WIN // Window name to be reloaded - _ERA_DIRTY_COMPS // There are dirty components which needs to be refreshed - _ERA_FOCUS_COMP // Focus a compnent + eraNoAction = iota // Event processing OK and no action required + eraReloadWin // Window name to be reloaded + eraDirtyComps // There are dirty components which needs to be refreshed + eraFocusComp // Focus a compnent ) // GWU session id cookie name -const _GWU_SESSID_COOKIE = "gwu-sessid" +const gwuSessidCookie = "gwu-sessid" // SessionHandler interface defines a callback to get notified // for certain events related to session life-cycles. @@ -101,7 +99,7 @@ type Server interface { // AddSessCreatorName registers a nonexistent window name // whose path auto-creates a new session. - // + // // Normally sessions are created from event handlers during // event dispatching by calling Event.NewSession(). This // requires a public window and an event source component @@ -111,10 +109,10 @@ type Server interface { // session creation (if the current session is not private), and // with a registered SessionHandler you can build the window and // add it to the auto-created new session prior to it being served. - // + // // The text linking to the name will be included in the window list - // if text is a non-empty string. - // + // if text is a non-empty string. + // // Tip: You can use this to pre-register a login window for example. // You can call // AddSessCreatorName("login", "Login Window") @@ -129,15 +127,31 @@ type Server interface { // AddSHandler adds a new session handler. AddSHandler(handler SessionHandler) + // SetHeaders sets extra HTTP response headers that are added to all responses. + // Supplied values are copied, so changes to the passed map afterwards have no effect. + // + // For example to add an extra "Gowut-Server" header whose value is the Gowut version: + // server.SetHeaders(map[string][]string{ + // "Gowut-Server": {gwu.GowutVersion}, + // }) + SetHeaders(headers map[string][]string) + + // Headers returns the extra HTTP response headers that are added to all repsonses. + // A copy is returned, so changes to the returned map afterwards have no effect. + Headers() map[string][]string + // AddStaticDir registers a directory whose content (files) recursively // will be served by the server when requested. // path is an app-path relative path to address a file, dir is the root directory // to search in. - // + // Note that the app name must be included in absolute request paths, + // and it may be omitted if you want to use relative paths. + // Extra headers set by SetHeaders() will also be included in responses serving the static files. + // // Example: // AddStaticDir("img", "/tmp/myimg") - // And then the request "/appname/img/faces/happy.gif" will serve "/tmp/myimg/faces/happy.gif". - // Note that the app name must be included in the request path! + // Then request for absolute path "/appname/img/faces/happy.gif" will serve + // "/tmp/myimg/faces/happy.gif", just as the the request for relative path "img/faces/happy.gif". AddStaticDir(path, dir string) error // Theme returns the default CSS theme of the server. @@ -152,7 +166,7 @@ type Server interface { SetLogger(logger *log.Logger) // Start starts the GUI server and waits for incoming connections. - // + // // Sessionless window names may be specified as optional parameters // that will be opened in the default browser. // Tip: Pass an empty string to open the window list. @@ -177,12 +191,13 @@ type serverImpl struct { sessionHandlers []SessionHandler // Registered session handlers theme string // Default CSS theme of the server logger *log.Logger // Logger. + headers http.Header // Extra headers that will be added to all responses. } // NewServer creates a new GUI server in HTTP mode. // The specified app name will be part of the application path (the first part). // If addr is empty string, "localhost:3434" will be used. -// +// // Tip: Pass an empty string as appName to place the GUI server to the root path ("/"). func NewServer(appName, addr string) Server { return newServerImpl(appName, addr, "", "") @@ -191,30 +206,30 @@ func NewServer(appName, addr string) Server { // NewServerTLS creates a new GUI server in secure (HTTPS) mode. // The specified app name will be part of the application path (the first part). // If addr is empty string, "localhost:3434" will be used. -// +// // Tip: Pass an empty string as appName to place the GUI server to the root path ("/"). // Tip: You can use generate_cert.go in crypto/tls to generate -// a test certificate and key file (cert.pem andkey.pem). +// a test certificate and key file (cert.pem andkey.pem). func NewServerTLS(appName, addr, certFile, keyFile string) Server { return newServerImpl(appName, addr, certFile, keyFile) } // newServerImpl creates a new serverImpl. func newServerImpl(appName, addr, certFile, keyFile string) *serverImpl { - if len(addr) == 0 { + if addr == "" { addr = "localhost:3434" } s := &serverImpl{sessionImpl: newSessionImpl(false), appName: appName, addr: addr, sessions: make(map[string]Session), - sessCreatorNames: make(map[string]string), theme: THEME_DEFAULT} + sessCreatorNames: make(map[string]string), theme: ThemeDefault} - if len(s.appName) == 0 { + if s.appName == "" { s.appPath = "/" } else { s.appPath = "/" + s.appName + "/" } - if len(certFile) == 0 || len(keyFile) == 0 { + if certFile == "" || keyFile == "" { s.secure = false s.appUrl = "http://" + addr + s.appPath } else { @@ -267,6 +282,7 @@ func (s *serverImpl) newSession(e *eventImpl) Session { // Store new session s.sessions[sess.Id()] = sess + log.Println("SESSION created:", sess.Id()) if s.logger != nil { s.logger.Println("SESSION created:", sess.Id()) } @@ -295,6 +311,7 @@ func (s *serverImpl) removeSess(e *eventImpl) { // the public session is a no-op. func (s *serverImpl) removeSess2(sess Session) { if sess.Private() { + log.Println("SESSION removed:", sess.Id()) if s.logger != nil { s.logger.Println("SESSION removed:", sess.Id()) } @@ -314,7 +331,7 @@ func (s *serverImpl) addSessCookie(sess Session, w http.ResponseWriter) { // HttpOnly: do not allow non-HTTP access to it (like javascript) to prevent stealing it... // Secure: only send it over HTTPS // MaxAge: to specify the max age of the cookie in seconds, else it's a session cookie and gets deleted after the browser is closed. - c := http.Cookie{Name: _GWU_SESSID_COOKIE, Value: sess.Id(), Path: s.appPath, HttpOnly: true, Secure: s.secure, + c := http.Cookie{Name: gwuSessidCookie, Value: sess.Id(), Path: s.appPath, HttpOnly: true, Secure: s.secure, MaxAge: 72 * 60 * 60} // 72 hours max age http.SetCookie(w, &c) @@ -340,12 +357,39 @@ func (s *serverImpl) sessCleaner() { } } +func (s *serverImpl) SetHeaders(headers map[string][]string) { + s.headers = make(map[string][]string, len(headers)) + for k, v := range headers { + // Also copy value which is a slice + s.headers[k] = append(make([]string, 0, len(v)), v...) + } +} + +func (s *serverImpl) Headers() map[string][]string { + headers := make(map[string][]string, len(s.headers)) + for k, v := range s.headers { + // Also copy value which is a slice + headers[k] = append(make([]string, 0, len(v)), v...) + } + return headers +} + +// addHeaders adds the extra headers to the specified response. +func (s *serverImpl) addHeaders(w http.ResponseWriter) { + header := w.Header() + for k, v := range s.headers { + for _, v2 := range v { + header.Add(k, v2) + } + } +} + func (s *serverImpl) AddStaticDir(path, dir string) error { if strings.HasPrefix(path, "/") { path = path[1:] } - if len(path) == 0 { + if path == "" { return errors.New("path cannot be empty string!") } @@ -355,11 +399,16 @@ func (s *serverImpl) AddStaticDir(path, dir string) error { path = s.appPath + path - if path == s.appPath+_PATH_STATIC { - return errors.New("path cannot be '" + _PATH_STATIC + "' (reserved)!") + if path == s.appPath+pathStatic || path == s.appPath+pathEvent || path == s.appPath+pathRenderComp { + return errors.New("Path cannot be '" + pathStatic + "' (reserved)!") } - http.Handle(path, http.StripPrefix(path, http.FileServer(http.Dir(dir)))) + handler := http.StripPrefix(path, http.FileServer(http.Dir(dir))) + // To include extra headers in the response of static handler: + http.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) { + s.addHeaders(w) + handler.ServeHTTP(w, r) + }) return nil } @@ -376,70 +425,21 @@ func (s *serverImpl) SetLogger(logger *log.Logger) { s.logger = logger } -// open opens the specified URL in the default browser of the user. -func open(url string) error { - var cmd string - var args []string - - switch runtime.GOOS { - case "windows": - cmd = "cmd" - args = []string{"/c", "start"} - case "darwin": - cmd = "open" - default: // "linux", "freebsd", "openbsd", "netbsd" - cmd = "xgd-open" - } - args = append(args, url) - return exec.Command(cmd, args...).Start() -} - -func (s *serverImpl) Start(openWins ...string) error { - http.HandleFunc(s.appPath, func(w http.ResponseWriter, r *http.Request) { - s.serveHTTP(w, r) - }) - - http.HandleFunc(s.appPath+_PATH_STATIC, func(w http.ResponseWriter, r *http.Request) { - s.serveStatic(w, r) - }) - - fmt.Println("Starting GUI server on:", s.appUrl) - if s.logger != nil { - s.logger.Println("Starting GUI server on:", s.appUrl) - } - - for _, winName := range openWins { - open(s.appUrl + winName) - } - - go s.sessCleaner() - - var err error - if s.secure { - err = http.ListenAndServeTLS(s.addr, s.certFile, s.keyFile, nil) - } else { - err = http.ListenAndServe(s.addr, nil) - } - - if err != nil { - return err - } - return nil -} - // serveStatic handles the static contents of GWU. func (s *serverImpl) serveStatic(w http.ResponseWriter, r *http.Request) { + s.addHeaders(w) + // Parts example: "/appname/_gwu_static/gwu-0.8.0.js" => {"", "appname", "_gwu_static", "gwu-0.8.0.js"} parts := strings.Split(r.URL.Path, "/") - if len(s.appName) == 0 { + if s.appName == "" { // No app name, gui server resides in root if len(parts) < 2 { // This should never happen. Path is always at least a slash ("/"). http.NotFound(w, r) return } - // Omit the first empty string and _PATH_STATIC + // Omit the first empty string and pathStatic parts = parts[2:] } else { // We have app name @@ -448,13 +448,13 @@ func (s *serverImpl) serveStatic(w http.ResponseWriter, r *http.Request) { http.NotFound(w, r) return } - // Omit the first empty string, app name and _PATH_STATIC + // Omit the first empty string, app name and pathStatic parts = parts[3:] } res := parts[0] - if res == _RES_NAME_STATIC_JS { - w.Header().Set("Expires", time.Now().Add(72*time.Hour).Format(http.TimeFormat)) // Set 72 hours caching + if res == resNameStaticJs { + w.Header().Set("Expires", time.Now().UTC().Add(72*time.Hour).Format(http.TimeFormat)) // Set 72 hours caching w.Header().Set("Content-Type", "application/x-javascript; charset=utf-8") w.Write(staticJs) return @@ -462,7 +462,7 @@ func (s *serverImpl) serveStatic(w http.ResponseWriter, r *http.Request) { if strings.HasSuffix(res, ".css") { cssCode := staticCss[res] if cssCode != nil { - w.Header().Set("Expires", time.Now().Add(72*time.Hour).Format(http.TimeFormat)) // Set 72 hours caching + w.Header().Set("Expires", time.Now().UTC().Add(72*time.Hour).Format(http.TimeFormat)) // Set 72 hours caching w.Header().Set("Content-Type", "text/css; charset=utf-8") w.Write(cssCode) return @@ -480,9 +480,11 @@ func (s *serverImpl) serveHTTP(w http.ResponseWriter, r *http.Request) { s.logger.Println("Incoming: ", r.URL.Path) } + s.addHeaders(w) + // Check session var sess Session - c, err := r.Cookie(_GWU_SESSID_COOKIE) + c, err := r.Cookie(gwuSessidCookie) if err == nil { sess = s.sessions[c.Value] } @@ -557,12 +559,12 @@ func (s *serverImpl) serveHTTP(w http.ResponseWriter, r *http.Request) { rwMutex := sess.rwMutex() switch path { - case _PATH_EVENT: + case pathEvent: rwMutex.Lock() defer rwMutex.Unlock() s.handleEvent(sess, win, w, r) - case _PATH_RENDER_COMP: + case pathRenderComp: rwMutex.RLock() defer rwMutex.RUnlock() @@ -577,7 +579,7 @@ func (s *serverImpl) serveHTTP(w http.ResponseWriter, r *http.Request) { } } -// renderWinList renders the window list of a session as HTML document with clickable links. +// renderWinList renders the window list of a session as HTML document with clickable links. func (s *serverImpl) renderWinList(sess Session, wr http.ResponseWriter, r *http.Request) { if s.logger != nil { s.logger.Println("\tRending windows list.") @@ -624,9 +626,9 @@ func (s *serverImpl) renderWinList(sess Session, wr http.ResponseWriter, r *http w.Writes("") } -// renderComp renders just a component. +// renderComp renders just a component. func (s *serverImpl) renderComp(win Window, w http.ResponseWriter, r *http.Request) { - id, err := AtoID(r.FormValue(_PARAM_COMP_ID)) + id, err := AtoID(r.FormValue(paramCompId)) if err != nil { http.Error(w, "Invalid component id!", http.StatusBadRequest) return @@ -648,12 +650,12 @@ func (s *serverImpl) renderComp(win Window, w http.ResponseWriter, r *http.Reque // handleEvent handles the event dispatching. func (s *serverImpl) handleEvent(sess Session, win Window, wr http.ResponseWriter, r *http.Request) { - focCompId, err := AtoID(r.FormValue(_PARAM_FOCUSED_COMP_ID)) + focCompId, err := AtoID(r.FormValue(paramFocusedCompId)) if err == nil { win.SetFocusedCompId(focCompId) } - id, err := AtoID(r.FormValue(_PARAM_COMP_ID)) + id, err := AtoID(r.FormValue(paramCompId)) if err != nil { http.Error(wr, "Invalid component id!", http.StatusBadRequest) return @@ -668,7 +670,7 @@ func (s *serverImpl) handleEvent(sess Session, win Window, wr http.ResponseWrite return } - etype := parseIntParam(r, _PARAM_EVENT_TYPE) + etype := parseIntParam(r, paramEventType) if etype < 0 { http.Error(wr, "Invalid event type!", http.StatusBadRequest) return @@ -680,18 +682,18 @@ func (s *serverImpl) handleEvent(sess Session, win Window, wr http.ResponseWrite event := newEventImpl(EventType(etype), comp, s, sess) shared := event.shared - event.x = parseIntParam(r, _PARAM_MOUSE_X) + event.x = parseIntParam(r, paramMouseX) if event.x >= 0 { - event.y = parseIntParam(r, _PARAM_MOUSE_Y) - shared.wx = parseIntParam(r, _PARAM_MOUSE_WX) - shared.wy = parseIntParam(r, _PARAM_MOUSE_WY) - shared.mbtn = MouseBtn(parseIntParam(r, _PARAM_MOUSE_BTN)) + event.y = parseIntParam(r, paramMouseY) + shared.wx = parseIntParam(r, paramMouseWX) + shared.wy = parseIntParam(r, paramMouseWY) + shared.mbtn = MouseBtn(parseIntParam(r, paramMouseBtn)) } else { event.y, shared.wx, shared.wy, shared.mbtn = -1, -1, -1, -1 } - shared.modKeys = parseIntParam(r, _PARAM_MOD_KEYS) - shared.keyCode = Key(parseIntParam(r, _PARAM_KEY_CODE)) + shared.modKeys = parseIntParam(r, paramModKeys) + shared.keyCode = Key(parseIntParam(r, paramKeyCode)) comp.preprocessEvent(event, r) @@ -710,34 +712,34 @@ func (s *serverImpl) handleEvent(sess Session, win Window, wr http.ResponseWrite // If we reload, nothing else matters if shared.reload { hasAction = true - w.Writevs(_ERA_RELOAD_WIN, _STR_COMMA, shared.reloadWin) + w.Writevs(eraReloadWin, strComma, shared.reloadWin) } else { if len(shared.dirtyComps) > 0 { hasAction = true - w.Writev(_ERA_DIRTY_COMPS) + w.Writev(eraDirtyComps) for id, _ := range shared.dirtyComps { - w.Write(_STR_COMMA) + w.Write(strComma) w.Writev(int(id)) } } if shared.focusedComp != nil { if hasAction { - w.Write(_STR_SEMICOL) + w.Write(strSemicol) } else { hasAction = true } - w.Writevs(_ERA_FOCUS_COMP, _STR_COMMA, int(shared.focusedComp.Id())) + w.Writevs(eraFocusComp, strComma, int(shared.focusedComp.Id())) // Also register focusable comp at window win.SetFocusedCompId(shared.focusedComp.Id()) } } if !hasAction { - w.Writev(_ERA_NO_ACTION) + w.Writev(eraNoAction) } } // parseIntParam parses an int param. -// If error occurs, -1 will be returned. +// If error occurs, -1 will be returned. func parseIntParam(r *http.Request, paramName string) int { if num, err := strconv.Atoi(r.FormValue(paramName)); err == nil { return num diff --git a/gwu/server_start.go b/gwu/server_start.go new file mode 100644 index 0000000..82f3257 --- /dev/null +++ b/gwu/server_start.go @@ -0,0 +1,78 @@ +// +build !appengine + +// Copyright (C) 2013 Andras Belicza. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// Implementation of the GUI server Start in standalone apps (non-GAE). + +package gwu + +import ( + "log" + "net/http" + "os/exec" + "runtime" +) + +// open opens the specified URL in the default browser of the user. +func open(url string) error { + var cmd string + var args []string + + switch runtime.GOOS { + case "windows": + cmd = "cmd" + args = []string{"/c", "start"} + case "darwin": + cmd = "open" + default: // "linux", "freebsd", "openbsd", "netbsd" + cmd = "xgd-open" + } + args = append(args, url) + return exec.Command(cmd, args...).Start() +} + +func (s *serverImpl) Start(openWins ...string) error { + http.HandleFunc(s.appPath, func(w http.ResponseWriter, r *http.Request) { + s.serveHTTP(w, r) + }) + + http.HandleFunc(s.appPath+pathStatic, func(w http.ResponseWriter, r *http.Request) { + s.serveStatic(w, r) + }) + + log.Println("Starting GUI server on:", s.appUrl) + if s.logger != nil { + s.logger.Println("Starting GUI server on:", s.appUrl) + } + + for _, winName := range openWins { + open(s.appUrl + winName) + } + + go s.sessCleaner() + + var err error + if s.secure { + err = http.ListenAndServeTLS(s.addr, s.certFile, s.keyFile, nil) + } else { + err = http.ListenAndServe(s.addr, nil) + } + + if err != nil { + return err + } + return nil +} diff --git a/gwu/server_start_gae.go b/gwu/server_start_gae.go new file mode 100644 index 0000000..5100ba9 --- /dev/null +++ b/gwu/server_start_gae.go @@ -0,0 +1,44 @@ +// +build appengine + +// Copyright (C) 2013 Andras Belicza. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// Implementation of the GUI server Start on Google App Engine. + +package gwu + +import ( + "log" + "net/http" +) + +func (s *serverImpl) Start(openWins ...string) error { + http.HandleFunc(s.appPath, func(w http.ResponseWriter, r *http.Request) { + s.serveHTTP(w, r) + }) + + http.HandleFunc(s.appPath+pathStatic, func(w http.ResponseWriter, r *http.Request) { + s.serveStatic(w, r) + }) + + log.Println("GAE - Starting GUI server on path:", s.appPath) + if s.logger != nil { + s.logger.Println("GAE - Starting GUI server on path:", s.appPath) + } + + go s.sessCleaner() + + return nil +} diff --git a/gwu/session.go b/gwu/session.go index 4b9cab4..c6d4579 100644 --- a/gwu/session.go +++ b/gwu/session.go @@ -1,15 +1,15 @@ // Copyright (C) 2013 Andras Belicza. All rights reserved. -// +// // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// +// // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program. If not, see . @@ -21,7 +21,7 @@ import ( "crypto/rand" "errors" "fmt" - "io" + "log" "sort" "sync" "time" @@ -117,40 +117,30 @@ func newSessionImpl(private bool) sessionImpl { attrs: make(map[string]interface{}), timeout: 30 * time.Minute, rwMutex_: &sync.RWMutex{}} } -// Number of valid id runes. -// Must be a power of 2! -const _ID_RUNES_COUNT = 64 - -// Mask to get an id rune idx from a random byte. -const _ID_RUNES_IDX_MASK = _ID_RUNES_COUNT - 1 - -// Valid runes to be used for session ids -// Its length must be _ID_RUNES_COUNT. -var _ID_RUNES = []rune("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_") +// Valid characters (bytes) to be used in session ids +// Its length must be a power of 2. +const idChars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_" func init() { - // Is _ID_RUNES_COUNT a power of 2? - if _ID_RUNES_COUNT&(_ID_RUNES_COUNT-1) != 0 { - panic(fmt.Sprint("_ID_RUNES_COUNT is not a power of 2: ", _ID_RUNES_COUNT)) - } - if len(_ID_RUNES) != _ID_RUNES_COUNT { - panic(fmt.Sprint("len(_ID_RUNES) != ", _ID_RUNES_COUNT)) + // Is len(idChars) a power of 2? + if i := byte(len(idChars)); i&(i-1) != 0 { + panic(fmt.Sprint("len(idChars) must be power of 2: ", i)) } } // Length of the session ids -const _ID_LENGTH = 22 +const idLength = 22 -// genId generates a session id. +// genId generates a new session id. func genId() string { - r := make([]byte, _ID_LENGTH) - io.ReadFull(rand.Reader, r) - - id := make([]rune, _ID_LENGTH) - for i := 0; i < _ID_LENGTH; i++ { - id[i] = _ID_RUNES[r[i]&_ID_RUNES_IDX_MASK] + id := make([]byte, idLength) + if _, err := rand.Read(id); err != nil { + log.Printf("Failed to read from secure random: %v", err) } + for i, v := range id { + id[i] = idChars[v&byte(len(idChars)-1)] + } return string(id) } diff --git a/gwu/state_buttons.go b/gwu/state_buttons.go index c007b5d..a8e9c51 100644 --- a/gwu/state_buttons.go +++ b/gwu/state_buttons.go @@ -1,15 +1,15 @@ // Copyright (C) 2013 Andras Belicza. All rights reserved. -// +// // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// +// // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program. If not, see . @@ -40,9 +40,9 @@ type StateButton interface { // CheckBox interface defines a check box, a button which has // 2 states: selected/deselected. -// -// Suggested event type to handle changes: ETYPE_CLICK -// +// +// Suggested event type to handle changes: ETypeClick +// // Default style classes: "gwu-CheckBox", "gwu-CheckBox-Disabled" type CheckBox interface { // CheckBox is a StateButton. @@ -51,9 +51,9 @@ type CheckBox interface { // SwitchButton interface defines a button which can be switched // ON and OFF. -// -// Suggested event type to handle changes: ETYPE_CLICK -// +// +// Suggested event type to handle changes: ETypeClick +// // Default style classes: "gwu-SwitchButton", "gwu-SwitchButton-On-Active" // "gwu-SwitchButton-On-Inactive", "gwu-SwitchButton-Off-Active", // "gwu-SwitchButton-Off-Inactive" @@ -103,9 +103,9 @@ type RadioGroup interface { // and in each group only one radio button can be selected. // Selecting an unselected radio button deselects the selected // radio button of the group, if there was one. -// -// Suggested event type to handle changes: ETYPE_CLICK -// +// +// Suggested event type to handle changes: ETypeClick +// // Default style classes: "gwu-RadioButton", "gwu-RadioButton-Disabled" type RadioButton interface { // RadioButton is a StateButton. @@ -128,7 +128,7 @@ type radioGroupImpl struct { // StateButton implementation. type stateButtonImpl struct { - buttonImpl // Button implementation + buttonImpl // Button implementation state bool // State of the button inputType []byte // Type of the underlying input tag @@ -151,15 +151,15 @@ func NewRadioGroup(name string) RadioGroup { } var ( - _STR_CHECKBOX = []byte("checkbox") // "checkbox" - _STR_RADIO = []byte("radio") // "radio" - _STR_THIS_CHECKED = []byte("this.checked") // "this.checked" + strCheckbox = []byte("checkbox") // "checkbox" + strRadio = []byte("radio") // "radio" + strThisChecked = []byte("this.checked") // "this.checked" ) // NewCheckBox creates a new CheckBox. // The initial state is false. func NewCheckBox(text string) CheckBox { - c := newStateButtonImpl(text, _STR_CHECKBOX, nil, "gwu-CheckBox-Disabled") + c := newStateButtonImpl(text, strCheckbox, nil, "gwu-CheckBox-Disabled") c.Style().AddClass("gwu-CheckBox") return c } @@ -177,7 +177,7 @@ func NewSwitchButton() SwitchButton { valueProviderJs := []byte("sbtnVal(event,'" + onButton.Id().String() + "','" + offButton.Id().String() + "')") c := &switchButtonImpl{newCompImpl(valueProviderJs), &onButton, &offButton, true} // Note the "true" state, so the following SetState(false) will be executed (different states)! - c.AddSyncOnETypes(ETYPE_CLICK) + c.AddSyncOnETypes(ETypeClick) c.SetAttr("cellspacing", "0") c.SetAttr("cellpadding", "0") c.Style().AddClass("gwu-SwitchButton") @@ -188,16 +188,16 @@ func NewSwitchButton() SwitchButton { // NewRadioButton creates a new radio button. // The initial state is false. func NewRadioButton(text string, group RadioGroup) RadioButton { - c := newStateButtonImpl(text, _STR_RADIO, group, "gwu-RadioButton-Disabled") + c := newStateButtonImpl(text, strRadio, group, "gwu-RadioButton-Disabled") c.Style().AddClass("gwu-RadioButton") return c } // newStateButtonImpl creates a new stateButtonImpl. func newStateButtonImpl(text string, inputType []byte, group RadioGroup, disabledClass string) *stateButtonImpl { - c := &stateButtonImpl{newButtonImpl(_STR_THIS_CHECKED, text), false, inputType, group, nextCompId(), disabledClass} - // Use ETYPE_CLICK because IE fires onchange only when focus is lost... - c.AddSyncOnETypes(ETYPE_CLICK) + c := &stateButtonImpl{newButtonImpl(strThisChecked, text), false, inputType, group, nextCompId(), disabledClass} + // Use ETypeClick because IE fires onchange only when focus is lost... + c.AddSyncOnETypes(ETypeClick) return c } @@ -277,7 +277,7 @@ func (c *stateButtonImpl) setStateProp(state bool) { } func (c *stateButtonImpl) preprocessEvent(event Event, r *http.Request) { - value := r.FormValue(_PARAM_COMP_VALUE) + value := r.FormValue(paramCompValue) if len(value) == 0 { return } @@ -290,46 +290,46 @@ func (c *stateButtonImpl) preprocessEvent(event Event, r *http.Request) { } var ( - _STR_INPUT = []byte(`" + strInput = []byte(`" ) -func (c *stateButtonImpl) Render(w writer) { +func (c *stateButtonImpl) Render(w Writer) { // Proper state button consists of multiple HTML tags (input and label), so render a wrapper tag for them: - w.Write(_STR_SPAN_OP) + w.Write(strSpanOp) c.renderAttrsAndStyle(w) - w.Write(_STR_GT) + w.Write(strGT) - w.Write(_STR_INPUT) + w.Write(strInput) w.Write(c.inputType) - w.Write(_STR_ID) + w.Write(strId) w.Writev(int(c.inputId)) - w.Write(_STR_QUOTE) + w.Write(strQuote) if c.group != nil { - w.Write(_STR_NAME) + w.Write(strName) w.Writes(c.group.Name()) - w.Write(_STR_QUOTE) + w.Write(strQuote) } if c.state { - w.Write(_STR_CHECKED) + w.Write(strChecked) } c.renderEnabled(w) c.renderEHandlers(w) - w.Write(_STR_LABEL_FOR) + w.Write(strLabelFor) w.Writev(int(c.inputId)) - w.Write(_STR_QUOTE) + w.Write(strQuote) // TODO readding click handler here causes double event sending... // But we might add mouseover and other handlers still... //c.renderEHandlers(w) - w.Write(_STR_GT) + w.Write(strGT) c.renderText(w) - w.Write(_STR_LABEL_CL) - w.Write(_STR_SPAN_CL) + w.Write(strLabelCl) + w.Write(strSpanCl) } func (c *switchButtonImpl) Enabled() bool { @@ -375,7 +375,7 @@ func (c *switchButtonImpl) SetOnOff(on, off string) { } func (c *switchButtonImpl) preprocessEvent(event Event, r *http.Request) { - value := r.FormValue(_PARAM_COMP_VALUE) + value := r.FormValue(paramCompValue) if len(value) == 0 { return } @@ -390,27 +390,27 @@ func (c *switchButtonImpl) preprocessEvent(event Event, r *http.Request) { } var ( - _STR_CL_TR = []byte(">") // ">" - _STR_TD_50 = []byte(``) // `` + strClTr = []byte(">") // ">" + strTD50 = []byte(``) // `` ) -func (c *switchButtonImpl) Render(w writer) { - w.Write(_STR_TABLE_OP) +func (c *switchButtonImpl) Render(w Writer) { + w.Write(strTableOp) c.renderAttrsAndStyle(w) c.renderEHandlers(w) // For Internet Explorer only: - // Since state synchronization is done on ETYPE_CLICK, which will add a click handler + // Since state synchronization is done on ETypeClick, which will add a click handler // to the wrapper tag and not to the on/off buttons, the wrapper tag itself must be // disabled (must have a 'disabled' attribute) if the switch button is disabled in order // for clicks really be disabled. c.onButton.renderEnabled(w) - w.Write(_STR_CL_TR) + w.Write(strClTr) - w.Write(_STR_TD_50) + w.Write(strTD50) c.onButton.Render(w) - w.Write(_STR_TD_50) + w.Write(strTD50) c.offButton.Render(w) - w.Write(_STR_TABLE_CL) + w.Write(strTableCl) } diff --git a/gwu/style.go b/gwu/style.go index ba11127..15252dd 100644 --- a/gwu/style.go +++ b/gwu/style.go @@ -1,15 +1,15 @@ // Copyright (C) 2013 Andras Belicza. All rights reserved. -// +// // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// +// // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program. If not, see . @@ -23,110 +23,110 @@ import ( // Style attribute constants. const ( - ST_BACKGROUND = "background" // Background (color) - ST_BORDER = "border" // Border - ST_BORDER_LEFT = "border-left" // Left border - ST_BORDER_RIGHT = "border-right" // Right border - ST_BORDER_TOP = "border-top" // Top border - ST_BORDER_BOTTOM = "border-bottom" // Bottom border - ST_COLOR = "color" // (Foreground) color - ST_CURSOR = "cursor" // Cursor - ST_DISPLAY = "display" // Display - ST_FONT_SIZE = "font-size" // Font size - ST_FONT_STYLE = "font-style" // Font style - ST_FONT_WEIGHT = "font-weight" // Font weight - ST_HEIGHT = "height" // Height - ST_MARGIN = "margin" // Margin - ST_MARGIN_LEFT = "margin-left" // Left margin - ST_MARGIN_RIGHT = "margin-right" // Right margin - ST_MARGIN_TOP = "margin-top" // Top margin - ST_MARGIN_BOTTOM = "margin-bottom" // Bottom margin - ST_PADDING = "padding" // Padding - ST_PADDING_LEFT = "padding-left" // Left padding - ST_PADDING_RIGHT = "padding-right" // Right padding - ST_PADDING_TOP = "padding-top" // Top padding - ST_PADDING_BOTTOM = "padding-bottom" // Bottom padding - ST_WHITE_SPACE = "white-space" // White-space - ST_WIDTH = "width" // Width + StBackground = "background" // Background (color) + StBorder = "border" // Border + StBorderLeft = "border-left" // Left border + StBorderRight = "border-right" // Right border + StBorderTop = "border-top" // Top border + StBorderBottom = "border-bottom" // Bottom border + StColor = "color" // (Foreground) color + StCursor = "cursor" // Cursor + StDisplay = "display" // Display + StFontSize = "font-size" // Font size + StFontStyle = "font-style" // Font style + StFontWeight = "font-weight" // Font weight + StHeight = "height" // Height + StMargin = "margin" // Margin + StMarginLeft = "margin-left" // Left margin + StMarginRight = "margin-right" // Right margin + StMarginTop = "margin-top" // Top margin + StMarginBottom = "margin-bottom" // Bottom margin + StPadding = "padding" // Padding + StPaddingLeft = "padding-left" // Left padding + StPaddingRight = "padding-right" // Right padding + StPaddingTop = "padding-top" // Top padding + StPaddingBottom = "padding-bottom" // Bottom padding + StWhiteSpace = "white-space" // White-space + StWidth = "width" // Width ) // The 17 standard color constants. const ( - CLR_AQUA = "Aqua" // Aqua (#00FFFF) - CLR_BLACK = "Black" // Black (#000000) - CLR_BLUE = "Blue" // Blue (#0000FF) - CLR_FUCHSIA = "Fuchsia" // Fuchsia (#FF00FF) - CLR_GRAY = "Gray" // Gray (#808080) - CLR_GREY = "Grey" // Grey (#808080) - CLR_GREEN = "Green" // Green (#008000) - CLR_LIME = "Lime" // Lime (#00FF00) - CLR_MAROON = "Maroon" // Maroon (#800000) - CLR_NAVY = "Navy" // Navy (#000080) - CLR_OLIVE = "Olive" // Olive (#808000) - CLR_PURPLE = "Purple" // Purple (#800080) - CLR_RED = "Red" // Red (#FF0000) - CLR_SILVER = "Silver" // Silver (#C0C0C0) - CLR_TEAL = "Teal" // Teal (#008080) - CLR_WHITE = "White" // White (#FFFFFF) - CLR_YELLOW = "Yellow" // Yellow (#FFFF00) + ClrAqua = "Aqua" // Aqua (#00FFFF) + ClrBlack = "Black" // Black (#000000) + ClrBlue = "Blue" // Blue (#0000FF) + ClrFuchsia = "Fuchsia" // Fuchsia (#FF00FF) + ClrGray = "Gray" // Gray (#808080) + ClrGrey = "Grey" // Grey (#808080) + ClrGreen = "Green" // Green (#008000) + ClrLime = "Lime" // Lime (#00FF00) + ClrMaroon = "Maroon" // Maroon (#800000) + ClrNavy = "Navy" // Navy (#000080) + ClrOlive = "Olive" // Olive (#808000) + ClrPurple = "Purple" // Purple (#800080) + ClrRed = "Red" // Red (#FF0000) + ClrSilver = "Silver" // Silver (#C0C0C0) + ClrTeal = "Teal" // Teal (#008080) + ClrWhite = "White" // White (#FFFFFF) + ClrYellow = "Yellow" // Yellow (#FFFF00) ) // Border style constants. const ( - BRD_STYLE_SOLID = "solid" // Solid - BRD_STYLE_DASHED = "dashed" // Dashed - BRD_STYLE_DOTTED = "dotted" // Dotted - BRD_STYLE_DOUBLE = "double" // Double - BRD_STYLE_GROOVE = "groove" // 3D grooved border - BRD_STYLE_RIDGE = "ridge" // 3D ridged border - BRD_STYLE_INSET = "inset" // 3D inset border - BRD_STYLE_OUTSET = "outset" // 3D outset border + BrdStyleSolid = "solid" // Solid + BrdStyleDashed = "dashed" // Dashed + BrdStyleDotted = "dotted" // Dotted + BrdStyleDouble = "double" // Double + BrdStyleGroove = "groove" // 3D grooved border + BrdStyleRidge = "ridge" // 3D ridged border + BrdStyleInset = "inset" // 3D inset border + BrdStyleOutset = "outset" // 3D outset border ) // Font weight constants. const ( - FONT_WEIGHT_NORMAL = "normal" // Normal - FONT_WEIGHT_BOLD = "bold" // Bold - FONT_WEIGHT_BOLDER = "bolder" // Bolder - FONT_WEIGHT_LIGHTER = "lighter" // Lighter + FontWeightNormal = "normal" // Normal + FontWeightBold = "bold" // Bold + FontWeightBolder = "bolder" // Bolder + FontWeightLighter = "lighter" // Lighter ) // Font style constants. const ( - FONT_STYLE_NORMAL = "normal" // Normal - FONT_STYLE_ITALIC = "italic" // Italic + FontStyleNormal = "normal" // Normal + FontStyleItalic = "italic" // Italic ) // Mouse cursor constants. const ( - CURSOR_AUTO = "auto" // Default. Web browser sets the cursor. - CURSOR_CROSSHAIR = "crosshair" // Crosshair - CURSOR_DEFAULT = "default" // The default cursor. - CURSOR_HELP = "help" // Help - CURSOR_MOVE = "move" // Move - CURSOR_POINTER = "pointer" // Pointer - CURSOR_PROGRESS = "progress" // Progress - CURSOR_TEXT = "text" // Text - CURSOR_WAIT = "wait" // Wait - CURSOR_INHERIT = "inherit" // The cursor should be inherited from the parent element. + CursorAuto = "auto" // Default. Web browser sets the cursor. + CursorCrosshair = "crosshair" // Crosshair + CursorDefault = "default" // The default cursor. + CursorHelp = "help" // Help + CursorMove = "move" // Move + CursorPointer = "pointer" // Pointer + CursorProgress = "progress" // Progress + CursorText = "text" // Text + CursorWait = "wait" // Wait + CursorInherit = "inherit" // The cursor should be inherited from the parent element. ) // Display mode constants. const ( - DISPLAY_NONE = "none" // The element will not be displayed. - DISPLAY_BLOCK = "block" // The element is displayed as a block. - DISPLAY_INLINE = "inline" // The element is displayed as an in-line element. This is the default. - DISPLAY_INHERIT = "inherit" // The display property value will be inherited from the parent element. + DisplayNone = "none" // The element will not be displayed. + DisplayBlock = "block" // The element is displayed as a block. + DisplayInline = "inline" // The element is displayed as an in-line element. This is the default. + DisplayInherit = "inherit" // The display property value will be inherited from the parent element. ) // White space constants. const ( - WHITE_SPACE_NORMAL = "normal" // Sequences of white spaces are collapsed into a single whitespace. Text will wrap when neccessary. This is the default. - WHITE_SPACE_NOWRAP = "nowrap" // Sequences of whitespace will collapse into a single whitespace. Text will never wrap to the next line (the text is in one line). - WHITE_SPACE_PRE = "pre" // Whitespace is preserved. Text will only wrap on line breaks. - WHITE_SPACE_PRE_LINE = "pre-line" // Sequences of whitespace will collapse into a single whitespace. Text will wrap when necessary and on line breaks. - WHITE_SPACE_PRE_WRAP = "pre-wrap" // Whitespace is preserved. Text will wrap when necessary, and on line breaks. - WHITE_SPACE_INHERIT = "inherit" // Whitespace property will be inherited from the parent element. + WhiteSpaceNormal = "normal" // Sequences of white spaces are collapsed into a single whitespace. Text will wrap when neccessary. This is the default. + WhiteSpaceNowrap = "nowrap" // Sequences of whitespace will collapse into a single whitespace. Text will never wrap to the next line (the text is in one line). + WhiteSpacePre = "pre" // Whitespace is preserved. Text will only wrap on line breaks. + WhiteSpacePreLine = "pre-line" // Sequences of whitespace will collapse into a single whitespace. Text will wrap when necessary and on line breaks. + WhiteSpacePreWrap = "pre-wrap" // Whitespace is preserved. Text will wrap when necessary, and on line breaks. + WhiteSpaceInherit = "inherit" // Whitespace property will be inherited from the parent element. ) // Style interface contains utility methods for manipulating @@ -420,13 +420,13 @@ type Style interface { // render renders all style information (style class names // and style attributes). - render(w writer) + render(w Writer) // renderClasses renders the style class names. - renderClasses(w writer) + renderClasses(w Writer) // renderAttrs renders the style attributes. - renderAttrs(w writer) + renderAttrs(w Writer) } type styleImpl struct { @@ -483,12 +483,12 @@ func (s *styleImpl) Set(name, value string) Style { } func (s *styleImpl) Size() (width, height string) { - return s.Get(ST_WIDTH), s.Get(ST_HEIGHT) + return s.Get(StWidth), s.Get(StHeight) } func (s *styleImpl) SetSize(width, height string) Style { - s.Set(ST_WIDTH, width) - s.Set(ST_HEIGHT, height) + s.Set(StWidth, width) + s.Set(StHeight, height) return s } @@ -501,11 +501,11 @@ func (s *styleImpl) SetFullSize() Style { } func (s *styleImpl) Padding() string { - return s.Get(ST_PADDING) + return s.Get(StPadding) } func (s *styleImpl) SetPadding(value string) Style { - return s.Set(ST_PADDING, value) + return s.Set(StPadding, value) } func (s *styleImpl) SetPadding2(top, right, bottom, left string) Style { @@ -517,11 +517,11 @@ func (s *styleImpl) SetPaddingPx(top, right, bottom, left int) Style { } func (s *styleImpl) PaddingLeft() string { - return s.Get(ST_PADDING_LEFT) + return s.Get(StPaddingLeft) } func (s *styleImpl) SetPaddingLeft(value string) Style { - return s.Set(ST_PADDING_LEFT, value) + return s.Set(StPaddingLeft, value) } func (s *styleImpl) SetPaddingLeftPx(width int) Style { @@ -529,11 +529,11 @@ func (s *styleImpl) SetPaddingLeftPx(width int) Style { } func (s *styleImpl) PaddingRight() string { - return s.Get(ST_PADDING_RIGHT) + return s.Get(StPaddingRight) } func (s *styleImpl) SetPaddingRight(value string) Style { - return s.Set(ST_PADDING_RIGHT, value) + return s.Set(StPaddingRight, value) } func (s *styleImpl) SetPaddingRightPx(width int) Style { @@ -541,11 +541,11 @@ func (s *styleImpl) SetPaddingRightPx(width int) Style { } func (s *styleImpl) PaddingTop() string { - return s.Get(ST_PADDING_TOP) + return s.Get(StPaddingTop) } func (s *styleImpl) SetPaddingTop(value string) Style { - return s.Set(ST_PADDING_TOP, value) + return s.Set(StPaddingTop, value) } func (s *styleImpl) SetPaddingTopPx(height int) Style { @@ -553,11 +553,11 @@ func (s *styleImpl) SetPaddingTopPx(height int) Style { } func (s *styleImpl) PaddingBottom() string { - return s.Get(ST_PADDING_BOTTOM) + return s.Get(StPaddingBottom) } func (s *styleImpl) SetPaddingBottom(value string) Style { - return s.Set(ST_PADDING_BOTTOM, value) + return s.Set(StPaddingBottom, value) } func (s *styleImpl) SetPaddingBottomPx(height int) Style { @@ -565,11 +565,11 @@ func (s *styleImpl) SetPaddingBottomPx(height int) Style { } func (s *styleImpl) Margin() string { - return s.Get(ST_MARGIN) + return s.Get(StMargin) } func (s *styleImpl) SetMargin(value string) Style { - return s.Set(ST_MARGIN, value) + return s.Set(StMargin, value) } func (s *styleImpl) SetMargin2(top, right, bottom, left string) Style { @@ -581,11 +581,11 @@ func (s *styleImpl) SetMarginPx(top, right, bottom, left int) Style { } func (s *styleImpl) MarginLeft() string { - return s.Get(ST_MARGIN_LEFT) + return s.Get(StMarginLeft) } func (s *styleImpl) SetMarginLeft(value string) Style { - return s.Set(ST_MARGIN_LEFT, value) + return s.Set(StMarginLeft, value) } func (s *styleImpl) SetMarginLeftPx(width int) Style { @@ -593,11 +593,11 @@ func (s *styleImpl) SetMarginLeftPx(width int) Style { } func (s *styleImpl) MarginRight() string { - return s.Get(ST_MARGIN_RIGHT) + return s.Get(StMarginRight) } func (s *styleImpl) SetMarginRight(value string) Style { - return s.Set(ST_MARGIN_RIGHT, value) + return s.Set(StMarginRight, value) } func (s *styleImpl) SetMarginRightPx(width int) Style { @@ -605,11 +605,11 @@ func (s *styleImpl) SetMarginRightPx(width int) Style { } func (s *styleImpl) MarginTop() string { - return s.Get(ST_MARGIN_TOP) + return s.Get(StMarginTop) } func (s *styleImpl) SetMarginTop(value string) Style { - return s.Set(ST_MARGIN_TOP, value) + return s.Set(StMarginTop, value) } func (s *styleImpl) SetMarginTopPx(height int) Style { @@ -617,11 +617,11 @@ func (s *styleImpl) SetMarginTopPx(height int) Style { } func (s *styleImpl) MarginBottom() string { - return s.Get(ST_MARGIN_BOTTOM) + return s.Get(StMarginBottom) } func (s *styleImpl) SetMarginBottom(value string) Style { - return s.Set(ST_MARGIN_BOTTOM, value) + return s.Set(StMarginBottom, value) } func (s *styleImpl) SetMarginBottomPx(height int) Style { @@ -629,19 +629,19 @@ func (s *styleImpl) SetMarginBottomPx(height int) Style { } func (s *styleImpl) Background() string { - return s.Get(ST_BACKGROUND) + return s.Get(StBackground) } func (s *styleImpl) SetBackground(value string) Style { - return s.Set(ST_BACKGROUND, value) + return s.Set(StBackground, value) } func (s *styleImpl) Border() string { - return s.Get(ST_BORDER) + return s.Get(StBorder) } func (s *styleImpl) SetBorder(value string) Style { - return s.Set(ST_BORDER, value) + return s.Set(StBorder, value) } func (s *styleImpl) SetBorder2(width int, style, color string) Style { @@ -649,11 +649,11 @@ func (s *styleImpl) SetBorder2(width int, style, color string) Style { } func (s *styleImpl) BorderLeft() string { - return s.Get(ST_BORDER_LEFT) + return s.Get(StBorderLeft) } func (s *styleImpl) SetBorderLeft(value string) Style { - return s.Set(ST_BORDER_LEFT, value) + return s.Set(StBorderLeft, value) } func (s *styleImpl) SetBorderLeft2(width int, style, color string) Style { @@ -661,11 +661,11 @@ func (s *styleImpl) SetBorderLeft2(width int, style, color string) Style { } func (s *styleImpl) BorderRight() string { - return s.Get(ST_BORDER_RIGHT) + return s.Get(StBorderRight) } func (s *styleImpl) SetBorderRight(value string) Style { - return s.Set(ST_BORDER_RIGHT, value) + return s.Set(StBorderRight, value) } func (s *styleImpl) SetBorderRight2(width int, style, color string) Style { @@ -673,11 +673,11 @@ func (s *styleImpl) SetBorderRight2(width int, style, color string) Style { } func (s *styleImpl) BorderTop() string { - return s.Get(ST_BORDER_TOP) + return s.Get(StBorderTop) } func (s *styleImpl) SetBorderTop(value string) Style { - return s.Set(ST_BORDER_TOP, value) + return s.Set(StBorderTop, value) } func (s *styleImpl) SetBorderTop2(width int, style, color string) Style { @@ -685,11 +685,11 @@ func (s *styleImpl) SetBorderTop2(width int, style, color string) Style { } func (s *styleImpl) BorderBottom() string { - return s.Get(ST_BORDER_BOTTOM) + return s.Get(StBorderBottom) } func (s *styleImpl) SetBorderBottom(value string) Style { - return s.Set(ST_BORDER_BOTTOM, value) + return s.Set(StBorderBottom, value) } func (s *styleImpl) SetBorderBottom2(width int, style, color string) Style { @@ -697,59 +697,59 @@ func (s *styleImpl) SetBorderBottom2(width int, style, color string) Style { } func (s *styleImpl) Color() string { - return s.Get(ST_COLOR) + return s.Get(StColor) } func (s *styleImpl) SetColor(value string) Style { - return s.Set(ST_COLOR, value) + return s.Set(StColor, value) } func (s *styleImpl) Cursor() string { - return s.Get(ST_CURSOR) + return s.Get(StCursor) } func (s *styleImpl) SetCursor(value string) Style { - return s.Set(ST_CURSOR, value) + return s.Set(StCursor, value) } func (s *styleImpl) Display() string { - return s.Get(ST_DISPLAY) + return s.Get(StDisplay) } func (s *styleImpl) SetDisplay(value string) Style { - return s.Set(ST_DISPLAY, value) + return s.Set(StDisplay, value) } func (s *styleImpl) FontSize() string { - return s.Get(ST_FONT_SIZE) + return s.Get(StFontSize) } func (s *styleImpl) SetFontSize(value string) Style { - return s.Set(ST_FONT_SIZE, value) + return s.Set(StFontSize, value) } func (s *styleImpl) FontStyle() string { - return s.Get(ST_FONT_STYLE) + return s.Get(StFontStyle) } func (s *styleImpl) SetFontStyle(value string) Style { - return s.Set(ST_FONT_STYLE, value) + return s.Set(StFontStyle, value) } func (s *styleImpl) FontWeight() string { - return s.Get(ST_FONT_WEIGHT) + return s.Get(StFontWeight) } func (s *styleImpl) SetFontWeight(value string) Style { - return s.Set(ST_FONT_WEIGHT, value) + return s.Set(StFontWeight, value) } func (s *styleImpl) Height() string { - return s.Get(ST_HEIGHT) + return s.Get(StHeight) } func (s *styleImpl) SetHeight(value string) Style { - return s.Set(ST_HEIGHT, value) + return s.Set(StHeight, value) } func (s *styleImpl) SetHeightPx(height int) Style { return s.SetHeight(strconv.Itoa(height) + "px") @@ -760,11 +760,11 @@ func (s *styleImpl) SetFullHeight() Style { } func (s *styleImpl) Width() string { - return s.Get(ST_WIDTH) + return s.Get(StWidth) } func (s *styleImpl) SetWidth(value string) Style { - return s.Set(ST_WIDTH, value) + return s.Set(StWidth, value) } func (s *styleImpl) SetWidthPx(width int) Style { @@ -776,41 +776,41 @@ func (s *styleImpl) SetFullWidth() Style { } func (s *styleImpl) WhiteSpace() string { - return s.Get(ST_WHITE_SPACE) + return s.Get(StWhiteSpace) } func (s *styleImpl) SetWhiteSpace(value string) Style { - return s.Set(ST_WHITE_SPACE, value) + return s.Set(StWhiteSpace, value) } -func (s *styleImpl) render(w writer) { +func (s *styleImpl) render(w Writer) { s.renderClasses(w) if s.attrs != nil { - w.Write(_STR_STYLE) + w.Write(strStyle) s.renderAttrs(w) - w.Write(_STR_QUOTE) + w.Write(strQuote) } } -func (s *styleImpl) renderClasses(w writer) { +func (s *styleImpl) renderClasses(w Writer) { if len(s.classes) > 0 { - w.Write(_STR_CLASS) + w.Write(strClass) for i, class := range s.classes { if i > 0 { - w.Write(_STR_SPACE) + w.Write(strSpace) } w.Writes(class) } - w.Write(_STR_QUOTE) + w.Write(strQuote) } } -func (s *styleImpl) renderAttrs(w writer) { +func (s *styleImpl) renderAttrs(w Writer) { for name, value := range s.attrs { w.Writes(name) - w.Write(_STR_COLON) + w.Write(strColon) w.Writes(value) - w.Write(_STR_SEMICOL) + w.Write(strSemicol) } } diff --git a/gwu/table.go b/gwu/table.go index d603e94..19dcde0 100644 --- a/gwu/table.go +++ b/gwu/table.go @@ -1,15 +1,15 @@ // Copyright (C) 2013 Andras Belicza. All rights reserved. -// +// // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// +// // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program. If not, see . @@ -23,7 +23,7 @@ package gwu // if table size is known or can be guessed before/during building it, // it is recommended to call EnsureSize to minimize reallocations // in the background. -// +// // Default style class: "gwu-Table" type Table interface { // Table is a TableView. @@ -81,6 +81,16 @@ type Table interface { // If the table does not have a cell specified by row and col, // this is a no-op. SetColSpan(row, col, colSpan int) + + // Trim trims all the rows: removes trailing cells that has nil component + // by making the rows shorter. + // This comes handy for example if the table contains cells where colspan > 1 is set; + // by calling this method we can ensure no empty cells will be rendered at the end of such rows. + Trim() + + // TrimRow trims the specified row: removes trailing cells that has nil value + // by making the row shorter. + TrimRow(row int) } // cellIdx type specifies a cell by its row and col indices. @@ -98,8 +108,8 @@ type tableImpl struct { } // NewTable creates a new Table. -// Default horizontal alignment is HA_DEFAULT, -// default vertical alignment is VA_DEFAULT. +// Default horizontal alignment is HADefault, +// default vertical alignment is VADefault. func NewTable() Table { c := &tableImpl{tableViewImpl: newTableViewImpl()} c.Style().AddClass("gwu-Table") @@ -271,8 +281,11 @@ func (c *tableImpl) Add(c2 Comp, row, col int) bool { if row < 0 || col < 0 { return false } - if row >= len(c.comps) || col >= len(c.comps[row]) { - c.EnsureSize(row+1, col+1) + if row >= len(c.comps) { + c.ensureRows(row + 1) + } + if col >= len(c.comps[row]) { + c.EnsureCols(row, col+1) } rowComps := c.comps[row] @@ -332,11 +345,33 @@ func (c *tableImpl) SetColSpan(row, col, colSpan int) { } } -func (c *tableImpl) Render(w writer) { - w.Write(_STR_TABLE_OP) +func (c *tableImpl) Trim() { + for row := range c.comps { + c.TrimRow(row) + } +} + +func (c *tableImpl) TrimRow(row int) { + if row < 0 || row >= len(c.comps) { + return + } + + rowComps := c.comps[row] + ci := cellIdx{row: row, col: len(rowComps) - 1} // Create a reusable cell index + for ; ci.col >= 0 && rowComps[ci.col] == nil; ci.col-- { + // Cell is about to "disappear", remove its formatter (if any) + delete(c.cellFmts, ci) + } + + // ci.col now points to a non-nil component (or is -1) + c.comps[row] = rowComps[:ci.col+1] +} + +func (c *tableImpl) Render(w Writer) { + w.Write(strTableOp) c.renderAttrsAndStyle(w) c.renderEHandlers(w) - w.Write(_STR_GT) + w.Write(strGT) // Create a reusable cell index ci := cellIdx{} @@ -352,11 +387,11 @@ func (c *tableImpl) Render(w writer) { } } - w.Write(_STR_TABLE_CL) + w.Write(strTableCl) } // renderRowTr renders the formatted HTML TR tag for the specified row. -func (c *tableImpl) renderRowTr(row int, w writer) { +func (c *tableImpl) renderRowTr(row int, w Writer) { var defha HAlign = c.halign // default halign of the table var defva VAlign = c.valign // default valign of the table @@ -366,22 +401,22 @@ func (c *tableImpl) renderRowTr(row int, w writer) { // If rf does not specify alignments, it means alignments must not be overriden, // default alignments of the table must be used! ha, va := rf.halign, rf.valign - if ha == HA_DEFAULT { + if ha == HADefault { ha = defha } - if va == VA_DEFAULT { + if va == VADefault { va = defva } - rf.renderWithAligns(_STR_TR_OP, ha, va, w) + rf.renderWithAligns(strTROp, ha, va, w) } } // renderTd renders the formatted HTML TD tag for the specified cell. -func (c *tableImpl) renderTd(ci cellIdx, w writer) { +func (c *tableImpl) renderTd(ci cellIdx, w Writer) { if cf := c.cellFmts[ci]; cf == nil { - w.Write(_STR_TD) + w.Write(strTD) } else { - cf.render(_STR_TD_OP, w) + cf.render(strTDOp, w) } } diff --git a/gwu/tabpanel.go b/gwu/tabpanel.go index d37c9f5..3960c25 100644 --- a/gwu/tabpanel.go +++ b/gwu/tabpanel.go @@ -1,15 +1,15 @@ // Copyright (C) 2013 Andras Belicza. All rights reserved. -// +// // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// +// // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program. If not, see . @@ -19,7 +19,7 @@ package gwu // TabBar interface defines the tab bar for selecting the visible // component of a TabPanel. -// +// // Note: Removing a tab component through the tab bar also // removes the content component from the tab panel of the tab bar. // @@ -65,16 +65,16 @@ type TabBarPlacement int // Tab bar placements. const ( - TB_PLACEMENT_TOP TabBarPlacement = iota // Tab bar placement to Top - TB_PLACEMENT_BOTTOM // Tab bar placement to Bottom - TB_PLACEMENT_LEFT // Tab bar placement to Left - TB_PLACEMENT_RIGHT // Tab bar placement to Right + TbPlacementTop TabBarPlacement = iota // Tab bar placement to Top + TbPlacementBottom // Tab bar placement to Bottom + TbPlacementLeft // Tab bar placement to Left + TbPlacementRight // Tab bar placement to Right ) // TabPanel interface defines a PanelView which has multiple child components // but only one is visible at a time. The visible child can be visually selected // using an internal TabBar component. -// +// // Both the tab panel and its internal tab bar component are PanelViews. // This gives high layout configuration possibilities. // Usually you only need to set the tab bar placement with the SetTabBarPlacement() @@ -87,12 +87,12 @@ const ( // the content alignment, e.g. with the SetAlignment() method. You can also set different // alignments for individual tab components using TabBar().CellFmt(). You can also set // other cell formatting applied to the tab bar using TabBarFmt() method. -// -// You can register ETYPE_STATE_CHANGE event handlers which will be called when the user +// +// You can register ETypeStateChange event handlers which will be called when the user // changes tab selection by clicking on a tab. The event source will be the tab panel. // The event will have a parent event whose source will be the clicked tab and will // contain the mouse coordinates. -// +// // Default style classes: "gwu-TabPanel", "gwu-TabPanel-Content" type TabPanel interface { // TabPanel is a Container. @@ -149,15 +149,15 @@ type tabPanelImpl struct { } // NewTabPanel creates a new TabPanel. -// Default tab bar placement is TB_PLACEMENT_TOP, -// default horizontal alignment is HA_DEFAULT, -// default vertical alignment is VA_DEFAULT. +// Default tab bar placement is TbPlacementTop, +// default horizontal alignment is HADefault, +// default vertical alignment is VADefault. func NewTabPanel() TabPanel { c := &tabPanelImpl{panelImpl: newPanelImpl(), tabBarImpl: newTabBarImpl(), tabBarFmt: newCellFmtImpl(), selected: -1, prevSelected: -1} c.tabBarFmt.Style().AddClass("gwu-TabBar") c.tabBarImpl.setParent(c) - c.SetTabBarPlacement(TB_PLACEMENT_TOP) - c.tabBarFmt.SetAlign(HA_LEFT, VA_TOP) + c.SetTabBarPlacement(TbPlacementTop) + c.tabBarFmt.SetAlign(HALeft, VATop) c.Style().AddClass("gwu-TabPanel") return c } @@ -243,34 +243,34 @@ func (c *tabPanelImpl) SetTabBarPlacement(tabBarPlacement TabBarPlacement) { // Remove old style class switch c.tabBarPlacement { - case TB_PLACEMENT_TOP: + case TbPlacementTop: style.RemoveClass("gwu-TabBar-Top") - case TB_PLACEMENT_BOTTOM: + case TbPlacementBottom: style.RemoveClass("gwu-TabBar-Bottom") - case TB_PLACEMENT_LEFT: + case TbPlacementLeft: style.RemoveClass("gwu-TabBar-Left") - case TB_PLACEMENT_RIGHT: + case TbPlacementRight: style.RemoveClass("gwu-TabBar-Right") } c.tabBarPlacement = tabBarPlacement switch tabBarPlacement { - case TB_PLACEMENT_TOP: - c.tabBarImpl.SetLayout(LAYOUT_HORIZONTAL) - c.tabBarImpl.SetAlign(HA_LEFT, VA_BOTTOM) + case TbPlacementTop: + c.tabBarImpl.SetLayout(LayoutHorizontal) + c.tabBarImpl.SetAlign(HALeft, VABottom) style.AddClass("gwu-TabBar-Top") - case TB_PLACEMENT_BOTTOM: - c.tabBarImpl.SetLayout(LAYOUT_HORIZONTAL) - c.tabBarImpl.SetAlign(HA_LEFT, VA_TOP) + case TbPlacementBottom: + c.tabBarImpl.SetLayout(LayoutHorizontal) + c.tabBarImpl.SetAlign(HALeft, VATop) style.AddClass("gwu-TabBar-Bottom") - case TB_PLACEMENT_LEFT: - c.tabBarImpl.SetLayout(LAYOUT_VERTICAL) - c.tabBarImpl.SetAlign(HA_RIGHT, VA_TOP) + case TbPlacementLeft: + c.tabBarImpl.SetLayout(LayoutVertical) + c.tabBarImpl.SetAlign(HARight, VATop) style.AddClass("gwu-TabBar-Left") - case TB_PLACEMENT_RIGHT: - c.tabBarImpl.SetLayout(LAYOUT_VERTICAL) - c.tabBarImpl.SetAlign(HA_LEFT, VA_TOP) + case TbPlacementRight: + c.tabBarImpl.SetLayout(LayoutVertical) + c.tabBarImpl.SetAlign(HALeft, VATop) style.AddClass("gwu-TabBar-Right") } } @@ -293,15 +293,15 @@ func (c *tabPanelImpl) Add(tab, content Comp) { tab.AddEHandlerFunc(func(e Event) { c.SetSelected(c.CompIdx(content)) e.MarkDirty(c) - if c.handlers[ETYPE_STATE_CHANGE] != nil { - c.dispatchEvent(e.forkEvent(ETYPE_STATE_CHANGE, c)) + if c.handlers[ETypeStateChange] != nil { + c.dispatchEvent(e.forkEvent(ETypeStateChange, c)) } - }, ETYPE_CLICK) + }, ETypeClick) } func (c *tabPanelImpl) AddString(tab string, content Comp) { tabc := NewLabel(tab) - tabc.Style().SetDisplay(DISPLAY_BLOCK) // Display: block - so the whole cell of the tab is clickable + tabc.Style().SetDisplay(DisplayBlock) // Display: block - so the whole cell of the tab is clickable c.Add(tabc, content) } @@ -336,48 +336,48 @@ func (c *tabPanelImpl) SetSelected(idx int) { } } -func (c *tabPanelImpl) Render(w writer) { - w.Write(_STR_TABLE_OP) +func (c *tabPanelImpl) Render(w Writer) { + w.Write(strTableOp) c.renderAttrsAndStyle(w) c.renderEHandlers(w) - w.Write(_STR_GT) + w.Write(strGT) switch c.tabBarPlacement { - case TB_PLACEMENT_TOP: - w.Write(_STR_TR) - c.tabBarFmt.render(_STR_TD_OP, w) + case TbPlacementTop: + w.Write(strTR) + c.tabBarFmt.render(strTDOp, w) c.tabBarImpl.Render(w) c.renderTr(w) c.renderContent(w) - case TB_PLACEMENT_BOTTOM: + case TbPlacementBottom: c.renderTr(w) c.renderContent(w) - w.Write(_STR_TR) - c.tabBarFmt.render(_STR_TD_OP, w) + w.Write(strTR) + c.tabBarFmt.render(strTDOp, w) c.tabBarImpl.Render(w) - case TB_PLACEMENT_LEFT: + case TbPlacementLeft: c.renderTr(w) - c.tabBarFmt.render(_STR_TD_OP, w) + c.tabBarFmt.render(strTDOp, w) c.tabBarImpl.Render(w) c.renderContent(w) - case TB_PLACEMENT_RIGHT: + case TbPlacementRight: c.renderTr(w) c.renderContent(w) - c.tabBarFmt.render(_STR_TD_OP, w) + c.tabBarFmt.render(strTDOp, w) c.tabBarImpl.Render(w) } - w.Write(_STR_TABLE_CL) + w.Write(strTableCl) } // renderContent renders the selected content component. -func (c *tabPanelImpl) renderContent(w writer) { +func (c *tabPanelImpl) renderContent(w Writer) { // Render only the selected content component if c.selected >= 0 { c2 := c.comps[c.selected] c.renderTd(c2, w) c2.Render(w) } else { - w.Write(_STR_TD) + w.Write(strTD) } } diff --git a/gwu/textbox_pwbox.go b/gwu/textbox_pwbox.go index 71e22e1..84458a6 100644 --- a/gwu/textbox_pwbox.go +++ b/gwu/textbox_pwbox.go @@ -1,15 +1,15 @@ // Copyright (C) 2013 Andras Belicza. All rights reserved. -// +// // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// +// // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program. If not, see . @@ -23,17 +23,17 @@ import ( ) // TextBox interface defines a component for text input purpose. -// -// Suggested event type to handle actions: ETYPE_CHANGE +// +// Suggested event type to handle actions: ETypeChange // // By default the value of the TextBox is synchronized with the server -// on ETYPE_CHANGE event which is when the TextBox loses focus +// on ETypeChange event which is when the TextBox loses focus // or when the ENTER key is pressed. // If you want a TextBox to synchronize values during editing -// (while you type in characters), add the ETYPE_KEY_UP event type +// (while you type in characters), add the ETypeKeyUp event type // to the events on which synchronization happens by calling: -// AddSyncOnETypes(ETYPE_KEY_UP) -// +// AddSyncOnETypes(ETypeKeyUp) +// // Default style class: "gwu-TextBox" type TextBox interface { // TextBox is a component. @@ -77,17 +77,17 @@ type TextBox interface { } // PasswBox interface defines a text box for password input purpose. -// -// Suggested event type to handle actions: ETYPE_CHANGE +// +// Suggested event type to handle actions: ETypeChange // // By default the value of the PasswBox is synchronized with the server -// on ETYPE_CHANGE event which is when the PasswBox loses focus +// on ETypeChange event which is when the PasswBox loses focus // or when the ENTER key is pressed. // If you want a PasswBox to synchronize values during editing -// (while you type in characters), add the ETYPE_KEY_UP event type +// (while you type in characters), add the ETypeKeyUp event type // to the events on which synchronization happens by calling: -// AddSyncOnETypes(ETYPE_KEY_UP) -// +// AddSyncOnETypes(ETypeKeyUp) +// // Default style class: "gwu-PasswBox" type PasswBox interface { // PasswBox is a TextBox. @@ -105,19 +105,19 @@ type textBoxImpl struct { } var ( - _STR_ENC_URI_THIS_V = []byte("encodeURIComponent(this.value)") // "encodeURIComponent(this.value)" + strEncURIThisV = []byte("encodeURIComponent(this.value)") // "encodeURIComponent(this.value)" ) // NewTextBox creates a new TextBox. func NewTextBox(text string) TextBox { - c := newTextBoxImpl(_STR_ENC_URI_THIS_V, text, false) + c := newTextBoxImpl(strEncURIThisV, text, false) c.Style().AddClass("gwu-TextBox") return &c } // NewPasswBox creates a new PasswBox. func NewPasswBox(text string) TextBox { - c := newTextBoxImpl(_STR_ENC_URI_THIS_V, text, true) + c := newTextBoxImpl(strEncURIThisV, text, true) c.Style().AddClass("gwu-PasswBox") return &c } @@ -125,7 +125,7 @@ func NewPasswBox(text string) TextBox { // newTextBoxImpl creates a new textBoxImpl. func newTextBoxImpl(valueProviderJs []byte, text string, isPassw bool) textBoxImpl { c := textBoxImpl{newCompImpl(valueProviderJs), newHasTextImpl(text), newHasEnabledImpl(), isPassw, 1, 20} - c.AddSyncOnETypes(ETYPE_CHANGE) + c.AddSyncOnETypes(ETypeChange) return c } @@ -177,20 +177,20 @@ func (c *textBoxImpl) SetMaxLength(maxLength int) { func (c *textBoxImpl) preprocessEvent(event Event, r *http.Request) { // Empty string for text box is a valid value. - // So we have to check whether it is supplied, not just whether its len() > 0 - value := r.FormValue(_PARAM_COMP_VALUE) + // So we have to check whether it is supplied, not just whether its len() > 0 + value := r.FormValue(paramCompValue) if len(value) > 0 { c.text = value } else { // Empty string might be a valid value, if the component value param is present: - values, present := r.Form[_PARAM_COMP_VALUE] // Form is surely parsed (we called FormValue()) + values, present := r.Form[paramCompValue] // Form is surely parsed (we called FormValue()) if present && len(values) > 0 { c.text = values[0] } } } -func (c *textBoxImpl) Render(w writer) { +func (c *textBoxImpl) Render(w Writer) { if c.rows <= 1 || c.isPassw { c.renderInput(w) } else { @@ -199,45 +199,45 @@ func (c *textBoxImpl) Render(w writer) { } var ( - _STR_INPUT_OP = []byte(``) // `"/>` + strInputOp = []byte(``) // `"/>` ) // renderInput renders the component as an input HTML tag. -func (c *textBoxImpl) renderInput(w writer) { - w.Write(_STR_INPUT_OP) +func (c *textBoxImpl) renderInput(w Writer) { + w.Write(strInputOp) if c.isPassw { - w.Write(_STR_PASSWORD) + w.Write(strPassword) } else { - w.Write(_STR_TEXT) + w.Write(strText) } - w.Write(_STR_SIZE) + w.Write(strSize) w.Writev(c.cols) - w.Write(_STR_QUOTE) + w.Write(strQuote) c.renderAttrsAndStyle(w) c.renderEnabled(w) c.renderEHandlers(w) - w.Write(_STR_VALUE) + w.Write(strValue) c.renderText(w) - w.Write(_STR_INPUT_CL) + w.Write(strInputCl) } var ( - _STR_TEXTAREA_OP = []byte("\n") // "\">\n" - _STR_TEXTAREA_CL = []byte("") // "" + strTextareaOp = []byte("\n") // "\">\n" + strTextAreaCl = []byte("") // "" ) // renderTextArea renders the component as an textarea HTML tag. -func (c *textBoxImpl) renderTextArea(w writer) { - w.Write(_STR_TEXTAREA_OP) +func (c *textBoxImpl) renderTextArea(w Writer) { + w.Write(strTextareaOp) c.renderAttrsAndStyle(w) c.renderEnabled(w) c.renderEHandlers(w) @@ -245,12 +245,12 @@ func (c *textBoxImpl) renderTextArea(w writer) { // New line char after the