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 .
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"
2014-03-02 22:28:43 -06:00
// /* cgo doesn't like nil */
// id nilid = nil;
2014-03-02 22:11:29 -06:00
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 (
_NSMutableDictionary = objc_getClass ( "NSMutableDictionary" )
_dictionaryWithObjectForKey = sel_getUid ( "dictionaryWithObject:forKey:" )
_objectForKey = sel_getUid ( "objectForKey:" )
listboxItemKey = toNSString ( _listboxItemKey )
)
func toListboxItem ( what string ) C . id {
return C . objc_msgSend_id_id ( _NSMutableDictionary ,
_dictionaryWithObjectForKey ,
toNSString ( what ) , listboxItemKey )
}
func fromListboxItem ( dict C . id ) string {
return fromNSString ( C . objc_msgSend_id ( dict , _objectForKey , listboxItemKey ) )
}
/ *
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 .
* /
var (
_NSArrayController = objc_getClass ( "NSArrayController" )
_setAutomaticallyRearrangesObjects = sel_getUid ( "setAutomaticallyRearrangesObjects:" )
_addObject = sel_getUid ( "addObject:" )
2014-03-03 00:51:54 -06:00
_insertObjectAtArrangedObjectIndex = sel_getUid ( "insertObject:atArrangedObjectIndex:" )
_removeObjectAtArrangedObjectIndex = sel_getUid ( "removeObjectAtArrangedObjectIndex:" )
2014-03-02 22:11:29 -06:00
_arrangedObjects = sel_getUid ( "arrangedObjects" )
_objectAtIndex = sel_getUid ( "objectAtIndex:" )
)
func newListboxArray ( ) C . id {
2014-04-04 16:50:27 -05:00
array := C . objc_msgSend_noargs ( _NSArrayController , _new )
2014-03-02 22:11:29 -06:00
C . objc_msgSend_bool ( array , _setAutomaticallyRearrangesObjects , C . BOOL ( C . NO ) )
return array
}
func appendListboxArray ( array C . id , what string ) {
C . objc_msgSend_id ( array , _addObject , toListboxItem ( what ) )
}
func insertListboxArrayBefore ( array C . id , what string , before int ) {
2014-03-03 00:51:54 -06:00
C . objc_msgSend_id_uint ( array , _insertObjectAtArrangedObjectIndex ,
2014-03-02 22:28:43 -06:00
toListboxItem ( what ) , C . uintptr_t ( before ) )
2014-03-02 22:11:29 -06:00
}
func deleteListboxArray ( array C . id , index int ) {
2014-04-04 17:56:37 -05:00
C . objc_msgSend_uint ( array , _removeObjectAtArrangedObjectIndex ,
C . uintptr_t ( index ) )
2014-03-02 22:11:29 -06:00
}
func indexListboxArray ( array C . id , index int ) string {
array = C . objc_msgSend_noargs ( array , _arrangedObjects )
2014-04-04 17:56:37 -05:00
dict := C . objc_msgSend_uint ( array , _objectAtIndex , 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 (
_bindToObjectWithKeyPathOptions = sel_getUid ( "bind:toObject:withKeyPath:options:" )
_infoForBinding = sel_getUid ( "infoForBinding:" )
tableColumnBinding = toNSString ( "value" )
listboxItemKeyPath = toNSString ( _listboxItemKeyPath )
)
func bindListboxArray ( tableColumn C . id , array C . id ) {
C . objc_msgSend_id_id_id_id ( tableColumn , _bindToObjectWithKeyPathOptions ,
tableColumnBinding ,
array , listboxItemKeyPath ,
2014-03-02 22:28:43 -06:00
C . nilid ) // no options
2014-03-02 22:11:29 -06:00
}
2014-03-03 13:50:03 -06:00
func listboxArrayController ( tableColumn C . id ) C . id {
2014-03-02 22:11:29 -06:00
dict := C . objc_msgSend_id ( tableColumn , _infoForBinding , tableColumnBinding )
return C . objc_msgSend_id ( dict , _objectForKey , * C . _NSObservedObjectKey )
}
/ *
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
* /
var (
_NSTableColumn = objc_getClass ( "NSTableColumn" )
_initWithIdentifier = sel_getUid ( "initWithIdentifier:" )
2014-03-03 00:43:18 -06:00
_tableColumnWithIdentifier = sel_getUid ( "tableColumnWithIdentifier:" )
2014-04-05 14:10:02 -05:00
_dataCell = sel_getUid ( "dataCell" )
_setDataCell = sel_getUid ( "setDataCell:" )
2014-03-09 10:33:05 -05:00
// _setEditable in sysdata_darwin.go
2014-03-02 22:11:29 -06:00
)
func newListboxTableColumn ( ) C . id {
2014-04-04 19:34:35 -05:00
column := C . objc_msgSend_noargs ( _NSTableColumn , _alloc )
2014-03-02 22:11:29 -06:00
column = C . objc_msgSend_id ( column , _initWithIdentifier , listboxItemKey )
2014-03-09 10:33:05 -05:00
C . objc_msgSend_bool ( column , _setEditable , C . BOOL ( C . NO ) )
2014-04-05 14:10:02 -05:00
// to set the font for each item, we set the font of the "data cell", which is more aptly called the "cell template"
dataCell := C . objc_msgSend_noargs ( column , _dataCell )
applyStandardControlFont ( dataCell )
C . objc_msgSend_id ( column , _setDataCell , dataCell )
2014-03-02 22:11:29 -06:00
// TODO other properties?
bindListboxArray ( column , newListboxArray ( ) )
return column
}
func listboxTableColumn ( listbox C . id ) C . id {
2014-03-03 00:43:18 -06:00
return C . objc_msgSend_id ( listbox , _tableColumnWithIdentifier , 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
* /
var (
2014-04-05 21:51:01 -05:00
_setBorderType = sel_getUid ( "setBorderType:" )
2014-03-03 13:50:03 -06:00
)
func newListboxScrollView ( listbox C . id ) C . id {
2014-04-05 21:51:01 -05:00
const (
_NSBezelBorder = 2
)
2014-04-13 11:52:10 -05:00
scrollview := newScrollView ( listbox )
2014-04-05 21:51:01 -05:00
C . objc_msgSend_uint ( scrollview , _setBorderType , _NSBezelBorder ) // 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 .
* /
var (
_NSTableView = objc_getClass ( "NSTableView" )
_addTableColumn = sel_getUid ( "addTableColumn:" )
_setAllowsMultipleSelection = sel_getUid ( "setAllowsMultipleSelection:" )
_setAllowsEmptySelection = sel_getUid ( "setAllowsEmptySelection:" )
_setHeaderView = sel_getUid ( "setHeaderView:" )
_selectedRowIndexes = sel_getUid ( "selectedRowIndexes" )
2014-03-02 22:37:50 -06:00
_count = sel_getUid ( "count" )
2014-05-12 01:06:05 -05:00
_firstIndex = sel_getUid ( "firstIndex" )
_indexGreaterThanIndex = sel_getUid ( "indexGreaterThanIndex:" )
2014-03-08 16:25:19 -06:00
_numberOfRows = sel_getUid ( "numberOfRows" )
2014-04-12 21:05:34 -05:00
_deselectAll = sel_getUid ( "deselectAll:" )
2014-03-02 22:11:29 -06:00
)
2014-05-10 13:59:11 -05:00
func makeListbox ( parentWindow C . id , alternate bool , s * sysData ) C . id {
2014-04-04 19:34:35 -05:00
listbox := C . objc_msgSend_noargs ( _NSTableView , _alloc )
2014-04-04 18:51:23 -05:00
listbox = initWithDummyFrame ( listbox )
2014-03-02 22:11:29 -06:00
C . objc_msgSend_id ( listbox , _addTableColumn , newListboxTableColumn ( ) )
multi := C . BOOL ( C . NO )
if alternate {
multi = C . BOOL ( C . YES )
}
C . objc_msgSend_bool ( listbox , _setAllowsMultipleSelection , multi )
C . objc_msgSend_bool ( listbox , _setAllowsEmptySelection , C . BOOL ( C . YES ) )
2014-03-02 22:28:43 -06:00
C . objc_msgSend_id ( listbox , _setHeaderView , C . nilid )
2014-03-02 22:11:29 -06:00
// TODO others?
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-03-03 13:50:03 -06:00
indices := C . objc_msgSend_noargs ( listboxInScrollView ( listbox ) , _selectedRowIndexes )
2014-03-02 22:28:43 -06:00
count := int ( C . objc_msgSend_uintret_noargs ( indices , _count ) )
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-12 01:06:05 -05:00
list [ 0 ] = int ( C . objc_msgSend_uintret_noargs ( indices , _firstIndex ) )
for i := 1 ; i < count ; i ++ {
list [ i ] = int ( C . objc_msgSend_uintret_uint ( indices , _indexGreaterThanIndex , 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 {
return int ( C . objc_msgSend_intret_noargs ( listboxInScrollView ( listbox ) , _numberOfRows ) )
}
2014-04-12 21:05:34 -05:00
func selectListboxIndices ( id C . id , indices [ ] int ) {
listbox := listboxInScrollView ( id )
if len ( indices ) == 0 {
C . objc_msgSend_id ( listbox , _deselectAll , listbox )
return
}
panic ( "selectListboxIndices() > 0 not yet implemented (TODO)" )
}