erm/vendor/github.com/lxn/walk/treeview.go
2021-07-30 23:29:20 +01:00

657 lines
15 KiB
Go

// Copyright 2010 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"
"github.com/lxn/win"
)
type treeViewItemInfo struct {
handle win.HTREEITEM
child2Handle map[TreeItem]win.HTREEITEM
}
type TreeView struct {
WidgetBase
model TreeModel
lazyPopulation bool
itemsResetEventHandlerHandle int
itemChangedEventHandlerHandle int
itemInsertedEventHandlerHandle int
itemRemovedEventHandlerHandle int
item2Info map[TreeItem]*treeViewItemInfo
handle2Item map[win.HTREEITEM]TreeItem
currItem TreeItem
hIml win.HIMAGELIST
usingSysIml bool
imageUintptr2Index map[uintptr]int32
filePath2IconIndex map[string]int32
expandedChangedPublisher TreeItemEventPublisher
currentItemChangedPublisher EventPublisher
itemActivatedPublisher EventPublisher
}
func NewTreeView(parent Container) (*TreeView, error) {
tv := new(TreeView)
if err := InitWidget(
tv,
parent,
"SysTreeView32",
win.WS_TABSTOP|win.WS_VISIBLE|win.TVS_HASBUTTONS|win.TVS_LINESATROOT|win.TVS_SHOWSELALWAYS|win.TVS_TRACKSELECT,
win.WS_EX_CLIENTEDGE); err != nil {
return nil, err
}
succeeded := false
defer func() {
if !succeeded {
tv.Dispose()
}
}()
if hr := win.HRESULT(tv.SendMessage(win.TVM_SETEXTENDEDSTYLE, win.TVS_EX_DOUBLEBUFFER, win.TVS_EX_DOUBLEBUFFER)); win.FAILED(hr) {
return nil, errorFromHRESULT("TVM_SETEXTENDEDSTYLE", hr)
}
if err := tv.setTheme("Explorer"); err != nil {
return nil, err
}
tv.GraphicsEffects().Add(InteractionEffect)
tv.GraphicsEffects().Add(FocusEffect)
tv.MustRegisterProperty("CurrentItem", NewReadOnlyProperty(
func() interface{} {
return tv.CurrentItem()
},
tv.CurrentItemChanged()))
tv.MustRegisterProperty("CurrentItemLevel", NewReadOnlyProperty(
func() interface{} {
level := -1
item := tv.CurrentItem()
for item != nil {
level++
item = item.Parent()
}
return level
},
tv.CurrentItemChanged()))
tv.MustRegisterProperty("HasCurrentItem", NewReadOnlyBoolProperty(
func() bool {
return tv.CurrentItem() != nil
},
tv.CurrentItemChanged()))
succeeded = true
return tv, nil
}
func (tv *TreeView) Dispose() {
tv.WidgetBase.Dispose()
tv.disposeImageListAndCaches()
}
func (tv *TreeView) SetBackground(bg Brush) {
tv.WidgetBase.SetBackground(bg)
color := Color(win.GetSysColor(win.COLOR_WINDOW))
if bg != nil {
type Colorer interface {
Color() Color
}
if c, ok := bg.(Colorer); ok {
color = c.Color()
}
}
tv.SendMessage(win.TVM_SETBKCOLOR, 0, uintptr(color))
}
func (tv *TreeView) Model() TreeModel {
return tv.model
}
func (tv *TreeView) SetModel(model TreeModel) error {
if tv.model != nil {
tv.model.ItemsReset().Detach(tv.itemsResetEventHandlerHandle)
tv.model.ItemChanged().Detach(tv.itemChangedEventHandlerHandle)
tv.model.ItemInserted().Detach(tv.itemInsertedEventHandlerHandle)
tv.model.ItemRemoved().Detach(tv.itemRemovedEventHandlerHandle)
tv.disposeImageListAndCaches()
}
tv.model = model
if model != nil {
tv.lazyPopulation = model.LazyPopulation()
tv.itemsResetEventHandlerHandle = model.ItemsReset().Attach(func(parent TreeItem) {
if parent == nil {
tv.resetItems()
} else if tv.item2Info[parent] != nil {
tv.SetSuspended(true)
defer tv.SetSuspended(false)
if err := tv.removeDescendants(parent); err != nil {
return
}
if err := tv.insertChildren(parent); err != nil {
return
}
}
})
tv.itemChangedEventHandlerHandle = model.ItemChanged().Attach(func(item TreeItem) {
if item == nil || tv.item2Info[item] == nil {
return
}
if err := tv.updateItem(item); err != nil {
return
}
})
tv.itemInsertedEventHandlerHandle = model.ItemInserted().Attach(func(item TreeItem) {
tv.SetSuspended(true)
defer tv.SetSuspended(false)
var hInsertAfter win.HTREEITEM
parent := item.Parent()
for i := parent.ChildCount() - 1; i >= 0; i-- {
if parent.ChildAt(i) == item {
if i > 0 {
hInsertAfter = tv.item2Info[parent.ChildAt(i-1)].handle
} else {
hInsertAfter = win.TVI_FIRST
}
}
}
if _, err := tv.insertItemAfter(item, hInsertAfter); err != nil {
return
}
})
tv.itemRemovedEventHandlerHandle = model.ItemRemoved().Attach(func(item TreeItem) {
if err := tv.removeItem(item); err != nil {
return
}
})
}
return tv.resetItems()
}
func (tv *TreeView) CurrentItem() TreeItem {
return tv.currItem
}
func (tv *TreeView) SetCurrentItem(item TreeItem) error {
if item == tv.currItem {
return nil
}
if item != nil {
if err := tv.ensureItemAndAncestorsInserted(item); err != nil {
return err
}
}
handle, err := tv.handleForItem(item)
if err != nil {
return err
}
if 0 == tv.SendMessage(win.TVM_SELECTITEM, win.TVGN_CARET, uintptr(handle)) {
return newError("SendMessage(TVM_SELECTITEM) failed")
}
tv.currItem = item
return nil
}
func (tv *TreeView) EnsureVisible(item TreeItem) error {
handle, err := tv.handleForItem(item)
if err != nil {
return err
}
tv.SendMessage(win.TVM_ENSUREVISIBLE, 0, uintptr(handle))
return nil
}
func (tv *TreeView) handleForItem(item TreeItem) (win.HTREEITEM, error) {
if item != nil {
if info := tv.item2Info[item]; info == nil {
return 0, newError("invalid item")
} else {
return info.handle, nil
}
}
return 0, newError("invalid item")
}
// ItemAt determines the location of the specified point in native pixels relative to the client area of a tree-view control.
func (tv *TreeView) ItemAt(x, y int) TreeItem {
hti := win.TVHITTESTINFO{Pt: Point{x, y}.toPOINT()}
tv.SendMessage(win.TVM_HITTEST, 0, uintptr(unsafe.Pointer(&hti)))
if item, ok := tv.handle2Item[hti.HItem]; ok {
return item
}
return nil
}
// ItemHeight returns the height of each item in native pixels.
func (tv *TreeView) ItemHeight() int {
return int(tv.SendMessage(win.TVM_GETITEMHEIGHT, 0, 0))
}
// SetItemHeight sets the height of the tree-view items in native pixels.
func (tv *TreeView) SetItemHeight(height int) {
tv.SendMessage(win.TVM_SETITEMHEIGHT, uintptr(height), 0)
}
func (tv *TreeView) resetItems() error {
tv.SetSuspended(true)
defer tv.SetSuspended(false)
if err := tv.clearItems(); err != nil {
return err
}
if tv.model == nil {
return nil
}
if err := tv.insertRoots(); err != nil {
return err
}
return nil
}
func (tv *TreeView) clearItems() error {
if 0 == tv.SendMessage(win.TVM_DELETEITEM, 0, 0) {
return newError("SendMessage(TVM_DELETEITEM) failed")
}
tv.item2Info = make(map[TreeItem]*treeViewItemInfo)
tv.handle2Item = make(map[win.HTREEITEM]TreeItem)
return nil
}
func (tv *TreeView) insertRoots() error {
for i := tv.model.RootCount() - 1; i >= 0; i-- {
if _, err := tv.insertItem(tv.model.RootAt(i)); err != nil {
return err
}
}
return nil
}
func (tv *TreeView) ApplyDPI(dpi int) {
tv.WidgetBase.ApplyDPI(dpi)
tv.disposeImageListAndCaches()
}
func (tv *TreeView) applyImageListForImage(image interface{}) {
tv.hIml, tv.usingSysIml, _ = imageListForImage(image, tv.DPI())
tv.SendMessage(win.TVM_SETIMAGELIST, 0, uintptr(tv.hIml))
tv.imageUintptr2Index = make(map[uintptr]int32)
tv.filePath2IconIndex = make(map[string]int32)
}
func (tv *TreeView) disposeImageListAndCaches() {
if tv.hIml != 0 && !tv.usingSysIml {
win.ImageList_Destroy(tv.hIml)
}
tv.hIml = 0
tv.imageUintptr2Index = nil
tv.filePath2IconIndex = nil
}
func (tv *TreeView) setTVITEMImageInfo(tvi *win.TVITEM, item TreeItem) {
if imager, ok := item.(Imager); ok {
if tv.hIml == 0 {
tv.applyImageListForImage(imager.Image())
}
// FIXME: If not setting TVIF_SELECTEDIMAGE and tvi.ISelectedImage,
// some default icon will show up, even though we have not asked for it.
tvi.Mask |= win.TVIF_IMAGE | win.TVIF_SELECTEDIMAGE
tvi.IImage = imageIndexMaybeAdd(
imager.Image(),
tv.hIml,
tv.usingSysIml,
tv.imageUintptr2Index,
tv.filePath2IconIndex,
tv.DPI())
tvi.ISelectedImage = tvi.IImage
}
}
func (tv *TreeView) insertItem(item TreeItem) (win.HTREEITEM, error) {
return tv.insertItemAfter(item, win.TVI_FIRST)
}
func (tv *TreeView) insertItemAfter(item TreeItem, hInsertAfter win.HTREEITEM) (win.HTREEITEM, error) {
var tvins win.TVINSERTSTRUCT
tvi := &tvins.Item
tvi.Mask = win.TVIF_CHILDREN | win.TVIF_TEXT
tvi.PszText = win.LPSTR_TEXTCALLBACK
tvi.CChildren = win.I_CHILDRENCALLBACK
tv.setTVITEMImageInfo(tvi, item)
parent := item.Parent()
if parent == nil {
tvins.HParent = win.TVI_ROOT
} else {
info := tv.item2Info[parent]
if info == nil {
return 0, newError("invalid parent")
}
tvins.HParent = info.handle
}
tvins.HInsertAfter = hInsertAfter
hItem := win.HTREEITEM(tv.SendMessage(win.TVM_INSERTITEM, 0, uintptr(unsafe.Pointer(&tvins))))
if hItem == 0 {
return 0, newError("TVM_INSERTITEM failed")
}
tv.item2Info[item] = &treeViewItemInfo{hItem, make(map[TreeItem]win.HTREEITEM)}
tv.handle2Item[hItem] = item
if !tv.lazyPopulation {
if err := tv.insertChildren(item); err != nil {
return 0, err
}
}
return hItem, nil
}
func (tv *TreeView) insertChildren(parent TreeItem) error {
info := tv.item2Info[parent]
for i := parent.ChildCount() - 1; i >= 0; i-- {
child := parent.ChildAt(i)
if handle, err := tv.insertItem(child); err != nil {
return err
} else {
info.child2Handle[child] = handle
}
}
return nil
}
func (tv *TreeView) updateItem(item TreeItem) error {
tvi := &win.TVITEM{
Mask: win.TVIF_TEXT,
HItem: tv.item2Info[item].handle,
PszText: win.LPSTR_TEXTCALLBACK,
}
tv.setTVITEMImageInfo(tvi, item)
if 0 == tv.SendMessage(win.TVM_SETITEM, 0, uintptr(unsafe.Pointer(tvi))) {
return newError("SendMessage(TVM_SETITEM) failed")
}
return nil
}
func (tv *TreeView) removeItem(item TreeItem) error {
if err := tv.removeDescendants(item); err != nil {
return err
}
info := tv.item2Info[item]
if info == nil {
return newError("invalid item")
}
if 0 == tv.SendMessage(win.TVM_DELETEITEM, 0, uintptr(info.handle)) {
return newError("SendMessage(TVM_DELETEITEM) failed")
}
if parentInfo := tv.item2Info[item.Parent()]; parentInfo != nil {
delete(parentInfo.child2Handle, item)
}
delete(tv.item2Info, item)
delete(tv.handle2Item, info.handle)
return nil
}
func (tv *TreeView) removeDescendants(parent TreeItem) error {
for item, _ := range tv.item2Info[parent].child2Handle {
if err := tv.removeItem(item); err != nil {
return err
}
}
return nil
}
func (tv *TreeView) ensureItemAndAncestorsInserted(item TreeItem) error {
if item == nil {
return newError("invalid item")
}
tv.SetSuspended(true)
defer tv.SetSuspended(false)
var hierarchy []TreeItem
for item != nil && tv.item2Info[item] == nil {
item = item.Parent()
if item != nil {
hierarchy = append(hierarchy, item)
} else {
return newError("invalid item")
}
}
for i := len(hierarchy) - 1; i >= 0; i-- {
if err := tv.insertChildren(hierarchy[i]); err != nil {
return err
}
}
return nil
}
func (tv *TreeView) Expanded(item TreeItem) bool {
if tv.item2Info[item] == nil {
return false
}
tvi := &win.TVITEM{
HItem: tv.item2Info[item].handle,
Mask: win.TVIF_STATE,
StateMask: win.TVIS_EXPANDED,
}
if 0 == tv.SendMessage(win.TVM_GETITEM, 0, uintptr(unsafe.Pointer(tvi))) {
newError("SendMessage(TVM_GETITEM) failed")
}
return tvi.State&win.TVIS_EXPANDED != 0
}
func (tv *TreeView) SetExpanded(item TreeItem, expanded bool) error {
if expanded {
if err := tv.ensureItemAndAncestorsInserted(item); err != nil {
return err
}
}
info := tv.item2Info[item]
if info == nil {
return newError("invalid item")
}
var action uintptr
if expanded {
action = win.TVE_EXPAND
} else {
action = win.TVE_COLLAPSE
}
if 0 == tv.SendMessage(win.TVM_EXPAND, action, uintptr(info.handle)) {
return newError("SendMessage(TVM_EXPAND) failed")
}
return nil
}
func (tv *TreeView) ExpandedChanged() *TreeItemEvent {
return tv.expandedChangedPublisher.Event()
}
func (tv *TreeView) CurrentItemChanged() *Event {
return tv.currentItemChangedPublisher.Event()
}
func (tv *TreeView) ItemActivated() *Event {
return tv.itemActivatedPublisher.Event()
}
func (tv *TreeView) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr {
switch msg {
case win.WM_GETDLGCODE:
if wParam == win.VK_RETURN {
return win.DLGC_WANTALLKEYS
}
case win.WM_NOTIFY:
nmhdr := (*win.NMHDR)(unsafe.Pointer(lParam))
switch nmhdr.Code {
case win.TVN_GETDISPINFO:
nmtvdi := (*win.NMTVDISPINFO)(unsafe.Pointer(lParam))
item := tv.handle2Item[nmtvdi.Item.HItem]
if nmtvdi.Item.Mask&win.TVIF_TEXT != 0 {
var text string
rc := win.RECT{Left: int32(nmtvdi.Item.HItem)}
if 0 != tv.SendMessage(win.TVM_GETITEMRECT, 0, uintptr(unsafe.Pointer(&rc))) {
// Only retrieve text if the item is visible. Why isn't Windows doing this for us?
text = item.Text()
}
utf16 := syscall.StringToUTF16(text)
buf := (*[264]uint16)(unsafe.Pointer(nmtvdi.Item.PszText))
max := mini(len(utf16), int(nmtvdi.Item.CchTextMax))
copy((*buf)[:], utf16[:max])
(*buf)[max-1] = 0
}
if nmtvdi.Item.Mask&win.TVIF_CHILDREN != 0 {
if hc, ok := item.(HasChilder); ok {
if hc.HasChild() {
nmtvdi.Item.CChildren = 1
} else {
nmtvdi.Item.CChildren = 0
}
} else {
nmtvdi.Item.CChildren = int32(item.ChildCount())
}
}
case win.TVN_ITEMEXPANDING:
nmtv := (*win.NMTREEVIEW)(unsafe.Pointer(lParam))
item := tv.handle2Item[nmtv.ItemNew.HItem]
if nmtv.Action == win.TVE_EXPAND && tv.lazyPopulation {
info := tv.item2Info[item]
if len(info.child2Handle) == 0 {
tv.insertChildren(item)
}
}
case win.TVN_ITEMEXPANDED:
nmtv := (*win.NMTREEVIEW)(unsafe.Pointer(lParam))
item := tv.handle2Item[nmtv.ItemNew.HItem]
switch nmtv.Action {
case win.TVE_COLLAPSE:
tv.expandedChangedPublisher.Publish(item)
case win.TVE_COLLAPSERESET:
case win.TVE_EXPAND:
tv.expandedChangedPublisher.Publish(item)
case win.TVE_EXPANDPARTIAL:
case win.TVE_TOGGLE:
}
case win.NM_DBLCLK:
tv.itemActivatedPublisher.Publish()
case win.TVN_KEYDOWN:
nmtvkd := (*win.NMTVKEYDOWN)(unsafe.Pointer(lParam))
if nmtvkd.WVKey == uint16(KeyReturn) {
tv.itemActivatedPublisher.Publish()
}
case win.TVN_SELCHANGED:
nmtv := (*win.NMTREEVIEW)(unsafe.Pointer(lParam))
tv.currItem = tv.handle2Item[nmtv.ItemNew.HItem]
tv.currentItemChangedPublisher.Publish()
}
}
return tv.WidgetBase.WndProc(hwnd, msg, wParam, lParam)
}
func (*TreeView) NeedsWmSize() bool {
return true
}
func (tv *TreeView) CreateLayoutItem(ctx *LayoutContext) LayoutItem {
return NewGreedyLayoutItem()
}