diff --git a/x/window.go b/x/window.go index 510dfc4..991ad68 100644 --- a/x/window.go +++ b/x/window.go @@ -1,6 +1,7 @@ package x import "image" +import "strings" import "git.tebibyte.media/tomo/tomo" import "git.tebibyte.media/tomo/tomo/data" @@ -41,7 +42,10 @@ type window struct { innerBounds image.Rectangle // bounds of the drawable area } - onClose event.FuncBroadcaster + on struct { + close event.FuncBroadcaster + tryClose event.Broadcaster[func () bool] + } } type windowLink struct { @@ -64,27 +68,12 @@ func (this *windowLink) NotifyMinimumSizeChange () { this.window.doMinimumSize() } -func (this *Backend) NewWindow ( - bounds image.Rectangle, -) ( - output tomo.Window, - err error, -) { - this.assert() - return this.newWindow(bounds, false) -} - -func (this *Backend) NewPlainWindow ( - bounds image.Rectangle, -) ( - output tomo.Window, - err error, -) { +func (this *Backend) NewWindow (kind tomo.WindowKind, bounds image.Rectangle) (tomo.Window, error) { this.assert() window, err := this.newWindow(bounds, false) - window.setType("dock") - - return window, err + if err != nil { return nil, err } + window.setKind(kind) + return window, nil } func (this *Backend) newWindow ( @@ -131,7 +120,11 @@ func (this *Backend) newWindow ( if err != nil { return } window.xWindow.WMGracefulClose (func (xWindow *xwindow.Window) { - window.Close() + holdOff := false + for _, callback := range window.on.tryClose.Listeners() { + if !callback() { holdOff = true } + } + if !holdOff { window.Close() } }) xevent.ExposeFun(window.handleExpose). @@ -247,56 +240,29 @@ func (this *window) SetBounds (bounds image.Rectangle) { bounds.Min.Y + bounds.Dy()) } -func (this *window) NewChild (bounds image.Rectangle) (tomo.Window, error) { +func (this *window) NewChild (kind tomo.WindowKind, bounds image.Rectangle) (tomo.Window, error) { leader := this.leader child, err := this.backend.newWindow ( bounds.Add(this.metrics.innerBounds.Min), false) - child.leader = leader if err != nil { return nil, err } - child.setClientLeader(leader) - leader.setClientLeader(leader) + child.leader = leader + err = child.setKind(kind) + if err != nil { return nil, err } + if kind == tomo.WindowKindModal { + this.hasModal = true + child.modalParent = this + } icccm.WmTransientForSet ( this.backend.x, child.xWindow.Id, leader.xWindow.Id) - child.setType("UTILITY") // child.inheritProperties(leader.window) return child, err } -func (this *window) NewMenu (bounds image.Rectangle) (tomo.Window, error) { - menu, err := this.backend.newWindow ( - bounds.Add(this.metrics.innerBounds.Min), true) - menu.shy = true - icccm.WmTransientForSet ( - this.backend.x, - menu.xWindow.Id, - this.xWindow.Id) - menu.setType("POPUP_MENU") - // menu.inheritProperties(this) - return menu, err -} - -func (this *window) NewModal (bounds image.Rectangle) (tomo.Window, error) { - modal, err := this.backend.newWindow ( - bounds.Add(this.metrics.innerBounds.Min), false) - icccm.WmTransientForSet ( - this.backend.x, - modal.xWindow.Id, - this.xWindow.Id) - ewmh.WmStateSet ( - this.backend.x, - modal.xWindow.Id, - []string { "_NET_WM_STATE_MODAL" }) - modal.modalParent = this - this.hasModal = true - // modal.inheritProperties(window) - return modal, err -} - func (this *window) Modifiers () input.Modifiers { return this.hierarchy.Modifiers() } @@ -330,12 +296,12 @@ func (this *window) Visible () bool { return this.visible } -func (this *window) Close () { +func (this *window) Close () error { xevent .Detach(this.backend.x, this.xWindow.Id) keybind .Detach(this.backend.x, this.xWindow.Id) mousebind.Detach(this.backend.x, this.xWindow.Id) - this.onClose.Broadcast() + this.on.close.Broadcast() if this.modalParent != nil { // we are a modal dialog, so unlock the parent this.modalParent.hasModal = false @@ -345,10 +311,15 @@ func (this *window) Close () { delete(this.backend.windows, this.xWindow.Id) this.xWindow.Destroy() this.hierarchy.Close() + return nil // TODO maybe return an error? maybe join them? } func (this *window) OnClose (callback func ()) event.Cookie { - return this.onClose.Connect(callback) + return this.on.close.Connect(callback) +} + +func (this *window) OnTryClose (callback func () bool) event.Cookie { + return this.on.tryClose.Connect(callback) } func (this *window) grabInput () { @@ -364,6 +335,19 @@ func (this *window) ungrabInput () { mousebind.UngrabPointer(this.backend.x) } +func (this *window) setKind (kind tomo.WindowKind) error { + err := this.setType(windowKindToType(kind)) + if err != nil { return err } + if kind == tomo.WindowKindModal { + err = this.setState("MODAL") + if err != nil { return err } + } + if kind == tomo.WindowKindMenu { + this.shy = true + } + return nil +} + func (this *window) setType (ty string) error { return ewmh.WmWindowTypeSet ( this.backend.x, @@ -371,6 +355,13 @@ func (this *window) setType (ty string) error { []string { "_NET_WM_WINDOW_TYPE_" + ty }) } +func (this *window) setState (state string) error { + return ewmh.WmStateSet ( + this.backend.x, + this.xWindow.Id, + []string { "_NET_WM_STATE_" + state }) +} + func (this *window) setClientLeader (leader *window) error { hints, _ := icccm.WmHintsGet(this.backend.x, this.xWindow.Id) if hints == nil { @@ -476,3 +467,15 @@ func (this *window) doMinimumSize () { this.xWindow.Resize(newWidth, newHeight) } } + +func windowKindToType (kind tomo.WindowKind) string { + switch kind { + case tomo.WindowKindNormal: return "NORMAL" + case tomo.WindowKindPlain: return "DOCK" + case tomo.WindowKindUtility: return "UTILITY" + case tomo.WindowKindToolbar: return "TOOLBAR" + case tomo.WindowKindMenu: return "POPUP_MENU" + case tomo.WindowKindModal: return "NORMAL" + default: return strings.ReplaceAll(strings.ToUpper(string(kind)), " ", "_") + } +}