#lang racket/base (require ffi/unsafe ffi/unsafe/define racket/class (only-in racket/list take drop) "../../syntax.rkt" "../../lock.rkt" "item.rkt" "utils.rkt" "types.rkt" "window.rkt" "const.rkt" "panel.rkt" "../common/event.rkt") (provide (protect-out list-box%)) ;; ---------------------------------------- (define-cstruct _GtkTreeIter ([stamp _int] [user_data _pointer] [user_data2 _pointer] [user_data3 _pointer])) (define _GtkListStore (_cpointer 'GtkListStore)) (define _GtkCellRenderer (_cpointer 'GtkCellRenderer)) (define _GtkTreeViewColumn _GtkWidget) ; (_cpointer 'GtkTreeViewColumn) (define GTK_SELECTION_SINGLE 1) (define GTK_SELECTION_MULTIPLE 3) (define GTK_TREE_VIEW_COLUMN_AUTOSIZE 1) (define GTK_TREE_VIEW_COLUMN_FIXED 2) (define-gtk gtk_scrolled_window_new (_fun _pointer _pointer -> _GtkWidget)) (define-gtk gtk_scrolled_window_set_policy (_fun _GtkWidget _int _int -> _void)) (define-gtk gtk_list_store_newv (_fun _int (_list i _long) -> _GtkListStore)) (define-gtk gtk_list_store_clear (_fun _GtkListStore -> _void)) (define-gtk gtk_list_store_append (_fun _GtkListStore _GtkTreeIter-pointer _pointer -> _void)) (define-gtk gtk_list_store_set (_fun _GtkListStore _GtkTreeIter-pointer _int _string _int -> _void)) (define-gtk gtk_tree_view_new_with_model (_fun _GtkListStore -> _GtkWidget)) (define-gtk gtk_tree_view_set_model (_fun _GtkWidget _GtkListStore -> _void)) (define-gtk gtk_tree_view_set_headers_visible (_fun _GtkWidget _gboolean -> _void)) (define-gtk gtk_cell_renderer_text_new (_fun -> _GtkCellRenderer)) (define-gtk gtk_tree_view_column_new_with_attributes (_fun _string _GtkCellRenderer _string _int _pointer -> _GtkTreeViewColumn)) (define-gtk gtk_tree_view_column_set_attributes (_fun _GtkTreeViewColumn _GtkCellRenderer _string _int _pointer -> _void)) (define-gtk gtk_tree_view_column_set_resizable (_fun _GtkTreeViewColumn _gboolean -> _void)) (define-gtk gtk_tree_view_column_set_clickable (_fun _GtkTreeViewColumn _gboolean -> _void)) (define-gtk gtk_tree_view_column_set_reorderable (_fun _GtkTreeViewColumn _gboolean -> _void)) (define-gtk gtk_tree_view_append_column (_fun _GtkWidget _GtkTreeViewColumn -> _void)) (define-gtk gtk_tree_view_remove_column (_fun _GtkWidget _GtkTreeViewColumn -> _void)) (define-gtk gtk_tree_view_get_selection (_fun _GtkWidget -> _GtkWidget)) (define-gtk gtk_tree_selection_set_mode (_fun _GtkWidget _int -> _void)) (define-gtk gtk_list_store_remove (_fun _GtkListStore _GtkTreeIter-pointer -> _gboolean)) (define-gtk gtk_tree_model_get_iter (_fun _GtkListStore _GtkTreeIter-pointer _pointer -> _gboolean)) (define-gtk gtk_tree_view_scroll_to_cell (_fun _GtkWidget _pointer _pointer _gboolean _gfloat _gfloat -> _void)) (define-gtk gtk_tree_view_get_column (_fun _GtkWidget _int -> _GtkTreeViewColumn)) (define-gtk gtk_tree_view_move_column_after (_fun _GtkWidget _GtkTreeViewColumn (_or-null _GtkTreeViewColumn) -> _void)) (define-gtk gtk_tree_view_column_set_title (_fun _GtkTreeViewColumn _string -> _void)) (define-gtk gtk_tree_view_column_set_sizing (_fun _GtkTreeViewColumn _int -> _void)) (define-gtk gtk_tree_view_column_get_width (_fun _GtkTreeViewColumn -> _int)) (define-gtk gtk_tree_view_column_get_min_width (_fun _GtkTreeViewColumn -> _int)) (define-gtk gtk_tree_view_column_get_max_width (_fun _GtkTreeViewColumn -> _int)) (define-gtk gtk_tree_view_column_set_fixed_width (_fun _GtkTreeViewColumn _int -> _void)) (define-gtk gtk_tree_view_column_set_min_width (_fun _GtkTreeViewColumn _int -> _void)) (define-gtk gtk_tree_view_column_set_max_width (_fun _GtkTreeViewColumn _int -> _void)) (define _GList (_cpointer 'List)) (define-glib g_list_foreach (_fun _GList (_fun _pointer -> _void) _pointer -> _void)) (define-glib g_list_free (_fun _GList -> _void)) (define-gtk gtk_tree_selection_get_selected_rows (_fun _GtkWidget _pointer -> (_or-null _GList))) (define-gtk gtk_tree_selection_path_is_selected (_fun _GtkWidget _pointer -> _gboolean)) (define-gtk gtk_tree_selection_unselect_all (_fun _GtkWidget -> _void)) (define-gtk gtk_tree_selection_select_path (_fun _GtkWidget _pointer -> _void)) (define-gtk gtk_tree_selection_unselect_path (_fun _GtkWidget _pointer -> _void)) (define-gtk gtk_tree_path_new_from_indices (_fun _int _int -> _pointer)) (define-gtk gtk_tree_path_free (_fun _pointer -> _void)) (define-gtk gtk_tree_path_get_indices (_fun _pointer -> _pointer)) (define-gtk gtk_tree_view_get_visible_range (_fun _GtkWidget [sp : (_ptr o _pointer)] [ep : (_ptr o _pointer)] -> [ok? : _gboolean] -> (values (if ok? sp #f) (if ok? ep #f)))) (define-signal-handler connect-changed "changed" (_fun _GtkWidget -> _void) (lambda (gtk) (let ([wx (gtk->wx gtk)]) (when wx (send wx queue-changed))))) (define-signal-handler connect-clicked "clicked" (_fun _GtkWidget -> _void) (lambda (gtk) (let ([wx (gtk->wx gtk)]) (when wx (send wx column-clicked gtk))))) (define-signal-handler connect-activated "row-activated" (_fun _GtkWidget _pointer _pointer -> _void) (lambda (gtk path column) (let ([wx (gtk->wx gtk)]) (when wx (send wx queue-activated))))) (defclass list-box% item% (init parent cb label kind x y w h choices style font label-font columns column-order) (inherit get-gtk set-auto-size is-window-enabled?) (define empty-columns (for/list ([l (in-list (cdr columns))]) "")) (define itemss (for/list ([i (in-list choices)]) (cons i empty-columns))) (define data (map (lambda (c) (box #f)) choices)) (define (make-store count) (as-gobject-allocation (gtk_list_store_newv count (for/list ([i (in-range count)]) G_TYPE_STRING)))) (define store (make-store (length columns))) (define (reset-content) (let ([iter (make-GtkTreeIter 0 #f #f #f)]) (for ([items (in-list itemss)]) (gtk_list_store_append store iter #f) (for ([item (in-list items)] [col (in-naturals 0)]) (gtk_list_store_set store iter col item -1)))) (maybe-init-select)) (define/private (maybe-init-select) ;; For consistency with other platforms, ;; don't try to select an item initially. (when #f (when (and (= (get-selection) -1) (pair? data)) (set-selection 0)))) (define-values (gtk scrolled-gtk) (cond [gtk3? ;; See `panel%` for information on why an extra ;; event-box layer is needed here. (define gtk (as-gtk-allocation (gtk_event_box_new))) (define scrolled-gtk (gtk_scrolled_window_new #f #f)) (gtk_container_add gtk scrolled-gtk) (gtk_widget_show scrolled-gtk) (values gtk scrolled-gtk)] [else (define scrolled-gtk (as-gtk-allocation (gtk_scrolled_window_new #f #f))) (values scrolled-gtk scrolled-gtk)])) (gtk_scrolled_window_set_policy scrolled-gtk GTK_POLICY_AUTOMATIC GTK_POLICY_ALWAYS) (define headers? (memq 'column-headers style)) (define click-headers? (and headers? (memq 'clickable-headers style))) (define reorder-headers? (and headers? (memq 'reorderable-headers style))) (define renderer (gtk_cell_renderer_text_new)) (define/private (make-column label col) (let* ([column (gtk_tree_view_column_new_with_attributes label renderer "text" col #f)]) (when headers? (gtk_tree_view_column_set_resizable column #t) (gtk_tree_view_column_set_min_width column 1) (when click-headers? (gtk_tree_view_column_set_clickable column #t)) (when reorder-headers? (gtk_tree_view_column_set_reorderable column #t))) column)) (define-values (client-gtk column-gtks) (atomically (let* ([client-gtk (gtk_tree_view_new_with_model store)] [columns (for/list ([label (in-list columns)] [col (in-naturals)]) (make-column label col))]) (gobject-unref store) (unless headers? (gtk_tree_view_set_headers_visible client-gtk #f)) (for ([column (in-list columns)]) (gtk_tree_view_append_column client-gtk column)) (values client-gtk columns)))) (when column-order (set-column-order column-order)) (define/public (set-column-order column-order) (let loop ([prev #f] [l column-order]) (unless (null? l) (let ([column-gtk (list-ref column-gtks (car l))]) (gtk_tree_view_move_column_after client-gtk column-gtk prev) (loop column-gtk (cdr l)))))) (gtk_container_add scrolled-gtk client-gtk) (gtk_widget_show client-gtk) (define selection (gtk_tree_view_get_selection client-gtk)) (gtk_tree_selection_set_mode selection (if (or (eq? kind 'extended) (eq? kind 'multiple)) GTK_SELECTION_MULTIPLE GTK_SELECTION_SINGLE)) (super-new [parent parent] [gtk gtk] [extra-gtks (list* client-gtk selection (if (memq 'clickable-headers style) column-gtks null))] [callback cb] [font font] [no-show? (memq 'deleted style)]) (set-auto-size 32) ; 32 is extra width (connect-changed selection) (connect-activated client-gtk) (for ([column (in-list column-gtks)]) (column-finish column)) (define/private (column-finish column) (connect-clicked column) (let ([w (gtk_tree_view_column_get_width column)]) (gtk_tree_view_column_set_sizing column GTK_TREE_VIEW_COLUMN_FIXED) (gtk_tree_view_column_set_fixed_width column (max 50 w)))) (define/override (get-client-gtk) client-gtk) (define callback cb) (define ignore-click? #f) (define/private (do-queue-changed type) ;; Called from event-handling thread (unless ignore-click? (queue-window-event this (lambda () (unless (null? itemss) (callback this (new control-event% [event-type type] [time-stamp (current-milliseconds)]))))))) (define/public (queue-changed) (do-queue-changed 'list-box)) (define/public (queue-activated) (do-queue-changed 'list-box-dclick)) (define/private (column->pos col) (let loop ([l column-gtks] [pos 0]) (cond [(null? l) #f] [(ptr-equal? (car l) col) pos] [else (loop (cdr l) (add1 pos))]))) (define/public (column-clicked col) (let ([pos (column->pos col)]) (when pos (queue-window-event this (lambda () (callback this (new column-control-event% [event-type 'list-box-column] [column pos] [time-stamp (current-milliseconds)]))))))) (define/public (get-column-order) (for/list ([i (in-range (length column-gtks))]) (column->pos (gtk_tree_view_get_column client-gtk i)))) (define/private (get-iter i) (atomically (let ([iter (make-GtkTreeIter 0 #f #f #f)] [p (gtk_tree_path_new_from_indices i -1)]) (gtk_tree_model_get_iter store iter p) (gtk_tree_path_free p) iter))) (def/public-unimplemented get-label-font) (define/private (replace-nth items i s) (append (take items i) (list s) (drop items (add1 i)))) (define/public (set-string i s [col 0]) (set! itemss (replace-nth itemss i (replace-nth (list-ref itemss i) col s))) (gtk_list_store_set store (get-iter i) col s -1)) (define/public (set-column-label i s) (gtk_tree_view_column_set_title (list-ref column-gtks i) s)) (define/public (set-column-size i w mn mx) (let ([col (list-ref column-gtks i)]) (gtk_tree_view_column_set_min_width col mn) (gtk_tree_view_column_set_max_width col mx) (gtk_tree_view_column_set_fixed_width col w))) (define/public (get-column-size i) (let ([col (list-ref column-gtks i)]) (values (gtk_tree_view_column_get_width col) (max (gtk_tree_view_column_get_min_width col) 0) (let ([v (gtk_tree_view_column_get_max_width col)]) (if (negative? v) 10000 v))))) (define/public (set-first-visible-item i) (atomically (let ([p (gtk_tree_path_new_from_indices i -1)]) (gtk_tree_view_scroll_to_cell client-gtk p #f #t 0.0 0.0) (gtk_tree_path_free p)))) (define/public (set choices . more-choices) (atomically (set! ignore-click? #t) (clear) (set! itemss (apply map (lambda (i . rest) (cons i rest)) choices more-choices)) (set! data (map (lambda (x) (box #f)) choices)) (reset-content) (set! ignore-click? #f))) (define/public (get-selections) (atomically (let ([list (gtk_tree_selection_get_selected_rows selection #f)]) (if list (let ([v null]) (g_list_foreach list (lambda (t) (set! v (cons (ptr-ref (gtk_tree_path_get_indices t) _int) v))) #f) (g_list_foreach list gtk_tree_path_free #f) (g_list_free list) (reverse v)) null)))) (define/public (get-selection) (let ([l (get-selections)]) (if (null? l) -1 (car l)))) (define/private (get-visible-range) (atomically (let-values ([(sp ep) (gtk_tree_view_get_visible_range client-gtk)]) (begin0 (values (if sp (ptr-ref (gtk_tree_path_get_indices sp) _int) 0) (if ep (ptr-ref (gtk_tree_path_get_indices ep) _int) 0)) (when sp (gtk_tree_path_free sp)) (when ep (gtk_tree_path_free ep)))))) (define/public (get-first-item) (let-values ([(start end) (get-visible-range)]) start)) (define/public (number-of-visible-items) (let-values ([(start end) (get-visible-range)]) (add1 (- end start)))) (define/public (number) (length itemss)) (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) (atomically (let ([p (gtk_tree_path_new_from_indices i -1)]) (begin0 (gtk_tree_selection_path_is_selected selection p) (gtk_tree_path_free p))))) (define/public (select i [on? #t] [extend? #t]) (atomically (set! ignore-click? #t) (let ([p (gtk_tree_path_new_from_indices i -1)]) (if on? (begin (unless extend? (gtk_tree_selection_unselect_all selection)) (gtk_tree_selection_select_path selection p)) (gtk_tree_selection_unselect_path selection p)) (gtk_tree_path_free p)) (set! ignore-click? #f))) (define/public (set-selection i) (select i #t #f)) (define/public (delete i) (set! itemss (append (take itemss i) (drop itemss (add1 i)))) (set! data (append (take data i) (drop data (add1 i)))) (gtk_list_store_remove store (get-iter i)) (void)) (define/public (clear) (set! itemss null) (set! data null) (gtk_list_store_clear store)) (public [append* append]) (define (append* s [v #f]) (atomically (set! ignore-click? #t) (set! itemss (append itemss (list (cons s empty-columns)))) (set! data (append data (list (box v)))) (let ([iter (make-GtkTreeIter 0 #f #f #f)]) (gtk_list_store_append store iter #f) (gtk_list_store_set store iter 0 s -1)) (maybe-init-select) (set! ignore-click? #f))) (define/public (append-column label) (let ([col (add1 (length empty-columns))]) (set! store (make-store (add1 col))) (set! empty-columns (cons "" empty-columns)) (set! itemss (for/list ([items (in-list itemss)]) (append items (list "")))) (gtk_tree_view_set_model client-gtk store) (let ([renderer (gtk_cell_renderer_text_new)]) (gtk_tree_view_column_new_with_attributes label renderer "text" col #f)) (let ([column-gtk (make-column label col)]) (g_object_set_data column-gtk "wx" (g_object_get_data client-gtk "wx")) (set! column-gtks (append column-gtks (list column-gtk))) (gtk_tree_view_append_column client-gtk column-gtk) (reset-content) (column-finish column-gtk)))) (define/public (delete-column i) (define (remove-nth l i) (cond [(zero? i) (cdr l)] [else (cons (car l) (remove-nth (cdr l) (sub1 i)))])) (set! empty-columns (cdr empty-columns)) (set! itemss (for/list ([items (in-list itemss)]) (remove-nth items i))) (let ([old (list-ref column-gtks i)]) (set! column-gtks (remove-nth column-gtks i)) (gtk_tree_view_remove_column client-gtk old)) (for ([column-gtk (in-list column-gtks)] [pos (in-naturals)]) (when (pos . >= . i) (gtk_tree_view_column_set_attributes column-gtk renderer "text" pos #f))) (gtk_list_store_clear store) (reset-content)) (atomically (reset-content)))