andlabs-ui/sysdata_unix.go

356 lines
7.8 KiB
Go

// +build !windows,!darwin,!plan9
// 16 february 2014
package ui
import (
"time"
)
type sysData struct {
cSysData
widget *gtkWidget
container *gtkWidget // for moving
pulse chan bool // for sysData.progressPulse()
}
type classData struct {
make func() *gtkWidget
makeAlt func() *gtkWidget
setText func(widget *gtkWidget, text string)
text func(widget *gtkWidget) string
append func(widget *gtkWidget, text string)
insert func(widget *gtkWidget, index int, text string)
selected func(widget *gtkWidget) int
selMulti func(widget *gtkWidget) []int
smtexts func(widget *gtkWidget) []string
delete func(widget *gtkWidget, index int)
len func(widget *gtkWidget) int
// ...
signals map[string]func(*sysData) func() bool
}
var classTypes = [nctypes]*classData{
c_window: &classData{
make: gtk_window_new,
setText: gtk_window_set_title,
text: gtk_window_get_title,
signals: map[string]func(*sysData) func() bool{
"delete-event": func(s *sysData) func() bool {
return func() bool {
s.signal()
return true // do not close the window
}
},
"configure-event": func(s *sysData) func() bool {
return func() bool {
if s.container != nil && s.resize != nil { // wait for init
width, height := gtk_window_get_size(s.widget)
// top-left is (0,0) so no need for winheight
err := s.resize(0, 0, width, height, 0)
if err != nil {
panic("child resize failed: " + err.Error())
}
}
// returning false indicates that we continue processing events related to configure-event; if we choose not to, then after some controls have been added, the layout fails completely and everything stays in the starting position/size
// TODO make sure this is the case
return false
}
},
},
},
c_button: &classData{
make: gtk_button_new,
setText: gtk_button_set_label,
text: gtk_button_get_label,
signals: map[string]func(*sysData) func() bool{
"clicked": func(s *sysData) func() bool {
return func() bool {
s.signal()
return true // do not close the window
}
},
},
},
c_checkbox: &classData{
make: gtk_check_button_new,
setText: gtk_button_set_label,
text: gtk_button_get_label,
},
c_combobox: &classData{
make: gtk_combo_box_text_new,
makeAlt: gtk_combo_box_text_new_with_entry,
// TODO setText
text: gtk_combo_box_text_get_active_text,
append: gtk_combo_box_text_append_text,
insert: gtk_combo_box_text_insert_text,
selected: gtk_combo_box_get_active,
delete: gtk_combo_box_text_remove,
len: gtkComboBoxLen,
},
c_lineedit: &classData{
make: gtk_entry_new,
makeAlt: gtkPasswordEntryNew,
setText: gtk_entry_set_text,
text: gtk_entry_get_text,
},
c_label: &classData{
make: gtk_label_new,
setText: gtk_label_set_text,
text: gtk_label_get_text,
},
c_listbox: &classData{
make: gListboxNewSingle,
makeAlt: gListboxNewMulti,
// TODO setText
text: gListboxText,
append: gListboxAppend,
insert: gListboxInsert,
selMulti: gListboxSelectedMulti,
smtexts: gListboxSelMultiTexts,
delete: gListboxDelete,
len: gListboxLen,
},
c_progressbar: &classData{
make: gtk_progress_bar_new,
},
}
func (s *sysData) make(initText string, window *sysData) error {
ct := classTypes[s.ctype]
ret := make(chan *gtkWidget)
defer close(ret)
uitask <- func() {
if s.alternate {
ret <- ct.makeAlt()
return
}
ret <- ct.make()
}
s.widget = <-ret
if window == nil {
uitask <- func() {
fixed := gtk_fixed_new()
gtk_container_add(s.widget, fixed)
// TODO return the container before assigning the signals?
for signal, generator := range ct.signals {
g_signal_connect(s.widget, signal, generator(s))
}
ret <- fixed
}
s.container = <-ret
} else {
s.container = window.container
uitask <- func() {
gtk_container_add(s.container, s.widget)
for signal, generator := range ct.signals {
g_signal_connect(s.widget, signal, generator(s))
}
ret <- nil
}
<-ret
}
s.setText(initText)
return nil
}
// used for Windows; nothing special needed elsewhere
func (s *sysData) firstShow() error {
s.show()
return nil
}
func (s *sysData) show() {
ret := make(chan struct{})
defer close(ret)
uitask <- func() {
gtk_widget_show(s.widget)
ret <- struct{}{}
}
<-ret
}
func (s *sysData) hide() {
ret := make(chan struct{})
defer close(ret)
uitask <- func() {
gtk_widget_hide(s.widget)
ret <- struct{}{}
}
<-ret
}
func (s *sysData) setText(text string) {
if classTypes[s.ctype].setText == nil { // does not have concept of text
return
}
ret := make(chan struct{})
defer close(ret)
uitask <- func() {
classTypes[s.ctype].setText(s.widget, text)
ret <- struct{}{}
}
<-ret
}
func (s *sysData) setRect(x int, y int, width int, height int, winheight int) error {
gtk_fixed_move(s.container, s.widget, x, y)
gtk_widget_set_size_request(s.widget, width, height)
return nil
}
func (s *sysData) isChecked() bool {
ret := make(chan bool)
defer close(ret)
uitask <- func() {
ret <- gtk_toggle_button_get_active(s.widget)
}
return <-ret
}
func (s *sysData) text() string {
ret := make(chan string)
defer close(ret)
uitask <- func() {
ret <- classTypes[s.ctype].text(s.widget)
}
return <-ret
}
func (s *sysData) append(what string) {
ret := make(chan struct{})
defer close(ret)
uitask <- func() {
classTypes[s.ctype].append(s.widget, what)
ret <- struct{}{}
}
<-ret
}
func (s *sysData) insertBefore(what string, before int) {
ret := make(chan struct{})
defer close(ret)
uitask <- func() {
classTypes[s.ctype].insert(s.widget, before, what)
ret <- struct{}{}
}
<-ret
}
func (s *sysData) selectedIndex() int {
ret := make(chan int)
defer close(ret)
uitask <- func() {
ret <- classTypes[s.ctype].selected(s.widget)
}
return <-ret
}
func (s *sysData) selectedIndices() []int {
ret := make(chan []int)
defer close(ret)
uitask <- func() {
ret <- classTypes[s.ctype].selMulti(s.widget)
}
return <-ret
}
func (s *sysData) selectedTexts() []string {
ret := make(chan []string)
defer close(ret)
uitask <- func() {
ret <- classTypes[s.ctype].smtexts(s.widget)
}
return <-ret
}
func (s *sysData) setWindowSize(width int, height int) error {
ret := make(chan struct{})
defer close(ret)
uitask <- func() {
gtk_window_resize(s.widget, width, height)
ret <- struct{}{}
}
<-ret
return nil
}
func (s *sysData) delete(index int) {
ret := make(chan struct{})
defer close(ret)
uitask <- func() {
classTypes[s.ctype].delete(s.widget, index)
ret <- struct{}{}
}
<-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() {
gtk_progress_bar_set_fraction(s.widget, percent)
ret <- struct{}{}
}
<-ret
}
func (s *sysData) len() int {
ret := make(chan int)
defer close(ret)
uitask <- func() {
ret <- classTypes[s.ctype].len(s.widget)
}
return <-ret
}