Пример #1
1
func initGUI() {

	/*
		ff := ui.ListFontFamilies()
		for i := 0; i < ff.NumFamilies(); i++ {
			log.Printf("%3d. Font family '%s'\n", i + 1, ff.Family(i))
		}
	*/

	window := ui.NewWindow("Привет мир!", 800, 480, false)
	window.SetMargined(true)

	progress := ui.NewProgressBar()
	labelTime := ui.NewLabel("")

	labelInfo := ui.NewLabel("Info")
	labelInfoButtonHandler := func(b *ui.Button) {
		labelInfo.SetText("Click button " + b.Text())
	}

	hbox := ui.NewHorizontalBox()
	hbox.SetPadded(true)

	hbox.Append(func() *ui.Box {
		box := ui.NewVerticalBox()
		box.SetPadded(true)

		box.Append(func() *ui.Button {
			button := ui.NewButton("Button 1")
			button.OnClicked(labelInfoButtonHandler)
			return button
		}(), false)
		box.Append(func() *ui.Button {
			button := ui.NewButton("Button 2")
			button.OnClicked(labelInfoButtonHandler)
			return button
		}(), false)
		box.Append(func() *ui.Button {
			button := ui.NewButton("Button 3")
			button.OnClicked(labelInfoButtonHandler)
			return button
		}(), false)
		box.Append(ui.NewHorizontalSeparator(), false)

		label := ui.NewLabel("It's all")
		box.Append(label, true)

		box.Append(func() *ui.Button {
			button := ui.NewButton("Exit")
			button.OnClicked(func(*ui.Button) {
				ui.Quit()
			})
			return button
		}(), false)

		return box
	}(), false)

	areaHandler := NewHistogramAreaHandler(20)
	area := ui.NewArea(areaHandler)

	hbox.Append(func() *ui.Box {
		box := ui.NewVerticalBox()
		box.SetPadded(true)
		box.Append(labelInfo, false)
		box.Append(labelTime, false)

		tab := ui.NewTab()
		tab.Append("Histogram demo", area)
		tab.Append("Controls demo", func() *ui.Box {
			box := ui.NewVerticalBox()
			box.SetPadded(true)

			box.Append(ui.NewEntry(), false)
			box.Append(ui.NewCheckbox("Check it"), false)
			box.Append(func() *ui.RadioButtons {
				radio := ui.NewRadioButtons()
				radio.Append("Radio button 1")
				radio.Append("Radio button 2")
				radio.Append("Radio button 3")
				return radio
			}(), false)
			box.Append(func() *ui.Group {
				combo := ui.NewCombobox()
				combo.Append("First")
				combo.Append("Second")
				combo.Append("Third")
				combo.Append("Fourth")
				combo.OnSelected(func(cb *ui.Combobox) {
					ui.MsgBoxError(window, "OnSelected", "Line #"+strconv.Itoa(cb.Selected()+1))
				})
				group := ui.NewGroup("Can't get text, only index")
				group.SetChild(combo)
				return group
			}(), false)
			box.Append(ui.NewSlider(0, 100), false)
			box.Append(ui.NewSpinbox(0, 10), false)
			box.Append(ui.NewDatePicker(), false)
			box.Append(ui.NewDateTimePicker(), false)

			return box
		}())
		tab.Append("Tab 3", ui.NewLabel("At tab 3"))
		box.Append(tab, true)

		box.Append(progress, false)

		return box
	}(), true)

	window.SetChild(hbox)

	window.OnClosing(func(*ui.Window) bool {
		log.Println("Window close")
		ui.Quit()
		return true
	})
	window.Show()

	progressCounter := 0
	progressTicker := time.NewTicker(time.Millisecond * 50)
	go func() {
		for _ = range progressTicker.C {
			// Что бы записать значение в виджет используем потокобезопасный вызов
			ui.QueueMain(func() {
				progress.SetValue(progressCounter)
			})
			progressCounter++
			if progressCounter > 100 {
				progressCounter = 0
			}
		}
	}()

	timeTicker := time.NewTicker(time.Millisecond * 10)
	go func() {
		for t := range timeTicker.C {
			// Что бы записать значение в виджет используем потокобезопасный вызов
			ui.QueueMain(func() {
				labelTime.SetText(t.Format(time.StampMilli))
			})
		}
	}()

	hystogrammTicker := time.NewTicker(time.Millisecond * 500)
	go func() {
		for _ = range hystogrammTicker.C {
			// Что бы записать значение в виджет используем потокобезопасный вызов
			ui.QueueMain(func() {
				areaHandler.Push(rand.Intn(100))
				area.QueueRedrawAll()
			})
		}
	}()

	log.Println("InitGUI done")
}
Пример #2
0
func gui() {
	commands := spotify.Commands()
	leftButton := ui.NewButton("<<")
	playButton := ui.NewButton("||")
	rightButton := ui.NewButton(">>")

	leftButton.OnClicked(func() {
		spotify.Execute(commands["previousTrack"])
	})

	playButton.OnClicked(func() {
		spotify.Execute(commands["playpause"])
	})

	rightButton.OnClicked(func() {
		spotify.Execute(commands["nextTrack"])
	})

	stack := ui.NewHorizontalStack(
		leftButton,
		playButton,
		rightButton)

	w := ui.NewWindow("Spotify Client", 90, 25, stack)
	w.OnClosing(func() bool {
		ui.Stop()
		return true
	})
	w.Show()
}
Пример #3
0
func newCluster() {

	clusterNameLabel := ui.NewLabel("Cluster Name:")
	clusterNameTextField := ui.NewTextField()
	clusterNodeCountLabel := ui.NewLabel("Number of Nodes:")
	clusterNodeCountTextField := ui.NewTextField()
	clusterNodeCountTextField.SetText("1")
	autoscaleLabel := ui.NewLabel("Autoscale:")
	autoscaleCheckbox := ui.NewCheckbox("")
	newClusterBtn := ui.NewButton("Create Cluster")
	cancelBtn := ui.NewButton("Cancel")

	newClusterGrid := ui.NewSimpleGrid(2,
		clusterNameLabel, clusterNameTextField,
		clusterNodeCountLabel, clusterNodeCountTextField,
		autoscaleLabel, autoscaleCheckbox,
		newClusterBtn, cancelBtn)

	newClusterGrid.SetPadded(true)

	newClusterGrp := ui.NewGroup("", newClusterGrid)
	newClusterGrp.SetMargined(true)

	newWin := ui.NewWindow("New Cluster", 400, 300, newClusterGrp)
	newWin.SetMargined(true)
	newWin.Show()

	newClusterBtn.OnClicked(func() {
		var c libcarina.Cluster
		c.ClusterName = clusterNameTextField.Text()
		n, _ := strconv.Atoi(clusterNodeCountTextField.Text())
		c.Nodes = libcarina.Number(n)
		c.AutoScale = autoscaleCheckbox.Checked()
		carinaClient.Create(c)
		time.Sleep(250 * time.Millisecond)
		newWin.Close()
	})

	cancelBtn.OnClicked(func() {
		newWin.Close()
	})

	newWin.OnClosing(func() bool {
		newWin.Close()
		return true
	})

}
Пример #4
0
func main() {
	err := ui.Main(func() {
		name := ui.NewEntry()
		button := ui.NewButton("Greet")
		greeting := ui.NewLabel("")
		box := ui.NewVerticalBox()
		box.Append(ui.NewLabel("Enter your name:"), false)
		box.Append(name, false)
		box.Append(button, false)
		box.Append(greeting, false)
		window := ui.NewWindow("Hello", 200, 100, false)
		window.SetChild(box)
		button.OnClicked(func(*ui.Button) {
			greeting.SetText("Hello, " + name.Text() + "!")
		})
		window.OnClosing(func(*ui.Window) bool {
			ui.Quit()
			return true
		})
		window.Show()
	})
	if err != nil {
		panic(err)
	}
}
Пример #5
0
func main() {
	go ui.Do(func() {
		name := ui.NewTextField()
		button := ui.NewButton("greet")
		greeting := ui.NewLabel("")

		stack := ui.NewVerticalStack(
			ui.NewLabel("name:"),
			name,
			button,
			greeting)

		window = ui.NewWindow("hello world", 200, 100, stack)

		button.OnClicked(func() {
			greeting.SetText("hello, " + name.Text() + "!")
		})

		window.OnClosing(func() bool {
			ui.Stop()
			return true
		})

		window.Show()
	})

	err := ui.Go()
	if err != nil {
		panic(err)
	}
}
Пример #6
0
//gui界面,用的 github.com/andlabs/ui 的ui库
func gui() {
	input = ui.NewTextField()
	input.OnChanged(func() {
		go feedback() // 主要是这里在不同的刷新(table)内容
	})
	button := ui.NewButton("查词")
	button.OnClicked(guiTranslate)
	inputBox := ui.NewHorizontalStack(input, button)
	inputBox.SetStretchy(0)
	tab := ui.NewTab()
	table = ui.NewTable(reflect.TypeOf(str{}))
	table.OnSelected(tableSelected)
	tab.Append("单词", table)
	si = ui.NewLabel("Simple")
	tab.Append("简约", si)
	al = ui.NewLabel("All")
	tab.Append("全部", al)
	stack := ui.NewVerticalStack(inputBox, tab)
	stack.SetStretchy(1)
	w = ui.NewWindow("Window", 280, 350, stack)
	w.OnClosing(func() bool {
		ui.Stop()
		return true
	})
	w.Show()
}
Пример #7
0
func main() {

	file, err := os.Open("document.json")
	if err != nil {
		panic(err)
	}
	defer file.Close()
	buffer := new(bytes.Buffer)
	_, err = buffer.ReadFrom(file)
	if err != nil {
		panic(err)
	}
	var document uidoc.Element

	err = ui.Main(func() {
		document, err = uidoc.Parse(buffer.Bytes())
		if err != nil {
			panic(err)
		}

		font := ui.LoadClosestFont(&ui.FontDescriptor{
			Family: "Deja Vu",
			Size:   12,
		})
		name := ui.NewEntry()
		button := ui.NewButton("Greet")
		doc := uidoc.New()
		doc.SetDocument(document)
		box := ui.NewVerticalBox()
		box.Append(ui.NewLabel("Enter your name:"), false)
		box.Append(name, false)
		box.Append(button, false)
		box.Append(doc, true)
		window := ui.NewWindow("Hello", 400, 700, false)
		window.SetChild(box)
		button.OnClicked(func(*ui.Button) {
			element := uidoc.NewText("Hello, "+name.Text()+"!", font)
			document.(*uidoc.Group).Append(element)
			doc.Layout()
		})
		window.OnClosing(func(*ui.Window) bool {
			ui.Quit()
			return true
		})
		window.Show()
	})
	if err != nil {
		panic(err)
	}
}
Пример #8
0
func initGUI() {
	b := ui.NewButton("Button")
	c := ui.NewCheckbox("Checkbox")
	tf := ui.NewTextField()
	tf.SetText("Text Field")
	pf := ui.NewPasswordField()
	pf.SetText("Password Field")
	l := ui.NewStandaloneLabel("Label")

	t := ui.NewTab()
	t.Append("Tab 1", ui.Space())
	t.Append("Tab 2", ui.Space())
	t.Append("Tab 3", ui.Space())

	g := ui.NewGroup("Group", ui.Space())

	icons, il := readIcons()
	table := ui.NewTable(reflect.TypeOf(icons[0]))
	table.Lock()
	d := table.Data().(*[]icon)
	*d = icons
	table.Unlock()
	table.LoadImageList(il)

	area := ui.NewArea(200, 200, &areaHandler{tileImage(20)})

	stack := ui.NewVerticalStack(
		b,
		c,
		tf,
		pf,
		l,
		t,
		g,
		table,
		area)
	stack.SetStretchy(5)
	stack.SetStretchy(6)
	stack.SetStretchy(7)
	stack.SetStretchy(8)

	w = ui.NewWindow("Window", 400, 500, stack)
	w.OnClosing(func() bool {
		ui.Stop()
		return true
	})
	w.Show()
}
Пример #9
0
func initUI() {
	button := ui.NewButton("Load")
	button.OnClicked(func() {
		ui.OpenFile(w, func(filename string) {
			go renderPreview(filename)
		})
	})

	stack := ui.NewVerticalStack(button)

	w = ui.NewWindow("Video", 800, 600, stack)
	w.OnClosing(func() bool {
		ui.Stop()
		return true
	})
	w.Show()
}
Пример #10
0
func gui() {
	connect()
	var c Container

	//Stack for the control
	l := ui.NewLabel("Image to start")
	imageName := ui.NewTextField()
	imageName.SetText("ipython/scipystack")
	startBtn := ui.NewButton("Launch")
	controlStack := ui.NewVerticalStack(l, imageName, startBtn)
	controlGrp := ui.NewGroup("Launch Image", controlStack)
	controlGrp.SetMargined(true)

	// Table of running containers
	table := ui.NewTable(reflect.TypeOf(c))
	openBrowserBtn := ui.NewButton("Open in browser")
	killBtn := ui.NewButton("Kill")
	manageRunningContainerGrp := ui.NewHorizontalStack(openBrowserBtn, killBtn)
	containerControlGrp := ui.NewVerticalStack(table, manageRunningContainerGrp)
	containerListGrp := ui.NewGroup("Running containers", containerControlGrp)
	containerListGrp.SetMargined(true)

	//Container info area
	selectedContainerInfo := ui.NewTextField()

	// Now make a new 2 column stack
	topStack := ui.NewHorizontalStack(controlGrp, containerListGrp)
	topStack.SetStretchy(0)
	topStack.SetStretchy(1)

	mainStack := ui.NewVerticalStack(topStack, selectedContainerInfo)
	mainStack.SetStretchy(0)
	mainStack.SetStretchy(1)

	startBtn.OnClicked(func() {
		go Start(imageName.Text())
	})

	table.OnSelected(func() {
		c := table.Selected()
		table.Lock()
		d := table.Data().(*[]Container)
		//this makes a shallow copy of the structure so that we can access elements per
		//   http://giantmachines.tumblr.com/post/51007535999/golang-struct-shallow-copy
		newC := *d
		table.Unlock()
		if c > -1 {
			fmt.Println("Getting info for container ", newC[c].Name)
			selectedContainerInfo.SetText(Info(newC[c].Name))
		}
	})

	openBrowserBtn.OnClicked(func() {
		c := table.Selected()
		table.Lock()
		d := table.Data().(*[]Container)
		//this makes a shallow copy of the structure so that we can access elements per
		//   http://giantmachines.tumblr.com/post/51007535999/golang-struct-shallow-copy
		newC := *d
		table.Unlock()
		url := fmt.Sprintf("%s.%s", newC[c].Name, os.Getenv("DOMAIN_NAME"))
		webbrowser.Open(url)
	})

	killBtn.OnClicked(func() {
		c := table.Selected()
		table.Lock()
		d := table.Data().(*[]Container)
		//this makes a shallow copy of the structure so that we can access elements per
		//   http://giantmachines.tumblr.com/post/51007535999/golang-struct-shallow-copy
		newC := *d
		table.Unlock()
		go Kill(newC[c].Name)
	})

	w = ui.NewWindow("Manage Containers on RCS", 600, 450, mainStack)
	w.SetMargined(true)

	w.OnClosing(func() bool {
		ui.Stop()
		return true
	})
	go updateTable(table)
	w.Show()

}
Пример #11
0
// Initializes the GUI. Function is passed to ui.Do.
func initGui() {
	var cs, ps, ns song
	var player *gst.Element
	var playing bool

	// Creates the initial songs that will be used as long as the program runs.
	cs = song{api: &current.Current.songAPIType}
	ps = song{api: &current.Previous1.songAPIType}
	// ps2 := song{api: &current.Previous2.songAPIType}
	ns = song{api: &current.Next1.songAPIType}
	// ns2 := song{api: &current.Next2.songAPIType}

	// Creates the player and the controls for the player (as well as label).
	player = initPlayer()
	playing = true
	psl := ui.NewLabel("Currently Playing")
	ppbtn := ui.NewButton("Pause")
	ppbtn.OnClicked(func() {
		if playing {
			ppbtn.SetText("Play")
			player.SetState(gst.STATE_PAUSED)
			psl.SetText("Currently Paused")
		} else {
			ppbtn.SetText("Pause")
			psl.SetText("Currently Playing")
			player.SetState(gst.STATE_PLAYING)
		}
		playing = !playing
	})

	// Creates the notification system
	nt := notificator.New(notificator.Options{
		DefaultIcon: "icon/default.png",
		AppName:     "GoFip",
	})

	// Notification settings. Will be passed to the updateGui goroutine to Check
	// whether or not to send a system notification when the music changes.
	ntc := ui.NewCheckbox("Notifications")
	ntc.SetChecked(true)
	// Defines a closableTicker that is used in the updateGui goroutine
	// This allows to close the ticker when the setting button is unchecked.
	ct := closableTicker{
		ticker: time.NewTicker(1 * time.Minute),
		halt:   make(chan bool, 1),
	}
	prc := ui.NewCheckbox("Periodic Check")
	prc.SetChecked(true)
	prc.OnToggled(func() {
		if !prc.Checked() {
			ct.stop()
		} else {
			ct = closableTicker{
				ticker: time.NewTicker(1 * time.Minute),
				halt:   make(chan bool, 1),
			}
			go updateGui(ct, nt, ntc, &cs, &ps, &ns)
		}
	})

	// Start the goroutine to update the GUI every minute (default behaviour)
	// Uses the closableTicker defined earlier so the goroutine can be stopped.
	go updateGui(ct, nt, ntc, &cs, &ps, &ns)

	// Creating the tabs with the songs as well as settings and credits.
	createTabs(&cs, &ps, &ns)
	ts := ui.NewTab()
	ts.Append("Current", cs.stack)
	ts.Append("Previous", ps.stack)
	ts.Append("Next", ns.stack)
	ts.Append("Settings", ui.NewVerticalStack(ntc, prc))
	ts.Append("Credits", ui.NewLabel("Depado 2015"))

	// Creates the main vertical stack that is passed to the main window.
	mvs := ui.NewVerticalStack(ts, ppbtn, psl)
	// The tab control must be set to stretchy otherwise it won't display the content.
	mvs.SetStretchy(0)

	// Creates the main window and the behaviour on close event.
	window = ui.NewWindow("GoFIP", width, height, mvs)
	window.OnClosing(func() bool {
		ui.Stop()
		return true
	})
	window.Show()

	// Starts the player once the window is shown.
	player.SetState(gst.STATE_PLAYING)
}
// createButton returns a new button with passed onClick callback
func createButton(buttonText string, onClick func(*ui.Button)) *ui.Button {
	button := ui.NewButton(buttonText)
	button.OnClicked(onClick)

	return button
}
Пример #13
0
func gui() {

	//Define endpoint
	apiEndpointLabel := ui.NewLabel("API Endpoint:")
	apiEndpointTextField := ui.NewTextField()
	if len(os.Getenv("CARINA_API_ENDPOINT")) > 0 {
		apiEndpointTextField.SetText(os.Getenv("CARINA_API_ENDPOINT"))
	} else {
		apiEndpointTextField.SetText(libcarina.BetaEndpoint)
	}
	//Define credentials area
	usernameLabel := ui.NewLabel("Username:"******"CARINA_USERNAME")) > 0 {
		usernameTextField.SetText(os.Getenv("CARINA_USERNAME"))
	}
	apiKeyLabel := ui.NewLabel("API Key:")
	apiKeyTextField := ui.NewPasswordField()
	if len(os.Getenv("CARINA_APIKEY")) > 0 {
		apiKeyTextField.SetText(os.Getenv("CARINA_APIKEY"))
	}
	connectBtn := ui.NewButton("Connect")

	// layout the login controls on a grid
	loginGrid := ui.NewGrid()
	loginGrid.Add(apiEndpointLabel, nil, ui.East, true, ui.LeftTop, false, ui.Center, 1, 1)
	loginGrid.Add(apiEndpointTextField, apiEndpointLabel, ui.South, true, ui.Fill, false, ui.Center, 1, 1)
	loginGrid.Add(usernameLabel, apiEndpointLabel, ui.East, true, ui.LeftTop, false, ui.Center, 1, 1)
	loginGrid.Add(usernameTextField, usernameLabel, ui.South, true, ui.Fill, false, ui.Center, 1, 1)
	loginGrid.Add(apiKeyLabel, usernameLabel, ui.East, true, ui.LeftTop, false, ui.Center, 1, 1)
	loginGrid.Add(apiKeyTextField, apiKeyLabel, ui.South, true, ui.Fill, false, ui.Center, 1, 1)
	loginGrid.Add(connectBtn, nil, ui.East, true, ui.LeftTop, false, ui.Center, 1, 1)
	loginGrid.SetPadded(true)

	//div grp1
	divGrp1 := ui.NewGroup("", ui.Space())
	divGrp1.SetMargined(true)

	// Define the table that lists all running clusters
	var c libcarina.Cluster
	clusterListTable := ui.NewTable(reflect.TypeOf(c))

	// Create control buttons
	newBtn := ui.NewButton("New")
	growBtn := ui.NewButton("Grow")
	rebuildBtn := ui.NewButton("Rebuild")
	credentialsBtn := ui.NewButton("Credentials")
	deleteBtn := ui.NewButton("Delete")
	buttonStack := ui.NewVerticalStack(newBtn, growBtn, rebuildBtn, credentialsBtn, deleteBtn)

	//div grp2
	divGrp2 := ui.NewGroup("", ui.Space())
	divGrp2.SetMargined(true)

	//Show containers on the cluster
	containerListLabel := ui.NewLabel("Containers")
	var cont dockerclient.Container
	containerListTable := ui.NewTable(reflect.TypeOf(cont))

	mainGrid := ui.NewGrid()
	mainGrid.Add(loginGrid, nil, ui.East, true, ui.Fill, false, ui.Center, 12, 1)
	mainGrid.Add(divGrp1, loginGrid, ui.South, true, ui.Fill, false, ui.Center, 12, 1)
	mainGrid.Add(clusterListTable, divGrp1, ui.South, true, ui.Fill, false, ui.Center, 9, 1)
	mainGrid.Add(buttonStack, clusterListTable, ui.East, true, ui.Fill, false, ui.Center, 3, 1)
	mainGrid.Add(divGrp2, clusterListTable, ui.South, true, ui.Fill, false, ui.Center, 12, 1)
	mainGrid.Add(containerListLabel, divGrp2, ui.South, true, ui.Fill, false, ui.Center, 12, 1)
	mainGrid.Add(containerListTable, containerListLabel, ui.South, true, ui.Fill, false, ui.Center, 12, 1)
	mainGrid.SetPadded(true)

	connectBtn.OnClicked(func() {
		connect(apiEndpointTextField.Text(), usernameTextField.Text(), apiKeyTextField.Text())
		go monitorClusterList(clusterListTable)
	})

	clusterListTable.OnSelected(func() {
		c, found := getSelectedCluster(clusterListTable)
		if found {
			if c.Status == "active" {
				containers := getContainers(c.ClusterName)
				containerListTable.Lock()
				d := containerListTable.Data().(*[]dockerclient.Container)
				*d = containers
				containerListTable.Unlock()
				txt := fmt.Sprintf("%d containers running on %s cluster", len(containers), c.ClusterName)
				containerListLabel.SetText(txt)
			}
		}
	})

	newBtn.OnClicked(func() {
		if loggedInFlag {
			newCluster()
		}
	})

	deleteBtn.OnClicked(func() {
		c, found := getSelectedCluster(clusterListTable)
		if found {
			carinaClient.Delete(c.ClusterName)
			fmt.Println("Deleting", c.ClusterName)
		}
	})

	rebuildBtn.OnClicked(func() {
		c, found := getSelectedCluster(clusterListTable)
		if found {
			fmt.Println("Rebuiding", c.ClusterName)
			carinaClient.Rebuild(c.ClusterName)
		}
	})

	credentialsBtn.OnClicked(func() {
		c, found := getSelectedCluster(clusterListTable)
		if found {
			fmt.Println("Getting credentials for", c.ClusterName)
			carinaClient.GetCredentials(c.ClusterName)
		}
	})

	growBtn.OnClicked(func() {
		c, found := getSelectedCluster(clusterListTable)
		if found {
			fmt.Println("Growing", c.ClusterName)
		}
	})

	//Main stack of the interfaces
	w = ui.NewWindow("Carina by Rackspace GUI Client ("+VERSION+")", 620, 300, mainGrid)
	w.SetMargined(true)

	w.OnClosing(func() bool {
		ui.Stop()
		return true
	})
	w.Show()

}
Пример #14
0
func myMain() {
	var cmd *exec.Cmd
	var timer *time.Timer
	var timerChan <-chan time.Time
	var w *ui.Window

	status := ui.NewLabel("")

	stop := func() {
		if cmd != nil { // stop the command if it's running
			err := cmd.Process.Kill()
			if err != nil {
				ui.MsgBoxError(w,
					fmt.Sprintf("Error killing process: %v", err),
					"You may need to kill it manually.")
			}
			err = cmd.Process.Release()
			if err != nil {
				ui.MsgBoxError(w,
					fmt.Sprintf("Error releasing process: %v", err),
					"")
			}
			cmd = nil
		}
		if timer != nil { // stop the timer if we started it
			timer.Stop()
			timer = nil
			timerChan = nil
		}
		status.SetText("")
	}

	w = ui.NewWindow("wakeup", 400, 100)
	ui.AppQuit = w.Closing // treat application close as main window close
	cmdbox := ui.NewLineEdit(defCmdLine)
	timebox := ui.NewLineEdit(defTime)
	bStart := ui.NewButton("Start")
	bStop := ui.NewButton("Stop")

	// a Stack to keep both buttons at the same size
	btnbox := ui.NewHorizontalStack(bStart, bStop)
	btnbox.SetStretchy(0)
	btnbox.SetStretchy(1)
	// and a Stack around that Stack to keep them at a reasonable size, with space to their right
	btnbox = ui.NewHorizontalStack(btnbox, status)

	// the main layout
	grid := ui.NewGrid(2,
		ui.NewLabel("Command"), cmdbox,
		ui.NewLabel("Time"), timebox,
		ui.Space(), ui.Space(), // the Space on the right will consume the window blank space
		ui.Space(), btnbox)
	grid.SetStretchy(2, 1) // make the Space noted above consume
	grid.SetFilling(0, 1)  // make the two textboxes grow horizontally
	grid.SetFilling(1, 1)

	w.Open(grid)

mainloop:
	for {
		select {
		case <-w.Closing:
			break mainloop
		case <-bStart.Clicked:
			stop() // only one alarm at a time
			alarmTime, err := time.Parse(timeFmt, timebox.Text())
			if err != nil {
				ui.MsgBoxError(w,
					fmt.Sprintf("Error parsing time %q: %v", timebox.Text(), err),
					fmt.Sprintf("Make sure your time is in the form %q (without quotes).", timeFmt))
				continue
			}
			now := time.Now()
			later := bestTime(now, alarmTime)
			timer = time.NewTimer(later.Sub(now))
			timerChan = timer.C
			status.SetText("Started")
		case <-timerChan:
			cmd = exec.Command("/bin/sh", "-c", "exec "+cmdbox.Text())
			// keep stdin /dev/null in case user wants to run multiple alarms on one instance (TODO should I allow this program to act as a pipe?)
			// keep stdout /dev/null to avoid stty mucking
			cmd.Stderr = os.Stderr
			err := cmd.Start()
			status.SetText("Firing")
			if err != nil {
				ui.MsgBoxError(w,
					fmt.Sprintf("Error running program: %v", err),
					"")
				cmd = nil
				status.SetText("")
			}
			timer = nil
			timerChan = nil
		case <-bStop.Clicked:
			stop()
		}
	}

	// clean up
	stop()
}