// Copyright 2013 The Walk Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // +build windows package walk import ( "syscall" "unsafe" ) import ( "github.com/lxn/win" ) // StatusBar is a widget that displays status messages. type StatusBar struct { WidgetBase items *StatusBarItemList } // NewStatusBar returns a new StatusBar as child of container parent. func NewStatusBar(parent Container) (*StatusBar, error) { sb := new(StatusBar) if err := InitWidget( sb, parent, "msctls_statusbar32", win.SBARS_SIZEGRIP|win.SBARS_TOOLTIPS, 0); err != nil { return nil, err } sb.items = newStatusBarItemList(sb) return sb, nil } // Items returns the list of items in the StatusBar. func (sb *StatusBar) Items() *StatusBarItemList { return sb.items } // SetVisible sets whether the StatusBar is visible. func (sb *StatusBar) SetVisible(visible bool) { sb.WidgetBase.SetVisible(visible) sb.RequestLayout() } func (sb *StatusBar) ApplyDPI(dpi int) { sb.WidgetBase.ApplyDPI(dpi) sb.update() } func (sb *StatusBar) update() error { if err := sb.updateParts(); err != nil { return err } for i, item := range sb.items.items { if err := item.update(i); err != nil { return err } } sb.SetVisible(sb.items.Len() > 0) return nil } func (sb *StatusBar) updateParts() error { items := sb.items.items dpi := sb.DPI() rightEdges := make([]int32, len(items)) var right int32 for i, item := range items { right += int32(IntFrom96DPI(item.width, dpi)) rightEdges[i] = right } var rep *int32 if len(rightEdges) > 0 { rep = &rightEdges[0] } if len(rightEdges) == 1 { rightEdges[0] = -1 } if 0 == sb.SendMessage( win.SB_SETPARTS, uintptr(len(items)), uintptr(unsafe.Pointer(rep))) { return newError("SB_SETPARTS") } return nil } func (sb *StatusBar) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr { switch msg { case win.WM_NOTIFY: nmhdr := (*win.NMHDR)(unsafe.Pointer(lParam)) switch nmhdr.Code { case win.NM_CLICK: lpnm := (*win.NMMOUSE)(unsafe.Pointer(lParam)) if n := int(lpnm.DwItemSpec); n >= 0 && n < sb.items.Len() { sb.items.At(n).raiseClicked() } } } return sb.WidgetBase.WndProc(hwnd, msg, wParam, lParam) } func (*StatusBar) CreateLayoutItem(ctx *LayoutContext) LayoutItem { return new(statusBarLayoutItem) } type statusBarLayoutItem struct { LayoutItemBase } func (*statusBarLayoutItem) LayoutFlags() LayoutFlags { return 0 } func (*statusBarLayoutItem) IdealSize() Size { return Size{} } // StatusBarItem represents a section of a StatusBar that can have its own icon, // text, tool tip text and width. type StatusBarItem struct { sb *StatusBar icon *Icon text string toolTipText string width int clickedPublisher EventPublisher } // NewStatusBarItem returns a new StatusBarItem. func NewStatusBarItem() *StatusBarItem { return &StatusBarItem{width: 100} } // Icon returns the Icon of the StatusBarItem. func (sbi *StatusBarItem) Icon() *Icon { return sbi.icon } // SetIcon sets the Icon of the StatusBarItem. func (sbi *StatusBarItem) SetIcon(icon *Icon) error { if icon == sbi.icon { return nil } old := sbi.icon sbi.icon = icon return sbi.maybeTry(sbi.updateIcon, func() { sbi.icon = old }) } // Text returns the text of the StatusBarItem. func (sbi *StatusBarItem) Text() string { return sbi.text } // SetText sets the text of the StatusBarItem. func (sbi *StatusBarItem) SetText(text string) error { if text == sbi.text { return nil } old := sbi.text sbi.text = text return sbi.maybeTry(sbi.updateText, func() { sbi.text = old }) } // ToolTipText returns the tool tip text of the StatusBarItem. func (sbi *StatusBarItem) ToolTipText() string { return sbi.toolTipText } // SetToolTipText sets the tool tip text of the StatusBarItem. func (sbi *StatusBarItem) SetToolTipText(toolTipText string) error { if toolTipText == sbi.toolTipText { return nil } old := sbi.toolTipText sbi.toolTipText = toolTipText return sbi.maybeTry(sbi.updateToolTipText, func() { sbi.toolTipText = old }) } // Width returns the width of the StatusBarItem. func (sbi *StatusBarItem) Width() int { return sbi.width } // SetWidth sets the width of the StatusBarItem. func (sbi *StatusBarItem) SetWidth(width int) error { if width == sbi.width { return nil } old := sbi.width sbi.width = width if sbi.sb != nil { succeeded := false defer func() { if !succeeded { sbi.width = old } }() if err := sbi.sb.updateParts(); err != nil { return err } succeeded = true } return nil } func (sbi *StatusBarItem) Clicked() *Event { return sbi.clickedPublisher.Event() } func (sbi *StatusBarItem) raiseClicked() { sbi.clickedPublisher.Publish() } func (sbi *StatusBarItem) maybeTry(f func(index int) error, rollback func()) error { if sbi.sb != nil { succeeded := false defer func() { if !succeeded { rollback() } }() if err := f(sbi.sb.items.Index(sbi)); err != nil { return err } succeeded = true } return nil } func (sbi *StatusBarItem) update(index int) error { if err := sbi.updateIcon(index); err != nil { return err } if err := sbi.updateText(index); err != nil { return err } if err := sbi.updateToolTipText(index); err != nil { return err } return nil } func (sbi *StatusBarItem) updateIcon(index int) error { var hIcon win.HICON if sbi.icon != nil { hIcon = sbi.icon.handleForDPI(sbi.sb.DPI()) } if 0 == sbi.sb.SendMessage( win.SB_SETICON, uintptr(index), uintptr(hIcon)) { return newError("SB_SETICON") } return nil } func (sbi *StatusBarItem) updateText(index int) error { utf16, err := syscall.UTF16PtrFromString(sbi.text) if err != nil { return err } if 0 == sbi.sb.SendMessage( win.SB_SETTEXT, uintptr(win.MAKEWORD(byte(index), 0)), uintptr(unsafe.Pointer(utf16))) { return newError("SB_SETTEXT") } return nil } func (sbi *StatusBarItem) updateToolTipText(index int) error { utf16, err := syscall.UTF16PtrFromString(sbi.toolTipText) if err != nil { return err } sbi.sb.SendMessage( win.SB_SETTIPTEXT, uintptr(index), uintptr(unsafe.Pointer(utf16))) return nil } type StatusBarItemList struct { sb *StatusBar items []*StatusBarItem } func newStatusBarItemList(statusBar *StatusBar) *StatusBarItemList { return &StatusBarItemList{sb: statusBar} } func (l *StatusBarItemList) Add(item *StatusBarItem) error { return l.Insert(len(l.items), item) } func (l *StatusBarItemList) At(index int) *StatusBarItem { return l.items[index] } func (l *StatusBarItemList) Clear() error { old := l.items l.items = l.items[:0] succeeded := false defer func() { if !succeeded { l.items = old l.sb.update() } }() if err := l.sb.update(); err != nil { return err } succeeded = true return nil } func (l *StatusBarItemList) Index(item *StatusBarItem) int { for i, it := range l.items { if it == item { return i } } return -1 } func (l *StatusBarItemList) Contains(item *StatusBarItem) bool { return l.Index(item) > -1 } func (l *StatusBarItemList) Insert(index int, item *StatusBarItem) error { if item.sb != nil { return newError("item already contained in a StatusBar") } l.items = append(l.items, nil) copy(l.items[index+1:], l.items[index:]) l.items[index] = item item.sb = l.sb succeeded := false defer func() { if !succeeded { item.sb = nil l.items = append(l.items[:index], l.items[index+1:]...) l.sb.update() } }() if err := l.sb.update(); err != nil { return err } succeeded = true return nil } func (l *StatusBarItemList) Len() int { return len(l.items) } func (l *StatusBarItemList) Remove(item *StatusBarItem) error { index := l.Index(item) if index == -1 { return nil } return l.RemoveAt(index) } func (l *StatusBarItemList) RemoveAt(index int) error { item := l.items[index] item.sb = nil l.items = append(l.items[:index], l.items[index+1:]...) succeeded := false defer func() { if !succeeded { l.items = append(l.items, nil) copy(l.items[index+1:], l.items[index:]) l.items[index] = item item.sb = l.sb l.sb.update() } }() if err := l.sb.update(); err != nil { return err } succeeded = true return nil }