private/frtime/demos/spreadsheet/spread.ss
(module spread (lib "frtime-big.ss" "frtime")
  
  (require (lib "class.ss")
           (all-except (lib "mred.ss" "mred") send-event)
           (rename mzscheme mz:define-struct define-struct)
           "preprocessor2.ss"
           (lifted "ss-funcs.ss" inflate-data)
           "quotes.ss"
           (as-is:unchecked (lib "match.ss") match-lambda)
           (as-is:unchecked (lib "frp-core.ss" "frtime") signal-value
                            proc->signal)
           (lib "framework.ss" "framework")
           (as-is:unchecked (lib "string.ss") expr->string)
           (as-is:unchecked (lib "etc.ss") build-vector)
           (lifted mzscheme regexp-match)
           (as-is:unchecked mzscheme make-hash-table hash-table-put! hash-table-get
                            hash-table-remove! let*-values vector-set! make-string
                            exn?
                            open-input-file open-output-file read write hash-table-map
                            file-exists? delete-file open-input-string eof
                            flush-output close-output-port dynamic-require))
  ;;
  ;; TO DO:
  ;;
  ;; rewrite cleanly
  ;; case-insensitive and relative identifier expansion with ranges
  ;; select multiple cells
  ;; fill row or column
  ;; copy and paste formula
  ;; disable text field when selection empty
  ;; allow resizing of columns
  
  ;; KNOWN BUGS:
  ;; when loading file, expression text field does not update
  
  (define stock-price
    (opt-lambda (name-string [seconds-between 1200])
      (lift-strict (lambda (name _) (stock-quote name)) name-string (quotient seconds seconds-between))))
  
  (define-syntax for
    (syntax-rules (=)
      [(_ (var = init) condn delta proc ...)
       (let loop ([var init])
         (when condn
           proc ...
           (loop (delta var))))]))
  
  (set-cell! raise-exceptions #t)
  
  (mz:define-struct ss-loc (row col))
  
  (define (ss-format val)
    (if (or (and (signal? val)
                 (undefined? (signal-value val)))
            (and (not (signal? val))
                 (undefined? val)))
        ""
        (format "~a" (signal-value val))))
  
  (define (@e r c)
    (ss-get-cell-value/force r c))
  
  (define (@c r0 r1 c)
    (build-list (add1 (abs (- r1 r0)))
                (lambda (i)
                  (@e (+ i (min r1 r0)) c))))
  
  (define (@r r c0 c1)
    (build-list (add1 (abs (- c1 c0)))
                (lambda (i)
                  (@e r (+ i (min c1 c0))))))
  
  (define (@m r0 r1 c0 c1)
    (build-list (add1 (abs (- c1 c0)))
                (lambda (i)
                  (@c r0 r1 (+ i (min c1 c0))))))
  
  (define frame
    (instantiate frame% ("Spreadsheet") (width 600) (height 400)))
  
  (define menu-bar
    (instantiate menu-bar% (frame)))
  
  (define file-menu
    (instantiate menu% ("File" menu-bar)))
  
  (define open-item
    (instantiate menu-item%
      ("Open..."
       file-menu
       (lambda (_ event) 
         (cond
           [(finder:get-file)
            =>
            (lambda (filename)
              (let ([p (open-input-file filename)])
                (for (i = 0) (< i cols) add1
                     (vector-set! vec i (make-hash-table))
                     (for-each (lambda (elt) #;(printf "adding ~a ~a ~a~n" (first elt) i (second elt))
                                 (ss-set-cell-processed-expr! (first elt) i (process (second elt) '@e '@r '@c '@m (first elt) i))) (read p))))
              (send canvas refresh))])))
      (shortcut #\O)))
  
  (define save-item
    (instantiate menu-item%
      ("Save as..."
       file-menu
       (lambda (_ event)
         (cond
           [(finder:put-file)
            =>
            (lambda (filename)
              (when (file-exists? filename)
                (delete-file filename))
              (let ([p (open-output-file filename)])
                (for (i = 0) (i . < . cols) add1
                     (let ([v (hash-table-map (vector-ref vec i) (lambda (row cell) (list row (ss-cell-expr cell))))])
                       #;(printf "~a~n" v)
                       (write v p)))
                (flush-output p)
                (close-output-port p)))])))
      (shortcut #\S)))
  
  (define edit-menu
    (instantiate menu% ("Edit" menu-bar)))
  
  (define text-field
    (instantiate text-field%
      ("Formula:"
       frame
       (lambda (this control-event)
         (case (send control-event get-event-type)
           [(text-field-enter) (send canvas new-expression (send this get-value))])))))
  
  (define value-field
    (instantiate text-field%
      ("Value:" frame void)))
  (send value-field enable #f)
  
  (define rows 1000)
  (define cols 100)
  
  (define vec
    (build-vector
     cols
     (lambda (_) (make-hash-table))))
  
  (mz:define-struct ss-cell (expr value updater))
  
  (define (ss-get-cell-text row col)
    (cond
      [(hash-table-get (vector-ref vec col) row (lambda () #f))
       => (lambda (cell)
            (let ([expr (unprocess (ss-cell-expr cell) '@e '@r '@c '@m row col)])
              (if (eq? expr 'undefined)
                  ""
                  (expr->string expr))))]
      [else ""]))
  
  (define (ss-get-cell-value row col)
    (cond
      [(hash-table-get (vector-ref vec col) row (lambda () #f))
       => ss-cell-value]
      [else undefined]))
  
  (define (fresh-ss-cell row col)
    (let* ([value (new-cell)]
           [ret (make-ss-cell
                 'undefined value
                 (proc->signal
                  (lambda () (send canvas draw-cell row col))
                  value))])
      (hash-table-put! (vector-ref vec col) row ret)
      ret))
  
  (define (ss-get-cell-value/force row col)
    (ss-cell-value (hash-table-get (vector-ref vec col) row (lambda () (fresh-ss-cell row col)))))
  
  (define (text->processed-expr txt row col)
    (let* ([expr
            (with-handlers
                ([exn? (lambda (exn)
                         (message-box
                          "Error"
                          (format "The expression you entered is invalid:~n~a"
                                  (exn-message exn))
                          frame
                          '(ok stop))
                         eof)])
              (read (open-input-string txt)))])
      (if (eof-object? expr)
          'undefined
          (process expr '@e '@r '@c '@m row col))))
  
  ;; should not (and does not) remove when cells are emptied
  ;; should not reset when expression is the same
  (define (ss-set-cell-processed-expr! row col processed-expr)
    (let* ([cell
            (hash-table-get
             (vector-ref vec col) row
             (lambda ()
               (fresh-ss-cell row col)))])
      (when (not (equal? (ss-cell-expr cell) processed-expr))
        (set-ss-cell-expr! cell processed-expr)
        (set-cell! (ss-cell-value cell)
                   (with-handlers
                       ([exn? (lambda (exn)
                                (message-box
                                 "Error"
                                 (format "The following error occurred while evaluating a formula:~n~a"
                                         (exn-message exn))
                                 frame
                                 '(ok stop))
                                "#<Error>")])
                     (eval `(let ([row ,row]
                                  [col ,col])
                              ,processed-expr))))
        ;(synchronize)
        (send canvas draw-cell row col))
      (send canvas focus)))
  
  (define chars-per-cell 14)
  
  (define (take-upto n lst)
    (if (and (positive? n)
             (cons? lst))
        (cons (first lst) (take-upto (sub1 n) (rest lst)))
        empty))
  
  (define (history-e n b)
    (collect-e (changes b) (list (value-now b)) (lambda (ev acc) (take-upto n (cons ev acc)))))
  
  (define (clip lo x hi)
    (max lo (min x hi)))
  
  (define (between x y z)
    (or (<= x y z)
        (<= z y x)))
  
  (define ss-canvas%
    (class canvas%
      (super-instantiate ())
      
      (inherit
        refresh
        get-dc
        get-scroll-pos
        get-client-size
        set-scroll-range
        set-scroll-page
        init-manual-scrollbars)
      
      (override
        set-scroll-pos
        on-event
        on-paint
        on-scroll
        on-size
        on-char)
      
      (field
       [can-refresh? #t]
       [offscreen-dc (new bitmap-dc% (bitmap (make-object bitmap% 1280 1024 #f)))]
       
       [char-width (inexact->exact (send offscreen-dc get-char-width))]
       [cell-width (* chars-per-cell char-width)]
       [cell-height (+ 2 (inexact->exact (send offscreen-dc get-char-height)))]
       
       [left-margin (* 5 char-width)]
       [top-margin cell-height]
       
       [canvas-width-rcvr (event-receiver)]
       [canvas-height-rcvr (event-receiver)]
       [h-scroll-rcvr (event-receiver)]
       [v-scroll-rcvr (event-receiver)]
       [mouse-x-rcvr (event-receiver)]
       [mouse-y-rcvr (event-receiver)]
       [left-clicks (event-receiver)]
       [left-releases (event-receiver)]
       [key-events (event-receiver)]
       
       [canvas-width~ (hold canvas-width-rcvr)]
       [canvas-height~ (hold canvas-height-rcvr)]
       
       [mouse-x~ (hold mouse-x-rcvr 0)]
       [mouse-y~ (hold mouse-y-rcvr 0)]
       
       [left-button-down~ (hold (merge-e (left-clicks   . -=> . #t)
                                         (left-releases . -=> . #f))
                                #f)]
       
       [h-chars-per-page~ (quotient (- canvas-width~ left-margin) char-width)]
       [v-cells-per-page~ (quotient (- canvas-height~ top-margin) cell-height)]
       [h-scroll-range~ (max 0 (- (* cols chars-per-cell) h-chars-per-page~))]
       [v-scroll-range~ (max 0 (- rows v-cells-per-page~))]
       
       [h-scroll-pos~ (hold h-scroll-rcvr 0)]
       [v-scroll-pos~ (hold v-scroll-rcvr 0)]
       [h-scroll-cells~ (quotient h-scroll-pos~ chars-per-cell)]
       [h-scroll-offset~ (* char-width (remainder h-scroll-pos~ chars-per-cell))]
       [v-scroll-cells~ v-scroll-pos~]
       
       [mouse-row~ (y->row mouse-y~)]
       [mouse-col~ (x->col mouse-x~)]
       
       [first-vis-row~ (y->row (add1 top-margin))]
       [last-vis-row~ (y->row (sub1 canvas-height~))]
       [first-vis-col~ (x->col (add1 left-margin))]
       [last-vis-col~ (x->col (sub1 canvas-width~))]
       
       [start-sel-row~
        (accum-b
         (merge-e
          (left-clicks . -=> . (lambda (_) (value-now mouse-row~)))
          (key-events  . ==> . (lambda (key)
                                 (lambda (prev)
                                   (case (send key get-key-code)
                                     [(up) (max 0 (sub1 prev))]
                                     [(down) (min (sub1 rows) (add1 prev))]
                                     [else prev])))))
         0)]
       [start-sel-col~
        (accum-b
         (merge-e
          (left-clicks . -=> . (lambda (_) (value-now mouse-col~)))
          (key-events  . ==> . (lambda (key)
                                 (lambda (prev)
                                   (case (send key get-key-code)
                                     [(left) (max 0 (sub1 prev))]
                                     [(right) (min (sub1 cols) (add1 prev))]
                                     [else prev])))))
         0)]
       
       [cur-sel-row~
        (hold (merge-e
               (changes start-sel-row~)
               ((changes start-sel-col~) . -=> . (value-now start-sel-row~))
               ((changes mouse-row~) . =#> . (lambda (_)
                                               left-button-down~))) 0)]
       [cur-sel-col~
        (hold (merge-e
               (changes start-sel-col~)
               ((changes start-sel-row~) . -=> . (value-now start-sel-col~))
               ((changes mouse-col~) . =#> . (lambda (_)
                                               left-button-down~))) 0)]
       
       [scrollbar-updater
        (list
         (lift #t (lambda (pg) (set-scroll-page 'horizontal (clip 1 (- pg chars-per-cell -1) 10000))) h-chars-per-page~)
         (lift #t (lambda (pg) (set-scroll-page 'vertical (clip 1 (sub1 pg) 10000))) v-cells-per-page~)
         (lift #t (lambda (rng) (set-scroll-range 'horizontal (clip 1 rng 10000))) h-scroll-range~)
         (lift #t (lambda (rng) (set-scroll-range 'vertical (clip 1 rng 10000))) v-scroll-range~))]
       
       [scroller ((merge-e (changes h-scroll-pos~)
                           (changes v-scroll-pos~)) . -=> . (refresh))]
       
       [v-auto-scroller (merge-e
                         ((while-e (and left-button-down~
                                        (>= cur-sel-row~ last-vis-row~)
                                        (< cur-sel-row~ (sub1 rows))
                                        (not (= cur-sel-row~ start-sel-row~))) 50)
                          . -=> . (set-scroll-pos 'vertical (add1 (value-now v-scroll-pos~))))
                         ((while-e (and left-button-down~
                                        (<= cur-sel-row~ first-vis-row~)
                                        (> cur-sel-row~ 0)
                                        (not (= cur-sel-row~ start-sel-row~))) 50)
                          . -=> . (set-scroll-pos 'vertical (sub1 (value-now v-scroll-pos~))))
                         (key-events
                          . ==> .
                          (lambda (ev)
                            (case (send ev get-key-code)
                              [(prior) (set-scroll-pos 'vertical (max 0 (- (value-now v-scroll-pos~) (value-now v-cells-per-page~))))]
                              [(next) (set-scroll-pos 'vertical (min (value-now v-scroll-range~)
                                                                     (+ (value-now v-scroll-pos~) (value-now v-cells-per-page~))))]))))]
       
       [h-auto-scroller (merge-e
                         ((while-e (and left-button-down~
                                        (>= cur-sel-col~ last-vis-col~)
                                        (< h-scroll-pos~ h-scroll-range~)) 50)
                          . -=> . (set-scroll-pos 'horizontal (+ 3 (value-now h-scroll-pos~))))
                         ((while-e (and left-button-down~
                                        (<= cur-sel-col~ first-vis-col~)
                                        (> h-scroll-pos~ 0)) 50)
                          . -=> . (set-scroll-pos 'horizontal (+ -3 (value-now h-scroll-pos~)))))]
       
       [highlighter (merge-e
                     ((history-e 2 (list mouse-row~ mouse-col~))
                      . ==> .
                      (lambda (lst)
                        (for-each
                         (lambda (p)
                           (draw-cell (first p) (second p)))
                         lst)))
                     ((history-e 2 (list start-sel-row~ start-sel-col~ cur-sel-row~ cur-sel-col~))
                      . ==> .
                      (match-lambda
                        [((r01 c01 rf1 cf1) (r00 c00 rf0 cf0))
                         (cond
                           [(and (= r01 rf1) (= c01 cf1))
                            ; fresh selection: clear old selection, redraw new cell
                            (draw-cell-block r00 rf0 c00 cf0)
                            (draw-cell r01 c01)]
                           [else
                            ; extended selection, so r00 = r01 and c00 = c01
                            (draw-cell-block rf0 rf1 (min c00 cf0 cf1) (max c00 cf0 cf1))
                            (draw-cell-block (min r00 rf0 rf1) (max r00 rf0 rf1) cf0 cf1)
                            (draw-cell-block rf0 rf1 cf0 cf1)])])))]
       
       [focuser ((key-events . =#> . (lambda (ev) (eq? #\return (send ev get-key-code))))
                 . -=> . (send text-field focus))]
       
       [text-field-switcher (lift #t (lambda (row col)
                                       (unless (or (negative? row)
                                                   (negative? col))
                                         (send text-field set-value (ss-get-cell-text row col))))
                                  start-sel-row~ start-sel-col~)]
       
       [light-steel-blue (make-object color% "LightSteelBlue")]
       [lavender (make-object color% "Lavender")]
       [white (make-object color% "White")]
       [line-pen (make-object pen% (make-object color% "DimGray") 1 'solid)]
       [light-gray (make-object color% "LightGray")]
       [trans-pen (make-object pen%)]
       [default-font (send offscreen-dc get-font)]
       [label-font (make-object font% 11 'roman 'normal 'bold)]
       [gray-brush (make-object brush% light-gray 'solid)]
       [highlight-brush (make-object brush% lavender 'solid)]
       [selected-brush (make-object brush% light-steel-blue 'solid)]
       [clear-brush (make-object brush% white 'solid)])
      
      (send trans-pen set-style 'transparent)
      
      (define (set-scroll-pos which pos)
        (super set-scroll-pos which pos)
        (send-event
         (case which
           [(horizontal) h-scroll-rcvr]
           [(vertical) v-scroll-rcvr]) pos))
      
      (define/private (x->col x)
        (if (> x left-margin)
            (+ h-scroll-cells~ (quotient (+ (- x left-margin) h-scroll-offset~) cell-width))
            -1))
      
      (define/private (y->row y)
        (if (> y top-margin)
            (+ v-scroll-cells~ (quotient (- y top-margin) cell-height))
            -1))
      
      (define/private (row->y-top row)
        (snapshot/sync (v-scroll-cells~)
                       (+ (* cell-height (- row v-scroll-cells~))
                          top-margin)))
      
      (define/private (col->x-left col)
        (snapshot/sync (h-scroll-cells~ h-scroll-offset~)
                       (+ (* (- col h-scroll-cells~) cell-width)
                          (- h-scroll-offset~)
                          left-margin)))
      
      #;(define foo (lift #t printf "~a ~a ~a ~a~n" cur-sel-row~ cur-sel-col~ start-sel-row~ start-sel-col~))
      
      (define/public (draw-cell-block r0 rf c0 cf)
        (let ([r0 (min r0 rf)]
              [rf (max r0 rf)]
              [c0 (min c0 cf)]
              [cf (max c0 cf)])
          (for (i = r0) (i . <= . rf) add1
               (for (j = c0) (j . <= . cf) add1
                    (draw-cell-offscreen i j)))
          (let ([x0 (col->x-left c0)]
                [y0 (row->y-top r0)]
                [xf (col->x-left (add1 cf))]
                [yf (row->y-top (add1 rf))])
            (send (get-dc)
                  draw-bitmap-section (send offscreen-dc get-bitmap)
                  x0 y0 x0 y0 (- xf x0) (- yf y0)))))
      
      (define/public (draw-cell-block-offscreen r0 rf c0 cf)
        (let ([r0 (min r0 rf)]
              [rf (max r0 rf)]
              [c0 (min c0 cf)]
              [cf (max c0 cf)])
          (for (i = r0) (i . <= . rf) add1
               (for (j = c0) (j . <= . cf) add1
                    (draw-cell-offscreen i j)))))
      
      (define/public (new-expression text)
        (snapshot/sync (cur-sel-row~ cur-sel-col~ start-sel-row~ start-sel-col~)
                       (let ([r0 (min cur-sel-row~ start-sel-row~)]
                             [r1 (max cur-sel-row~ start-sel-row~)]
                             [c0 (min cur-sel-col~ start-sel-col~)]
                             [c1 (max cur-sel-col~ start-sel-col~)]
                             [processed-expr (text->processed-expr text start-sel-row~ start-sel-col~)])
                         (for (row = r0) (row . <= . r1) add1
                              (for (col = c0) (col . <= . c1) add1
                                   (ss-set-cell-processed-expr! row col processed-expr))))
                       (send canvas focus)))
      
      (define (draw-cell-offscreen row col)
        (snapshot/sync (first-vis-row~
                        last-vis-row~
                        first-vis-col~ last-vis-col~
                        mouse-row~ mouse-col~
                        start-sel-row~ start-sel-col~
                        cur-sel-row~ cur-sel-col~)
                       (let ([x (col->x-left col)]
                             [y (row->y-top row)])
                         (when (and (< -1 row rows)
                                    (< -1 col cols))
                           (let ([text (ss-format (ss-get-cell-value row col))])
                             (when (and (= row start-sel-row~)
                                        (= col start-sel-col~))
                               (send value-field set-value text))
                             (when (and (<= first-vis-row~ row last-vis-row~)
                                        (<= first-vis-col~ col last-vis-col~))
                               (send offscreen-dc set-clipping-rect
                                     (max x (+ left-margin 1)) y cell-width cell-height)
                               (send offscreen-dc set-brush
                                     (cond
                                       [(and (between start-sel-row~ row cur-sel-row~)
                                             (between start-sel-col~ col cur-sel-col~)) selected-brush]
                                       [(and (= row mouse-row~)
                                             (= col mouse-col~)) highlight-brush]
                                       [else clear-brush]))
                               (send offscreen-dc draw-rectangle x y (+ cell-width 1) (+ cell-height 1))
                               (send offscreen-dc draw-text text
                                     (- (+ x cell-width) 2
                                        (let-values ([(width height descent space)
                                                      (send offscreen-dc get-text-extent text #f #f 0)])
                                          width))
                                     (+ y 1) #f 0 0)
                               (send offscreen-dc set-clipping-region #f)))))))
      
      (define/public (draw-cell row col)
        (draw-cell-offscreen row col)
        (let ([x (col->x-left col)]
              [y (row->y-top row)])
          (send (get-dc)
                draw-bitmap-section (send offscreen-dc get-bitmap)
                x y x y cell-width cell-height)))
      
      (define (get-text-width dc text)
        (let-values ([(width height descent space)
                      (send dc get-text-extent text #f #f 0)])
          width))
      
      (define (num->char n)
        (integer->char (+ n (char->integer #\A))))
      
      (define (column->string col)
        (list->string
         (if (< col 26)
             (list (num->char col))
             (list (num->char (sub1 (quotient col 26)))
                   (num->char (remainder col 26))))))
      
      (define (on-char event)
        (send-event key-events event)
        (synchronize))
      
      (define (on-scroll scroll-event)
        (case (send scroll-event get-direction)
          [(vertical) (send-event v-scroll-rcvr (send scroll-event get-position))]
          [(horizontal) (send-event h-scroll-rcvr (send scroll-event get-position))])
        (synchronize))
      
      (define (on-event event)
        (case (send event get-event-type)
          [(enter motion)
           (send-event mouse-x-rcvr (send event get-x))
           (send-event mouse-y-rcvr (send event get-y))]
          [(leave)
           (send-event mouse-x-rcvr -1)
           (send-event mouse-y-rcvr -1)]
          [(left-down) (send-event left-clicks #t)]
          [(left-up) (send-event left-releases #t)])
        (synchronize))
      
      (define (on-size width height)
        (let-values ([(width height) (get-client-size)])
          (send-event canvas-width-rcvr width)
          (send-event canvas-height-rcvr height)
          (synchronize)))
      
      (define (on-paint)
        (snapshot/sync (canvas-width~
                        canvas-height~
                        first-vis-row~ last-vis-row~
                        first-vis-col~ last-vis-col~
                        h-scroll-cells~ h-scroll-offset~ v-scroll-cells~)
                       (let ([dc offscreen-dc])
                         (send dc set-clipping-region #f)
                         (send dc clear)
                         ;(send dc set-pen line-pen)
                         ;(send dc set-brush highlight-brush)
                         (send dc set-pen trans-pen)
                         (send dc set-brush gray-brush)
                         (send dc draw-rectangle 0 0 left-margin canvas-height~)
                         (send dc draw-rectangle 0 0 canvas-width~ top-margin)
                         (send dc set-pen line-pen)
                         (send dc draw-line 0 0 0 canvas-height~)
                         (send dc draw-line 0 0 canvas-width~ 0)
                         (send dc set-brush clear-brush)
                         (send dc set-font label-font)
                         ;; draw horizontal rules and row labels
                         (for (row = first-vis-row~) (row . <= . (min last-vis-row~ (sub1 rows))) add1
                              (let ([y (row->y-top row)]
                                    [text (number->string row)])
                                (send dc draw-line 0 y canvas-width~ y)
                                (send dc draw-text text (- left-margin (get-text-width dc text) 2) (add1 y) #f 0 0)))
                         ;; draw vertical rules and column labels
                         (send dc draw-line left-margin 0 left-margin canvas-height~)
                         (send dc set-clipping-rect (+ left-margin 1) 0 (- canvas-width~ left-margin 1) canvas-height~)
                         (for (col = first-vis-col~) (col . <= . (min last-vis-col~ (sub1 cols))) add1
                              (let ([x (col->x-left col)]
                                    [text (column->string col)])
                                (send dc draw-text text (+ x (quotient (- cell-width (get-text-width dc text)) 2)) 0 #f 0 0)
                                (send dc draw-line x 0 x canvas-height~)))
                         (send dc set-font default-font)
                         (draw-cell-block-offscreen first-vis-row~ last-vis-row~ first-vis-col~ last-vis-col~)
                         (send (get-dc) draw-bitmap-section (send dc get-bitmap) 0 0 0 0 canvas-width~ canvas-height~))))
      
      (let-values ([(width height) (get-client-size)])
        (send-event canvas-width-rcvr width)
        (send-event canvas-height-rcvr height))
      (synchronize)
      (init-manual-scrollbars 1 1 1 1 0 0)
      (send offscreen-dc set-pen line-pen)
      (send offscreen-dc set-brush highlight-brush)))
  
  (define canvas
    (instantiate ss-canvas% (frame) (style (list 'hscroll 'vscroll 'no-autoclear))))
  
  (send frame show #t)
  (send canvas focus))