1#lang racket/base
2(provide text-inline-overview@)
3
4(require racket/unit
5         mred/mred-sig
6         racket/class
7         "../preferences.rkt"
8         "text-sig.rkt"
9         "sig.rkt"
10         (prefix-in unsafe: (only-in racket/draw/private/bitmap make-bitmap)))
11
12(define-local-member-name
13  get-primary-bmp
14  get-secondary-bmp
15  maybe-queue-do-a-little-work?
16  do-a-little-work
17  do-all-of-the-work
18  up-to-date?
19  get-invalid-start
20  get-invalid-end)
21
22(define maximum-bitmap-width 200)
23
24(define-unit text-inline-overview@
25  (import mred^
26          [prefix color-prefs: framework:color-prefs^])
27  (export text-inline-overview^)
28
29  (define transparent-color (make-object color% 255 255 255 0))
30  (define extra-blue-parts-margin 10)
31  (define arrow-cursor (make-object cursor% 'arrow))
32
33  (define inline-overview<%> (interface ((class->interface text%))
34                               get-inline-overview-enabled?
35                               set-inline-overview-enabled?
36                               is-inline-overview-work-pending?
37                               ))
38  (define inline-overview-mixin
39    (mixin ((class->interface text%)) (inline-overview<%>)
40      (define is-do-a-little-work-enqueued? #f)
41      (define invalid-start #f)
42      (define invalid-end #f)
43      (define primary-bmp #f)
44      (define secondary-bmp #f)
45      (define bmp-width 0)
46      (define enabled? #f)
47      ;; known-blank : nat
48      ;; the lines after and including known-blank are known to be blank in the bitmap
49      (define known-blank +inf.0)
50
51      (define/public (is-inline-overview-work-pending?) is-do-a-little-work-enqueued?)
52
53      (define/public (get-inline-overview-enabled?) enabled?)
54      (define/public (set-inline-overview-enabled? _e?)
55        (define e? (and _e? #t))
56        (unless (equal? e? enabled?)
57          (set! enabled? e?)
58          (cond
59            [enabled?
60             (reset-entire-overview)]
61            [else
62             (invalidate-entire-overview-region #f)
63             (set! bmp-width 0)
64             (set! scratch-string #f)
65             (set! primary-bmp #f)
66             (set! secondary-bmp #f)
67             (set! known-blank +inf.0)])))
68
69      (define/private (reset-entire-overview)
70        (define h (last-paragraph))
71        (update-bmp-width 0 h)
72        (define to-create-h (+ h 20))
73        (unless (and primary-bmp
74                     (= (send primary-bmp get-width) bmp-width)
75                     (= (send primary-bmp get-height) to-create-h))
76          (set! primary-bmp (unsafe:make-bitmap bmp-width to-create-h))
77          (set! secondary-bmp (unsafe:make-bitmap bmp-width to-create-h))
78          (set! known-blank 0))
79        (union-invalid 0 h)
80        (maybe-queue-do-a-little-work?))
81
82      (define/public (get-primary-bmp) primary-bmp)
83      (define/public (get-secondary-bmp) secondary-bmp)
84
85      (super-new)
86
87      (define loading-file? #f)
88      (define/augment (on-load-file filename format)
89        (set! loading-file? #t)
90        (inner (void) on-load-file filename format))
91      (define/augment (after-load-file success?)
92        (inner (void) after-load-file success?)
93        (set! loading-file? #f)
94        (reset-entire-overview))
95
96      (define/augment (after-insert start len)
97        (inner (void) after-insert start len)
98        (when (and enabled? (not loading-file?))
99          (set! width-could-have-changed-since-last-do-a-little-work? #t)
100          (define ps (position-paragraph start))
101          (define pe (position-paragraph (+ start len)))
102          (cond
103            [(= ps pe)
104             ;; edit is just on a single line,
105             ;; so we mark that line as invalid
106             (union-invalid ps pe)]
107            [else
108             ;; the insertion spans multiple lines, so we
109             ;; copy the known good stuff to the secondary
110             ;; bitmap and swap the bitmaps and just give
111             ;; up on tracking which parts are blank
112             (set! known-blank +inf.0)
113             (define current-h (send primary-bmp get-height))
114             (define w (send primary-bmp get-width))
115             (define h
116               (cond
117                 [(>= (last-paragraph) current-h)
118                  (if (< (* 2 current-h) (last-paragraph))
119                      (+ 20 (last-paragraph))
120                      (* 2 current-h))]
121                 [else current-h]))
122             (define make-new-bitmaps? (not (= current-h h)))
123             (define bmp-to-draw-into
124               (if make-new-bitmaps?
125                   (unsafe:make-bitmap bmp-width h)
126                   secondary-bmp))
127             (define bdc (new bitmap-dc% [bitmap bmp-to-draw-into]))
128             (unless make-new-bitmaps? (send bdc erase))
129             (send bdc draw-bitmap-section primary-bmp 0 0 0 0 w ps)
130             (send bdc draw-bitmap-section primary-bmp 0 pe 0 ps w (- current-h ps))
131             (when make-new-bitmaps?
132               (set! secondary-bmp bmp-to-draw-into)
133               (set! primary-bmp (unsafe:make-bitmap bmp-width h))
134               (set! known-blank 0))
135             (swap-bitmaps)
136             (union-invalid ps pe)])
137          (maybe-queue-do-a-little-work?)))
138
139      (define/augment (on-delete start len)
140        (inner (void) on-delete start len)
141        (when (and enabled? (not loading-file?))
142          (set! width-could-have-changed-since-last-do-a-little-work? #t)
143          (define ps (position-paragraph start))
144          (define pe (position-paragraph (+ start len)))
145          (cond
146            [(= ps pe)
147             (union-invalid ps ps)]
148            [else
149             (set! known-blank +inf.0)
150             (define h (send secondary-bmp get-height))
151             (define w (send secondary-bmp get-width))
152             (define invalid-region-size (- (+ (last-paragraph) 1) pe))
153
154             (define bdc (new bitmap-dc% [bitmap secondary-bmp]))
155             (send bdc erase)
156
157             ; copy stuff before start of invalid region to other bitmap
158             (send bdc draw-bitmap-section primary-bmp 0 0 0 0 w ps)
159
160             ; copy stuff after end of invalid region to other bitmap
161             (send bdc draw-bitmap-section primary-bmp
162                   0 ps
163                   0 pe
164                   w invalid-region-size)
165             (send bdc set-pen "black" 1 'transparent)
166             (send bdc set-brush transparent-color 'solid)
167             (send bdc draw-rectangle
168                   0
169                   (- (last-paragraph) 1)
170                   w
171                   (- h ps (- invalid-region-size pe)))
172
173             (swap-bitmaps)
174
175             (union-invalid ps ps)])
176          (maybe-queue-do-a-little-work?)))
177
178      (define/augment (after-change-style start len)
179        (inner (void) after-change-style start len)
180        (when enabled?
181          (define ps (position-paragraph start))
182          (define pe (position-paragraph (+ start len)))
183          (union-invalid ps pe)
184          (maybe-queue-do-a-little-work?)))
185
186      (define last-time-on-paint-called #f)
187      (define/override (on-paint before? dc left top right bottom dx dy draw-caret)
188        (super on-paint before? dc left top right bottom dx dy draw-caret)
189        (when (and enabled? before? (get-admin))
190          (define-values (view-height
191                          bitmap-first-visible-paragraph
192                          top-paragraph
193                          bot-paragraph
194                          bitmap-x-coordinate
195                          bitmap-y-coordinate)
196            (get-bitmap-placement-info))
197
198          (when (or (<= left bitmap-x-coordinate right)
199                    (<= left (+ bitmap-x-coordinate bmp-width) right))
200            (define visible-height (- bot-paragraph top-paragraph))
201            (define old-pen (send dc get-pen))
202            (define old-brush (send dc get-brush))
203            (send dc set-pen "black" 1 'transparent)
204            (send dc set-brush
205                  (color-prefs:lookup-in-color-scheme 'framework:program-contour-current-location-bar)
206                  'solid)
207            (send dc draw-rectangle
208                  (- (+ dx bitmap-x-coordinate) extra-blue-parts-margin)
209                  (+ dy
210                     bitmap-y-coordinate
211                     (- top-paragraph bitmap-first-visible-paragraph))
212                  (+ extra-blue-parts-margin (send primary-bmp get-width))
213                  visible-height)
214            (send dc draw-bitmap-section primary-bmp
215                  (+ dx bitmap-x-coordinate)
216                  (+ dy bitmap-y-coordinate)
217                  0 bitmap-first-visible-paragraph
218                  (send primary-bmp get-width) view-height)
219
220            (send dc set-brush old-brush)
221            (send dc set-pen old-pen))))
222
223      (define/override (after-scroll-to)
224        (when enabled?
225          ;; we a scroll happens, we need to redraw
226          ;; the the entire overview region, as scrolling
227          ;; invalidates only the newly exposed region
228          (invalidate-entire-overview-region #f)))
229
230      (define/private (invalidate-entire-overview-region just-union?)
231        (define-values (view-height
232                        bitmap-first-visible-paragraph
233                        top-paragraph
234                        bot-paragraph
235                        bitmap-x-coordinate
236                        bitmap-y-coordinate)
237          (get-bitmap-placement-info))
238        (define x (- bitmap-x-coordinate extra-blue-parts-margin))
239        (define w (+ bmp-width extra-blue-parts-margin))
240        (cond
241          [just-union?
242           (union-region-to-invalidate x
243                                       bitmap-y-coordinate
244                                       w
245                                       view-height)]
246          [else
247           (invalidate-bitmap-cache x
248                                    bitmap-y-coordinate
249                                    w
250                                    view-height)]))
251
252      ;; pre: admin is not #f
253      (define/private (get-bitmap-placement-info)
254        ;; bitmap-first-visible-paragraph is which paragraph of the first
255        ;;   line of the bitmap that should be visible at the top of the screen.
256        ;; top-paragraph is the paragraph of the main body of text that's
257        ;;   at the top of the window
258        (define-values (x y view-width view-height) (get-view-info))
259        (define view-right (+ x view-width))
260
261        ;; in editor coordinates, the location where the upper-left part of the
262        ;; drawn part of the bitmap goes (the drawing uses draw-bitmap-section
263        ;; so this won't be where the upper-left corner of the
264        ;; content of the bitmap goes)
265        (define bitmap-x-coordinate (- view-right (send primary-bmp get-width)))
266        (define bitmap-y-coordinate y)
267
268        (define top-paragraph (xy-to-paragraph x y))
269        (define bot-paragraph (xy-to-paragraph x (+ y view-height)))
270        (define bitmap-first-visible-paragraph
271          (get-bitmap-first-visible-paragraph view-height top-paragraph))
272        (values view-height
273                bitmap-first-visible-paragraph
274                top-paragraph
275                bot-paragraph
276                bitmap-x-coordinate
277                bitmap-y-coordinate))
278
279      ;; pre: admin is not #f
280      (define/private (get-view-info)
281        (define xb (box 0))
282        (define yb (box 0))
283        (define wb (box 0))
284        (define hb (box 0))
285        (define admin (get-admin))
286        (send admin get-view xb yb wb hb)
287        (values (unbox xb) (unbox yb) (unbox wb) (unbox hb)))
288
289      (define/private (get-bitmap-first-visible-paragraph view-height top-paragraph)
290        (cond
291          [(<= (last-paragraph) view-height) 0]
292          ;top
293          [(> (- (ceiling (/ view-height 2)) top-paragraph) 0) 0]
294          [(< (- (last-paragraph) top-paragraph) (ceiling (/ view-height 2)))
295           (- (last-paragraph) view-height)]
296          ; subtract half the size of the editor from where we want to
297          ; draw the blue box to find the start of the bitmap
298          [else
299           (- top-paragraph (/ view-height 2))]))
300
301      (define/override (on-default-event event)
302        (cond
303          [(and enabled? (get-admin))
304           (cond
305             [(send event button-up? 'left)
306              (define-values (mx my) (dc-location-to-editor-location
307                                      (send event get-x)
308                                      (send event get-y)))
309              (define-values (view-height
310                              bitmap-first-visible-paragraph
311                              top-paragraph
312                              bot-paragraph
313                              bitmap-x-coordinate
314                              bitmap-y-coordinate)
315                (get-bitmap-placement-info))
316              (cond
317                [(mouse-event-in-range? event mx my
318                                        bitmap-x-coordinate bitmap-y-coordinate
319                                        bot-paragraph bitmap-first-visible-paragraph)
320                 (define p (+ (paragraph-start-position
321                               (inexact->exact
322                                (ceiling (+ (- my bitmap-y-coordinate)
323                                            bitmap-first-visible-paragraph))))
324                              (inexact->exact
325                               (ceiling
326                                (- mx bitmap-x-coordinate)))))
327                 (define by (box 0))
328                 (position-location p #f by)
329                 (begin-edit-sequence)
330                 (set-position p p)
331                 (scroll-editor-to 0 (- (unbox by) (/ view-height 2))
332                                   0 view-height
333                                   #t 'none)
334                 (end-edit-sequence)]
335                [else (super on-default-event event)])]
336             [else (super on-default-event event)])]
337          [else (super on-default-event event)]))
338
339      (define/override (adjust-cursor event)
340        (cond
341          [(and enabled? (get-admin))
342           (define-values (mx my) (dc-location-to-editor-location
343                                   (send event get-x)
344                                   (send event get-y)))
345           (define-values (view-height
346                           bitmap-first-visible-paragraph
347                           top-paragraph
348                           bot-paragraph
349                           bitmap-x-coordinate
350                           bitmap-y-coordinate)
351             (get-bitmap-placement-info))
352           (cond
353             [(mouse-event-in-range? event mx my
354                                     bitmap-x-coordinate bitmap-y-coordinate
355                                     bot-paragraph bitmap-first-visible-paragraph)
356              arrow-cursor]
357             [else (super adjust-cursor event)])]
358          [else (super adjust-cursor event)]))
359
360      (define/private (mouse-event-in-range? event mx my
361                                             bitmap-x-coordinate bitmap-y-coordinate
362                                             bot-paragraph bitmap-first-visible-paragraph)
363        (and (<= (- bitmap-x-coordinate extra-blue-parts-margin)
364                 mx
365                 (+ bitmap-x-coordinate (send primary-bmp get-width)))
366             (<= bitmap-y-coordinate
367                 my
368                 (+ bitmap-y-coordinate
369                    (- (last-paragraph) bitmap-first-visible-paragraph)))))
370
371
372      (inherit invalidate-bitmap-cache
373               split-snip get-snip-position
374               paragraph-start-position
375               dc-location-to-editor-location
376               paragraph-end-position
377               position-paragraph
378               position-location
379               last-paragraph
380               get-character
381               insert
382               delete
383               get-text
384               find-snip
385               get-canvas
386               get-admin
387               find-position
388               set-position
389               scroll-editor-to
390               begin-edit-sequence
391               end-edit-sequence)
392
393      (define/private (xy-to-paragraph x y)
394        (position-paragraph (find-position x y)))
395
396      (define/private (swap-bitmaps)
397        (define temp primary-bmp)
398        (set! primary-bmp secondary-bmp)
399        (set! secondary-bmp temp))
400
401      (define/private (update-invalid-start nstart)
402        (set! invalid-start nstart))
403
404      (define/private (union-invalid start end)
405        (set! invalid-start
406              (if invalid-start
407                  (min start invalid-start)
408                  start))
409        (set! invalid-end
410              (if invalid-end
411                  (max end invalid-end)
412                  end)))
413      (define/private (clear-invalid)
414        (set! invalid-start #f)
415        (set! invalid-end #f))
416
417      (define/private (update-bmp-width ps pe)
418        ;; initialize this to `1` so that we always have a non-empty bitmap
419        (define text-width 1)
420        (for ([i (in-range ps (+ 1 pe))])
421          (define w (- (paragraph-end-position i) (paragraph-start-position i)))
422          (set! text-width (max text-width w)))
423        (when (> text-width bmp-width)
424          (set! bmp-width (min maximum-bitmap-width (+ 20 text-width))))
425        (when (or (not scratch-string)
426                  (< (string-length scratch-string) bmp-width))
427          (set! scratch-string (make-string bmp-width))))
428
429      (define/public (maybe-queue-do-a-little-work?)
430        (let loop ()
431          (cond
432            [(or (up-to-date?) is-do-a-little-work-enqueued?)
433             (void)]
434            [else
435             (set! is-do-a-little-work-enqueued? #t)
436             (queue-callback
437              (λ ()
438                (set! is-do-a-little-work-enqueued? #f)
439                (when enabled?
440                  (do-a-little-work)
441                  (loop)))
442              #f)])))
443
444      (define/public (do-all-of-the-work)
445        (let loop ()
446          (unless (up-to-date?)
447            (do-a-little-work)
448            (loop))))
449      (define width-could-have-changed-since-last-do-a-little-work? #f)
450      (define/public (do-a-little-work)
451        (cond
452          [(up-to-date?)
453           (void)]
454          [(or invalid-start invalid-end)
455           (define start-time (current-inexact-milliseconds))
456           (define bmp-width-changed?
457             (cond
458               [width-could-have-changed-since-last-do-a-little-work?
459                (set! width-could-have-changed-since-last-do-a-little-work? #f)
460                (define previous-bmp-width bmp-width)
461                (update-bmp-width invalid-start invalid-end)
462                (not (= previous-bmp-width bmp-width))]
463               [else #f]))
464           (when bmp-width-changed?
465             (define new-primary-bmp (unsafe:make-bitmap bmp-width (send primary-bmp get-height)))
466             (define new-secondary-bmp (unsafe:make-bitmap bmp-width (send primary-bmp get-height)))
467             (define bdc (new bitmap-dc% [bitmap primary-bmp]))
468             (send bdc set-bitmap new-primary-bmp)
469             (send bdc erase)
470             (send bdc draw-bitmap primary-bmp 0 0)
471             (set! primary-bmp new-primary-bmp)
472             (set! secondary-bmp new-secondary-bmp)
473             (set! known-blank 0))
474           (define start-of-updated-lines invalid-start)
475           (define end-of-updated-lines
476             (let loop ([line-line-last-snip #f]
477                        [y invalid-start])
478               (define relevant-portion-known-blank? (>= y known-blank))
479               (cond
480                 [(= y invalid-end)
481                  (update-one-line y line-line-last-snip relevant-portion-known-blank?)
482                  (clear-invalid)
483                  y]
484                 [else
485                  (define this-line-last-snip
486                    (update-one-line y line-line-last-snip relevant-portion-known-blank?))
487                  (cond
488                    [(< (+ start-time 10) (current-inexact-milliseconds))
489                     (update-invalid-start (+ 1 y))
490                     y]
491                    [else
492                     (loop this-line-last-snip
493                           (+ y 1))])])))
494           (set! known-blank (if invalid-start
495                                 (max known-blank invalid-start)
496                                 +inf.0))
497           (when (get-admin)
498             (define-values (view-height
499                             bitmap-first-visible-paragraph
500                             top-paragraph
501                             bot-paragraph
502                             bitmap-x-coordinate
503                             bitmap-y-coordinate)
504               (get-bitmap-placement-info))
505             (cond
506               [bmp-width-changed?
507                (invalidate-entire-overview-region #t)]
508               [else
509                (union-region-to-invalidate
510                 (- bitmap-x-coordinate extra-blue-parts-margin)
511                 (+ (- bitmap-y-coordinate
512                       bitmap-first-visible-paragraph)
513                    start-of-updated-lines)
514                 (+ bmp-width extra-blue-parts-margin)
515                 (+ (- end-of-updated-lines start-of-updated-lines) 1))]))]
516          [else ;; region-to-invalidate must be #t here
517           (invalidate-region-to-invalidate)]))
518
519      ;; (or/c #f (vector l t r b <time-first-invalid-region-was-known>))
520      (define region-to-invalidate #f)
521      (define/private (union-region-to-invalidate l t w h)
522        (define r (+ l w))
523        (define b (+ t h))
524        (cond
525          [region-to-invalidate
526           (set! region-to-invalidate
527                 (vector (min l (vector-ref region-to-invalidate 0))
528                         (min t (vector-ref region-to-invalidate 1))
529                         (max r (vector-ref region-to-invalidate 2))
530                         (max b (vector-ref region-to-invalidate 3))
531                         (vector-ref region-to-invalidate 4)))]
532          [else
533           (set! region-to-invalidate (vector l t r b (current-inexact-milliseconds)))])
534
535        (cond
536          [(< (+ (vector-ref region-to-invalidate 4) 1000.) (current-inexact-milliseconds))
537           ;; when we have had an invalid region for a while, then
538           ;; just go ahead an invalidate it so the user sees some progress
539           (invalidate-region-to-invalidate)]
540          [else
541           ;; when we have an invalid region that starts in the viewing
542           ;; region but continues on beyond the viewing region, then
543           ;; we draw it right now (even if we are not finished computing
544           ;; the entire overview)
545           ;; the thought is that the user might not be scrolling, and
546           ;; so this region is the one that they're going to be
547           ;; looking at, so lets draw it right away instead of making
548           ;; them wait until we've figured out the overview for the
549           ;; entire file (which might be big)
550           (define-values (x y view-width view-height) (get-view-info))
551           (define view-b (+ y view-height))
552           (define top-inside? (<= y (vector-ref region-to-invalidate 1) view-b))
553           (define bottom-inside? (<= y (vector-ref region-to-invalidate 3) view-b))
554           (when (and top-inside? (not bottom-inside?))
555             (invalidate-region-to-invalidate))]))
556
557      ;; pre: region-to-invalidate =/= #f
558      (define/private (invalidate-region-to-invalidate)
559        (invalidate-bitmap-cache (vector-ref region-to-invalidate 0)
560                                 (vector-ref region-to-invalidate 1)
561                                 (- (vector-ref region-to-invalidate 2)
562                                    (vector-ref region-to-invalidate 0))
563                                 (- (vector-ref region-to-invalidate 3)
564                                    (vector-ref region-to-invalidate 1)))
565        (set! region-to-invalidate #f))
566
567      (define scratch-string #f)
568      ;; update-one-line : nat bitmap-dc% (or/c #f snip%) nat -> (or/c #f snip%)
569      ;; it returns the last snip on the line (if it has one to return)
570      ;; and that value is passed back into the function so it can shortcircuit
571      ;; the call to `find-snip`
572      (define/private (update-one-line y last-lines-last-snip relevant-portion-known-blank?)
573        (define para-start (paragraph-start-position y))
574        (define this-lines-first-snip
575          (if last-lines-last-snip
576              (send-generic last-lines-last-snip snip-next)
577              (find-snip para-start 'after-or-none)))
578        (let loop ([snip this-lines-first-snip]
579                   [x 0])
580          (cond
581            [snip
582             ;; we didn't run past the end of the buffer
583             ;; iterate over the current snip's content
584             ;; filling in this line of the bitmap
585             (define count (send-generic snip snip-get-count))
586             (send-generic snip snip-get-text! scratch-string 0 (min maximum-bitmap-width count) 0)
587             (cond
588               [(and (= 1 count)
589                     (eq? #\newline (string-ref scratch-string 0)))
590                (copy-single-line-bytes-out y x)
591                (unless relevant-portion-known-blank?
592                  (erase-rest-of-line primary-bmp bmp-width x y))
593                snip]
594               [(< x maximum-bitmap-width)
595                (setup-color (send (send-generic snip snip-get-style) get-foreground))
596                (for ([ch (in-string scratch-string)]
597                      [i (in-range x (min maximum-bitmap-width (+ count x)))])
598                  (cond
599                    [(char-whitespace? ch)
600                     (set-transparent-pixel i)]
601                    [else
602                     (set-colored-pixel i)]))
603                (loop (send-generic snip snip-next)
604                      (+ x count))]
605               [else
606                (copy-single-line-bytes-out y x)
607                #f])]
608            [else
609             (copy-single-line-bytes-out y x)
610             (unless relevant-portion-known-blank?
611               (erase-rest-of-line primary-bmp bmp-width x y))
612             #f])))
613
614      (define single-line-bytes (make-bytes (* maximum-bitmap-width 4)))
615      (define (copy-single-line-bytes-out y _w)
616        (define w (min maximum-bitmap-width _w))
617        (send-generic primary-bmp bitmap-set-argb-pixels 0 y w 1 single-line-bytes))
618
619      (define color-bytes (bytes 0 0 0 0))
620      (define/private (setup-color c)
621        (define α (send-generic c color-alpha))
622        (cond
623          [(= α 1.0)
624           (bytes-set! color-bytes 0 255)]
625          [else
626           (bytes-set! color-bytes 0 (inexact->exact (round (* α 255))))])
627        (bytes-set! color-bytes 1 (send-generic c color-red))
628        (bytes-set! color-bytes 2 (send-generic c color-green))
629        (bytes-set! color-bytes 3 (send-generic c color-blue)))
630      (define/private (set-colored-pixel x)
631        (bytes-copy! single-line-bytes (* x 4) color-bytes))
632      (define/private (set-transparent-pixel x)
633        (bytes-copy! single-line-bytes (* x 4) transparent-bytes 0 4))
634
635      (define/public (up-to-date?)
636        (and (not invalid-start)
637             (not invalid-end)
638             (not region-to-invalidate)))
639      (define/public (get-invalid-start)
640        invalid-start)
641      (define/public (get-invalid-end)
642        invalid-end)))
643
644  (define snip-get-style (generic snip% get-style))
645  (define snip-get-count (generic snip% get-count))
646  (define snip-get-text! (generic snip% get-text!))
647  (define snip-next (generic snip% next))
648  (define bitmap-set-argb-pixels (generic bitmap% set-argb-pixels))
649  (define color-red (generic color% red))
650  (define color-green (generic color% green))
651  (define color-blue (generic color% blue))
652  (define color-alpha (generic color% alpha)))
653
654;; this is a cheat, as we get bitmap% both from
655;; the unit import and from the a direct require
656;; of racket/gui/base, meaning that we're not really
657;; parameterized over mred^
658(require (only-in racket/gui/base bitmap%))
659
660;; provided for the test suite
661(provide set-transparent-pixels
662         erase-rest-of-line
663         transparent-bytes-count
664         maximum-bitmap-width
665         do-all-of-the-work
666         get-primary-bmp)
667
668(define bitmap-set-argb-pixels (generic bitmap% set-argb-pixels))
669(define transparent-bytes
670  (bytes 0 255 255 255 0 255 255 255
671         0 255 255 255 0 255 255 255
672         0 255 255 255 0 255 255 255
673         0 255 255 255 0 255 255 255))
674(define transparent-bytes-count (/ (bytes-length transparent-bytes) 4))
675(define (set-transparent-pixels bmp x y n)
676  (send-generic bmp bitmap-set-argb-pixels x y n 1 transparent-bytes))
677
678(define (erase-rest-of-line bmp w x y)
679  (cond
680    [(>= x w) (void)]
681    [else
682     (for ([x (in-range x (- w transparent-bytes-count) transparent-bytes-count)])
683       (set-transparent-pixels bmp x y transparent-bytes-count))
684     (define leftover (modulo (- w x) transparent-bytes-count))
685     (cond
686       [(= 0 leftover)
687        (set-transparent-pixels bmp
688                                (- w transparent-bytes-count)
689                                y
690                                transparent-bytes-count)]
691       [else
692        (set-transparent-pixels bmp (- w leftover) y leftover)])]))
693