// Copyright 2011 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 ( "encoding/json" "fmt" "math/big" "reflect" "syscall" "time" "unsafe" "github.com/lxn/win" ) const tableViewWindowClass = `\o/ Walk_TableView_Class \o/` func init() { MustRegisterWindowClass(tableViewWindowClass) } var ( defaultTVRowBGColor = Color(win.GetSysColor(win.COLOR_WINDOW)) white = win.COLORREF(RGB(255, 255, 255)) checkmark = string([]byte{0xE2, 0x9C, 0x94}) tableViewFrozenLVWndProcPtr = syscall.NewCallback(tableViewFrozenLVWndProc) tableViewNormalLVWndProcPtr = syscall.NewCallback(tableViewNormalLVWndProc) ) const ( tableViewCurrentIndexChangedTimerId = 1 + iota tableViewSelectedIndexesChangedTimerId ) // TableView is a model based widget for record centric, tabular data. // // TableView is implemented as a virtual mode list view to support quite large // amounts of data. type TableView struct { WidgetBase hwndFrozen win.HWND frozenLVOrigWndProcPtr uintptr hwndNormal win.HWND normalLVOrigWndProcPtr uintptr columns *TableViewColumnList model TableModel providedModel interface{} itemChecker ItemChecker imageProvider ImageProvider styler CellStyler style CellStyle customDrawItemHot bool hIml win.HIMAGELIST usingSysIml bool imageUintptr2Index map[uintptr]int32 filePath2IconIndex map[string]int32 rowsResetHandlerHandle int rowChangedHandlerHandle int rowsInsertedHandlerHandle int rowsRemovedHandlerHandle int sortChangedHandlerHandle int selectedIndexes []int prevIndex int currentIndex int currentIndexChangedPublisher EventPublisher selectedIndexesChangedPublisher EventPublisher itemActivatedPublisher EventPublisher columnClickedPublisher IntEventPublisher columnsOrderableChangedPublisher EventPublisher columnsSizableChangedPublisher EventPublisher publishNextSelClear bool inSetSelectedIndexes bool lastColumnStretched bool inEraseBkgnd bool persistent bool itemStateChangedEventDelay int alternatingRowBGColor Color hasDarkAltBGColor bool delayedCurrentIndexChangedCanceled bool sortedColumnIndex int sortOrder SortOrder formActivatingHandle int scrolling bool inSetCurrentIndex bool inMouseEvent bool hasFrozenColumn bool } // NewTableView creates and returns a *TableView as child of the specified // Container. func NewTableView(parent Container) (*TableView, error) { return NewTableViewWithStyle(parent, win.LVS_SHOWSELALWAYS) } // NewTableViewWithStyle creates and returns a *TableView as child of the specified // Container and with the provided additional style bits set. func NewTableViewWithStyle(parent Container, style uint32) (*TableView, error) { tv := &TableView{ alternatingRowBGColor: defaultTVRowBGColor, imageUintptr2Index: make(map[uintptr]int32), filePath2IconIndex: make(map[string]int32), formActivatingHandle: -1, } tv.columns = newTableViewColumnList(tv) if err := InitWidget( tv, parent, tableViewWindowClass, win.WS_BORDER|win.WS_VISIBLE, win.WS_EX_CONTROLPARENT); err != nil { return nil, err } succeeded := false defer func() { if !succeeded { tv.Dispose() } }() if tv.hwndFrozen = win.CreateWindowEx( 0, syscall.StringToUTF16Ptr("SysListView32"), nil, win.WS_CHILD|win.WS_CLIPSIBLINGS|win.WS_TABSTOP|win.WS_VISIBLE|win.LVS_OWNERDATA|win.LVS_REPORT|style, win.CW_USEDEFAULT, win.CW_USEDEFAULT, win.CW_USEDEFAULT, win.CW_USEDEFAULT, tv.hWnd, 0, 0, nil, ); tv.hwndFrozen == 0 { return nil, newErr("creating frozen lv failed") } tv.frozenLVOrigWndProcPtr = win.SetWindowLongPtr(tv.hwndFrozen, win.GWLP_WNDPROC, tableViewFrozenLVWndProcPtr) if tv.frozenLVOrigWndProcPtr == 0 { return nil, lastError("SetWindowLongPtr") } if tv.hwndNormal = win.CreateWindowEx( 0, syscall.StringToUTF16Ptr("SysListView32"), nil, win.WS_CHILD|win.WS_CLIPSIBLINGS|win.WS_TABSTOP|win.WS_VISIBLE|win.LVS_OWNERDATA|win.LVS_REPORT|style, win.CW_USEDEFAULT, win.CW_USEDEFAULT, win.CW_USEDEFAULT, win.CW_USEDEFAULT, tv.hWnd, 0, 0, nil, ); tv.hwndNormal == 0 { return nil, newErr("creating normal lv failed") } tv.normalLVOrigWndProcPtr = win.SetWindowLongPtr(tv.hwndNormal, win.GWLP_WNDPROC, tableViewNormalLVWndProcPtr) if tv.normalLVOrigWndProcPtr == 0 { return nil, lastError("SetWindowLongPtr") } tv.SetPersistent(true) exStyle := win.SendMessage(tv.hwndFrozen, win.LVM_GETEXTENDEDLISTVIEWSTYLE, 0, 0) exStyle |= win.LVS_EX_DOUBLEBUFFER | win.LVS_EX_FULLROWSELECT | win.LVS_EX_HEADERDRAGDROP | win.LVS_EX_LABELTIP | win.LVS_EX_SUBITEMIMAGES win.SendMessage(tv.hwndFrozen, win.LVM_SETEXTENDEDLISTVIEWSTYLE, 0, exStyle) win.SendMessage(tv.hwndNormal, win.LVM_SETEXTENDEDLISTVIEWSTYLE, 0, exStyle) if hr := win.SetWindowTheme(tv.hwndFrozen, syscall.StringToUTF16Ptr("Explorer"), nil); win.FAILED(hr) { return nil, errorFromHRESULT("SetWindowTheme", hr) } if hr := win.SetWindowTheme(tv.hwndNormal, syscall.StringToUTF16Ptr("Explorer"), nil); win.FAILED(hr) { return nil, errorFromHRESULT("SetWindowTheme", hr) } win.SendMessage(tv.hwndFrozen, win.WM_CHANGEUISTATE, uintptr(win.MAKELONG(win.UIS_SET, win.UISF_HIDEFOCUS)), 0) win.SendMessage(tv.hwndNormal, win.WM_CHANGEUISTATE, uintptr(win.MAKELONG(win.UIS_SET, win.UISF_HIDEFOCUS)), 0) tv.currentIndex = -1 tv.GraphicsEffects().Add(InteractionEffect) tv.GraphicsEffects().Add(FocusEffect) tv.MustRegisterProperty("ColumnsOrderable", NewBoolProperty( func() bool { return tv.ColumnsOrderable() }, func(b bool) error { tv.SetColumnsOrderable(b) return nil }, tv.columnsOrderableChangedPublisher.Event())) tv.MustRegisterProperty("ColumnsSizable", NewBoolProperty( func() bool { return tv.ColumnsSizable() }, func(b bool) error { return tv.SetColumnsSizable(b) }, tv.columnsSizableChangedPublisher.Event())) tv.MustRegisterProperty("CurrentIndex", NewProperty( func() interface{} { return tv.CurrentIndex() }, func(v interface{}) error { return tv.SetCurrentIndex(v.(int)) }, tv.CurrentIndexChanged())) tv.MustRegisterProperty("CurrentItem", NewReadOnlyProperty( func() interface{} { if i := tv.CurrentIndex(); i > -1 { if rm, ok := tv.providedModel.(reflectModel); ok { return reflect.ValueOf(rm.Items()).Index(i).Interface() } } return nil }, tv.CurrentIndexChanged())) tv.MustRegisterProperty("HasCurrentItem", NewReadOnlyBoolProperty( func() bool { return tv.CurrentIndex() != -1 }, tv.CurrentIndexChanged())) tv.MustRegisterProperty("SelectedCount", NewReadOnlyProperty( func() interface{} { return len(tv.selectedIndexes) }, tv.SelectedIndexesChanged())) succeeded = true return tv, nil } // Dispose releases the operating system resources, associated with the // *TableView. func (tv *TableView) Dispose() { tv.columns.unsetColumnsTV() tv.disposeImageListAndCaches() if tv.hWnd != 0 { if !win.KillTimer(tv.hWnd, tableViewCurrentIndexChangedTimerId) { lastError("KillTimer") } if !win.KillTimer(tv.hWnd, tableViewSelectedIndexesChangedTimerId) {