// Copyright 2012 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" "github.com/lxn/win" ) // BindingValueProvider is the interface that a model must implement to support // data binding with widgets like ComboBox. type BindingValueProvider interface { BindingValue(index int) interface{} } // ListModel is the interface that a model must implement to support widgets // like ComboBox. type ListModel interface { // ItemCount returns the number of items in the model. ItemCount() int // Value returns the value that should be displayed for the given index. Value(index int) interface{} // ItemsReset returns the event that the model should publish when the // number of its items changes. ItemsReset() *Event // ItemChanged returns the event that the model should publish when an item // was changed. ItemChanged() *IntEvent // ItemsInserted returns the event that the model should publish when a // contiguous range of items was inserted. ItemsInserted() *IntRangeEvent // ItemsRemoved returns the event that the model should publish when a // contiguous range of items was removed. ItemsRemoved() *IntRangeEvent } // ListModelBase implements the ItemsReset and ItemChanged methods of the // ListModel interface. type ListModelBase struct { itemsResetPublisher EventPublisher itemChangedPublisher IntEventPublisher itemsInsertedPublisher IntRangeEventPublisher itemsRemovedPublisher IntRangeEventPublisher } func (lmb *ListModelBase) ItemsReset() *Event { return lmb.itemsResetPublisher.Event() } func (lmb *ListModelBase) ItemChanged() *IntEvent { return lmb.itemChangedPublisher.Event() } func (lmb *ListModelBase) ItemsInserted() *IntRangeEvent { return lmb.itemsInsertedPublisher.Event() } func (lmb *ListModelBase) ItemsRemoved() *IntRangeEvent { return lmb.itemsRemovedPublisher.Event() } func (lmb *ListModelBase) PublishItemsReset() { lmb.itemsResetPublisher.Publish() } func (lmb *ListModelBase) PublishItemChanged(index int) { lmb.itemChangedPublisher.Publish(index) } func (lmb *ListModelBase) PublishItemsInserted(from, to int) { lmb.itemsInsertedPublisher.Publish(from, to) } func (lmb *ListModelBase) PublishItemsRemoved(from, to int) { lmb.itemsRemovedPublisher.Publish(from, to) } // ReflectListModel provides an alternative to the ListModel interface. It // uses reflection to obtain data. type ReflectListModel interface { // Items returns the model data, which must be a slice of pointer to struct. Items() interface{} // ItemsReset returns the event that the model should publish when the // number of its items changes. ItemsReset() *Event // ItemChanged returns the event that the model should publish when an item // was changed. ItemChanged() *IntEvent // ItemsInserted returns the event that the model should publish when a // contiguous range of items was inserted. ItemsInserted() *IntRangeEvent // ItemsRemoved returns the event that the model should publish when a // contiguous range of items was removed. ItemsRemoved() *IntRangeEvent setValueFunc(value func(index int) interface{}) } // ReflectListModelBase implements the ItemsReset and ItemChanged methods of // the ReflectListModel interface. type ReflectListModelBase struct { ListModelBase value func(index int) interface{} } func (rlmb *ReflectListModelBase) setValueFunc(value func(index int) interface{}) { rlmb.value = value } func (rlmb *ReflectListModelBase) Value(index int) interface{} { return rlmb.value(index) } // TableModel is the interface that a model must implement to support widgets // like TableView. type TableModel interface { // RowCount returns the number of rows in the model. RowCount() int // Value returns the value that should be displayed for the given cell. Value(row, col int) interface{} // RowsReset returns the event that the model should publish when the number // of its rows changes. RowsReset() *Event // RowChanged returns the event that the model should publish when a row was // changed. RowChanged() *IntEvent // RowsChanged returns the event that the model should publish when a // contiguous range of items was changed. RowsChanged() *IntRangeEvent // RowsInserted returns the event that the model should publish when a // contiguous range of items was inserted. If the model supports sorting, it // is assumed to be sorted before the model publishes the event. RowsInserted() *IntRangeEvent // RowsRemoved returns the event that the model should publish when a // contiguous range of items was removed. RowsRemoved() *IntRangeEvent } // TableModelBase implements the RowsReset and RowChanged methods of the // TableModel interface. type TableModelBase struct { rowsResetPublisher EventPublisher rowChangedPublisher IntEventPublisher rowsChangedPublisher IntRangeEventPublisher rowsInsertedPublisher IntRangeEventPublisher rowsRemovedPublisher IntRangeEventPublisher } func (tmb *TableModelBase) RowsReset() *Event { return tmb.rowsResetPublisher.Event() } func (tmb *TableModelBase) RowChanged() *IntEvent { return tmb.rowChangedPublisher.Event() } func (tmb *TableModelBase) RowsChanged() *IntRangeEvent { return tmb.rowsChangedPublisher.Event() } func (tmb *TableModelBase) RowsInserted() *IntRangeEvent { return tmb.rowsInsertedPublisher.Event() } func (tmb *TableModelBase) RowsRemoved() *IntRangeEvent { return tmb.rowsRemovedPublisher.Event() } func (tmb *TableModelBase) PublishRowsReset() { tmb.rowsResetPublisher.Publish() } func (tmb *TableModelBase) PublishRowChanged(row int) { tmb.rowChangedPublisher.Publish(row) } func (tmb *TableModelBase) PublishRowsChanged(from, to int) { tmb.rowsChangedPublisher.Publish(from, to) } func (tmb *TableModelBase) PublishRowsInserted(from, to int) { tmb.rowsInsertedPublisher.Publish(from, to) } func (tmb *TableModelBase) PublishRowsRemoved(from, to int) { tmb.rowsRemovedPublisher.Publish(from, to) } // ReflectTableModel provides an alternative to the TableModel interface. It // uses reflection to obtain data. type ReflectTableModel interface { // Items returns the model data, which must be a slice of pointer to struct. Items() interface{} // RowsReset returns the event that the model should publish when the // number of its items changes. RowsReset() *Event // RowChanged returns the event that the model should publish when an item // was changed. RowChanged() *IntEvent // RowsChanged returns the event that the model should publish when a // contiguous range of items was changed. RowsChanged() *IntRangeEvent // RowsInserted returns the event that the model should publish when a // contiguous range of items was inserted. If the model supports sorting, it // is assumed to be sorted before the model publishes the event. RowsInserted() *IntRangeEvent // RowsRemoved returns the event that the model should publish when a // contiguous range of items was removed. RowsRemoved() *IntRangeEvent setValueFunc(value func(row, col int) interface{}) } // ReflectTableModelBase implements the ItemsReset and ItemChanged methods of // the ReflectTableModel interface. type ReflectTableModelBase struct { TableModelBase value func(row, col int) interface{} } func (rtmb *ReflectTableModelBase) setValueFunc(value func(row, col int) interface{}) { rtmb.value = value } func (rtmb *ReflectTableModelBase) Value(row, col int) interface{} { return rtmb.value(row, col) } type interceptedSorter interface { sorterBase() *SorterBase setSortFunc(sort func(col int, order SortOrder) error) } // SortedReflectTableModelBase implements the RowsReset and RowChanged methods // of the ReflectTableModel interface as well as the Sorter interface for // pre-implemented in-memory sorting. type SortedReflectTableModelBase struct { ReflectTableModelBase SorterBase sort func(col int, order SortOrder) error } func (srtmb *SortedReflectTableModelBase) setSortFunc(sort func(col int, order SortOrder) error) { srtmb.sort = sort } func (srtmb *SortedReflectTableModelBase) sorterBase() *SorterBase { return &srtmb.SorterBase } func (srtmb *SortedReflectTableModelBase) Sort(col int, order SortOrder) error { if srtmb.sort != nil { return srtmb.sort(col, order) } return srtmb.SorterBase.Sort(col, order) } // Populator is an interface that can be implemented by Reflect*Models and slice // types to populate themselves on demand. // // Widgets like TableView, ListBox and ComboBox support lazy population of a // Reflect*Model or slice, if it implements this interface. type Populator interface { // Populate initializes the slot specified by index. // // For best performance it is probably a good idea to populate more than a // single slot of the slice at once. Populate(index int) error } // ImageProvider is the interface that a model must implement to support // displaying an item image. type ImageProvider interface { // Image returns the image to display for the item at index index. // // Supported types are *walk.Bitmap, *walk.Icon and string. A string will be // interpreted as a file path and the icon associated with the file will be // used. It is not supported to use strings together with the other options // in the same model instance. Image(index int) interface{} } // CellStyler is the interface that must be implemented to provide a tabular // widget like TableView with cell display style information. type CellStyler interface { // StyleCell is called for each cell to pick up cell style information. StyleCell(style *CellStyle) } // CellStyle carries information about the display style of a cell in a tabular widget // like TableView. type CellStyle struct { row int col int bounds Rectangle // in native pixels hdc win.HDC dpi int canvas *Canvas BackgroundColor Color TextColor Color Font *Font // Image is the image to display in the cell. // // Supported types are *walk.Bitmap, *walk.Icon and string. A string will be // interpreted as a file path and the icon associated with the file will be // used. It is not supported to use strings together with the other options // in the same model instance. Image interface{} } func (cs *CellStyle) Row() int { return cs.row } func (cs *CellStyle) Col() int { return cs.col } func (cs *CellStyle) Bounds() Rectangle { return RectangleTo96DPI(cs.bounds, cs.dpi) } func (cs *CellStyle) BoundsPixels() Rectangle { return cs.bounds } func (cs *CellStyle) Canvas() *Canvas { if cs.canvas != nil { cs.canvas.dpi = cs.dpi return cs.canvas } if cs.hdc != 0 { cs.canvas, _ = newCanvasFromHDC(cs.hdc) cs.canvas.dpi = cs.dpi } return cs.canvas } // ListItemStyler is the interface that must be implemented to provide a list // widget like ListBox with item display style information. type ListItemStyler interface { // ItemHeightDependsOnWidth returns whether item height depends on width. ItemHeightDependsOnWidth() bool // DefaultItemHeight returns the initial height in native pixels for any item. DefaultItemHeight() int // ItemHeight is called for each item to retrieve the height of the item. width parameter and // return value are specified in native pixels. ItemHeight(index int, width int) int // StyleItem is called for each item to pick up item style information. StyleItem(style *ListItemStyle) } // ListItemStyle carries information about the display style of an item in a list widget // like ListBox. type ListItemStyle struct { BackgroundColor Color TextColor Color LineColor Color Font *Font index int hoverIndex int rc win.RECT bounds Rectangle // in native pixels state uint32 hTheme win.HTHEME hwnd win.HWND hdc win.HDC dpi int canvas *Canvas highContrastActive bool } func (lis *ListItemStyle) Index() int { return lis.index } func (lis *ListItemStyle) Bounds() Rectangle { return RectangleTo96DPI(lis.bounds, lis.dpi) } func (lis *ListItemStyle) BoundsPixels() Rectangle { return lis.bounds } func (lis *ListItemStyle) Canvas() *Canvas { if lis.canvas != nil { lis.canvas.dpi = lis.dpi return lis.canvas } if lis.hdc != 0 { lis.canvas, _ = newCanvasFromHDC(lis.hdc) lis.canvas.dpi = lis.dpi } return lis.canvas } func (lis *ListItemStyle) DrawBackground() error { canvas := lis.Canvas() if canvas == nil { return nil } stateID := lis.stateID() if lis.hTheme != 0 && stateID != win.LISS_NORMAL { if win.FAILED(win.DrawThemeBackground(lis.hTheme, lis.hdc, win.LVP_LISTITEM, stateID, &lis.rc, nil)) { return newError("DrawThemeBackground failed") } } else { brush, err := NewSolidColorBrush(lis.BackgroundColor) if err != nil { return err } defer brush.Dispose() if err := canvas.FillRectanglePixels(brush, lis.bounds); err != nil { return err } if lis.highContrastActive && (lis.index == lis.hoverIndex || stateID != win.LISS_NORMAL) { pen, err := NewCosmeticPen(PenSolid, lis.LineColor) if err != nil { return err } defer pen.Dispose() if err := canvas.DrawRectanglePixels(pen, lis.bounds); err != nil { return err } } } return nil } // DrawText draws text inside given bounds specified in native pixels. func (lis *ListItemStyle) DrawText(text string, bounds Rectangle, format DrawTextFormat) error { if lis.hTheme != 0 { if lis.Font != nil { hFontOld := win.SelectObject(lis.hdc, win.HGDIOBJ(lis.Font.handleForDPI(lis.dpi))) defer win.SelectObject(lis.hdc, hFontOld) } rc := bounds.toRECT() if win.FAILED(win.DrawThemeTextEx(lis.hTheme, lis.hdc, win.LVP_LISTITEM, lis.stateID(), syscall.StringToUTF16Ptr(text), int32(len(([]rune)(text))), uint32(format), &rc, nil)) { return newError("DrawThemeTextEx failed") } } else { if canvas := lis.Canvas(); canvas != nil { if err := canvas.DrawTextPixels(text, lis.Font, lis.TextColor, bounds, format); err != nil { return err } } } return nil } func (lis *ListItemStyle) stateID() int32 { if lis.state&win.ODS_CHECKED != 0 { if win.GetFocus() == lis.hwnd { if lis.index == lis.hoverIndex { return win.LISS_HOTSELECTED } else { return win.LISS_SELECTED } } else { return win.LISS_SELECTEDNOTFOCUS } } else if lis.index == lis.hoverIndex { return win.LISS_HOT } return win.LISS_NORMAL } // ItemChecker is the interface that a model must implement to support check // boxes in a widget like TableView. type ItemChecker interface { // Checked returns if the specified item is checked. Checked(index int) bool // SetChecked sets if the specified item is checked. SetChecked(index int, checked bool) error } // SortOrder specifies the order by which items are sorted. type SortOrder int const ( // SortAscending specifies ascending sort order. SortAscending SortOrder = iota // SortDescending specifies descending sort order. SortDescending ) // Sorter is the interface that a model must implement to support sorting with a // widget like TableView. type Sorter interface { // ColumnSortable returns whether column col is sortable. ColumnSortable(col int) bool // Sort sorts column col in order order. // // If col is -1 then no column is to be sorted. Sort must publish the event // returned from SortChanged() after sorting. Sort(col int, order SortOrder) error // SortChanged returns an event that is published after sorting. SortChanged() *Event // SortedColumn returns the index of the currently sorted column, or -1 if // no column is currently sorted. SortedColumn() int // SortOrder returns the current sort order. SortOrder() SortOrder } // SorterBase implements the Sorter interface. // // You still need to provide your own implementation of at least the Sort method // to actually sort and reset the model. Your Sort method should call the // SorterBase implementation so the SortChanged event, that e.g. a TableView // widget depends on, is published. type SorterBase struct { changedPublisher EventPublisher col int order SortOrder } func (sb *SorterBase) ColumnSortable(col int) bool { return true } func (sb *SorterBase) Sort(col int, order SortOrder) error { sb.col, sb.order = col, order sb.changedPublisher.Publish() return nil } func (sb *SorterBase) SortChanged() *Event { return sb.changedPublisher.Event() } func (sb *SorterBase) SortedColumn() int { return sb.col } func (sb *SorterBase) SortOrder() SortOrder { return sb.order } // Imager provides access to an image of objects like tree items. type Imager interface { // Image returns the image to display for an item. // // Supported types are *walk.Bitmap, *walk.Icon and string. A string will be // interpreted as a file path and the icon associated with the file will be // used. It is not supported to use strings together with the other options // in the same model instance. Image() interface{} } // TreeItem represents an item in a TreeView widget. type TreeItem interface { // Text returns the text of the item. Text() string // Parent returns the parent of the item. Parent() TreeItem // ChildCount returns the number of children of the item. ChildCount() int // ChildAt returns the child at the specified index. ChildAt(index int) TreeItem } // HasChilder enables widgets like TreeView to determine if an item has any // child, without enforcing to fully count all children. type HasChilder interface { HasChild() bool } // TreeModel provides widgets like TreeView with item data. type TreeModel interface { // LazyPopulation returns if the model prefers on-demand population. // // This is useful for models that potentially contain huge amounts of items, // e.g. a model that represents a file system. LazyPopulation() bool // RootCount returns the number of root items. RootCount() int // RootAt returns the root item at the specified index. RootAt(index int) TreeItem // ItemsReset returns the event that the model should publish when the // descendants of the specified item, or all items if no item is specified, // are reset. ItemsReset() *TreeItemEvent // ItemChanged returns the event that the model should publish when an item // was changed. ItemChanged() *TreeItemEvent // ItemInserted returns the event that the model should publish when an item // was inserted into the model. ItemInserted() *TreeItemEvent // ItemRemoved returns the event that the model should publish when an item // was removed from the model. ItemRemoved() *TreeItemEvent } // TreeModelBase partially implements the TreeModel interface. // // You still need to provide your own implementation of at least the // RootCount and RootAt methods. If your model needs lazy population, // you will also have to implement LazyPopulation. type TreeModelBase struct { itemsResetPublisher TreeItemEventPublisher itemChangedPublisher TreeItemEventPublisher itemInsertedPublisher TreeItemEventPublisher itemRemovedPublisher TreeItemEventPublisher } func (tmb *TreeModelBase) LazyPopulation() bool { return false } func (tmb *TreeModelBase) ItemsReset() *TreeItemEvent { return tmb.itemsResetPublisher.Event() } func (tmb *TreeModelBase) ItemChanged() *TreeItemEvent { return tmb.itemChangedPublisher.Event() } func (tmb *TreeModelBase) ItemInserted() *TreeItemEvent { return tmb.itemInsertedPublisher.Event() } func (tmb *TreeModelBase) ItemRemoved() *TreeItemEvent { return tmb.itemRemovedPublisher.Event() } func (tmb *TreeModelBase) PublishItemsReset(parent TreeItem) { tmb.itemsResetPublisher.Publish(parent) } func (tmb *TreeModelBase) PublishItemChanged(item TreeItem) { tmb.itemChangedPublisher.Publish(item) } func (tmb *TreeModelBase) PublishItemInserted(item TreeItem) { tmb.itemInsertedPublisher.Publish(item) } func (tmb *TreeModelBase) PublishItemRemoved(item TreeItem) { tmb.itemRemovedPublisher.Publish(item) }