2014-03-02 22:11:29 -06:00
// 2 march 2014
2014-03-12 20:55:45 -05:00
2014-03-02 22:11:29 -06:00
package ui
import (
2014-05-12 01:06:05 -05:00
// ...
2014-03-02 22:11:29 -06:00
)
/ *
The Cocoa API was not designed to be used directly in code ; you were intended to build your user interfaces with Interface Builder . There is no dedicated listbox class ; we have to synthesize it with a NSTableView . And this is difficult in code .
Under normal circumstances we would have to build our own data source class , as Cocoa doesn ' t provide premade data sources . Thankfully , Mac OS X 10.3 introduced the bindings system , which avoids all that . It ' s just not documented too well ( again , because you ' re supposed to use Interface Builder ) . Bear with me here .
2014-05-15 16:27:47 -05:00
After switching from using the Objective - C runtime to using Objective - C directly , you will now need to look both here and in listbox_darwin . m to get what ' s going on .
2014-03-02 22:11:29 -06:00
PERSONAL TODO - make a post somewhere that does all this in Objective - C itself , for the benefit of the programming community .
2014-03-03 13:57:20 -06:00
TODO - change the name of some of these functions ? specifically the functions that get data about the NSTableView ?
2014-03-02 22:11:29 -06:00
* /
// #cgo LDFLAGS: -lobjc -framework Foundation -framework AppKit
2014-03-02 22:28:43 -06:00
// #include <stdlib.h>
2014-03-02 22:11:29 -06:00
// #include "objc_darwin.h"
import "C"
/ *
We bind our sole NSTableColumn to a NSArrayController .
NSArrayController is a subclass of NSObjectController , which handles key - value pairs . The object of a NSObjectController by default is a NSMutableDictionary of key - value pairs . The keys are the critical part here .
In effect , each object in our NSArrayController is a NSMutableDictionary with one item : a marker key and the actual string as the value .
* /
const (
_listboxItemKey = "listboxitem"
)
var (
listboxItemKey = toNSString ( _listboxItemKey )
)
func toListboxItem ( what string ) C . id {
2014-05-15 16:27:47 -05:00
return C . toListboxItem ( listboxItemKey , toNSString ( what ) )
2014-03-02 22:11:29 -06:00
}
func fromListboxItem ( dict C . id ) string {
2014-05-15 16:27:47 -05:00
return fromNSString ( C . fromListboxItem ( dict , listboxItemKey ) )
2014-03-02 22:11:29 -06:00
}
/ *
NSArrayController is what we bind .
This is what provides the actual list modification methods .
- ( void ) addObject : ( id ) object
adds object to the end of the list
2014-03-03 00:51:54 -06:00
- ( void ) insertObject : ( id ) object atArrangedObjectIndex : ( NSInteger ) index
2014-03-02 22:11:29 -06:00
adds object in the list before index
- ( void ) removeObjectAtArrangedObjectIndex : ( NSInteger ) index
removes the object at index
- ( id ) arrangedObjects
returns the underlying array ; really a NSArray
But what is arrangedObjects ? Why care about arranging objects ? We don ' t have to arrange the objects ; if we don ' t , they won ' t be arranged , and arrangedObjects just acts as the unarranged array .
Of course , Mac OS X 10.5 adds the ability to automatically arrange objects . So let ' s just turn that off to be safe .
* /
func newListboxArray ( ) C . id {
2014-05-15 16:27:47 -05:00
return C . newListboxArray ( )
2014-03-02 22:11:29 -06:00
}
func appendListboxArray ( array C . id , what string ) {
2014-05-15 16:27:47 -05:00
C . listboxArrayAppend ( array , toListboxItem ( what ) )
2014-03-02 22:11:29 -06:00
}
func insertListboxArrayBefore ( array C . id , what string , before int ) {
2014-05-15 16:27:47 -05:00
C . listboxArrayInsertBefore ( array , toListboxItem ( what ) , C . uintptr_t ( before ) )
2014-03-02 22:11:29 -06:00
}
func deleteListboxArray ( array C . id , index int ) {
2014-05-15 16:27:47 -05:00
C . listboxArrayDelete ( array , C . uintptr_t ( index ) )
2014-03-02 22:11:29 -06:00
}
func indexListboxArray ( array C . id , index int ) string {
2014-05-15 16:27:47 -05:00
dict := C . listboxArrayItemAt ( array , C . uintptr_t ( index ) )
2014-03-02 22:11:29 -06:00
return fromListboxItem ( dict )
}
/ *
Now we have to establish the binding . To do this , we need the following things :
- the object to bind ( NSTableColumn )
- the property of the object to bind ( in this case , cell values , so the string @ "value" )
- the object to bind to ( NSArrayController )
- the "key path" of the data to get from the array controller
- any options for binding ; we won ' t have any
The key path is easy : it ' s [ the name of the list ] . [ the key to grab ] . The name of the list is arrangedObjects , as established above .
We will also need to be able to get the NSArrayController out to do things with it later ; alas , the only real way to do it without storing it separately is to get the complete information set on our binding each time ( a NSDictionary ) and extract just the bound object .
* /
const (
_listboxItemKeyPath = "arrangedObjects." + _listboxItemKey
)
var (
tableColumnBinding = toNSString ( "value" )
listboxItemKeyPath = toNSString ( _listboxItemKeyPath )
)
func bindListboxArray ( tableColumn C . id , array C . id ) {
2014-05-15 16:27:47 -05:00
C . bindListboxArray ( tableColumn , tableColumnBinding ,
array , listboxItemKeyPath )
2014-03-02 22:11:29 -06:00
}
2014-03-03 13:50:03 -06:00
func listboxArrayController ( tableColumn C . id ) C . id {
2014-05-15 16:27:47 -05:00
return C . boundListboxArray ( tableColumn , tableColumnBinding )
2014-03-02 22:11:29 -06:00
}
/ *
Now with all that done , we ' re ready to creat a table column .
Columns need string identifiers ; we ' ll just reuse the item key .
2014-03-09 10:33:05 -05:00
Editability is also handled here , as opposed to in NSTableView itself .
2014-03-02 22:11:29 -06:00
* /
func newListboxTableColumn ( ) C . id {
2014-05-15 16:27:47 -05:00
column := C . makeListboxTableColumn ( listboxItemKey )
2014-03-02 22:11:29 -06:00
bindListboxArray ( column , newListboxArray ( ) )
return column
}
func listboxTableColumn ( listbox C . id ) C . id {
2014-05-15 16:27:47 -05:00
return C . listboxTableColumn ( listbox , listboxItemKey )
2014-03-02 22:11:29 -06:00
}
2014-03-03 13:50:03 -06:00
/ *
2014-04-05 21:53:26 -05:00
The NSTableViews don ' t draw their own scrollbars ; we have to drop our NSTableViews in NSScrollViews for this . The NSScrollView is also what provides the Listbox ' s border .
2014-04-13 11:52:10 -05:00
The actual creation code was moved to objc_darwin . go .
2014-03-03 13:50:03 -06:00
* /
func newListboxScrollView ( listbox C . id ) C . id {
2014-04-13 11:52:10 -05:00
scrollview := newScrollView ( listbox )
2014-05-15 18:55:16 -05:00
C . giveScrollViewBezelBorder ( scrollview ) // this is what Interface Builder gives the scroll view
2014-03-03 13:50:03 -06:00
return scrollview
}
func listboxInScrollView ( scrollview C . id ) C . id {
2014-04-13 11:52:10 -05:00
return getScrollViewContent ( scrollview )
2014-03-03 13:50:03 -06:00
}
/ *
And now , a helper function that takes a scroll view and gets out the array .
* /
func listboxArray ( listbox C . id ) C . id {
return listboxArrayController ( listboxTableColumn ( listboxInScrollView ( listbox ) ) )
}
2014-03-02 22:11:29 -06:00
/ *
... and finally , we work with the NSTableView directly . These are the methods sysData calls .
We ' ll handle selections from the NSTableView too . The only trickery is dealing with the return value of - [ NSTableView selectedRowIndexes ] : NSIndexSet . The only way to get indices out of a NSIndexSet is to get them all out wholesale , and working with C arrays in Go is Not Fun .
* /
2014-05-10 13:59:11 -05:00
func makeListbox ( parentWindow C . id , alternate bool , s * sysData ) C . id {
2014-05-15 16:27:47 -05:00
listbox := C . makeListbox ( newListboxTableColumn ( ) , toBOOL ( alternate ) )
2014-03-03 13:50:03 -06:00
listbox = newListboxScrollView ( listbox )
2014-03-03 14:52:39 -06:00
addControl ( parentWindow , listbox )
2014-03-02 22:11:29 -06:00
return listbox
}
2014-03-02 22:28:43 -06:00
func appendListbox ( listbox C . id , what string , alternate bool ) {
2014-03-03 13:50:03 -06:00
array := listboxArray ( listbox )
2014-03-02 22:11:29 -06:00
appendListboxArray ( array , what )
}
2014-03-02 22:28:43 -06:00
func insertListboxBefore ( listbox C . id , what string , before int , alternate bool ) {
2014-03-03 13:50:03 -06:00
array := listboxArray ( listbox )
2014-03-02 22:11:29 -06:00
insertListboxArrayBefore ( array , what , before )
}
2014-05-12 01:06:05 -05:00
// technique from http://stackoverflow.com/questions/3773180/how-to-get-indexes-from-nsindexset-into-an-nsarray-in-cocoa
// we don't care that the indices were originally NSUInteger since by this point we have a problem anyway; Go programs generally use int indices anyway
// we also don't care about NSNotFound because we check the count first AND because NSIndexSet is always sorted (and NSNotFound can be a valid index if the list is large enough... since it's NSIntegerMax, not NSUIntegerMax)
2014-03-02 22:11:29 -06:00
func selectedListboxIndices ( listbox C . id ) ( list [ ] int ) {
2014-05-15 16:27:47 -05:00
indices := C . listboxSelectedRowIndexes ( listboxInScrollView ( listbox ) )
count := int ( C . listboxIndexesCount ( indices ) )
2014-03-02 22:11:29 -06:00
if count == 0 {
2014-03-02 22:28:43 -06:00
return nil
2014-03-02 22:11:29 -06:00
}
list = make ( [ ] int , count )
2014-05-15 16:27:47 -05:00
list [ 0 ] = int ( C . listboxIndexesFirst ( indices ) )
2014-05-12 01:06:05 -05:00
for i := 1 ; i < count ; i ++ {
2014-05-15 16:27:47 -05:00
list [ i ] = int ( C . listboxIndexesNext ( indices , C . uintptr_t ( list [ i - 1 ] ) ) )
2014-03-02 22:11:29 -06:00
}
2014-03-02 22:28:43 -06:00
return list
2014-03-02 22:11:29 -06:00
}
func selectedListboxTexts ( listbox C . id ) ( texts [ ] string ) {
indices := selectedListboxIndices ( listbox )
if len ( indices ) == 0 {
return nil
}
2014-03-03 13:50:03 -06:00
array := listboxArray ( listbox )
2014-03-02 22:11:29 -06:00
texts = make ( [ ] string , len ( indices ) )
for i := 0 ; i < len ( texts ) ; i ++ {
texts [ i ] = indexListboxArray ( array , indices [ i ] )
}
return texts
}
func deleteListbox ( listbox C . id , index int ) {
2014-03-03 13:50:03 -06:00
array := listboxArray ( listbox )
2014-03-02 22:11:29 -06:00
deleteListboxArray ( array , index )
}
2014-03-08 16:25:19 -06:00
func listboxLen ( listbox C . id ) int {
2014-05-15 16:27:47 -05:00
return int ( C . listboxLen ( listboxInScrollView ( listbox ) ) )
2014-03-08 16:25:19 -06:00
}
2014-04-12 21:05:34 -05:00
func selectListboxIndices ( id C . id , indices [ ] int ) {
listbox := listboxInScrollView ( id )
if len ( indices ) == 0 {
2014-05-15 16:27:47 -05:00
C . listboxDeselectAll ( listbox )
2014-04-12 21:05:34 -05:00
return
}
panic ( "selectListboxIndices() > 0 not yet implemented (TODO)" )
}