//go:build windows /* * Copyright (C) 2019 The Winc Authors. All Rights Reserved. */ package winc import ( "errors" "syscall" "unsafe" "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32" ) // TreeItem represents an item in a TreeView widget. type TreeItem interface { Text() string // Text returns the text of the item. ImageIndex() int // ImageIndex is used only if SetImageList is called on the treeview } type treeViewItemInfo struct { handle w32.HTREEITEM child2Handle map[TreeItem]w32.HTREEITEM } // StringTreeItem is helper for basic string lists. type StringTreeItem struct { Data string Image int } func (s StringTreeItem) Text() string { return s.Data } func (s StringTreeItem) ImageIndex() int { return s.Image } type TreeView struct { ControlBase iml *ImageList item2Info map[TreeItem]*treeViewItemInfo handle2Item map[w32.HTREEITEM]TreeItem currItem TreeItem onSelectedChange EventManager onExpand EventManager onCollapse EventManager onViewChange EventManager } func NewTreeView(parent Controller) *TreeView { tv := new(TreeView) tv.InitControl("SysTreeView32", parent, 0, w32.WS_CHILD|w32.WS_VISIBLE| w32.WS_BORDER|w32.TVS_HASBUTTONS|w32.TVS_LINESATROOT|w32.TVS_SHOWSELALWAYS| w32.TVS_TRACKSELECT /*|w32.WS_EX_CLIENTEDGE*/) tv.item2Info = make(map[TreeItem]*treeViewItemInfo) tv.handle2Item = make(map[w32.HTREEITEM]TreeItem) RegMsgHandler(tv) tv.SetFont(DefaultFont) tv.SetSize(200, 400) if err := tv.SetTheme("Explorer"); err != nil { // theme error is ignored } return tv } func (tv *TreeView) EnableDoubleBuffer(enable bool) { if enable { w32.SendMessage(tv.hwnd, w32.TVM_SETEXTENDEDSTYLE, 0, w32.TVS_EX_DOUBLEBUFFER) } else { w32.SendMessage(tv.hwnd, w32.TVM_SETEXTENDEDSTYLE, w32.TVS_EX_DOUBLEBUFFER, 0) } } // SelectedItem is current selected item after OnSelectedChange event. func (tv *TreeView) SelectedItem() TreeItem { return tv.currItem } func (tv *TreeView) SetSelectedItem(item TreeItem) bool { var handle w32.HTREEITEM if item != nil { if info := tv.item2Info[item]; info == nil { return false // invalid item } else { handle = info.handle } } if w32.SendMessage(tv.hwnd, w32.TVM_SELECTITEM, w32.TVGN_CARET, uintptr(handle)) == 0 { return false // set selected failed } tv.currItem = item return true } func (tv *TreeView) ItemAt(x, y int) TreeItem { hti := w32.TVHITTESTINFO{Pt: w32.POINT{int32(x), int32(y)}} w32.SendMessage(tv.hwnd, w32.TVM_HITTEST, 0, uintptr(unsafe.Pointer(&hti))) if item, ok := tv.handle2Item[hti.HItem]; ok { return item } return nil } func (tv *TreeView) Items() (list []TreeItem) { for item := range tv.item2Info { list = append(list, item) } return list } func (tv *TreeView) InsertItem(item, parent, insertAfter TreeItem) error { var tvins w32.TVINSERTSTRUCT tvi := &tvins.Item tvi.Mask = w32.TVIF_TEXT // w32.TVIF_CHILDREN | w32.TVIF_TEXT tvi.PszText = uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(item.Text()))) // w32.LPSTR_TEXTCALLBACK tvi.CChildren = 0 // w32.I_CHILDRENCALLBACK if parent == nil { tvins.HParent = w32.TVI_ROOT } else { info := tv.item2Info[parent] if info == nil { return errors.New("winc: invalid parent") } tvins.HParent = info.handle } if insertAfter == nil { tvins.HInsertAfter = w32.TVI_LAST } else { info := tv.item2Info[insertAfter] if info == nil { return errors.New("winc: invalid prev item") } tvins.HInsertAfter = info.handle } tv.applyImage(tvi, item) hItem := w32.HTREEITEM(w32.SendMessage(tv.hwnd, w32.TVM_INSERTITEM, 0, uintptr(unsafe.Pointer(&tvins)))) if hItem == 0 { return errors.New("winc: TVM_INSERTITEM failed") } tv.item2Info[item] = &treeViewItemInfo{hItem, make(map[TreeItem]w32.HTREEITEM)} tv.handle2Item[hItem] = item return nil } func (tv *TreeView) UpdateItem(item TreeItem) bool { it := tv.item2Info[item] if it == nil { return false } tvi := &w32.TVITEM{ Mask: w32.TVIF_TEXT, HItem: it.handle, PszText: uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(item.Text()))), } tv.applyImage(tvi, item) if w32.SendMessage(tv.hwnd, w32.TVM_SETITEM, 0, uintptr(unsafe.Pointer(tvi))) == 0 { return false } return true } func (tv *TreeView) DeleteItem(item TreeItem) bool { it := tv.item2Info[item] if it == nil { return false } if w32.SendMessage(tv.hwnd, w32.TVM_DELETEITEM, 0, uintptr(it.handle)) == 0 { return false } delete(tv.item2Info, item) delete(tv.handle2Item, it.handle) return true } func (tv *TreeView) DeleteAllItems() bool { if w32.SendMessage(tv.hwnd, w32.TVM_DELETEITEM, 0, 0) == 0 { return false } tv.item2Info = make(map[TreeItem]*treeViewItemInfo) tv.handle2Item = make(map[w32.HTREEITEM]TreeItem) return true } func (tv *TreeView) Expand(item TreeItem) bool { if w32.SendMessage(tv.hwnd, w32.TVM_EXPAND, w32.TVE_EXPAND, uintptr(tv.item2Info[item].handle)) == 0 { return false } return true } func (tv *TreeView) Collapse(item TreeItem) bool { if w32.SendMessage(tv.hwnd, w32.TVM_EXPAND, w32.TVE_COLLAPSE, uintptr(tv.item2Info[item].handle)) == 0 { return false } return true } func (tv *TreeView) EnsureVisible(item TreeItem) bool { if info := tv.item2Info[item]; info != nil { return w32.SendMessage(tv.hwnd, w32.TVM_ENSUREVISIBLE, 0, uintptr(info.handle)) != 0 } return false } func (tv *TreeView) SetImageList(imageList *ImageList) { w32.SendMessage(tv.hwnd, w32.TVM_SETIMAGELIST, 0, uintptr(imageList.Handle())) tv.iml = imageList } func (tv *TreeView) applyImage(tc *w32.TVITEM, item TreeItem) { if tv.iml != nil { tc.Mask |= w32.TVIF_IMAGE | w32.TVIF_SELECTEDIMAGE tc.IImage = int32(item.ImageIndex()) tc.ISelectedImage = int32(item.ImageIndex()) } } func (tv *TreeView) OnSelectedChange() *EventManager { return &tv.onSelectedChange } func (tv *TreeView) OnExpand() *EventManager { return &tv.onExpand } func (tv *TreeView) OnCollapse() *EventManager { return &tv.onCollapse } func (tv *TreeView) OnViewChange() *EventManager { return &tv.onViewChange } // Message processor func (tv *TreeView) WndProc(msg uint32, wparam, lparam uintptr) uintptr { switch msg { case w32.WM_NOTIFY: nm := (*w32.NMHDR)(unsafe.Pointer(lparam)) switch nm.Code { case w32.TVN_ITEMEXPANDED: nmtv := (*w32.NMTREEVIEW)(unsafe.Pointer(lparam)) switch nmtv.Action { case w32.TVE_COLLAPSE: tv.onCollapse.Fire(NewEvent(tv, nil)) case w32.TVE_COLLAPSERESET: case w32.TVE_EXPAND: tv.onExpand.Fire(NewEvent(tv, nil)) case w32.TVE_EXPANDPARTIAL: case w32.TVE_TOGGLE: } case w32.TVN_SELCHANGED: nmtv := (*w32.NMTREEVIEW)(unsafe.Pointer(lparam)) tv.currItem = tv.handle2Item[nmtv.ItemNew.HItem] tv.onSelectedChange.Fire(NewEvent(tv, nil)) case w32.TVN_GETDISPINFO: tv.onViewChange.Fire(NewEvent(tv, nil)) } } return w32.DefWindowProc(tv.hwnd, msg, wparam, lparam) }