Repos / s4g / dd54544b8e
commit dd54544b8e94264f2e4a7e02bd61b5ede00808fa
Author: Nhân <hi@imnhan.com>
Date: Mon Jul 17 15:19:17 2023 +0700
gui dialog to create/open
diff --git a/gui/gui.go b/gui/gui.go
index 93fd10a..cb3ec50 100644
--- a/gui/gui.go
+++ b/gui/gui.go
@@ -2,52 +2,46 @@
import (
"bufio"
+ _ "embed"
"fmt"
+ "io"
"os/exec"
+ "path/filepath"
+ "strings"
"go.imnhan.com/webmaker2000/gui/ipc"
)
-func Start(tclPath string) {
- cmd := exec.Command(tclPath, "-encoding", "utf-8")
- stdout, err := cmd.StdoutPipe()
- if err != nil {
- panic(err)
- }
+//go:embed tcl/main.tcl
+var tclMain []byte
- stdin, err := cmd.StdinPipe()
- if err != nil {
- panic(err)
- }
+//go:embed tcl/choose-task.tcl
+var tclChooseTask []byte
- stderr, err := cmd.StderrPipe()
- if err != nil {
- panic(err)
- }
-
- err = cmd.Start()
- if err != nil {
- panic(err)
- }
+func Main(tclPath string) {
+ interp := newInterp(tclPath)
go func() {
- errscanner := bufio.NewScanner(stderr)
+ errscanner := bufio.NewScanner(interp.stderr)
for errscanner.Scan() {
errtext := errscanner.Text()
fmt.Printf("XXX %s\n", errtext)
}
}()
- fmt.Fprintln(stdin, `source -encoding "utf-8" tcl/main.tcl`)
+ _, err := interp.stdin.Write(tclMain)
+ if err != nil {
+ panic(err)
+ }
println("Loaded main tcl script.")
- fmt.Fprintln(stdin, "initialize")
+ fmt.Fprintln(interp.stdin, "initialize")
respond := func(values ...string) {
- ipc.Respond(stdin, values)
+ ipc.Respond(interp.stdin, values)
}
- for req := range ipc.Requests(stdout) {
+ for req := range ipc.Requests(interp.stdout) {
switch req.Method {
case "forcefocus":
@@ -62,3 +56,73 @@ func Start(tclPath string) {
println("Tcl process terminated.")
}
+
+type Task string
+
+const (
+ TaskOpen Task = "open"
+ TaskCreate Task = "create"
+)
+
+func ChooseTask(tclPath string) (task Task, path string, ok bool) {
+ interp := newInterp(tclPath)
+ interp.stdin.Write(tclChooseTask)
+ interp.stdin.Close()
+ resp, err := io.ReadAll(interp.stdout)
+ if err != nil {
+ panic(err)
+ }
+
+ action, path, found := strings.Cut(strings.TrimSpace(string(resp)), " ")
+ if !found {
+ fmt.Println("No task chosen")
+ return "", "", false
+ }
+
+ switch action {
+ case string(TaskOpen):
+ return TaskOpen, filepath.Dir(path), true
+ case string(TaskCreate):
+ return TaskCreate, path, true
+ default:
+ fmt.Printf("Unexpected tclChooseTask output: %s\n", string(resp))
+ return "", "", false
+ }
+}
+
+type tclInterp struct {
+ cmd *exec.Cmd
+ stdin io.WriteCloser
+ stdout io.ReadCloser
+ stderr io.ReadCloser
+}
+
+func newInterp(tclPath string) *tclInterp {
+ cmd := exec.Command(tclPath, "-encoding", "utf-8")
+ stdout, err := cmd.StdoutPipe()
+ if err != nil {
+ panic(err)
+ }
+
+ stdin, err := cmd.StdinPipe()
+ if err != nil {
+ panic(err)
+ }
+
+ stderr, err := cmd.StderrPipe()
+ if err != nil {
+ panic(err)
+ }
+
+ err = cmd.Start()
+ if err != nil {
+ panic(err)
+ }
+
+ return &tclInterp{
+ cmd: cmd,
+ stdin: stdin,
+ stdout: stdout,
+ stderr: stderr,
+ }
+}
diff --git a/gui/tcl/choose-task.tcl b/gui/tcl/choose-task.tcl
new file mode 100644
index 0000000..74f92e2
--- /dev/null
+++ b/gui/tcl/choose-task.tcl
@@ -0,0 +1,41 @@
+package require Tk
+
+wm title . "Create or Open?"
+
+set OS [lindex $tcl_platform(os) 0]
+if {$OS == "Windows"} {
+ ttk::style theme use vista
+} elseif {$OS == "Darwin"} {
+ ttk::style theme use aqua
+} else {
+ ttk::style theme use clam
+}
+
+set types {
+ {{WebMaker2000 Files} {.wbmkr2k}}
+}
+
+ttk::frame .c -padding "10"
+ttk::label .c.label -text {Would you like to create a new blog, or open an existing one?}
+ttk::button .c.createBtn -text "Create..." -padding 5 -command {
+ #set filename [tk_getSaveFile -title "Create" -filetypes $types]
+ set filename [tk_chooseDirectory]
+ if {$filename != ""} {
+ puts "create ${filename}"
+ exit
+ }
+}
+ttk::button .c.openBtn -text "Open..." -padding 5 -command {
+ set filename [tk_getOpenFile -filetypes $types]
+ if {$filename != ""} {
+ puts "open ${filename}"
+ exit
+ }
+}
+
+grid .c -column 0 -row 0
+grid .c.label -column 0 -row 0 -columnspan 2 -pady "0 10"
+grid .c.createBtn -column 0 -row 1 -padx 10
+grid .c.openBtn -column 1 -row 1 -padx 10
+
+tk::PlaceWindow . center
diff --git a/gui/tcl/main.tcl b/gui/tcl/main.tcl
index 0893a67..7dbf5ea 100644
--- a/gui/tcl/main.tcl
+++ b/gui/tcl/main.tcl
@@ -10,183 +10,23 @@ foreach p {stdin stdout stderr} {
package require Tk
-wm title . "WebMaker200"
+wm title . "WebMaker2000"
tk appname webmaker2000
-# Proper Windows theme doesn't allow setting fieldbackground on text inputs,
-# so let's settle with `clam` instead.
-ttk::style theme use clam
+set OS [lindex $tcl_platform(os) 0]
+if {$OS == "Windows"} {
+ ttk::style theme use vista
+} elseif {$OS == "Darwin"} {
+ ttk::style theme use aqua
+} else {
+ ttk::style theme use clam
+}
wm protocol . WM_DELETE_WINDOW {
exit 0
}
-# Data that we send to the actual web-based overlay:
-array set scoreboard {
- description ""
- subtitle ""
- p1name ""
- p1country ""
- p1score 0
- p1team ""
- p2name ""
- p2country ""
- p2score 0
- p2team ""
-}
-
-# $applied_scoreboard represents data that has actually been applied
-# to the overlay. This is used to display diff in the UI, and to restore data
-# when user clicks "Discard".
-foreach key [array names scoreboard] {
- set applied_scoreboard($key) scoreboard($key)
-}
-
-array set var_to_widget {
- description .n.m.description.entry
- subtitle .n.m.subtitle.entry
- p1name .n.m.players.p1name
- p1country .n.m.players.p1country
- p1score .n.m.players.p1score
- p1team .n.m.players.p1team
- p2name .n.m.players.p2name
- p2country .n.m.players.p2country
- p2score .n.m.players.p2score
- p2team .n.m.players.p2team
-}
-
-array set startgg {
- token ""
- slug ""
- msg ""
-}
-
-# GUI has 2 tabs: Main (.n.m) and start.gg (.n.s)
-
-ttk::notebook .n
-ttk::frame .n.m -padding 5
-ttk::frame .n.s -padding 5
-.n add .n.m -text Main
-.n add .n.s -text start.gg
-grid .n -column 0 -row 0 -sticky NESW
-
-# Main tab:
-
-ttk::frame .n.m.description
-ttk::label .n.m.description.lbl -text "Title"
-ttk::entry .n.m.description.entry -textvariable scoreboard(description)
-ttk::frame .n.m.subtitle
-ttk::label .n.m.subtitle.lbl -text "Subtitle"
-ttk::entry .n.m.subtitle.entry -textvariable scoreboard(subtitle)
-ttk::frame .n.m.players
-ttk::label .n.m.players.p1lbl -text "Player 1"
-ttk::combobox .n.m.players.p1name -textvariable scoreboard(p1name) -width 35
-ttk::combobox .n.m.players.p1country -textvariable scoreboard(p1country) -width 5
-ttk::spinbox .n.m.players.p1score -textvariable scoreboard(p1score) -from 0 -to 999 -width 4
-ttk::button .n.m.players.p1win -text "▲ Win" -width 6 -command {incr scoreboard(p1score)}
-ttk::label .n.m.players.p1teamlbl -text "Team 1"
-ttk::combobox .n.m.players.p1team -textvariable scoreboard(p1team)
-ttk::separator .n.m.players.separator -orient horizontal
-ttk::label .n.m.players.p2lbl -text "Player 2"
-ttk::combobox .n.m.players.p2name -textvariable scoreboard(p2name) -width 35
-ttk::combobox .n.m.players.p2country -textvariable scoreboard(p2country) -width 5
-ttk::spinbox .n.m.players.p2score -textvariable scoreboard(p2score) -from 0 -to 999 -width 4
-ttk::button .n.m.players.p2win -text "▲ Win" -width 6 -command {incr scoreboard(p2score)}
-ttk::label .n.m.players.p2teamlbl -text "Team 2"
-ttk::combobox .n.m.players.p2team -textvariable scoreboard(p2team)
-ttk::frame .n.m.buttons
-ttk::button .n.m.buttons.apply -text "▶ Apply" -command applyscoreboard
-ttk::button .n.m.buttons.discard -text "✖ Discard" -command discardscoreboard
-ttk::button .n.m.buttons.reset -text "↶ Reset scores" -command {
- set scoreboard(p1score) 0
- set scoreboard(p2score) 0
-}
-ttk::button .n.m.buttons.swap -text "⇄ Swap players" -command {
- # Since country is updated whenever name is updated, we'll need to write
- # countries last.
- set p1country $scoreboard(p1country)
- set p2country $scoreboard(p2country)
- foreach key {name score team} {
- set tmp $scoreboard(p1$key)
- set scoreboard(p1$key) $scoreboard(p2$key)
- set scoreboard(p2$key) $tmp
- }
- set scoreboard(p1country) $p2country
- set scoreboard(p2country) $p1country
-}
-ttk::label .n.m.status -textvariable mainstatus
-grid .n.m.description -row 0 -column 0 -sticky NESW -pady {0 5}
-grid .n.m.description.lbl -row 0 -column 0 -padx {0 5}
-grid .n.m.description.entry -row 0 -column 1 -sticky EW
-grid columnconfigure .n.m.description 1 -weight 1
-grid .n.m.subtitle -row 1 -column 0 -sticky NESW -pady {0 5}
-grid .n.m.subtitle.lbl -row 0 -column 0 -padx {0 5}
-grid .n.m.subtitle.entry -row 0 -column 1 -sticky EW
-grid columnconfigure .n.m.subtitle 1 -weight 1
-grid .n.m.players -row 2 -column 0
-grid .n.m.players.p1lbl -row 0 -column 0
-grid .n.m.players.p1name -row 0 -column 1
-grid .n.m.players.p1country -row 0 -column 2
-grid .n.m.players.p1score -row 0 -column 3
-grid .n.m.players.p1win -row 0 -column 4 -padx {5 0} -rowspan 2 -sticky NS
-grid .n.m.players.p1teamlbl -row 1 -column 0
-grid .n.m.players.p1team -row 1 -column 1 -columnspan 3 -sticky EW
-grid .n.m.players.separator -row 2 -column 0 -columnspan 5 -pady 10 -sticky EW
-grid .n.m.players.p2lbl -row 3 -column 0
-grid .n.m.players.p2name -row 3 -column 1
-grid .n.m.players.p2country -row 3 -column 2
-grid .n.m.players.p2score -row 3 -column 3
-grid .n.m.players.p2win -row 3 -column 4 -padx {5 0} -rowspan 2 -sticky NS
-grid .n.m.players.p2teamlbl -row 4 -column 0
-grid .n.m.players.p2team -row 4 -column 1 -columnspan 3 -sticky EW
-grid .n.m.buttons -row 3 -column 0 -sticky W -pady {10 0}
-grid .n.m.buttons.apply -row 0 -column 0
-grid .n.m.buttons.discard -row 0 -column 1
-grid .n.m.buttons.reset -row 0 -column 2
-grid .n.m.buttons.swap -row 0 -column 3
-grid .n.m.status -row 4 -column 0 -columnspan 5 -pady {10 0} -sticky EW
-grid columnconfigure .n.m.players 2 -pad 5
-grid columnconfigure .n.m.buttons 1 -pad 15
-grid columnconfigure .n.m.buttons 3 -pad 15
-grid rowconfigure .n.m.players 1 -pad 5
-grid rowconfigure .n.m.players 3 -pad 5
-
-# start.gg tab:
-
-#.n select .n.s; # for debug only
-ttk::label .n.s.tokenlbl -text "Personal token: "
-ttk::entry .n.s.token -show * -textvariable startgg(token)
-ttk::label .n.s.tournamentlbl -text "Tournament slug: "
-ttk::entry .n.s.tournamentslug -textvariable startgg(slug)
-ttk::frame .n.s.buttons
-ttk::button .n.s.buttons.fetch -text "↓ Fetch players" -command fetchplayers
-ttk::button .n.s.buttons.clear -text "✘ Clear" -command clearstartgg
-ttk::label .n.s.msg -textvariable startgg(msg)
-
-grid .n.s.tokenlbl -row 0 -column 0 -sticky W
-grid .n.s.token -row 0 -column 1 -sticky EW
-grid .n.s.tournamentlbl -row 1 -column 0 -sticky W
-grid .n.s.tournamentslug -row 1 -column 1 -sticky EW
-grid .n.s.buttons -row 2 -column 1 -stick WE
-grid .n.s.buttons.fetch -stick W
-grid .n.s.buttons.clear -row 0 -column 1 -stick W -padx 5
-grid .n.s.msg -row 3 -column 1 -stick W
-grid columnconfigure .n.s 1 -weight 1
-grid rowconfigure .n.s 1 -pad 5
-grid rowconfigure .n.s 2 -pad 5
-
proc initialize {} {
- loadicon
- loadstartgg
- loadwebmsg
- loadcountrycodes
- loadscoreboard
- loadplayernames
-
- setupdiffcheck
- setupplayersuggestion
-
-
# By default this window is not focused and not even brought to
# foreground on Windows. I suspect it's because tcl is exec'ed from Go.
# The old "iconify, deiconify" trick no longer seems to work, so this time
diff --git a/main.go b/main.go
index 4849340..1de88ce 100644
--- a/main.go
+++ b/main.go
@@ -18,6 +18,7 @@
"time"
"go.imnhan.com/webmaker2000/djot"
+ "go.imnhan.com/webmaker2000/gui"
"go.imnhan.com/webmaker2000/livereload"
"go.imnhan.com/webmaker2000/writablefs"
)
@@ -37,7 +38,7 @@ func main() {
var cmd string
var args []string
if len(os.Args) < 2 {
- cmd = "serve"
+ cmd = "gui"
args = os.Args[1:]
} else {
cmd = os.Args[1]
@@ -60,6 +61,8 @@ func main() {
case "serve":
serveCmd.Parse(args)
handleServeCmd(serveFolder, servePort)
+ case "gui":
+ handleGuiCmd()
default:
invalidCommand()
}
@@ -73,6 +76,22 @@ func handleNewCmd(folder string) {
}
}
+func handleGuiCmd() {
+ task, path, ok := gui.ChooseTask(gui.DefaultTclPath)
+ if !ok {
+ return
+ }
+
+ switch task {
+ case gui.TaskOpen:
+ fmt.Println(task, path)
+ case gui.TaskCreate:
+ fmt.Println(task, path)
+ default:
+ panic(task)
+ }
+}
+
func handleServeCmd(folder, port string) {
djot.StartService()
fmt.Println("Started djot.js service")