github.com/secoba/wails/v2@v2.6.4/internal/frontend/desktop/windows/winc/listview.go (about)

     1  //go:build windows
     2  
     3  /*
     4   * Copyright (C) 2019 The Winc Authors. All Rights Reserved.
     5   */
     6  
     7  package winc
     8  
     9  import (
    10  	"errors"
    11  	"fmt"
    12  	"syscall"
    13  	"unsafe"
    14  
    15  	"github.com/secoba/wails/v2/internal/frontend/desktop/windows/winc/w32"
    16  )
    17  
    18  // ListItem represents an item in a ListView widget.
    19  type ListItem interface {
    20  	Text() []string  // Text returns the text of the multi-column item.
    21  	ImageIndex() int // ImageIndex is used only if SetImageList is called on the listview
    22  }
    23  
    24  // ListItemChecker is used for checkbox support in ListView.
    25  type ListItemChecker interface {
    26  	Checked() bool
    27  	SetChecked(checked bool)
    28  }
    29  
    30  // ListItemSetter is used in OnEndLabelEdit event.
    31  type ListItemSetter interface {
    32  	SetText(s string) // set first item in the array via LabelEdit event
    33  }
    34  
    35  // StringListItem is helper for basic string lists.
    36  type StringListItem struct {
    37  	ID    int
    38  	Data  string
    39  	Check bool
    40  }
    41  
    42  func (s StringListItem) Text() []string          { return []string{s.Data} }
    43  func (s StringListItem) Checked() bool           { return s.Check }
    44  func (s StringListItem) SetChecked(checked bool) { s.Check = checked }
    45  func (s StringListItem) ImageIndex() int         { return 0 }
    46  
    47  type ListView struct {
    48  	ControlBase
    49  
    50  	iml       *ImageList
    51  	lastIndex int
    52  	cols      int // count of columns
    53  
    54  	item2Handle map[ListItem]uintptr
    55  	handle2Item map[uintptr]ListItem
    56  
    57  	onEndLabelEdit EventManager
    58  	onDoubleClick  EventManager
    59  	onClick        EventManager
    60  	onKeyDown      EventManager
    61  	onItemChanging EventManager
    62  	onItemChanged  EventManager
    63  	onCheckChanged EventManager
    64  	onViewChange   EventManager
    65  	onEndScroll    EventManager
    66  }
    67  
    68  func NewListView(parent Controller) *ListView {
    69  	lv := new(ListView)
    70  
    71  	lv.InitControl("SysListView32", parent /*w32.WS_EX_CLIENTEDGE*/, 0,
    72  		w32.WS_CHILD|w32.WS_VISIBLE|w32.WS_TABSTOP|w32.LVS_REPORT|w32.LVS_EDITLABELS|w32.LVS_SHOWSELALWAYS)
    73  
    74  	lv.item2Handle = make(map[ListItem]uintptr)
    75  	lv.handle2Item = make(map[uintptr]ListItem)
    76  
    77  	RegMsgHandler(lv)
    78  
    79  	lv.SetFont(DefaultFont)
    80  	lv.SetSize(200, 400)
    81  
    82  	if err := lv.SetTheme("Explorer"); err != nil {
    83  		// theme error is ignored
    84  	}
    85  	return lv
    86  }
    87  
    88  // FIXME: Changes the state of an item in a list-view control. Refer LVM_SETITEMSTATE message.
    89  func (lv *ListView) setItemState(i int, state, mask uint) {
    90  	var item w32.LVITEM
    91  	item.State, item.StateMask = uint32(state), uint32(mask)
    92  	w32.SendMessage(lv.hwnd, w32.LVM_SETITEMSTATE, uintptr(i), uintptr(unsafe.Pointer(&item)))
    93  }
    94  
    95  func (lv *ListView) EnableSingleSelect(enable bool) {
    96  	SetStyle(lv.hwnd, enable, w32.LVS_SINGLESEL)
    97  }
    98  
    99  func (lv *ListView) EnableSortHeader(enable bool) {
   100  	SetStyle(lv.hwnd, enable, w32.LVS_NOSORTHEADER)
   101  }
   102  
   103  func (lv *ListView) EnableSortAscending(enable bool) {
   104  	SetStyle(lv.hwnd, enable, w32.LVS_SORTASCENDING)
   105  }
   106  
   107  func (lv *ListView) EnableEditLabels(enable bool) {
   108  	SetStyle(lv.hwnd, enable, w32.LVS_EDITLABELS)
   109  }
   110  
   111  func (lv *ListView) EnableFullRowSelect(enable bool) {
   112  	if enable {
   113  		w32.SendMessage(lv.hwnd, w32.LVM_SETEXTENDEDLISTVIEWSTYLE, 0, w32.LVS_EX_FULLROWSELECT)
   114  	} else {
   115  		w32.SendMessage(lv.hwnd, w32.LVM_SETEXTENDEDLISTVIEWSTYLE, w32.LVS_EX_FULLROWSELECT, 0)
   116  	}
   117  }
   118  
   119  func (lv *ListView) EnableDoubleBuffer(enable bool) {
   120  	if enable {
   121  		w32.SendMessage(lv.hwnd, w32.LVM_SETEXTENDEDLISTVIEWSTYLE, 0, w32.LVS_EX_DOUBLEBUFFER)
   122  	} else {
   123  		w32.SendMessage(lv.hwnd, w32.LVM_SETEXTENDEDLISTVIEWSTYLE, w32.LVS_EX_DOUBLEBUFFER, 0)
   124  	}
   125  }
   126  
   127  func (lv *ListView) EnableHotTrack(enable bool) {
   128  	if enable {
   129  		w32.SendMessage(lv.hwnd, w32.LVM_SETEXTENDEDLISTVIEWSTYLE, 0, w32.LVS_EX_TRACKSELECT)
   130  	} else {
   131  		w32.SendMessage(lv.hwnd, w32.LVM_SETEXTENDEDLISTVIEWSTYLE, w32.LVS_EX_TRACKSELECT, 0)
   132  	}
   133  }
   134  
   135  func (lv *ListView) SetItemCount(count int) bool {
   136  	return w32.SendMessage(lv.hwnd, w32.LVM_SETITEMCOUNT, uintptr(count), 0) != 0
   137  }
   138  
   139  func (lv *ListView) ItemCount() int {
   140  	return int(w32.SendMessage(lv.hwnd, w32.LVM_GETITEMCOUNT, 0, 0))
   141  }
   142  
   143  func (lv *ListView) ItemAt(x, y int) ListItem {
   144  	hti := w32.LVHITTESTINFO{Pt: w32.POINT{int32(x), int32(y)}}
   145  	w32.SendMessage(lv.hwnd, w32.LVM_HITTEST, 0, uintptr(unsafe.Pointer(&hti)))
   146  	return lv.findItemByIndex(int(hti.IItem))
   147  }
   148  
   149  func (lv *ListView) Items() (list []ListItem) {
   150  	for item := range lv.item2Handle {
   151  		list = append(list, item)
   152  	}
   153  	return list
   154  }
   155  
   156  func (lv *ListView) AddColumn(caption string, width int) {
   157  	var lc w32.LVCOLUMN
   158  	lc.Mask = w32.LVCF_TEXT
   159  	if width != 0 {
   160  		lc.Mask = lc.Mask | w32.LVCF_WIDTH
   161  		lc.Cx = int32(width)
   162  	}
   163  	lc.PszText = syscall.StringToUTF16Ptr(caption)
   164  	lv.insertLvColumn(&lc, lv.cols)
   165  	lv.cols++
   166  }
   167  
   168  // StretchLastColumn makes the last column take up all remaining horizontal
   169  // space of the *ListView.
   170  // The effect of this is not persistent.
   171  func (lv *ListView) StretchLastColumn() error {
   172  	if lv.cols == 0 {
   173  		return nil
   174  	}
   175  	if w32.SendMessage(lv.hwnd, w32.LVM_SETCOLUMNWIDTH, uintptr(lv.cols-1), w32.LVSCW_AUTOSIZE_USEHEADER) == 0 {
   176  		//panic("LVM_SETCOLUMNWIDTH failed")
   177  	}
   178  	return nil
   179  }
   180  
   181  // CheckBoxes returns if the *TableView has check boxes.
   182  func (lv *ListView) CheckBoxes() bool {
   183  	return w32.SendMessage(lv.hwnd, w32.LVM_GETEXTENDEDLISTVIEWSTYLE, 0, 0)&w32.LVS_EX_CHECKBOXES > 0
   184  }
   185  
   186  // SetCheckBoxes sets if the *TableView has check boxes.
   187  func (lv *ListView) SetCheckBoxes(value bool) {
   188  	exStyle := w32.SendMessage(lv.hwnd, w32.LVM_GETEXTENDEDLISTVIEWSTYLE, 0, 0)
   189  	oldStyle := exStyle
   190  	if value {
   191  		exStyle |= w32.LVS_EX_CHECKBOXES
   192  	} else {
   193  		exStyle &^= w32.LVS_EX_CHECKBOXES
   194  	}
   195  	if exStyle != oldStyle {
   196  		w32.SendMessage(lv.hwnd, w32.LVM_SETEXTENDEDLISTVIEWSTYLE, 0, exStyle)
   197  	}
   198  
   199  	mask := w32.SendMessage(lv.hwnd, w32.LVM_GETCALLBACKMASK, 0, 0)
   200  	if value {
   201  		mask |= w32.LVIS_STATEIMAGEMASK
   202  	} else {
   203  		mask &^= w32.LVIS_STATEIMAGEMASK
   204  	}
   205  
   206  	if w32.SendMessage(lv.hwnd, w32.LVM_SETCALLBACKMASK, mask, 0) == w32.FALSE {
   207  		panic("SendMessage(LVM_SETCALLBACKMASK)")
   208  	}
   209  }
   210  
   211  func (lv *ListView) applyImage(lc *w32.LVITEM, imIndex int) {
   212  	if lv.iml != nil {
   213  		lc.Mask |= w32.LVIF_IMAGE
   214  		lc.IImage = int32(imIndex)
   215  	}
   216  }
   217  
   218  func (lv *ListView) AddItem(item ListItem) {
   219  	lv.InsertItem(item, lv.ItemCount())
   220  }
   221  
   222  func (lv *ListView) InsertItem(item ListItem, index int) {
   223  	text := item.Text()
   224  	li := &w32.LVITEM{
   225  		Mask:    w32.LVIF_TEXT | w32.LVIF_PARAM,
   226  		PszText: syscall.StringToUTF16Ptr(text[0]),
   227  		IItem:   int32(index),
   228  	}
   229  
   230  	lv.lastIndex++
   231  	ix := new(int)
   232  	*ix = lv.lastIndex
   233  	li.LParam = uintptr(*ix)
   234  	lv.handle2Item[li.LParam] = item
   235  	lv.item2Handle[item] = li.LParam
   236  
   237  	lv.applyImage(li, item.ImageIndex())
   238  	lv.insertLvItem(li)
   239  
   240  	for i := 1; i < len(text); i++ {
   241  		li.Mask = w32.LVIF_TEXT
   242  		li.PszText = syscall.StringToUTF16Ptr(text[i])
   243  		li.ISubItem = int32(i)
   244  		lv.setLvItem(li)
   245  	}
   246  }
   247  
   248  func (lv *ListView) UpdateItem(item ListItem) bool {
   249  	lparam, ok := lv.item2Handle[item]
   250  	if !ok {
   251  		return false
   252  	}
   253  
   254  	index := lv.findIndexByItem(item)
   255  	if index == -1 {
   256  		return false
   257  	}
   258  
   259  	text := item.Text()
   260  	li := &w32.LVITEM{
   261  		Mask:    w32.LVIF_TEXT | w32.LVIF_PARAM,
   262  		PszText: syscall.StringToUTF16Ptr(text[0]),
   263  		LParam:  lparam,
   264  		IItem:   int32(index),
   265  	}
   266  
   267  	lv.applyImage(li, item.ImageIndex())
   268  	lv.setLvItem(li)
   269  
   270  	for i := 1; i < len(text); i++ {
   271  		li.Mask = w32.LVIF_TEXT
   272  		li.PszText = syscall.StringToUTF16Ptr(text[i])
   273  		li.ISubItem = int32(i)
   274  		lv.setLvItem(li)
   275  	}
   276  	return true
   277  }
   278  
   279  func (lv *ListView) insertLvColumn(lvColumn *w32.LVCOLUMN, iCol int) {
   280  	w32.SendMessage(lv.hwnd, w32.LVM_INSERTCOLUMN, uintptr(iCol), uintptr(unsafe.Pointer(lvColumn)))
   281  }
   282  
   283  func (lv *ListView) insertLvItem(lvItem *w32.LVITEM) {
   284  	w32.SendMessage(lv.hwnd, w32.LVM_INSERTITEM, 0, uintptr(unsafe.Pointer(lvItem)))
   285  }
   286  
   287  func (lv *ListView) setLvItem(lvItem *w32.LVITEM) {
   288  	w32.SendMessage(lv.hwnd, w32.LVM_SETITEM, 0, uintptr(unsafe.Pointer(lvItem)))
   289  }
   290  
   291  func (lv *ListView) DeleteAllItems() bool {
   292  	if w32.SendMessage(lv.hwnd, w32.LVM_DELETEALLITEMS, 0, 0) == w32.TRUE {
   293  		lv.item2Handle = make(map[ListItem]uintptr)
   294  		lv.handle2Item = make(map[uintptr]ListItem)
   295  		return true
   296  	}
   297  	return false
   298  }
   299  
   300  func (lv *ListView) DeleteItem(item ListItem) error {
   301  	index := lv.findIndexByItem(item)
   302  	if index == -1 {
   303  		return errors.New("item not found")
   304  	}
   305  
   306  	if w32.SendMessage(lv.hwnd, w32.LVM_DELETEITEM, uintptr(index), 0) == 0 {
   307  		return errors.New("SendMessage(TVM_DELETEITEM) failed")
   308  	}
   309  
   310  	h := lv.item2Handle[item]
   311  	delete(lv.item2Handle, item)
   312  	delete(lv.handle2Item, h)
   313  	return nil
   314  }
   315  
   316  func (lv *ListView) findIndexByItem(item ListItem) int {
   317  	lparam, ok := lv.item2Handle[item]
   318  	if !ok {
   319  		return -1
   320  	}
   321  
   322  	it := &w32.LVFINDINFO{
   323  		Flags:  w32.LVFI_PARAM,
   324  		LParam: lparam,
   325  	}
   326  	var i int = -1
   327  	return int(w32.SendMessage(lv.hwnd, w32.LVM_FINDITEM, uintptr(i), uintptr(unsafe.Pointer(it))))
   328  }
   329  
   330  func (lv *ListView) findItemByIndex(i int) ListItem {
   331  	it := &w32.LVITEM{
   332  		Mask:  w32.LVIF_PARAM,
   333  		IItem: int32(i),
   334  	}
   335  
   336  	if w32.SendMessage(lv.hwnd, w32.LVM_GETITEM, 0, uintptr(unsafe.Pointer(it))) == w32.TRUE {
   337  		if item, ok := lv.handle2Item[it.LParam]; ok {
   338  			return item
   339  		}
   340  	}
   341  	return nil
   342  }
   343  
   344  func (lv *ListView) EnsureVisible(item ListItem) bool {
   345  	if i := lv.findIndexByItem(item); i != -1 {
   346  		return w32.SendMessage(lv.hwnd, w32.LVM_ENSUREVISIBLE, uintptr(i), 1) == 0
   347  	}
   348  	return false
   349  }
   350  
   351  func (lv *ListView) SelectedItem() ListItem {
   352  	if items := lv.SelectedItems(); len(items) > 0 {
   353  		return items[0]
   354  	}
   355  	return nil
   356  }
   357  
   358  func (lv *ListView) SetSelectedItem(item ListItem) bool {
   359  	if i := lv.findIndexByItem(item); i > -1 {
   360  		lv.SetSelectedIndex(i)
   361  		return true
   362  	}
   363  	return false
   364  }
   365  
   366  // mask is used to set the LVITEM.Mask for ListView.GetItem which indicates which attributes you'd like to receive
   367  // of LVITEM.
   368  func (lv *ListView) SelectedItems() []ListItem {
   369  	var items []ListItem
   370  
   371  	var i int = -1
   372  	for {
   373  		if i = int(w32.SendMessage(lv.hwnd, w32.LVM_GETNEXTITEM, uintptr(i), uintptr(w32.LVNI_SELECTED))); i == -1 {
   374  			break
   375  		}
   376  
   377  		if item := lv.findItemByIndex(i); item != nil {
   378  			items = append(items, item)
   379  		}
   380  	}
   381  	return items
   382  }
   383  
   384  func (lv *ListView) SelectedCount() uint {
   385  	return uint(w32.SendMessage(lv.hwnd, w32.LVM_GETSELECTEDCOUNT, 0, 0))
   386  }
   387  
   388  // GetSelectedIndex first selected item index. Returns -1 if no item is selected.
   389  func (lv *ListView) SelectedIndex() int {
   390  	var i int = -1
   391  	return int(w32.SendMessage(lv.hwnd, w32.LVM_GETNEXTITEM, uintptr(i), uintptr(w32.LVNI_SELECTED)))
   392  }
   393  
   394  // Set i to -1 to select all items.
   395  func (lv *ListView) SetSelectedIndex(i int) {
   396  	lv.setItemState(i, w32.LVIS_SELECTED, w32.LVIS_SELECTED)
   397  }
   398  
   399  func (lv *ListView) SetImageList(imageList *ImageList) {
   400  	w32.SendMessage(lv.hwnd, w32.LVM_SETIMAGELIST, w32.LVSIL_SMALL, uintptr(imageList.Handle()))
   401  	lv.iml = imageList
   402  }
   403  
   404  // Event publishers
   405  func (lv *ListView) OnEndLabelEdit() *EventManager {
   406  	return &lv.onEndLabelEdit
   407  }
   408  
   409  func (lv *ListView) OnDoubleClick() *EventManager {
   410  	return &lv.onDoubleClick
   411  }
   412  
   413  func (lv *ListView) OnClick() *EventManager {
   414  	return &lv.onClick
   415  }
   416  
   417  func (lv *ListView) OnKeyDown() *EventManager {
   418  	return &lv.onKeyDown
   419  }
   420  
   421  func (lv *ListView) OnItemChanging() *EventManager {
   422  	return &lv.onItemChanging
   423  }
   424  
   425  func (lv *ListView) OnItemChanged() *EventManager {
   426  	return &lv.onItemChanged
   427  }
   428  
   429  func (lv *ListView) OnCheckChanged() *EventManager {
   430  	return &lv.onCheckChanged
   431  }
   432  
   433  func (lv *ListView) OnViewChange() *EventManager {
   434  	return &lv.onViewChange
   435  }
   436  
   437  func (lv *ListView) OnEndScroll() *EventManager {
   438  	return &lv.onEndScroll
   439  }
   440  
   441  // Message processer
   442  func (lv *ListView) WndProc(msg uint32, wparam, lparam uintptr) uintptr {
   443  	switch msg {
   444  	/*case w32.WM_ERASEBKGND:
   445  	lv.StretchLastColumn()
   446  	println("case w32.WM_ERASEBKGND")
   447  	return 1*/
   448  
   449  	case w32.WM_NOTIFY:
   450  		nm := (*w32.NMHDR)(unsafe.Pointer(lparam))
   451  		code := int32(nm.Code)
   452  
   453  		switch code {
   454  		case w32.LVN_BEGINLABELEDITW:
   455  			// println("Begin label edit")
   456  		case w32.LVN_ENDLABELEDITW:
   457  			nmdi := (*w32.NMLVDISPINFO)(unsafe.Pointer(lparam))
   458  			if nmdi.Item.PszText != nil {
   459  				fmt.Println(nmdi.Item.PszText, nmdi.Item)
   460  				if item, ok := lv.handle2Item[nmdi.Item.LParam]; ok {
   461  					lv.onEndLabelEdit.Fire(NewEvent(lv,
   462  						&LabelEditEventData{Item: item,
   463  							Text: w32.UTF16PtrToString(nmdi.Item.PszText)}))
   464  				}
   465  				return w32.TRUE
   466  			}
   467  		case w32.NM_DBLCLK:
   468  			lv.onDoubleClick.Fire(NewEvent(lv, nil))
   469  
   470  		case w32.NM_CLICK:
   471  			ac := (*w32.NMITEMACTIVATE)(unsafe.Pointer(lparam))
   472  			var hti w32.LVHITTESTINFO
   473  			hti.Pt = w32.POINT{ac.PtAction.X, ac.PtAction.Y}
   474  			w32.SendMessage(lv.hwnd, w32.LVM_HITTEST, 0, uintptr(unsafe.Pointer(&hti)))
   475  
   476  			if hti.Flags == w32.LVHT_ONITEMSTATEICON {
   477  				if item := lv.findItemByIndex(int(hti.IItem)); item != nil {
   478  					if item, ok := item.(ListItemChecker); ok {
   479  						checked := !item.Checked()
   480  						item.SetChecked(checked)
   481  						lv.onCheckChanged.Fire(NewEvent(lv, item))
   482  
   483  						if w32.SendMessage(lv.hwnd, w32.LVM_UPDATE, uintptr(hti.IItem), 0) == w32.FALSE {
   484  							panic("SendMessage(LVM_UPDATE)")
   485  						}
   486  					}
   487  				}
   488  			}
   489  
   490  			hti.Pt = w32.POINT{ac.PtAction.X, ac.PtAction.Y}
   491  			w32.SendMessage(lv.hwnd, w32.LVM_SUBITEMHITTEST, 0, uintptr(unsafe.Pointer(&hti)))
   492  			lv.onClick.Fire(NewEvent(lv, hti.ISubItem))
   493  
   494  		case w32.LVN_KEYDOWN:
   495  			nmkey := (*w32.NMLVKEYDOWN)(unsafe.Pointer(lparam))
   496  			if nmkey.WVKey == w32.VK_SPACE && lv.CheckBoxes() {
   497  				if item := lv.SelectedItem(); item != nil {
   498  					if item, ok := item.(ListItemChecker); ok {
   499  						checked := !item.Checked()
   500  						item.SetChecked(checked)
   501  						lv.onCheckChanged.Fire(NewEvent(lv, item))
   502  					}
   503  
   504  					index := lv.findIndexByItem(item)
   505  					if w32.SendMessage(lv.hwnd, w32.LVM_UPDATE, uintptr(index), 0) == w32.FALSE {
   506  						panic("SendMessage(LVM_UPDATE)")
   507  					}
   508  				}
   509  			}
   510  			lv.onKeyDown.Fire(NewEvent(lv, nmkey.WVKey))
   511  			key := nmkey.WVKey
   512  			w32.SendMessage(lv.Parent().Handle(), w32.WM_KEYDOWN, uintptr(key), 0)
   513  
   514  		case w32.LVN_ITEMCHANGING:
   515  			// This event also fires when listview has changed via code.
   516  			nmlv := (*w32.NMLISTVIEW)(unsafe.Pointer(lparam))
   517  			item := lv.findItemByIndex(int(nmlv.IItem))
   518  			lv.onItemChanging.Fire(NewEvent(lv, item))
   519  
   520  		case w32.LVN_ITEMCHANGED:
   521  			// This event also fires when listview has changed via code.
   522  			nmlv := (*w32.NMLISTVIEW)(unsafe.Pointer(lparam))
   523  			item := lv.findItemByIndex(int(nmlv.IItem))
   524  			lv.onItemChanged.Fire(NewEvent(lv, item))
   525  
   526  		case w32.LVN_GETDISPINFO:
   527  			nmdi := (*w32.NMLVDISPINFO)(unsafe.Pointer(lparam))
   528  			if nmdi.Item.StateMask&w32.LVIS_STATEIMAGEMASK > 0 {
   529  				if item, ok := lv.handle2Item[nmdi.Item.LParam]; ok {
   530  					if item, ok := item.(ListItemChecker); ok {
   531  
   532  						checked := item.Checked()
   533  						if checked {
   534  							nmdi.Item.State = 0x2000
   535  						} else {
   536  							nmdi.Item.State = 0x1000
   537  						}
   538  					}
   539  				}
   540  			}
   541  
   542  			lv.onViewChange.Fire(NewEvent(lv, nil))
   543  
   544  		case w32.LVN_ENDSCROLL:
   545  			lv.onEndScroll.Fire(NewEvent(lv, nil))
   546  		}
   547  	}
   548  	return w32.DefWindowProc(lv.hwnd, msg, wparam, lparam)
   549  }