Migrated listbox_darwin.go to using Objective-C directly instead of through the runtime. Due to linking issues on Mac OS X 10.6, I haven't made this live yet; I need to repair my 10.6 setup before I can actually test. This won't be done before midnight, so preemptive commit. The new listbox_darwin.go is lbnew.

This commit is contained in:
Pietro Gagliardi 2014-05-13 23:35:04 -04:00
parent b4cbccb402
commit 58b07a83ea
3 changed files with 447 additions and 0 deletions

285
lbnew Normal file
View File

@ -0,0 +1,285 @@
// 2 march 2014
package ui
import (
// ...
)
/*
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.
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.
PERSONAL TODO - make a post somewhere that does all this in Objective-C itself, for the benefit of the programming community.
TODO - change the name of some of these functions? specifically the functions that get data about the NSTableView?
*/
// #cgo LDFLAGS: -lobjc -framework Foundation -framework AppKit
// #include <stdlib.h>
// #include "objc_darwin.h"
// #include "listbox_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 (
_NSMutableDictionary = objc_getClass("NSMutableDictionary")
_dictionaryWithObjectForKey = sel_getUid("dictionaryWithObject:forKey:")
_objectForKey = sel_getUid("objectForKey:")
listboxItemKey = toNSString(_listboxItemKey)
)
func toListboxItem(what string) C.id {
return C.toListboxItem(listboxItemKey, toNSString(what))
}
func fromListboxItem(dict C.id) string {
return fromNSString(C.fromListboxItem(dict, 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
- (void)insertObject:(id)object atArrangedObjectIndex:(NSInteger)index
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:")
_insertObjectAtArrangedObjectIndex = sel_getUid("insertObject:atArrangedObjectIndex:")
_removeObjectAtArrangedObjectIndex = sel_getUid("removeObjectAtArrangedObjectIndex:")
_arrangedObjects = sel_getUid("arrangedObjects")
_objectAtIndex = sel_getUid("objectAtIndex:")
)
func newListboxArray() C.id {
return C.newListboxArray()
}
func appendListboxArray(array C.id, what string) {
C.listboxArrayAppend(array, toListboxItem(what))
}
func insertListboxArrayBefore(array C.id, what string, before int) {
C.listboxArrayInsertBefore(array, toListboxItem(what), C.uintptr_t(before))
}
func deleteListboxArray(array C.id, index int) {
C.listboxArrayDelete(array, C.uintptr_t(index))
}
func indexListboxArray(array C.id, index int) string {
dict := C.listboxArrayItemAt(array, C.uintptr_t(index))
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.bindListboxArray(tableColumn, tableColumnBinding,
array, listboxItemKeyPath)
}
func listboxArrayController(tableColumn C.id) C.id {
return C.boundListboxArray(tableColumn, tableColumnBinding)
}
/*
Now with all that done, we're ready to creat a table column.
Columns need string identifiers; we'll just reuse the item key.
Editability is also handled here, as opposed to in NSTableView itself.
*/
var (
_NSTableColumn = objc_getClass("NSTableColumn")
_initWithIdentifier = sel_getUid("initWithIdentifier:")
_tableColumnWithIdentifier = sel_getUid("tableColumnWithIdentifier:")
_dataCell = sel_getUid("dataCell")
_setDataCell = sel_getUid("setDataCell:")
// _setEditable in sysdata_darwin.go
)
func newListboxTableColumn() C.id {
column := C.makeListboxTableColumn(listboxItemKey)
bindListboxArray(column, newListboxArray())
return column
}
func listboxTableColumn(listbox C.id) C.id {
return C.listboxTableColumn(listbox, listboxItemKey)
}
/*
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.
The actual creation code was moved to objc_darwin.go.
*/
var (
_setBorderType = sel_getUid("setBorderType:")
)
func newListboxScrollView(listbox C.id) C.id {
const (
_NSBezelBorder = 2
)
scrollview := newScrollView(listbox)
C.objc_msgSend_uint(scrollview, _setBorderType, _NSBezelBorder) // this is what Interface Builder gives the scroll view
return scrollview
}
func listboxInScrollView(scrollview C.id) C.id {
return getScrollViewContent(scrollview)
}
/*
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)))
}
/*
...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")
_count = sel_getUid("count")
_firstIndex = sel_getUid("firstIndex")
_indexGreaterThanIndex = sel_getUid("indexGreaterThanIndex:")
_numberOfRows = sel_getUid("numberOfRows")
_deselectAll = sel_getUid("deselectAll:")
)
func makeListbox(parentWindow C.id, alternate bool, s *sysData) C.id {
listbox := C.makeListbox(newListboxTableColumn(), toBOOL(alternate))
listbox = newListboxScrollView(listbox)
addControl(parentWindow, listbox)
return listbox
}
func appendListbox(listbox C.id, what string, alternate bool) {
array := listboxArray(listbox)
appendListboxArray(array, what)
}
func insertListboxBefore(listbox C.id, what string, before int, alternate bool) {
array := listboxArray(listbox)
insertListboxArrayBefore(array, what, before)
}
// 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)
func selectedListboxIndices(listbox C.id) (list []int) {
indices := C.listboxSelectedRowIndexes(listboxInScrollView(listbox))
count := int(C.listboxIndexesCount(indices))
if count == 0 {
return nil
}
list = make([]int, count)
list[0] = int(C.listboxIndexesFirst(indices))
for i := 1; i < count; i++ {
list[i] = int(C.listboxIndexesNext(indices, C.uintptr_t(list[i - 1])))
}
return list
}
func selectedListboxTexts(listbox C.id) (texts []string) {
indices := selectedListboxIndices(listbox)
if len(indices) == 0 {
return nil
}
array := listboxArray(listbox)
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) {
array := listboxArray(listbox)
deleteListboxArray(array, index)
}
func listboxLen(listbox C.id) int {
return int(C.listboxLen(listboxInScrollView(listbox)))
}
func selectListboxIndices(id C.id, indices []int) {
listbox := listboxInScrollView(id)
if len(indices) == 0 {
C.listboxDeselectAll(listbox)
return
}
panic("selectListboxIndices() > 0 not yet implemented (TODO)")
}

20
listbox_darwin.h Normal file
View File

@ -0,0 +1,20 @@
// 13 may 2014
extern id toListboxItem(id, id);
extern id fromListboxItem(id, id);
extern id newListboxArray(void);
extern void listboxArrayAppend(id, id);
extern void listboxArrayInsertBefore(id, id, uintptr_t);
extern void listboxArrayDelete(id, uintptr_t);
extern id listboxArrayItemAt(id, uintptr_t);
extern void bindListboxArray(id, id, id, id);
extern id boundListboxArray(id, id);
extern id makeListboxTableColumn(id);
extern id listboxTableColumn(id, id);
extern id makeListbox(id, BOOL);
extern id listboxSelectedRowIndexes(id);
extern uintptr_t listboxIndexesCount(id);
extern uintptr_t listboxIndexesFirst(id);
extern uintptr_t listboxIndexesNext(id, uintptr_t);
extern intptr_t listboxLen(id);
extern void listboxDeselectAll(id);

142
listbox_darwin.m Normal file
View File

@ -0,0 +1,142 @@
// 13 may 2014
#include "objc_darwin.h"
#include "listbox_darwin.h"
#include <Foundation/NSDictionary.h>
#include <AppKit/NSArrayController.h>
#include <AppKit/NSTableColumn.h>
#include <AppKit/NSTableView.h>
#include <Foundation/NSIndexSet.h>
#define to(T, x) ((T *) (x))
#define toNSMutableDictionary(x) to(NSMutableDictionary, (x))
#define toNSArrayController(x) to(NSArrayController, (x))
#define toNSTableColumn(x) to(NSTableColumn, (x))
#define toNSTableView(x) to(NSTableView, (x))
#define toNSIndexSet(x) to(NSIndexSet, (x))
#define toNSInteger(x) ((NSInteger) (x))
#define fromNSInteger(x) ((intptr_t) (x))
#define toNSUInteger(x) ((NSUInteger) (x))
#define fromNSUInteger(x) ((uintptr_t) (x))
id toListboxItem(id key, id value)
{
return [NSMutableDictionary dictionaryWithObject:value forKey:key];
}
id fromListboxItem(id item, id key)
{
return [toNSMutableDictionary(item) objectForKey:key];
}
id newListboxArray(void)
{
NSArrayController *ac;
ac = [NSArrayController new];
[ac setAutomaticallyRearrangesObjects:NO];
return ac;
}
void listboxArrayAppend(id ac, id item)
{
[toNSArrayController(ac) addObject:item];
}
void listboxArrayInsertBefore(id ac, id item, uintptr_t before)
{
[toNSArrayController(ac) insertObject:item atArrangedObjectIndex:toNSUInteger(before)];
}
void listboxArrayDelete(id ac, uintptr_t index)
{
[toNSArrayController(ac) removeObjectAtArrangedObjectIndex:toNSUInteger(index)];
}
id listboxArrayItemAt(id ac, uintptr_t index)
{
NSArrayController *array;
array = toNSArrayController(ac);
return [[array arrangedObjects] objectAtIndex:toNSUInteger(index)];
}
void bindListboxArray(id tableColumn, id bindwhat, id ac, id keyPath)
{
[toNSTableColumn(tableColumn) bind:bindwhat
toObject:ac
withKeyPath:keyPath
options:nil]; // no options
}
id boundListboxArray(id tableColumn, id boundwhat)
{
return [[toNSTableColumn(tableColumn) infoForBinding:boundwhat]
objectForKey:NSObservedObjectKey];
}
id makeListboxTableColumn(id identifier)
{
NSTableColumn *column;
NSCell *dataCell;
column = [[NSTableColumn alloc] initWithIdentifier:identifier];
[column setEditable:NO];
// to set the font for each item, we set the font of the "data cell", which is more aptly called the "cell template"
dataCell = [column dataCell];
// TODO pull the one from sysdata_darwin.m
objc_setFont(dataCell, NSRegularControlSize);
[column setDataCell:dataCell];
// TODO other properties?
return column;
}
id listboxTableColumn(id listbox, id identifier)
{
return [toNSTableView(listbox) tableColumnWithIdentifier:identifier];
}
id makeListbox(id tableColumn, BOOL multisel)
{
NSTableView *listbox;
listbox = [[NSTableView alloc]
initWithFrame:NSMakeRect(0, 0, 100, 100)];
[listbox addTableColumn:tableColumn];
[listbox setAllowsMultipleSelection:multisel];
[listbox setAllowsEmptySelection:YES];
[listbox setHeaderView:nil];
// TODO other prperties?
return listbox;
}
id listboxSelectedRowIndexes(id listbox)
{
return [toNSTableView(listbox) selectedRowIndexes];
}
uintptr_t listboxIndexesCount(id indexes)
{
return fromNSUInteger([toNSIndexSet(indexes) count]);
}
uintptr_t listboxIndexesFirst(id indexes)
{
return fromNSUInteger([toNSIndexSet(indexes) firstIndex]);
}
uintptr_t listboxIndexesNext(id indexes, uintptr_t prev)
{
return fromNSUInteger([toNSIndexSet(indexes) indexGreaterThanIndex:toNSUInteger(prev)]);
}
intptr_t listboxLen(id listbox)
{
return fromNSInteger([toNSTableView(listbox) numberOfRows]);
}
void listboxDeselectAll(id listbox)
{
[toNSTableView(listbox) deselectAll:listbox];
}