// +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 }