#|

The paint-by-numbers-canavas% class accepts two initalization
arguments. They must be lists of lists of numbers and they must be the
same length. paint-by-numbers-canvas% objects accepts four methods:

set-rect : (int int (union 'on 'off 'unknown) -> void)
Sets the grid point specified by the first two arguments to the third.
The coordinates are from the top-left and the x coordinate comes first.

get-rect : (int int ->  (union 'on 'off 'unknown))
Gets the value of the grid at the coordinates specified by the two integers

paint-rect : (int int -> void)
Draws the rectangle specified by the arguments.
Call this after calling set-rect to see the changes updated on the screen.

get-grid : (-> (list-of (list-of (union 'on 'off 'unknown 'wrong))))
Returns the current state of the entire board as a list of lists.

set-grid : ((vector-of (vector-of (union 'on 'off 'unknown 'wrong)))-> void)
Sets the state of the board. No drawing takes place

on-paint : (-> void)
Redraws the entire canvas. May be used if many rects were set.

all-unknown : (-> void)
Sets all board positions to 'unknown

close-up : (-> void)
call when canvas is closed.

See the bottom of this file for the creation of a file and a test
paint by numbers.

|#

(module gui mzscheme
  (require (lib "mred.ss" "mred")
           (lib "framework.ss" "framework")
           (lib "etc.ss")
           (lib "class.ss"))

  (provide paint-by-numbers-canvas%
           design-paint-by-numbers-canvas%)
  
  (define UNKNOWN-BRUSH (send the-brush-list find-or-create-brush "DARK GRAY" 'solid))
  (define ON-BRUSH (send the-brush-list find-or-create-brush "BLUE" 'solid))
  (define OFF-BRUSH (send the-brush-list find-or-create-brush "WHITE" 'solid))
  (define WRONG-BRUSH (send the-brush-list find-or-create-brush "RED" 'solid))
  
  (define LINES/NUMBERS-PEN (send the-pen-list find-or-create-pen "BLACK" 1 'solid))
  
  (define BLACK-PEN (send the-pen-list find-or-create-pen "BLACK" 1 'solid))
  (define WHITE-PEN (send the-pen-list find-or-create-pen "WHITE" 1 'solid))
  (define WHITE-BRUSH (send the-brush-list find-or-create-brush "WHITE" 'solid))
  
  (define BAR-PEN (send the-pen-list find-or-create-pen "SALMON" 1 'solid))
  (define BAR-BRUSH (send the-brush-list find-or-create-brush "SALMON" 'solid))
  
  (define-struct ado (x y before after))
  
  (define-struct pt (x y))
  
  (define paint-by-numbers-canvas%
    (class canvas%
      (init-field row-numbers col-numbers)
      (inherit get-dc get-client-size)
      
      (define/private (get-font) (send (get-dc) get-font))
      
      (define/public (get-row-numbers) row-numbers)
      (define/public (get-col-numbers) col-numbers)
      
      (define/public (get-max-col-entries)
        (apply max (map length (get-col-numbers))))
      
      [define extra-space-every 5]
      
      [define grid-x-size (length (get-col-numbers))]
      [define grid-y-size (length (get-row-numbers))]
      [define y-margin 1]
      [define x-margin 3]
      [define row-label-width 10]
      [define row-label-height 10]
      [define col-label-width 10]
      [define col-label-height 10]
      
      [define/private (get-row-label-string l)
        (if (null? l)
            ""
            (let ([first (car l)]
                  [rest (cdr l)])
              (apply string-append 
                     (number->string first)
                     (map (lambda (x) (format " ~a" x)) rest))))]
      
      [define/private get-col-label-strings
        (lambda (l)
          (map number->string l))]
      
      [define grid (build-vector grid-x-size (lambda (i) (make-vector grid-y-size UNKNOWN-BRUSH)))]
      
      [define/private get-string-height
        (lambda (s)
          (let ([dc (get-dc)])
            (let-values ([(width height descent ascent) 
                          (send dc get-text-extent s)])
              (- height descent))))]
      [define/private get-string-height/descent
        (lambda (s)
          (let ([dc (get-dc)])
            (let-values ([(width height descent ascent) 
                          (send dc get-text-extent s)])
              height)))]
      [define/private get-string-ascent
        (lambda (s)
          (let ([dc (get-dc)])
            (let-values ([(width height descent ascent) 
                          (send dc get-text-extent s)])
              ascent)))]
      [define/private get-string-width
        (lambda (s)
          (let ([dc (get-dc)])
            (let-values ([(width height descent ascent) 
                          (send dc get-text-extent s)])
              width)))]
      
      [define/private loc->string
        (lambda (x y)
          (format "(~a,~a)" x y))]
      
      [define/private xy->grid
        (lambda (x y)
          (let* ([grid-width (/ (- (get-canvas-width) row-label-width) grid-x-size)]
                 [grid-height (/ (- (get-canvas-height) col-label-height) grid-y-size)]
                 [xp (- x row-label-width)]
                 [yp (- y col-label-height)]
                 [x (inexact->exact (floor (/ xp grid-width)))]
                 [y (inexact->exact (floor (/ yp grid-height)))])
            (if (and (<= 0 x)
                     (< x grid-x-size)
                     (<= 0 y)
                     (< y grid-y-size))
                (make-pt x y)
                #f)))]
      
      [define/private grid->rect
        (lambda (x y)
          (let* ([grid-width (- (get-canvas-width)
                                row-label-width
                                (quotient grid-x-size extra-space-every))]
                 [grid-height (- (get-canvas-height)
                                 col-label-height
                                 (quotient grid-y-size extra-space-every))]
                 [left (+ row-label-width
                          (quotient x extra-space-every)
                          (* x (/ grid-width grid-x-size)))]
                 [top (+ col-label-height
                         (quotient y extra-space-every)
                         (* y (/ grid-height grid-y-size)))]
                 [width (/ grid-width grid-x-size)]
                 [height (/ grid-height grid-y-size)])
            (values left top width height)))]

      (define/private (get-canvas-width) (let-values ([(w h) (get-client-size)]) w))
      (define/private (get-canvas-height) (let-values ([(w h) (get-client-size)]) h))
      
      [define undo-history null]
      [define redo-history null]
      [define/private do-do
        (lambda (do current-sel new-sel)
          (let* ([x (ado-x do)]
                 [y (ado-y do)]
                 [actual (get-raw-rect x y)]
                 [current (current-sel do)]
                 [new (new-sel do)]
                 [color->val
                  (lambda (brush)
                    (let ([color (send brush get-color)])
                      (list (send color red)
                            (send color green)
                            (send color blue))))])
            (unless (eq? current actual)
              (error 'do-do "expected ~a found ~a at (~a,~a)"
                     (color->val current)
                     (color->val actual)
                     x y))
            (set-raw-rect x y new)
            (paint-rect x y)))]
      
      [define/private brush->symbol
        (lambda (res)
          (cond
	    [(eq? res UNKNOWN-BRUSH) 'unknown]
	    [(eq? res OFF-BRUSH) 'off]
	    [(eq? res ON-BRUSH) 'on]
	    [(eq? res WRONG-BRUSH) 'wrong]))]
      [define/private sym->brush
        (lambda (sym)
          (case sym
            [(unknown) UNKNOWN-BRUSH]
            [(off) OFF-BRUSH]
            [(on) ON-BRUSH]
            [(wrong) WRONG-BRUSH]))]
      
      [define/private in-rect?
        (lambda (p cp1 cp2)
          (or (and (<= (pt-x cp1) (pt-x p) (pt-x cp2))
                   (<= (pt-y cp1) (pt-y p) (pt-y cp2)))
              (and (<= (pt-x cp2) (pt-x p) (pt-x cp1))
                   (<= (pt-y cp2) (pt-y p) (pt-y cp1)))))]
      
      ;; ((list-of (list-of (union 'unknown 'off 'on 'wrong))) -> void)
      [define/public set-grid
        (lambda (g)
          (set! undo-history null)
          (set! redo-history null)
          (set! grid
                (list->vector
                 (map (lambda (x) (list->vector (map (lambda (x) (sym->brush x)) x)))
                      g))))]
      
      ;; (-> (list-of (list-of (union 'unknown 'off 'on 'wrong))))
      [define/public get-grid
        (lambda ()
          (map (lambda (x) (map (lambda (x) (brush->symbol x)) (vector->list x)))
               (vector->list grid)))]
      
      ;; (-> void)
      [define/public undo
        (lambda ()
          (cond
	    [(null? undo-history) (bell)]
	    [else
	     (let ([do (car undo-history)])
	       (set! undo-history (cdr undo-history))
	       (set! redo-history (cons do redo-history))
	       (do-do do ado-after ado-before))]))]
      
      ;; (-> void)
      [define/public redo
        (lambda ()
          (cond
	    [(null? redo-history) (bell)]
	    [else
	     (let ([do (car redo-history)])
	       (set! redo-history (cdr redo-history))
	       (set! undo-history (cons do undo-history))
	       (do-do do ado-before ado-after))]))]
      
      [define/public paint-rect
        (lambda (i j)
          (send (get-dc) set-pen LINES/NUMBERS-PEN)
          (paint-rect/lines-numbers-pen i j))]
      
      ;; (int int -> void)
      [define/public paint-rect/lines-numbers-pen
        (lambda (i j)
          (let ([dc (get-dc)])
            (let-values ([(left top width height) (grid->rect i j)])
              (cond
                [(and draw-small-start-p
                      draw-small-end-p
                      (in-rect? (make-pt i j)
                                draw-small-start-p
                                draw-small-end-p))
		 (send dc set-pen WHITE-PEN)
		 (send dc set-brush WHITE-BRUSH)
		 (send dc draw-rectangle left top width height)
                 
		 (let ([spacing 2])
		   (send dc set-pen LINES/NUMBERS-PEN)
		   (send dc set-brush (new-brush (get-raw-rect (pt-x draw-small-start-p)
                                                               (pt-y draw-small-start-p))
                                                 modifier-on?))
		   (send dc draw-rectangle
			 (+ left spacing)
			 (+ top spacing)
			 (- width spacing spacing)
			 (- height spacing spacing)))]
                
		[else
		 (send dc set-brush (get-raw-rect i j))
		 (send dc draw-rectangle left top width height)]))))]
      
      
      ;; (int int -> (instance brush%))
      [define/public get-raw-rect
        (lambda (i j)
          '(unless (and (<= 0 i)
			(< i grid-x-size)
			(<= 0 j)
			(< j grid-y-size))
	     (error 'get-raw-rect "cannot get (~a, ~a) in ~ax~a board"
		    i j grid-x-size grid-y-size))
          (vector-ref (vector-ref grid i) j))]
      
      ;; (int int -> (union 'on 'off 'unknown 'wrong))
      [define/public get-rect
        (lambda (i j)
          (brush->symbol (get-raw-rect i j)))]
      
      ;; (int int (instance brush%) -> void)
      [define/public set-raw-rect
        (lambda (i j brush)
          '(unless (and (<= 0 i)
			(< i grid-x-size)
			(<= 0 j)
			(< j grid-y-size))
	     (error 'set-raw-rect "cannot set (~a, ~a) in ~ax~a board"
		    i j grid-x-size grid-y-size))
          (vector-set! (vector-ref grid i) j brush))]
      
      ;; (int int (union 'on 'off 'unknown 'wrong) -> void)
      [define/public set-rect
        (lambda (i j sym)
          (set-raw-rect i j (sym->brush sym)))]
      
      
      ;; (int int -> void)
      [define/public set-to-error
        (lambda (i j)
          (let ([brush (sym->brush 'wrong)])
            (set! undo-history (cons (make-ado i j (get-raw-rect i j) brush) undo-history))
            (set-raw-rect i j brush)
            (paint-rect i j)))]
      
      ;; (-> void)
      [define/public all-unknown
        (lambda ()
          (let loop ([i grid-x-size])
            (cond
	      [(zero? i) (void)]
	      [else
	       (let loop ([j grid-y-size])
		 (cond
                   [(zero? j) (void)]
                   [else (set-rect (- i 1) (- j 1) 'unknown)
                         (loop (- j 1))]))
	       (loop (- i 1))])))]
      
      [define highlight-row #f]
      [define highlight-col #f]
      (define/public draw-row-label
        (lambda (n)
          (let-values ([(gx gy gw gh) (grid->rect 0 n)])
            (when (and (gx . >= . 0)
                       (gy . >= . 0)
                       (gw . >= . 0)
                       (gh . >= . 0))
              (let* ([nums (list-ref (get-row-numbers) n)]
                     [dc (get-dc)]
                     [str (get-row-label-string nums)]
                     [str-height (get-string-height str)]
                     [str-ascent (get-string-ascent str)]
                     [str-width (get-string-width str)]
                     [sy (+ gy
                            (- (/ gh 2)
                               (/ str-height 2)))]
                     [sx (- row-label-width str-width x-margin)]
                     
                     [x 0]
                     [y gy]
                     [w gx]
                     [h gh])
                
                (if (and highlight-row
                         (= highlight-row n))
                    (begin
                      (send dc set-pen BAR-PEN)
                      (send dc set-brush BAR-BRUSH))
                    (begin
                      (send dc set-pen WHITE-PEN)
                      (send dc set-brush WHITE-BRUSH)))
                
                (send dc draw-rectangle x y w h)
                (send dc draw-text str sx sy))))))
      
      [define/public draw-col-label
        (lambda (n)
          (let-values ([(gx gy gw gh) (grid->rect n 0)])
            (when (and (gx . >= . 0)
                       (gy . >= . 0)
                       (gw . >= . 0)
                       (gh . >= . 0))
              (let* ([nums (list-ref (get-col-numbers) n)]
                     [strs (get-col-label-strings nums)]
                     [dc (get-dc)])
                
                (if (and highlight-col
                         (= highlight-col n))
                    (begin
                      (send dc set-pen BAR-PEN)
                      (send dc set-brush BAR-BRUSH))
                    (begin
                      (send dc set-pen WHITE-PEN)
                      (send dc set-brush WHITE-BRUSH)))
                
                (send dc draw-rectangle gx 0 gw gy)
                (let loop ([ss strs]
                           [line (- (get-max-col-entries) (length strs))])
                  (cond
                    [(null? ss) (void)]
                    [else
                     (let* ([s (car ss)]
                            [str-width (get-string-width s)]
                            [str-height (get-string-height s)]
                            [x (+ gx
                                  (- (/ gw 2)
                                     (/ str-width 2)))]
                            [y (* line (+ str-height y-margin))])
                       (send dc draw-text (car ss) x y)
                       (loop (cdr ss)
                             (+ line 1)))]))))))]
      
      [define/private new-brush
        (lambda (prev modifier?)
          (cond
            [(eq? prev UNKNOWN-BRUSH)
             (if modifier?
                 OFF-BRUSH
                 ON-BRUSH)]
            [(eq? prev ON-BRUSH) UNKNOWN-BRUSH]
            [(eq? prev OFF-BRUSH) UNKNOWN-BRUSH]
            [(eq? prev WRONG-BRUSH) UNKNOWN-BRUSH]
            [else
             (error 'internal-error
                    "unkown brush in board ~s~n" prev)]))]
      
      [define/private check-modifier
        (lambda (evt)
          (or (send evt get-right-down)
              (send evt button-up? 'right)
              (send evt get-alt-down)
              (send evt get-meta-down)
              (send evt get-control-down)
              (send evt get-shift-down)))]
      
      [define modifier-on? #f]
      [define last-p #f]
      
      
      ;; (union #f                   if button not down
      ;;        (make-pt num num))   if button down
      [define draw-small-start-p #f]
      
      ;; (union #f                   if button dragged outside board
      ;;        (make-pt num num))   if button dragged in board
      [define draw-small-end-p #f]
      
      [define coordinate-p #f]
      
      [define/private update-range-of-rects
        (lambda (p1 p2)
          (let ([x-small (min (pt-x p1) (pt-x p2))]
                [x-large (max (pt-x p1) (pt-x p2))]
                [y-small (min (pt-y p1) (pt-y p2))]
                [y-large (max (pt-y p1) (pt-y p2))])
            (let loop ([x x-small])
              (when (<= x x-large)
                (let loop ([y y-small])
                  (when (<= y y-large)
                    (paint-rect x y)
                    (loop (+ y 1))))
                (loop (+ x 1))))))]
      
      [define/override on-event
	(lambda (evt)
	  (let* ([x (send evt get-x)]
		 [y (send evt get-y)]
		 [p (xy->grid x y)])
	    (cond
              [(or (send evt moving?)
                   (send evt entering?)
                   (send evt leaving?))
               
               ;; update depressed squares
               (when draw-small-start-p
                 (let ([old-draw-small-end-p draw-small-end-p])
                   (cond
                     [(and draw-small-start-p
                           p
                           (or (= (pt-x p) (pt-x draw-small-start-p))
                               (= (pt-y p) (pt-y draw-small-start-p))))
                      (unless (equal? draw-small-end-p p)
                        (set! draw-small-end-p p)
                        (when old-draw-small-end-p
                          (update-range-of-rects draw-small-start-p old-draw-small-end-p))
                        (when draw-small-end-p
                          (update-range-of-rects draw-small-start-p draw-small-end-p)))]
                     [draw-small-start-p
                      (set! draw-small-end-p #f)
                      (when old-draw-small-end-p
                        (update-range-of-rects draw-small-start-p old-draw-small-end-p))])))
               
               (let ([dc (get-dc)])
                 
                 ;; update the bars
                 (let ([new-highlight-col
                        (if (and p
                                 (not (send evt leaving?)))
                            (pt-x p)
                            #f)]
                       [old-highlight-col highlight-col])
                   (unless (equal? old-highlight-col new-highlight-col)
                     (set! highlight-col new-highlight-col)
                     (when new-highlight-col
                       (draw-col-label new-highlight-col))
                     (when old-highlight-col
                       (draw-col-label old-highlight-col))))
                 
                 (let ([new-highlight-row
                        (if (and p
                                 (not (send evt leaving?)))
                            (pt-y p)
                            #f)]
                       [old-highlight-row highlight-row])
                   (unless (equal? old-highlight-row new-highlight-row)
                     (set! highlight-row new-highlight-row)
                     (when new-highlight-row
                       (draw-row-label new-highlight-row))
                     (when old-highlight-row
                       (draw-row-label old-highlight-row))))
                 
                 (set! last-p p)
                 
                 ;; update the coordinates
                 (send dc set-pen WHITE-PEN)
                 (send dc set-brush WHITE-BRUSH)
                 (send dc draw-rectangle 0 0 row-label-width col-label-height)
                 (when (and (not (send evt leaving?))
                            p)
                   (unless (equal? coordinate-p p)
                     (let* ([i (pt-x p)]
                            [j (pt-y p)]
                            [string (loc->string (+ i 1) (+ j 1))]
                            [width (get-string-width string)]
                            [height (get-string-height string)]
                            [sx (- (/ row-label-width 2)
                                   (/ width 2))]
                            [sy (- (/ col-label-height 2)
                                   (/ height 2))])
                       (send dc draw-text string sx sy)))))]
              [(send evt button-down?)
               (set! draw-small-start-p p)
               (set! draw-small-end-p p)
               (set! modifier-on? (check-modifier evt))
               (when p
                 (paint-rect (pt-x p) (pt-y p)))]
              [(send evt button-up?)
               (cond
                 [(and p (or (= (pt-x p) (pt-x draw-small-start-p))
                             (= (pt-y p) (pt-y draw-small-start-p))))
                  (let ([new (new-brush (get-raw-rect 
                                         (pt-x draw-small-start-p)
                                         (pt-y draw-small-start-p))
                                        (check-modifier evt))])
                    ;(set! undo-history (cons (make-ado i j prev new) undo-history))
                    ;(set! redo-history null)
                    (let ([x-small (min (pt-x draw-small-start-p) (pt-x p))]
                          [x-large (max (pt-x draw-small-start-p) (pt-x p))]
                          [y-small (min (pt-y draw-small-start-p) (pt-y p))]
                          [y-large (max (pt-y draw-small-start-p) (pt-y p))])
                      
                      (set! draw-small-start-p #f)
                      (set! draw-small-end-p #f)
                      (set! modifier-on? #f)
                      
                      (let loop ([x x-small])
                        (when (<= x x-large)
                          (let loop ([y y-small])
                            (when (<= y y-large)
                              (set-raw-rect x y new)
                              (paint-rect x y)
                              (loop (+ y 1))))
                          (loop (+ x 1))))))]
                 [else
                  (let ([old-draw-small-start-p draw-small-start-p]
                        [old-draw-small-end-p draw-small-end-p])
                    (set! draw-small-start-p #f)
                    (set! draw-small-end-p #f)
                    (set! modifier-on? (check-modifier evt))
                    (when (and old-draw-small-start-p
                               old-draw-small-end-p)
                      (update-range-of-rects old-draw-small-start-p
                                             old-draw-small-end-p)))])])))]
      [define/override on-paint
	(lambda ()
	  (let ([dc (get-dc)])
	    (send dc clear)
	    (let-values ([(width height) (get-client-size)])
              
	      (send dc set-pen LINES/NUMBERS-PEN)
              (let loop ([i grid-x-size])
                (cond
		  [(zero? i) (void)]
		  [else (let loop ([j grid-y-size])
			  (cond
                            [(zero? j) (void)]
                            [else (paint-rect/lines-numbers-pen (- i 1) (- j 1))
                                  (loop (- j 1))]))
			(loop (- i 1))]))
              
	      (let loop ([l (get-col-numbers)]
			 [n 0])
		(cond
                  [(null? l) (void)]
                  [else
                   
                   (draw-col-label n)
                   (loop (cdr l) (+ n 1))]))
              
	      (let loop ([l (get-row-numbers)]
			 [n 0])
		(cond
                  [(null? l) (void)]
                  [else
                   (if (and last-p
                            (= (pt-y last-p) n))
                       (begin
                         (send dc set-pen BAR-PEN)
                         (send dc set-brush BAR-BRUSH))
                       (begin
                         (send dc set-pen WHITE-PEN)
                         (send dc set-brush WHITE-BRUSH)))
                   (draw-row-label n)
                   (loop (cdr l)
                         (+ n 1))]))
	      
	      (void))))]
      
      [define/public calculate-row-margins
        (lambda ()
          (let* ([dc (get-dc)])
            (set! row-label-width
                  (max (get-string-width (loc->string grid-x-size grid-y-size))
                       (apply max (map (lambda (x) (+ x-margin
                                                      (get-string-width (get-row-label-string x))
                                                      x-margin))
                                       (get-row-numbers)))))
            
            (let-values ([(width height descent ascent) (send dc get-text-extent "0123456789")])
              (set! row-label-height (+ y-margin height y-margin)))))]
      [define/public calculate-col-margins
        (lambda ()
          (let* ([dc (get-dc)])
            
            (set! col-label-height
                  (max
                   (get-string-height/descent (loc->string grid-x-size grid-y-size))
                   (apply max
                          (map (lambda (l)
                                 (let* ([strs (get-col-label-strings l)]
                                        [margins (* (length strs) y-margin)]
                                        [height (apply + (map (lambda (x) (get-string-height x)) strs))])
                                   (+ margins height)))
                               (get-col-numbers)))))
            
            (set! col-label-width
                  (apply max
                         (map (lambda (l)
                                (let ([label-strings (get-col-label-strings l)])
                                  (if (null? label-strings)
                                      (+ x-margin x-margin) ;; Minimum column label width (no labels)
                                      (apply max
                                             (map (lambda (x) (+ x-margin
                                                                 (get-string-width x)
                                                                 x-margin))
                                                  label-strings)))))
                              (get-col-numbers))))))]
      [define/public update-min-spacing
        (lambda ()
          (min-width (inexact->exact (+ row-label-width (* grid-x-size col-label-width))))
          (min-height (inexact->exact (+ col-label-height (* grid-y-size row-label-height)))))]
      
      (inherit min-width min-height)
      (super-instantiate ())
      
      [define/public close-up
        (lambda ()
          (remove-pref-callback))]
      [define/public reset-font
        (lambda (font)
          (send (get-dc) set-font font)
          (calculate-row-margins)
          (calculate-col-margins)
          (update-min-spacing))]
      
      (define pref-callback (preferences:add-callback
                             'paint-by-numbers:font
                             (lambda (pref new-value)
                               (reset-font new-value))))
      [define/public (remove-pref-callback) (pref-callback)]
      
      (reset-font (preferences:get 'paint-by-numbers:font))
      (calculate-row-margins)
      (calculate-col-margins)
      (update-min-spacing)))
  
  (define design-paint-by-numbers-canvas%
    (class paint-by-numbers-canvas% 
      (init-field width height)
      [define row-spacing 5]
      [define col-spacing 5]
      [define row-numbers
        (vector->list (make-vector height (vector->list (make-vector row-spacing 1))))]
      [define col-numbers
        (vector->list (make-vector width (vector->list (make-vector col-spacing 1))))]
      
      [define/override get-max-col-entries
        (lambda ()
          col-spacing)]
      [define/override get-row-numbers
        (lambda ()
          row-numbers)]
      [define/override get-col-numbers
        (lambda ()
          col-numbers)]
      
      (inherit draw-col-label draw-row-label get-rect
               calculate-row-margins
               calculate-col-margins
               update-min-spacing
               on-paint)
      
      [define/private calculate-col/row
        (lambda (get-rect col/row-numbers num-row/cols)
          (let loop ([i num-row/cols]
                     [block-count 0]
                     [ans null])
            (cond
              [(zero? i) (if (= block-count 0)
                             ans
                             (cons block-count ans))]
              [else
               (let ([this (get-rect (- i 1))])
                 (case this
                   [(unknown off wrong)
                    (if (zero? block-count)
                        (loop (- i 1) 0 ans)
                        (loop (- i 1) 0 (cons block-count ans)))]
                   [(on) (loop (- i 1) (+ block-count 1) ans)]
                   [else (error 'calculate-col "unknown response from get-rect: ~a~n" this)]))])))]
      
      [define/private calculate-col
        (lambda (col)
          (calculate-col/row
           (lambda (i) (get-rect col i))
           col-numbers
           (length row-numbers)))]
      
      [define/private calculate-row
        (lambda (row)
          (calculate-col/row
           (lambda (i) (get-rect i row))
           row-numbers
           (length col-numbers)))]
      
      [define/private update-col/row
        (lambda (col/row col/row-numbers calculate-col/row draw-col/row-label)
          (let loop ([l col/row-numbers]
                     [n col/row])
            (cond
              [(null? l) (error 'update-col/row "col/row too big: ~a~n" col/row)]
              [(zero? n)
               (let ([new-col/row (calculate-col/row col/row)])
                 (set-car! l new-col/row)
                 (draw-col/row-label col/row))]
              [else
               (loop (cdr l)
                     (- n 1))])))]
      
      [define/private update-col
        (lambda (col)
          (update-col/row col
                          col-numbers
                          (lambda (x) (calculate-col x))
                          (lambda (n) (draw-col-label n)))
          (let ([len (length (list-ref col-numbers col))])
            (when (< col-spacing len)
              (set! col-spacing len)
              (calculate-col-margins)
              (update-min-spacing)
              (on-paint))))]
      
      [define/private update-row
        (lambda (row)
          (update-col/row row
                          row-numbers
                          (lambda (x) (calculate-row x))
                          (lambda (n) (draw-row-label n)))
          (let ([len (length (list-ref row-numbers row))])
            (when (< row-spacing len)
              (set! row-spacing len)
              (calculate-row-margins)
              (update-min-spacing)
              (on-paint))))]
      
      [define update-row-col? #t]
      [define/override set-raw-rect
        (lambda (i j n)
          (super set-raw-rect i j n)
          (when update-row-col?
            (update-col i)
            (update-row j)))]
      
      [define/private update-all-rows-cols
        (lambda ()
          (let loop ([i width])
            (unless (zero? i)
              (update-col (- i 1))
              (loop (- i 1))))
          (let loop ([i height])
            (unless (zero? i)
              (update-row (- i 1))
              (loop (- i 1)))))]
      (inherit set-rect)
      [define/public set-bitmap
        (lambda (bitmap)
          (set! update-row-col? #f)
          (let ([dc (make-object bitmap-dc% bitmap)]
                [c (make-object color%)]
                [width (send bitmap get-width)]
                [height (send bitmap get-height)]
                [warned? #f])
            (let loop ([i width])
              (unless (zero? i)
                (let loop ([j height])
                  (unless (zero? j)
                    (let ([m (- i 1)]
                          [n (- j 1)])
                      (send dc get-pixel m n c)
                      (when (and (not warned?)
                                 (not (or (and (= 0 (send c red))
                                               (= 0 (send c blue))
                                               (= 0 (send c green)))
                                          (and (= 255 (send c red))
                                               (= 255 (send c blue))
                                               (= 255 (send c green))))))
                        (set! warned? #t)
                        (message-box
                         "Paint by Numbers"
                         "WARNING: This is a color bitmap; non-white pixels will be considered black"))
                      (set-rect m n
                                (if (and (= 255 (send c red))
                                         (= 255 (send c blue))
                                         (= 255 (send c green)))
                                    'off
                                    'on)))
                    (loop (- j 1))))
                (loop (- i 1)))))
          
          (set! update-row-col? #t)
          (update-all-rows-cols))]
      
      [define/override set-grid
        (lambda (g)
          (set! update-row-col? #f)
          (super set-grid g)
          (set! update-row-col? #t)
          (update-all-rows-cols))]
      
      (super-instantiate () (row-numbers null) (col-numbers null))
      (set! row-numbers (vector->list (make-vector height null)))
      (set! col-numbers (vector->list (make-vector width null))))))
