From 6ee8d96a6e8c1e95b8ca2ab19fcc6a3a856d50c8 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Wed, 12 Mar 2014 17:31:13 -0400 Subject: [PATCH] Added GTK+ indeterminate ProgressBars. --- gtkcalls_unix.go | 4 ++++ progressbar.go | 1 + sysdata_unix.go | 52 +++++++++++++++++++++++++++++++++++++++++++++++- test/main.go | 5 ++++- todo.md | 1 + 5 files changed, 61 insertions(+), 2 deletions(-) diff --git a/gtkcalls_unix.go b/gtkcalls_unix.go index bde6b74..5a1a1ae 100644 --- a/gtkcalls_unix.go +++ b/gtkcalls_unix.go @@ -216,3 +216,7 @@ func gtk_progress_bar_set_fraction(w *gtkWidget, percent int) { p := C.gdouble(percent) / 100 C.gtk_progress_bar_set_fraction(togtkprogressbar(w), p) } + +func gtk_progress_bar_pulse(w *gtkWidget) { + C.gtk_progress_bar_pulse(togtkprogressbar(w)) +} diff --git a/progressbar.go b/progressbar.go index 4d16874..000996c 100644 --- a/progressbar.go +++ b/progressbar.go @@ -27,6 +27,7 @@ func NewProgressBar() *ProgressBar { // If percent is in the range [0,100], the progressBar shows that much percent complete. // If percent is -1, the ProgressBar is made indeterminate. // Otherwise, SetProgress panics. +// TODO what happens if you repeatedly call SetProgress(-1)? func (p *ProgressBar) SetProgress(percent int) { p.lock.Lock() defer p.lock.Unlock() diff --git a/sysdata_unix.go b/sysdata_unix.go index 6947e20..91b301c 100644 --- a/sysdata_unix.go +++ b/sysdata_unix.go @@ -4,7 +4,7 @@ package ui import ( - // ... + "time" ) type sysData struct { @@ -12,6 +12,7 @@ type sysData struct { widget *gtkWidget container *gtkWidget // for moving + pulse chan bool // for sysData.progressPulse() } type classData struct { @@ -285,7 +286,56 @@ func (s *sysData) delete(index int) { <-ret } +// With GTK+, we must manually pulse the indeterminate progressbar ourselves. This goroutine does that. +func (s *sysData) progressPulse() { + pulse := func() { + ret := make(chan struct{}) + defer close(ret) + uitask <- func() { + gtk_progress_bar_pulse(s.widget) + ret <- struct{}{} + } + <-ret + } + + var ticker *time.Ticker + var tickchan <-chan time.Time + + // the default on Windows + const pulseRate = 30 * time.Millisecond + + for { + select { + case start := <-s.pulse: + if start { + ticker = time.NewTicker(pulseRate) + tickchan = ticker.C + pulse() // start the pulse animation now, not 30ms later + } else { + if ticker != nil { + ticker.Stop() + } + ticker = nil + tickchan = nil + s.pulse <- true // notify sysData.setProgress() + } + case <-tickchan: + pulse() + } + } +} + func (s *sysData) setProgress(percent int) { + if s.pulse == nil { + s.pulse = make(chan bool) + go s.progressPulse() + } + if percent == -1 { + s.pulse <- true + return + } + s.pulse <- false + <-s.pulse // wait for sysData.progressPulse() to register that ret := make(chan struct{}) defer close(ret) uitask <- func() { diff --git a/test/main.go b/test/main.go index 77adfae..36339ca 100644 --- a/test/main.go +++ b/test/main.go @@ -129,8 +129,9 @@ func myMain() { prog := 0 incButton := NewButton("Inc") decButton := NewButton("Dec") + indetButton := NewButton("Indeterminate") invalidButton := NewButton("Run Invalid Test") - sincdec := NewHorizontalStack(incButton, decButton, invalidButton) + sincdec := NewHorizontalStack(incButton, decButton, indetButton, invalidButton) password := NewPasswordEdit() s0 := NewVerticalStack(s2, c, cb1, cb2, e, s3, pbar, sincdec, Space(), password) s0.SetStretchy(8) @@ -224,6 +225,8 @@ mainloop: prog = 0 } pbar.SetProgress(prog) + case <-indetButton.Clicked: + pbar.SetProgress(-1) case <-invalidButton.Clicked: invalidTest(cb1, lb1, nil, nil) } diff --git a/todo.md b/todo.md index b954682..a434321 100644 --- a/todo.md +++ b/todo.md @@ -23,6 +23,7 @@ so I don't forget: - change sysData.make() so it does not take the initial window text as an argument and instead have the respective Control/Window.make() call sysData.setText() expressly; this would allow me to remove the "no such concept of text" checks from the GTK+ and Mac OS X backends important things: +- GTK+ ProgressBar indeterminate animation is running like mad; pretty sure it's our idle task being evil - because the main event loop is not called if initialization fails, it is presently impossible for MsgBoxError() to work if UI initialization fails; this basically means we cannot allow initializiation to fail on Mac OS X if we want to be able to report UI init failures to the user with one (which would be desirable, maybe (would violate Windows HIG?)) - figure out where to auto-place windows in Cocoa (also window coordinates are still not flipped properly so (0,0) on screen is the bottom-left) - also provide a method to center windows; Cocoa provides one for us but