gui/gui-lib/mred/private/wx/cocoa/list-box.rkt
2016-01-08 07:58:53 -07:00

381 lines
14 KiB
Racket

#lang racket/base
(require ffi/unsafe/objc
ffi/unsafe
racket/class
(only-in racket/list take drop)
"../../syntax.rkt"
"../../lock.rkt"
"item.rkt"
"utils.rkt"
"types.rkt"
"const.rkt"
"window.rkt"
"font.rkt"
"../common/event.rkt")
(provide
(protect-out list-box%))
;; ----------------------------------------
(import-class NSScrollView NSTableView NSTableColumn NSCell NSIndexSet)
(import-protocol NSTableViewDataSource)
(define NSLineBreakByTruncatingTail 4)
(define-objc-class RacketTableView NSTableView
#:mixins (FocusResponder KeyMouseResponder CursorDisplayer)
[wxb]
[-a _id (preparedCellAtColumn: [_NSInteger column] row: [_NSInteger row])
(let ([wx (->wx wxb)])
(tell
(let ([c (tell (tell NSCell alloc) initTextCell: #:type _NSString
(if wx (send wx get-cell column row) "???"))]
[font (and wx (send wx get-cell-font))])
(tellv c setLineBreakMode: #:type _NSUInteger NSLineBreakByTruncatingTail)
(when font
(tellv c setFont: font))
c)
autorelease))]
[-a _void (doubleClicked: [_id sender])
(queue-window*-event wxb (lambda (wx) (send wx clicked 'list-box-dclick)))]
[-a _void (tableViewSelectionDidChange: [_id aNotification])
(queue-window*-event wxb (lambda (wx) (send wx clicked 'list-box)))]
[-a _void (tableView: [_id view] didClickTableColumn: [_id col])
(queue-window*-event wxb (lambda (wx) (send wx clicked-column col)))]
[-a _void (tableViewColumnDidMove: [_id view])
(let ([wx (->wx wxb)])
(when wx (send wx reset-column-order)))])
(define-objc-class RacketDataSource NSObject
#:protocols (NSTableViewDataSource)
[wxb]
[-a _NSInteger (numberOfRowsInTableView: [_id view])
(let ([wx (->wx wxb)])
(send wx number))]
[-a _NSString (tableView: [_id aTableView]
objectValueForTableColumn: [_id aTableColumn]
row: [_NSInteger rowIndex])
(let ([wx (->wx wxb)])
(if wx
(send wx get-cell aTableColumn rowIndex)
"???"))])
(define (remove-nth data i)
(cond
[(zero? i) (cdr data)]
[else (cons (car data) (remove-nth (cdr data) (sub1 i)))]))
(defclass list-box% item%
(init parent cb
label kind x y w h
choices style
font label-font
columns column-order)
(inherit set-size init-font
register-as-child)
(define source (as-objc-allocation
(tell (tell RacketDataSource alloc) init)))
(set-ivar! source wxb (->wxb this))
(define itemss (cons choices
(for/list ([i (in-list (cdr columns))])
(for/list ([i choices])
""))))
(define num-columns (length columns))
(define data (map (lambda (x) (box #f)) choices))
(define count (length choices))
(define cocoa (as-objc-allocation
(tell (tell NSScrollView alloc) init)))
(define-values (content-cocoa column-cocoas)
(let ([content-cocoa
(as-objc-allocation
(tell (tell RacketTableView alloc) init))])
(tellv content-cocoa setDelegate: content-cocoa)
(tellv content-cocoa setDataSource: source)
(define cols
(for/list ([title (in-list columns)])
(let ([col (as-objc-allocation
(tell (tell NSTableColumn alloc) initWithIdentifier: #:type _NSString title))])
(tellv content-cocoa addTableColumn: col)
(tellv (tell col headerCell) setStringValue: #:type _NSString title)
col)))
(init-font content-cocoa font)
(values content-cocoa cols)))
(set-ivar! content-cocoa wxb (->wxb this))
(tellv cocoa setDocumentView: content-cocoa)
(tellv cocoa setHasVerticalScroller: #:type _BOOL #t)
(tellv cocoa setHasHorizontalScroller: #:type _BOOL #t)
(unless (memq 'column-headers style)
(tellv content-cocoa setHeaderView: #f))
(define allow-multi? (not (eq? kind 'single)))
(when allow-multi?
(tellv content-cocoa setAllowsMultipleSelection: #:type _BOOL #t))
(unless (memq 'reorderable-headers style)
(tellv content-cocoa setAllowsColumnReordering: #:type _BOOL #f))
(when column-order
(set-column-order column-order))
(define/public (set-column-order column-order)
(atomically
(for ([c (in-list column-cocoas)])
(tellv c retain)
(tellv content-cocoa removeTableColumn: c))
(for ([pos (in-list column-order)])
(let ([c (list-ref column-cocoas pos)])
(tellv content-cocoa addTableColumn: c)
(tellv c release)))
(reset-column-order)))
(define/public (set-column-label i s)
(let ([col (list-ref column-cocoas i)])
(tellv (tell col headerCell) setStringValue: #:type _NSString s)
(reset)))
(define/public (set-column-size i w min-w max-w)
(let ([col (list-ref column-cocoas i)])
(tellv col setMinWidth: #:type _CGFloat min-w)
(tellv col setMaxWidth: #:type _CGFloat max-w)
(tellv col setWidth: #:type _CGFloat w)))
(define/public (get-column-size i)
(let ([col (list-ref column-cocoas i)]
[int (lambda (v) (inexact->exact (round v)))])
(values
(int (tell #:type _CGFloat col width))
(int (tell #:type _CGFloat col minWidth))
(min 10000 (int (tell #:type _CGFloat col maxWidth))))))
(define/override (get-cocoa-content) content-cocoa)
(define/override (get-cocoa-control) content-cocoa)
(super-new [parent parent]
[cocoa cocoa]
[no-show? (memq 'deleted style)]
[callback cb])
(set-size 0 0 32 50)
; (tellv content-cocoa sizeToFit)
(tellv content-cocoa setTarget: content-cocoa)
(tellv content-cocoa setDoubleAction: #:type _SEL (selector doubleClicked:))
(def/public-unimplemented get-label-font)
(define cell-font (and font (font->NSFont font)))
(when cell-font
(tellv content-cocoa setRowHeight: #:type _CGFloat
(+ (tell #:type _CGFloat cell-font defaultLineHeightForFont) 2)))
(define/public (get-cell-font)
cell-font)
(define/public (get-selection)
(if allow-multi?
(let ([l (get-selections)])
(if (null? l)
-1
(car l)))
(tell #:type _NSInteger content-cocoa selectedRow)))
(define/public (get-selections)
(atomically
(with-autorelease
(let ([v (tell content-cocoa selectedRowIndexes)])
(begin0
(let loop ([i (tell #:type _NSInteger v firstIndex)])
(cond
[(= i NSNotFound) null]
[else (cons i (loop (tell #:type _NSInteger v
indexGreaterThanIndex: #:type _NSInteger i)))])))))))
(define/private (header-height)
(let ([hv (tell content-cocoa headerView)])
(if hv
(NSSize-height (NSRect-size (tell #:type _NSRect hv frame)))
0)))
(define/public (number-of-visible-items)
(define doc (tell #:type _NSRect cocoa documentVisibleRect))
(define h (+ (tell #:type _CGFloat content-cocoa rowHeight)
(NSSize-height (tell #:type _NSSize content-cocoa intercellSpacing))))
(define doc-h (- (NSSize-height (NSRect-size doc))
(header-height)))
(define n (floor (/ doc-h h)))
(if (rational? n)
(max 1 (inexact->exact n))
1))
(define/public (get-first-item)
(define doc (tell #:type _NSRect cocoa documentVisibleRect))
(define h (header-height))
(NSRange-location (tell #:type _NSRange content-cocoa
rowsInRect: #:type _NSRect
(if (zero? h)
doc
(make-NSRect (NSRect-origin doc)
(make-NSSize (NSSize-width (NSRect-size doc))
(- (NSSize-height (NSRect-size doc)) h)))))))
(define/public (set-first-visible-item i)
(define num-vis (number-of-visible-items))
(define start (max 0 (min i (- count num-vis))))
(tellv content-cocoa scrollRowToVisible: #:type _NSInteger start)
(tellv content-cocoa scrollRowToVisible: #:type _NSInteger (+ start (sub1 num-vis))))
(define/private (replace items i s)
(append (take items i)
(list s)
(drop items (add1 i))))
(define/public (set-string i s [col 0])
(let ([new-itemss (replace
itemss
col
(replace (list-ref itemss col)
i
s))])
(set! itemss new-itemss))
(reset))
(define/public (number)
;; Can be called by event-handling thread
count)
(define/public (get-cell col n)
;; Can be called by event-handling thread
(let ([col (if (number? col)
(order->number col)
(col->number col))])
(if (col . > . num-columns) ; can happen as column is deleted
""
(list-ref (list-ref itemss col) n))))
(define/private (col->number col)
(let loop ([l column-cocoas] [pos 0])
(cond
[(null? l) #f]
[(ptr-equal? (car l) col) pos]
[else (loop (cdr l) (add1 pos))])))
;; When columns are rearranged, we have to be able to map
;; from current column numbers to original column numbers
(define order-vector #f)
(define/private (order->number col)
(prep-order-vector)
(vector-ref order-vector col))
(define/private (prep-order-vector)
(unless order-vector
(let ([vec (make-vector (length column-cocoas))])
(let ([array (tell content-cocoa tableColumns)])
(for/list ([i (in-range (tell #:type _NSUInteger array count))])
(let ([col (tell array objectAtIndex: #:type _NSUInteger i)])
(vector-set! vec i (col->number col)))))
(set! order-vector vec))))
(define/public (reset-column-order)
(set! order-vector #f))
(define/public (get-column-order)
(prep-order-vector)
(vector->list order-vector))
(define/public (append-column title)
(atomically
(let ([col (as-objc-allocation
(tell (tell NSTableColumn alloc) initWithIdentifier: #:type _NSString title))])
(tellv content-cocoa addTableColumn: col)
(tellv (tell col headerCell) setStringValue: #:type _NSString title)
(set! column-cocoas (append column-cocoas (list col)))
(set! itemss (append itemss
(list (for/list ([i (in-list (car itemss))])
""))))
(set! num-columns (add1 num-columns))
(reset-column-order)))
(reset))
(define/public (delete-column i)
(atomically
(let ([c (list-ref column-cocoas i)])
(define (drop-nth l i)
(cond
[(zero? i) (cdr l)]
[else (cons (car l) (drop-nth (cdr l) (sub1 i)))]))
(set! num-columns (sub1 num-columns))
(tellv content-cocoa removeTableColumn: c)
(set! column-cocoas (drop-nth column-cocoas i))
(set! itemss (drop-nth itemss i))
(reset-column-order)))
(reset))
(define callback cb)
(define/public (clicked event-type)
(unless (zero? count)
(callback this (new control-event%
[event-type event-type]
[time-stamp (current-milliseconds)]))))
(define can-click-column? (memq 'clickable-headers style))
(define/public (clicked-column col)
(when can-click-column?
(let ([pos (col->number col)])
(callback this (new column-control-event%
[event-type 'list-box-column]
[time-stamp (current-milliseconds)]
[column pos])))))
(define/public (set-data i v) (set-box! (list-ref data i) v))
(define/public (get-data i) (unbox (list-ref data i)))
(define/public (selected? i)
(tell #:type _BOOL content-cocoa isRowSelected: #:type _NSInteger i))
(define/public (select i [on? #t] [extend? #t])
(if on?
(atomically
(with-autorelease
(let ([index (tell (tell NSIndexSet alloc) initWithIndex: #:type _NSUInteger i)])
(tellv content-cocoa
selectRowIndexes: index
byExtendingSelection: #:type _BOOL (and extend? allow-multi?)))))
(tellv content-cocoa deselectRow: #:type _NSInteger i)))
(define/public (set-selection i)
(select i #t #f))
(define/public (delete i)
(atomically
(set! count (sub1 count))
(set! itemss (for/list ([items (in-list itemss)])
(remove-nth items i)))
(set! data (remove-nth data i)))
(reset))
(define/public (clear)
(atomically
(set! count 0)
(set! itemss (for/list ([items (in-list itemss)])
null))
(set! data null))
(reset))
(define/public (set choices . more-choices)
(atomically
(set! itemss (cons choices more-choices))
(set! data (map (lambda (x) (box #f)) choices))
(set! count (length choices)))
(reset))
(public [append* append])
(define (append* s [v #f])
(atomically
(set! count (add1 count))
(set! itemss (cons (append (car itemss) (list s))
(for/list ([items (in-list (cdr itemss))])
(append items (list "")))))
(set! data (append data (list (box v)))))
(reset))
(define/public (reset)
(tellv content-cocoa noteNumberOfRowsChanged)
(tellv content-cocoa reloadData))
(define/override (maybe-register-as-child parent on?)
(register-as-child parent on?)))