[plt-scheme] Help with unfinished multi-column listbox

From: Erich Rast (erich at snafu.de)
Date: Sun Sep 17 11:23:35 EDT 2006

I'm stuck with a multi-column listbox and would appreciate any help.  
I've put the sample code below as text, it should run in the module  
language if pasted into DrScheme. Note how the box dynamically  
resizes nicely. The headers will be improved later and I intend to  
add automatic sorting and lots of fancy options.

Right now, I can't figure out  how to visually display the selection  
of a whole line when one embedded editor is selected in display mode.  
The main classes are multi-list-box-cell% for a cell in the listbox  
that can be inline edited by alt-clicking into it, multi-list-box- 
heading% for the heading cells, and multi-list-box% for the listbox  
embedding and managing these. Look for (display 'selection) in the  
selection-callback at the end of the definition of multi-list-box%.  
Based on a multi-list-cell% given to the selection callback, I need  
to find all editors in the same line and color them or use some way  
how to highlight the *whole line* including all editors in it--not  
just the text in the editors, and also without scrolling the list.

I'm totally lost in the MrEd jungle. Does anyone have a suggestion?

Best regards,

Erich

---------- sample code below, run with (test)   
-------------------------------------------------------------
---------- ignore redundant lets, etc., as I've stripped away some  
code ----------------------------

(module guisample mzscheme
   (provide get-main-frame main-frame% test)
   (require (lib "etc.ss")
            (lib "class.ss")
            (lib "mred.ss" "mred")
            (prefix srfi43- (lib "43.ss" "srfi")))

   ;;; a few helper functions (originally from other modules)

   (define-syntax dotimes
     (syntax-rules ()
       ((_ (var n res) . body)
        (do ((limit n)
             (var 0 (+ var 1)))
          ((>= var limit) res)
          . body))
       ((_ (var n) . body)
        (do ((limit n)
             (var 0 (+ var 1)))
          ((>= var limit))
          . body))))

     (define-syntax repeat-times
     (syntax-rules ()
       ((_ number expr ...)
        (let loop ([i number])
          (when (> i 0)
            (begin
              expr ...
              (loop (- i 1))))))))

   (define-syntax with
     (syntax-rules ()
       ((_ name bind expr ...)
        (let ((name bind))
          expr ...))))

   (define (raise-empty-vector-error s v)
     (raise-mismatch-error s "expected-non-empty-vector-of-numbers,  
given empty vector " v))

   (define (vector-sum v)
     (if (srfi43-vector-empty? v)
         (raise-empty-vector-error 'vector-sum v)
         (srfi43-vector-fold
          (lambda (idx sum elem) (+ sum elem))
          0 v)))

   (define (vector-min v)
     (let ((result
            (srfi43-vector-fold
             (lambda (idx minimum elem)
               (if minimum
                   (if (< elem minimum)
                       elem
                       minimum)
                   elem))
             #f v)))
       (if result
           result
           (raise-empty-vector-error 'vector-min v))))

   (define (vector-max v)
     (let ((result
            (srfi43-vector-fold
             (lambda (idx minimum elem)
               (if minimum
                   (if (> elem minimum)
                       elem
                       minimum)
                   elem))
             #f v)))
       (if result
           result
           (raise-empty-vector-error 'vector-max v))))

   ;;; HELPER CLASSES

   (define display/edit-text%
     (class* text% ()
       (define mode 'display)

       (init-field (parent #f))

       (init-field (selection-callback void))
       (inherit select-all)

       (define private-lock? #f)

       (define/augment (after-set-position)
         (unless (send this has-private-lock?)
           (begin (send this private-lock #t)
                  (selection-callback this)))
         (send this private-lock #f))

       (define/public (set-selection-callback proc)
         (set! selection-callback proc))

       (define/public (get-parent)
         parent)

       (define/public (display-init)
         (display-update))


       (define/public private-lock
         (case-lambda
           [() (send this has-private-lock?)]
           [(bool) (set! private-lock? bool)]))

       (define/public (has-private-lock?)
         private-lock?)

       (define/public (display-update)
         (send this begin-edit-sequence)
         (set-suitable-cursor)
         (set-suitable-caret)
         (set-suitable-selection)
         (send this end-edit-sequence)
         )

       (define/override (on-focus on?)
         (unless on?
           (display-mode 'display)))

       (define/overment (on-default-event evt)
         (cond ((send evt get-left-down)
                (cond ((send evt get-alt-down)
                       (send this display-mode 'edit)
                       (super on-default-event evt))
                      (else (when (send this has-display-mode? 'display)
                              (let ((parent (get-parent))) (when  
parent (send parent on-display-mode-event evt this)))))))
               (else
                (when (send this has-display-mode? 'display)
                  (let ((parent (get-parent))) (when parent (send  
parent on-display-mode-event evt this)))))))

       (define (set-suitable-cursor)
         (case mode
           ((display) (send this set-cursor (make-object cursor%  
'arrow)))
           ((edit) (send this set-cursor (make-object cursor%  
'ibeam)))))

       (define (set-suitable-caret)
         (case mode
           ((display) (send this hide-caret #t))
           ((edit) (send this hide-caret #f))))

       (define (set-suitable-selection)
         (case mode
           ((display) (send this flash-off))
           ((edit) (send this flash-on 0 (send this last-position) #f  
0))))

       (define/public display-mode
         (case-lambda
           ([] mode)
           ([mod] (unless (member mod '(display edit))
                    (raise-mismatch-error 'set-mode "expected symbol  
in '(display edit), given " mod))
                  (set! mode mod)
                  (display-update))))

       (define/public (has-display-mode? sym)
         (equal? mode sym))

       (super-new)
       (display-init)))

   ;;; GUI CLASSES
   (define search-field%
     (class* text-field% ()
       (super-new)))

   (define multi-list-box-cell%
     (class* display/edit-text% ()


       (define/augment (after-insert s l)
         (let ((start (send this get-start-position))
               (end (send this get-end-position)))
           (cond ((and (not (send this has-private-lock?))
                       (= start (send this last-position)))
                  (send this private-lock #t)
                  (send this begin-edit-sequence)
                  (send this set-position (send this last-position)  
'same #f #f 'local)
                  (send this insert #\NEWLINE)
                  (send this set-position start end)
                  (send this end-edit-sequence))
                 (else (send this private-lock #f)))))

       (super-new)))

   (define multi-list-box-heading%
     (class* text% ()

       (inherit find-line)

       (define has-focus? #f)

       (define (adjust-display)
         (send this lock #f)
         (if has-focus?
             (send this change-style (make-object style-delta%  
'change-underline #t) 0 'end #f)
             (send this change-style (make-object style-delta%  
'change-underline #f) 0 'end #f))
         (send this lock #t))

       (define/override (on-focus on?)
         (set! has-focus? on?)
         (adjust-display))

       (define/public (set-label lab)
         (send this lock #f)
         (send this begin-edit-sequence)
         (send this change-style (make-object style-delta% 'change- 
bold) 'start 'end #f)
         (send this erase)
         (send this insert lab)
         (send this end-edit-sequence)
         (send this lock #t))

       (super-new)
       (send this lock #t)
       (send this hide-caret #t)))

   (define multi-list-box%
     (class* display/edit-text% ()

       (field (column-count 0))
       (field (column-headings #f))
       (field (column-min-widths #f))
       (field (column-rel-widths #f))


       (define (new-row)
         (send this insert "\n"))

       (define (cursor-to-end)
         (send this set-position (send this last-position)))

       (define (select-line n)
         (let* ((start (send this line-start-position n))
                (end (send this line-end-position n)))
           (send this set-position start end #f #f 'default)))

       ;; returns minimum width of all of the canvases displaying the  
list
       (define (get-display-width)
         (let ((w (box 0)))
           (send this get-view-size w #f)
           (unbox w))) ; CHECK CHANGE (size not correct?)

       ;; compute the current size of column idx based on its  
relative width
       (define (compute-width idx)
         (max
          (- (floor (* (/ (get-display-width) 100) (vector-ref column- 
rel-widths idx)))
             (/ 6 column-count)) ;arbitrary adjustement CHECK CHANGE
          (vector-ref column-min-widths idx)))

       (define (add-cell idx cell)
         (let* ((cell-editor (new multi-list-box-cell%
                                  (parent this)))
                (the-width (compute-width idx))
                (cell-editor-snip (new editor-snip%
                                       (editor cell-editor)
                                       (with-border? #f)
                                       (min-width (compute-width idx))
                                       (max-width (compute-width  
idx)))))
           (send cell-editor insert cell)
           (send this insert cell-editor-snip))
         )

       ;;; adjust the display after listbox has been resized
       ;;; recomputes and sets the widths of each cell
       (define/public (do-resize-adjustments)
         (send this begin-edit-sequence)
         ; (send this get-line-spacing)
         (for-each-cell
          (lambda (row col cell)
            (send cell resize (compute-width col) 26))) ; CHECK  
CHANGE hard-coded height! (how to get the real one??)
         (send this end-edit-sequence))

       (define/augment (on-display-size)
         (do-resize-adjustments))

       ;;; last-row
       ;;; returns the number of the last row
       ;;; the first row starts at 0 and so the
       ;;; value returned is the number of rows - 1
       (define/public (last-row)
         (send this last-line))

       ;;; iterate thunk over all cells in order row, cells
       ;;; thunk is a procedure taking two arguments: row, and cell  
number
       ;;; if changes-visible? is #t, the operation is not wrapped  
into an edit-sequence
       ;;; default is #f
       (define loop-over-cells
         (opt-lambda (thunk [changes-visible? #f])
           (with lines (last-row)
                 (unless changes-visible? (send this begin-edit- 
sequence))
                 (dotimes (row lines)
                          (dotimes (col column-count)
                                   (thunk row col)))
                 (unless changes-visible? (send this end-edit- 
sequence)))))

       ;;; get-cell row col
       ;;; get the cell at row <row> and column <col>
       ;;; returns an instance of class multi-listbox-cell%
       ;;; col and row are 0-indexed
       (define/public (get-cell row col)
         (send this find-snip (+ (send this line-start-position row  
#f) col) 'after #f))

       ;;; for-each-cell
       ;;; apply <proc> to each cell of type multi-listbox-cell%
       ;;; where <proc> is a procedure taking row number, column  
number, and cell as argument
       ;;; doesn't display changes until <proc> has been applied to  
all changes
       ;;; i.e. implicitly wrapped into begin-edit-sequence
       (define/public (for-each-cell thunk)
         (loop-over-cells
          (lambda (row col)
            (thunk row col (get-cell row col)))
          #f))

       ;;; display heading idx
       (define (display-heading idx text)
         (let* ((heading-editor (new multi-list-box-heading%))
                (the-width (compute-width idx))
                (heading-editor-snip (new editor-snip%
                                          (editor heading-editor)
                                          (with-border? #f)
                                          (min-width (compute-width  
idx))
                                          (max-width (compute-width  
idx)))))
           (send heading-editor-snip show-border #f)
           (send heading-editor set-label text)
           (send this insert heading-editor-snip))
         )
       ;;; display the column headings
       (define (display-headings)
         (srfi43-vector-for-each
          (lambda (idx heading) (display-heading idx heading))
          column-headings)
         (new-row))

       ;;; set-columns
       ;;;    <col-headings>: list of n strings
       ;;;    <col-specs>: list of n lists (rel-width min-width)
       (define/public (set-columns col-headings col-specs)
         ;; error checks
         (when (not (= (length col-headings) (length col-specs)))
           (raise-mismatch-error
            'set-columns
            (format "excpected value of type <column-specification>  
as second argument matching ~s, given " col-headings)
            col-specs))
         (let ((crel (list->vector (map car col-specs)))
               (cmin (list->vector (map cadr col-specs))))
           (when (<= (vector-min crel) 0)
             (raise-mismatch-error
              'set-columns
              "expected value of type <column-specification> as  
second argument, but the relative widths contain a value that is 0 or  
smaller, given "
              col-specs))
           (when (> (vector-sum crel) 100)
             (raise-mismatch-error
              'set-columns
              "expected value of type <column-specification> as  
second argument, but the sum of the relative widths given is above  
100 percent, given "
              col-specs))
           (when (< (vector-min cmin) 0)
             (raise-mismatch-error
              'set-columns
              "expected value of type <column-specification> as  
second argument, but one or more mimimum widths are not a positive  
integer, given "
              col-specs))
           ;; mutate
           (set! column-headings (list->vector col-headings))
           (set! column-count (vector-length crel))
           (set! column-rel-widths crel)
           (set! column-min-widths cmin)
           ;; display
           (display-headings)))

       (define/public (add-row row)
         (when (= column-count 0)
           (error 'add-row "the list-box has to be initialized using  
set-columns first"))
         (when (not (= (length row) column-count))
           (raise-mismatch-error
            'add-row
            (format "expected list of ~s strings, but given " column- 
count)
            row))
         (cursor-to-end)
         (srfi43-vector-for-each
          (lambda (idx min-width)
            (add-cell idx (list-ref row idx)))
          column-min-widths)
         (new-row))

       (define/public (on-display-mode-event evt snip)
         (when (eq? (send evt get-event-type) 'left-up)
           (select-line (send this find-line (send evt get-y) #f))))

       (super-new)
       (send this set-selection-callback
             (lambda (editor)
               (display 'selection) (newline)))))

   (define main-frame%
     (class* frame% ()

       (field (main-list #f))
       (field (status-panel #f))
       (field (v-panel #f))

       (define (make-layout)
         (set! v-panel (new vertical-panel% (parent this)))
         (set! status-panel (new horizontal-panel% (parent v-panel)))
         (let* ((status-text (new message%
                                  (label "Sample Header")
                                  (parent status-panel)
                                  (font small-control-font)
                                  (min-height 14)
                                  (min-width 40)
                                  ))

                (right-panel (new horizontal-panel%
                                  (parent status-panel)
                                  (min-width 80)
                                  (stretchable-width #t)
                                  (alignment '(right center))))
                (search-field (new search-field%
                                   (label #f)
                                   (parent right-panel)
                                   (font small-control-font)
                                   (min-width 20)
                                   (stretchable-width #f)
                                   (min-height 14)))
                (cat-choice (new choice%
                                 (label #f)
                                 (choices '("First Choice" "Second  
Choice" "Third Choice"))
                                 (parent right-panel)
                                 (font small-control-font)
                                 (min-width 20)
                                 ))
                (spacer1 (new horizontal-panel% (parent right-panel)  
(min-width 12) (stretchable-width #f) (stretchable-height #f)))
                (main-list-canvas (new editor-canvas%
                                       (parent v-panel)
                                       (horizontal-inset 0)
                                       (vertical-inset 0)))
                )
           (set! main-list (new multi-list-box%))
           (send main-list-canvas set-editor main-list)
           (send status-panel stretchable-height #f)))

       (define/override (on-toolbar-button-click)
         (send v-panel show (not (send v-panel is-shown?)))
         )

       (define/public (test)
         (send main-list begin-edit-sequence)
         (send main-list set-columns '("Column 1" "Column 2" "Column  
3" "Column 4") '((30 120) (30 120) (20 80) (20 80)))
         (repeat-times
          100
          (send main-list add-row `(,(symbol->string (gensym))
                                     ,(symbol->string (gensym))
                                     ,(symbol->string (gensym))
                                     ,(symbol->string (gensym)) )))
         (send main-list on-display-size)
         (send main-list for-each-cell (lambda (row col cell)  
(display (send cell get-text 0 200 #t))(newline)))
         (send main-list end-edit-sequence))


       (super-new
        (label "Untitled")
        (style '(toolbar-button))
        (height 450)
        (width 600))
       (make-layout)
       ))

   (define (get-main-frame)
     (let ((frame (new main-frame%)))
       frame))


   ;;; DEBUGGING
   (define (test)
     (define b (get-main-frame))
     (send b show #t)
     (send b test))
   )



Posted on the users mailing list.