451 lines
8.2 KiB
Go
451 lines
8.2 KiB
Go
|
// 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
|
||
|
}
|