412 lines
9.5 KiB
Go
412 lines
9.5 KiB
Go
// +build !windows,!darwin,!plan9
|
|
|
|
// 16 february 2014
|
|
|
|
package ui
|
|
|
|
import (
|
|
"time"
|
|
)
|
|
|
|
// #include "gtk_unix.h"
|
|
import "C"
|
|
|
|
type sysData struct {
|
|
cSysData
|
|
|
|
widget *C.GtkWidget
|
|
container *C.GtkWidget // for moving
|
|
pulse chan bool // for sysData.progressPulse()
|
|
clickCounter clickCounter // for Areas
|
|
// we probably don't need to save these, but we'll do so for sysData.preferredSize() just in case
|
|
areawidth int
|
|
areaheight int
|
|
}
|
|
|
|
type classData struct {
|
|
make func() *C.GtkWidget
|
|
makeAlt func() *C.GtkWidget
|
|
setText func(widget *C.GtkWidget, text string)
|
|
text func(widget *C.GtkWidget) string
|
|
append func(widget *C.GtkWidget, text string)
|
|
insert func(widget *C.GtkWidget, index int, text string)
|
|
selected func(widget *C.GtkWidget) int
|
|
selMulti func(widget *C.GtkWidget) []int
|
|
smtexts func(widget *C.GtkWidget) []string
|
|
delete func(widget *C.GtkWidget, index int)
|
|
len func(widget *C.GtkWidget) int
|
|
// ...
|
|
signals callbackMap
|
|
child func(widget *C.GtkWidget) *C.GtkWidget
|
|
childsigs callbackMap
|
|
}
|
|
|
|
var classTypes = [nctypes]*classData{
|
|
c_window: &classData{
|
|
make: gtk_window_new,
|
|
setText: gtk_window_set_title,
|
|
text: gtk_window_get_title,
|
|
signals: callbackMap{
|
|
"delete-event": window_delete_event_callback,
|
|
"configure-event": window_configure_event_callback,
|
|
},
|
|
},
|
|
c_button: &classData{
|
|
make: gtk_button_new,
|
|
setText: gtk_button_set_label,
|
|
text: gtk_button_get_label,
|
|
signals: callbackMap{
|
|
"clicked": button_clicked_callback,
|
|
},
|
|
},
|
|
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,
|
|
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,
|
|
text: gListboxText,
|
|
append: gListboxAppend,
|
|
insert: gListboxInsert,
|
|
selMulti: gListboxSelectedMulti,
|
|
smtexts: gListboxSelMultiTexts,
|
|
delete: gListboxDelete,
|
|
len: gListboxLen,
|
|
},
|
|
c_progressbar: &classData{
|
|
make: gtk_progress_bar_new,
|
|
},
|
|
c_area: &classData{
|
|
make: gtkAreaNew,
|
|
child: gtkAreaGetControl,
|
|
childsigs: callbackMap{
|
|
"draw": area_draw_callback,
|
|
"button-press-event": area_button_press_event_callback,
|
|
"button-release-event": area_button_release_event_callback,
|
|
"motion-notify-event": area_motion_notify_event_callback,
|
|
"enter-notify-event": area_enterleave_notify_event_callback,
|
|
"leave-notify-event": area_enterleave_notify_event_callback,
|
|
"key-press-event": area_key_press_event_callback,
|
|
"key-release-event": area_key_release_event_callback,
|
|
},
|
|
},
|
|
}
|
|
|
|
func (s *sysData) make(window *sysData) error {
|
|
ct := classTypes[s.ctype]
|
|
ret := make(chan *C.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 := gtkNewWindowLayout()
|
|
gtk_container_add(s.widget, fixed)
|
|
for signame, sigfunc := range ct.signals {
|
|
g_signal_connect(s.widget, signame, sigfunc, s)
|
|
}
|
|
ret <- fixed
|
|
}
|
|
s.container = <-ret
|
|
} else {
|
|
s.container = window.container
|
|
uitask <- func() {
|
|
gtkAddWidgetToLayout(s.container, s.widget)
|
|
for signame, sigfunc := range ct.signals {
|
|
g_signal_connect(s.widget, signame, sigfunc, s)
|
|
}
|
|
if ct.child != nil {
|
|
child := ct.child(s.widget)
|
|
for signame, sigfunc := range ct.childsigs {
|
|
g_signal_connect(child, signame, sigfunc, s)
|
|
}
|
|
}
|
|
ret <- nil
|
|
}
|
|
<-ret
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// see sysData.center()
|
|
func (s *sysData) resetposition() {
|
|
C.gtk_window_set_position(togtkwindow(s.widget), C.GTK_WIN_POS_NONE)
|
|
}
|
|
|
|
// 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)
|
|
s.resetposition()
|
|
ret <- struct{}{}
|
|
}
|
|
<-ret
|
|
}
|
|
|
|
func (s *sysData) hide() {
|
|
ret := make(chan struct{})
|
|
defer close(ret)
|
|
uitask <- func() {
|
|
gtk_widget_hide(s.widget)
|
|
s.resetposition()
|
|
ret <- struct{}{}
|
|
}
|
|
<-ret
|
|
}
|
|
|
|
func (s *sysData) setText(text string) {
|
|
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 {
|
|
gtkMoveWidgetInLayout(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() {
|
|
// does not take window geometry into account (and cannot, since the window manager won't give that info away)
|
|
// thanks to TingPing in irc.gimp.net/#gtk+
|
|
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 pulse rate used by Zenity (https://git.gnome.org/browse/zenity/tree/src/progress.c#n69 for blob cbffe08e8337ba1375a0ac7210eff5a2e4313bb8)
|
|
const pulseRate = 100 * time.Millisecond
|
|
|
|
for {
|
|
select {
|
|
case start := <-s.pulse:
|
|
if start {
|
|
ticker = time.NewTicker(pulseRate)
|
|
tickchan = ticker.C
|
|
pulse() // start the pulse animation now, not 100ms 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
|
|
}
|
|
|
|
func (s *sysData) setAreaSize(width int, height int) {
|
|
ret := make(chan struct{})
|
|
defer close(ret)
|
|
uitask <- func() {
|
|
c := gtkAreaGetControl(s.widget)
|
|
gtk_widget_set_size_request(c, width, height)
|
|
s.areawidth = width // for sysData.preferredSize()
|
|
s.areaheight = height
|
|
C.gtk_widget_queue_draw(c)
|
|
ret <- struct{}{}
|
|
}
|
|
<-ret
|
|
}
|
|
|
|
func (s *sysData) repaintAll() {
|
|
ret := make(chan struct{})
|
|
defer close(ret)
|
|
uitask <- func() {
|
|
c := gtkAreaGetControl(s.widget)
|
|
C.gtk_widget_queue_draw(c)
|
|
ret <- struct{}{}
|
|
}
|
|
<-ret
|
|
}
|
|
|
|
func (s *sysData) center() {
|
|
ret := make(chan struct{})
|
|
defer close(ret)
|
|
uitask <- func() {
|
|
if C.gtk_widget_get_visible(s.widget) == C.FALSE {
|
|
// hint to the WM to make it centered when it is shown again
|
|
// thanks to Jasper in irc.gimp.net/#gtk+
|
|
C.gtk_window_set_position(togtkwindow(s.widget), C.GTK_WIN_POS_CENTER)
|
|
} else {
|
|
var width, height C.gint
|
|
|
|
s.resetposition()
|
|
//we should be able to use gravity to simplify this, but it doesn't take effect immediately, and adding show calls does nothing (thanks Jasper in irc.gimp.net/#gtk+)
|
|
C.gtk_window_get_size(togtkwindow(s.widget), &width, &height)
|
|
C.gtk_window_move(togtkwindow(s.widget),
|
|
(C.gdk_screen_width() / 2) - (width / 2),
|
|
(C.gdk_screen_height() / 2) - (width / 2))
|
|
}
|
|
ret <- struct{}{}
|
|
}
|
|
<-ret
|
|
}
|