gui/gui-lib/framework/private/frame.rkt
Robby Findler ce1ded41f2 fix leak
2016-07-30 20:30:48 -05:00

2886 lines
114 KiB
Racket

#lang racket/unit
(require string-constants
racket/class
racket/contract/base
racket/path
"search.rkt"
"sig.rkt"
"../preferences.rkt"
"../gui-utils.rkt"
"bday.rkt"
"gen-standard-menus.rkt"
"interfaces.rkt"
framework/private/focus-table
mrlib/close-icon
mred/mred-sig)
(import mred^
[prefix group: framework:group^]
[prefix preferences: framework:preferences^]
[prefix icon: framework:icon^]
[prefix handler: framework:handler^]
[prefix application: framework:application^]
[prefix panel: framework:panel^]
[prefix finder: framework:finder^]
[prefix keymap: framework:keymap^]
[prefix text: framework:text^]
[prefix pasteboard: framework:pasteboard^]
[prefix editor: framework:editor^]
[prefix canvas: framework:canvas^]
[prefix menu: framework:menu^]
[prefix racket: framework:racket^]
[prefix exit: framework:exit^]
[prefix comment-box: framework:comment-box^])
(export (rename framework:frame^
[-editor<%> editor<%>]
[-pasteboard% pasteboard%]
[-text% text%]))
(init-depend mred^ framework:text^ framework:canvas^)
(define (reorder-menus frame)
(define items (send (send frame get-menu-bar) get-items))
(define (find-menu name)
(ormap (λ (i) (and (string=? (send i get-plain-label) name) i))
items))
(let* ([file-menu (find-menu (string-constant file-menu))]
[edit-menu (find-menu (string-constant edit-menu))]
[windows-menu (or (find-menu (string-constant windows-menu))
(find-menu (string-constant tabs-menu)))]
[help-menu (find-menu (string-constant help-menu))]
[other-items
(remq* (list file-menu edit-menu windows-menu help-menu) items)]
[re-ordered (filter values `(,file-menu ,edit-menu
,@other-items
,windows-menu ,help-menu))])
(for-each (λ (item) (send item delete)) items)
(for-each (λ (item) (send item restore)) re-ordered)))
(define (remove-empty-menus frame)
(define menus (send (send frame get-menu-bar) get-items))
(for-each (λ (menu) (send menu delete)) menus)
(for-each (λ (menu)
(when (pair? (send menu get-items)) (send menu restore)))
menus))
(define add-snip-menu-items
(lambda (edit-menu c% [func void])
(let* ([get-edit-target-object
(λ ()
(let ([menu-bar
(let loop ([p (send edit-menu get-parent)])
(cond
[(is-a? p menu-bar%)
p]
[(is-a? p menu%)
(loop (send p get-parent))]
[else #f]))])
(and menu-bar
(let ([frame (send menu-bar get-frame)])
(send frame get-edit-target-object)))))]
[edit-menu:do
(λ (const)
(λ (menu evt)
(let ([edit (get-edit-target-object)])
(when (and edit
(is-a? edit editor<%>))
(send edit do-edit-operation const)))
#t))]
[on-demand
(λ (menu-item)
(let ([edit (get-edit-target-object)])
(send menu-item enable (and edit (is-a? edit editor<%>)))))]
[insert-comment-box
(λ ()
(let ([text (get-edit-target-object)])
(when text
(let ([snip (make-object comment-box:snip%)])
(send text insert snip)
(send text set-caret-owner snip 'global)))))])
(let ([item
(new c%
[label (string-constant insert-comment-box-menu-item-label)]
[parent edit-menu]
[callback (λ (x y) (insert-comment-box))]
[demand-callback on-demand])])
(func item))
(define plain-insert-callback (edit-menu:do 'insert-image))
(define lowercase-imgs "*.png;*.jpg;*.jpeg;*.gif;*.xpm;*.bmp")
(define imgs (string-append
lowercase-imgs ";"
(string-upcase lowercase-imgs)))
(let ([item
(new c%
[label (string-constant insert-image-item)]
[parent edit-menu]
[callback (λ (menu evt)
(parameterize ([finder:default-filters
`(["Image" ,imgs]
["Any" "*.*"])])
(plain-insert-callback menu evt)))]
[demand-callback on-demand])])
(func item))
(void))))
(define frame-width 600)
(define frame-height 650)
(let ([window-trimming-upper-bound-width 20]
[window-trimming-upper-bound-height 50])
(let-values ([(w h) (get-display-size)])
(set! frame-width (min frame-width (- w window-trimming-upper-bound-width)))
(set! frame-height (min frame-height (- h window-trimming-upper-bound-height)))))
(define basic<%> frame:basic<%>)
(define focus-table<%> (interface (top-level-window<%>)))
(define focus-table-mixin
(mixin (top-level-window<%>) (focus-table<%>)
(inherit get-eventspace)
(define/override (show on?)
(define old (remove this (frame:lookup-focus-table (get-eventspace))))
(define new (if on? (cons this old) old))
(frame:set-focus-table (get-eventspace) new)
(super show on?))
(define/augment (on-close)
(frame:set-focus-table (get-eventspace) (remove this (frame:lookup-focus-table (get-eventspace))))
(inner (void) on-close))
(super-new)
(frame:set-focus-table (get-eventspace) (frame:lookup-focus-table (get-eventspace)))))
(define basic-mixin
(mixin ((class->interface frame%)) (basic<%>)
(define/override (show on?)
(if on?
(send (group:get-the-frame-group) insert-frame this)
(send (group:get-the-frame-group) remove-frame this))
(super show on?))
(define/override (can-exit?)
(and (exit:user-oks-exit)
(begin
(exit:set-exiting #t)
(let ([res (exit:can-exit?)])
(unless res
(exit:set-exiting #f))
res))))
(define/override (on-exit)
(exit:on-exit)
(queue-callback
(λ ()
(exit)
(exit:set-exiting #f))))
(define/public (make-visible filename) (void))
(define/public get-filename
(case-lambda
[() (get-filename #f)]
[(b) #f]))
(define/public (editing-this-file? filename) #f)
(define/override (on-superwindow-show shown?)
(send (group:get-the-frame-group) frame-shown/hidden this)
(super on-superwindow-show shown?))
(define after-init? #f)
(define/override on-drop-file
(λ (filename)
(handler:edit-file filename)))
;; added call to set label here to hopefully work around a problem in mac mred
(inherit set-label change-children)
(define/override after-new-child
(λ (child)
(when after-init?
(change-children (λ (l) (remq child l)))
(error 'frame:basic-mixin
(string-append
"do not add children directly to a frame:basic (unless using make-root-area-container); "
"use the get-area-container method instead")))))
(define/public get-area-container% (λ () vertical-panel%))
(define/public get-menu-bar% (λ () menu-bar%))
(define/public make-root-area-container
(λ (% parent)
(make-object % parent)))
(inherit on-close can-close?)
(define/public (close)
(when (can-close?)
(on-close)
(show #f)))
(inherit accept-drop-files)
(super-new)
(accept-drop-files #t)
(inherit set-icon)
(let ([icon (current-icon)])
(when icon
(if (pair? icon)
(let ([small (car icon)]
[large (cdr icon)])
(set-icon small (send small get-loaded-mask) 'small)
(set-icon large (send large get-loaded-mask) 'large))
(set-icon icon (send icon get-loaded-mask) 'both))))
(group:create-windows-menu (make-object (get-menu-bar%) this))
(reorder-menus this)
[define panel (make-root-area-container (get-area-container%) this)]
(define/public (get-area-container) panel)
(set! after-init? #t)))
(define current-icon (make-parameter #f))
(define size-pref<%>
(interface (basic<%>)
adjust-size-when-monitor-setup-changes?))
(define-local-member-name monitor-setup-changed)
(define size-pref-mixin
(mixin (basic<%>) (size-pref<%>)
(init-field size-preferences-key
[position-preferences-key #f])
(inherit is-maximized?)
(define/override (on-size w h)
;; if the monitor state is currently fluxuating, then
;; don't save preferences values, since the OS is doing
;; some adjustments for us.
(when (or (equal? (compute-current-monitor-information)
latest-monitor-information)
(not (adjust-size-when-monitor-setup-changes?)))
(define old-table (preferences:get size-preferences-key))
(define new-val
(cond
[(is-maximized?)
(define old (or (hash-ref old-table latest-monitor-information #f)
(hash-ref old-table #f)))
(cons #t (cdr old))]
[else
(list #f w h)]))
(preferences:set size-preferences-key
(hash-set*
old-table
latest-monitor-information new-val
#f new-val)))
(super on-size w h))
(define on-move-timer-arg-x #f)
(define on-move-timer-arg-y #f)
(define on-move-timer-arg-max? #f)
(define on-move-callback-running? #f)
(define/override (on-move x y)
(when position-preferences-key
(unless on-move-callback-running?
(set! on-move-callback-running? #t)
(queue-callback
(λ ()
(set! on-move-callback-running? #f)
(unless on-move-timer-arg-max?
;; if the monitor state is currently fluxuating, then
;; don't save preferences values, since the OS is doing
;; some adjustments for us.
(when (or (equal? (compute-current-monitor-information)
latest-monitor-information)
(not (adjust-size-when-monitor-setup-changes?)))
(define-values (monitor delta-x delta-y) (find-closest on-move-timer-arg-x on-move-timer-arg-y))
(define old-table (preferences:get position-preferences-key))
(define val (list monitor delta-x delta-y))
(preferences:set position-preferences-key
(hash-set*
old-table
latest-monitor-information val
#f val)))))
#f))
(set! on-move-timer-arg-x x)
(set! on-move-timer-arg-y y)
(set! on-move-timer-arg-max? (is-maximized?)))
(super on-move x y))
(define/augment (display-changed)
(restart-montior-information-timer)
(inner (void) display-changed))
;; if all of the offsets have some negative direction, then
;; just keep the thing relative to the original montior; otherwise
;; make it relative to whatever origin it is closest to.
(define/private (find-closest x y)
(define closest 0)
(define-values (delta-x delta-y dist) (find-distance x y 0))
(for ([m (in-range 1 (get-display-count))])
(define-values (new-delta-x new-delta-y new-dist)
(find-distance x y m))
(when (and new-delta-x
new-delta-y
(new-delta-x . >= . 0)
(new-delta-y . >= . 0))
(when (< new-dist dist)
(set! closest m)
(set!-values (delta-x delta-y dist) (values new-delta-x new-delta-y new-dist)))))
(values closest delta-x delta-y))
(define/private (find-distance x y mon)
(define-values (delta-x delta-y)
(let-values ([(l t) (get-display-left-top-inset #:monitor mon)])
(values (+ x l) (+ y t))))
(values delta-x
delta-y
(sqrt (+ (* delta-x delta-x)
(* delta-y delta-y)))))
(inherit maximize)
(define/private (get-sizes/maximzed)
(define size-table (preferences:get size-preferences-key))
(define-values (maximized? w h) (apply values (or (hash-ref size-table latest-monitor-information #f)
(hash-ref size-table #f))))
(define-values (x y origin-still-visible?)
(cond
[position-preferences-key
(define pos-table (preferences:get position-preferences-key))
(define-values (monitor delta-x delta-y) (apply values (or (hash-ref pos-table latest-monitor-information #f)
(hash-ref pos-table #f))))
(define-values (l t) (get-display-left-top-inset #:monitor monitor))
(define-values (mw mh) (get-display-size #:monitor monitor))
(if (and l t mw mh)
(values (- delta-x l)
(- delta-y t)
(and (<= 0 l mw)
(<= 0 t mh)))
(values #f #f #f))]
[else
(values #f #f #f)]))
(define (already-one-there? x y w h)
(for/or ([fr (in-list (get-top-level-windows))])
(and (equal? x (send fr get-x))
(equal? y (send fr get-y))
(equal? w (send fr get-width))
(equal? h (send fr get-height))
(equal? maximized? (send fr is-maximized?)))))
(cond
[(or (and (already-one-there? x y w h)
(not maximized?))
(not origin-still-visible?))
;; these are the situations where we look for a different position of the window
(let loop ([n 50]
[x 0]
[y 0])
(cond
[(zero? n)
(values #f #f #f #f maximized?)]
[(already-one-there? x y w h)
(define-values (dw dh) (get-display-size #:monitor 0))
(define sw (- dw w))
(define sh (- dh h))
(if (or (<= sw 0)
(<= sh 0))
(values #f #f #f #f maximized?)
(loop (- n 1)
(modulo (+ x 20) (- dw w))
(modulo (+ y 20) (- dh h))))]
[else
(values w h x y maximized?)]))]
[else
(values w h x y maximized?)]))
(define/public (adjust-size-when-monitor-setup-changes?) #f)
(inherit begin-container-sequence
end-container-sequence
move
resize)
(define/public (monitor-setup-changed)
(when (adjust-size-when-monitor-setup-changes?)
(define-values (w h x y maximized?) (get-sizes/maximzed))
(when (and w h x y)
(begin-container-sequence)
(move x y)
(resize w h)
(when maximized?
(maximize #t))
(end-container-sequence))))
(let-values ([(w h x y maximized?) (get-sizes/maximzed)])
(cond
[(and w h x y)
(super-new [width w] [height h] [x x] [y y])]
[else
(super-new)])
(when maximized?
(maximize #t)))))
(define (setup-size-pref size-preferences-key w h
#:maximized? [maximized? #f]
#:position-preferences [position-preferences-key #f])
(preferences:set-default size-preferences-key
(hash #f (list maximized? w h))
(hash/c (or/c (listof (list/c any/c any/c any/c any/c))
#f)
(list/c boolean?
exact-nonnegative-integer?
exact-nonnegative-integer?)
#:immutable #t
#:flat? #t))
(when position-preferences-key
(preferences:set-default position-preferences-key
(hash #f (list 0 0 0))
(hash/c (or/c (listof (list/c any/c any/c any/c any/c))
#f)
(list/c exact-nonnegative-integer?
exact-integer?
exact-integer?)
#:immutable #t
#:flat? #t))))
(define (compute-current-monitor-information)
(filter
values
(for/list ([m (in-range (get-display-count))])
(define-values (left top) (get-display-left-top-inset #:monitor m))
(define-values (width height) (get-display-size #:monitor m))
(and left top width height
(list left top width height)))))
(define latest-monitor-information (compute-current-monitor-information))
(define pending-monitor-information #f)
(define montior-information-timer
(new timer%
[notify-callback
(λ ()
(define new-monitor-information (compute-current-monitor-information))
(cond
[pending-monitor-information
(cond
[(equal? pending-monitor-information new-monitor-information)
(set! pending-monitor-information #f)
(set! latest-monitor-information new-monitor-information)
(queue-callback
(λ ()
(when (editor:get-change-font-size-when-monitors-change?)
(editor:set-current-preferred-font-size
(editor:get-current-preferred-font-size)))
(for ([frame (in-list (get-top-level-windows))])
(when (is-a? frame size-pref<%>)
(send frame monitor-setup-changed)))
#f))]
[else
(set! pending-monitor-information new-monitor-information)
(restart-montior-information-timer)])]
[else
(unless (equal? latest-monitor-information new-monitor-information)
(set! pending-monitor-information new-monitor-information)
(restart-montior-information-timer))]))]))
(define (restart-montior-information-timer)
(send montior-information-timer stop)
(send montior-information-timer start 250 #t))
(define register-group<%> (interface ()))
(define register-group-mixin
(mixin (basic<%>) (register-group<%>)
(define/augment (can-close?)
(and (inner #t can-close?)
(group:can-close-check)))
(define/augment (on-close)
(send (group:get-the-frame-group)
remove-frame
this)
(inner (void) on-close)
(group:on-close-action))
(define/override (on-activate on?)
(super on-activate on?)
(when on?
(send (group:get-the-frame-group) set-active-frame this)))
(super-new)
(send (group:get-the-frame-group) insert-frame this)))
(define locked-message (string-constant read-only))
(define lock-canvas%
(class canvas%
(field [locked? #f])
(inherit refresh)
(define/public (set-locked l)
(unless (eq? locked? l)
(set! locked? l)
(setup-sizes)
(refresh)))
(inherit get-client-size get-dc)
(define/override (on-paint)
(let* ([dc (get-dc)]
[draw
(λ (str bg-color bg-style line-color line-style)
(send dc set-font small-control-font)
(let-values ([(w h) (get-client-size)]
[(tw th _1 _2) (send dc get-text-extent str)])
(send dc set-pen (send the-pen-list find-or-create-pen line-color 1 line-style))
(send dc set-brush (send the-brush-list find-or-create-brush bg-color bg-style))
(send dc draw-rectangle 0 0 w h)
(send dc draw-text str
(- (/ w 2) (/ tw 2))
(- (/ h 2) (/ th 2)))))])
(when locked?
(draw locked-message "yellow" 'solid "black" 'solid))))
(inherit get-parent min-width min-height stretchable-width stretchable-height)
(define/private (setup-sizes)
(let ([dc (get-dc)])
(if locked?
(let-values ([(w h _1 _2) (send dc get-text-extent locked-message)])
(min-width (inexact->exact (floor (+ w 4))))
(min-height (inexact->exact (floor (+ h 2)))))
(begin
(min-width 0)
(min-height 0)))))
(super-new [style '(transparent no-focus)])
(send (get-dc) set-font small-control-font)
(setup-sizes)
(stretchable-width #f)
(stretchable-height #t)))
(define status-line<%>
(interface (basic<%>)
open-status-line
close-status-line
update-status-line))
;; status-line : (make-status-line symbol number)
(define-struct status-line (id count))
;; status-line-msg : (make-status-line-msg (is-a?/c message%) (union symbol #f))
(define-struct status-line-msg (message [id #:mutable]))
(define status-line-mixin
(mixin (basic<%>) (status-line<%>)
(field [status-line-container-panel #f]
;; status-lines : (listof status-line)
[status-lines null]
;; status-line-msgs : (listof status-line-msg)
[status-line-msgs null])
(define/override (make-root-area-container % parent)
(let* ([s-root (super make-root-area-container vertical-panel% parent)]
[r-root (make-object % s-root)])
(set! status-line-container-panel
(instantiate vertical-panel% ()
(parent s-root)
(stretchable-height #f)))
r-root))
(define/public (open-status-line id)
(do-main-thread
(λ ()
(when status-line-container-panel
(set! status-lines
(let loop ([status-lines status-lines])
(cond
[(null? status-lines)
(list (make-status-line id 1))]
[else (let ([status-line (car status-lines)])
(if (eq? id (status-line-id status-line))
(cons (make-status-line id (+ (status-line-count status-line) 1))
(cdr status-lines))
(cons status-line (loop (cdr status-lines)))))])))))))
(define/public (close-status-line id)
(do-main-thread
(λ ()
(when status-line-container-panel
;; decrement counter in for status line, or remove it if
;; counter goes to zero.
(set! status-lines
(let loop ([status-lines status-lines])
(cond
[(null? status-lines) (error 'close-status-line "status line not open ~e" id)]
[else (let* ([status-line (car status-lines)]
[this-line? (eq? (status-line-id status-line) id)])
(cond
[(and this-line? (= 1 (status-line-count status-line)))
(cdr status-lines)]
[this-line?
(cons (make-status-line id (- (status-line-count status-line) 1))
(cdr status-lines))]
[else (cons status-line (loop (cdr status-lines)))]))])))
;; make sure that there are only as many messages as different status lines, in total
(let ([status-line-msg (find-status-line-msg id)])
(when status-line-msg
(send (status-line-msg-message status-line-msg) set-label "")
(set-status-line-msg-id! status-line-msg #f)))
(let* ([msgs-that-can-be-removed (filter (λ (x) (not (status-line-msg-id x))) status-line-msgs)]
[max-to-include (length status-lines)]
[msgs-to-remove
(let loop ([n max-to-include]
[l msgs-that-can-be-removed])
(cond
[(null? l) l]
[(zero? n) l]
[else (loop (- n 1) (cdr l))]))])
(send status-line-container-panel
change-children
(λ (old-children)
(foldl (λ (status-line-msg l)
(remq (status-line-msg-message status-line-msg) l))
old-children
msgs-to-remove)))
(set! status-line-msgs
(let loop ([l msgs-to-remove]
[status-line-msgs status-line-msgs])
(cond
[(null? l) status-line-msgs]
[else (loop (cdr l)
(remq (car l) status-line-msgs))]))))))))
;; update-status-line : symbol (union #f string)
(define/public (update-status-line id msg-txt)
(do-main-thread
(λ ()
(unless (open-status-line? id)
(error 'update-status-line "unknown id ~e, other arg ~e" id msg-txt))
(if msg-txt
(cond
[(find-status-line-msg id)
=>
(λ (existing-status-line-msg)
(let ([msg (status-line-msg-message existing-status-line-msg)])
(unless (equal? (send msg get-label) msg-txt)
(send msg set-label msg-txt))))]
[(find-available-status-line-msg)
=>
(λ (available-status-line-msg)
(send (status-line-msg-message available-status-line-msg) set-label msg-txt)
(set-status-line-msg-id! available-status-line-msg id))]
[else
(set! status-line-msgs
(cons (make-new-status-line-msg id msg-txt)
status-line-msgs))])
(let ([status-line-msg (find-status-line-msg id)])
(when status-line-msg
(send (status-line-msg-message status-line-msg) set-label "")
(set-status-line-msg-id! status-line-msg #f)))))))
;; open-status-line? : symbol -> boolean
(define/private (open-status-line? id)
(let loop ([status-lines status-lines])
(cond
[(null? status-lines) #f]
[else
(let ([status-line (car status-lines)])
(or (eq? (status-line-id status-line) id)
(loop (cdr status-lines))))])))
;; find-available-status-line-msg : -> (union #f status-line-msg)
(define/private (find-available-status-line-msg)
(let loop ([status-line-msgs status-line-msgs])
(cond
[(null? status-line-msgs) #f]
[else (let ([status-line-msg (car status-line-msgs)])
(if (status-line-msg-id status-line-msg)
(loop (cdr status-line-msgs))
status-line-msg))])))
;; find-status-line-msg : symbol -> (union #f status-line-msg)
(define/private (find-status-line-msg id)
(let loop ([status-line-msgs status-line-msgs])
(cond
[(null? status-line-msgs) #f]
[else (let ([status-line-msg (car status-line-msgs)])
(if (eq? id (status-line-msg-id status-line-msg))
status-line-msg
(loop (cdr status-line-msgs))))])))
;; make-new-status-line-msg : symbol string -> status-line-msg
(define/private (make-new-status-line-msg id msg-txt)
(define msg
(new message%
[parent status-line-container-panel]
[stretchable-width #t]
[label ""]))
(send msg set-label msg-txt)
(make-status-line-msg msg id))
(inherit get-eventspace)
(define/private (do-main-thread t)
(let ([c-eventspace (current-eventspace)])
(if (and (eq? c-eventspace (get-eventspace))
(eq? (current-thread) (eventspace-handler-thread c-eventspace)))
(t)
(parameterize ([current-eventspace c-eventspace])
;; need high priority callbacks to ensure ordering wrt other callbacks
(queue-callback t #t)))))
(super-new)))
(define info<%> frame:info<%>)
(define magic-space 25)
(define info-mixin
(mixin (basic<%>) (info<%>)
[define rest-panel 'uninitialized-root]
[define super-root 'uninitialized-super-root]
(define/override (make-root-area-container % parent)
(let* ([s-root (super make-root-area-container
vertical-panel%
parent)]
[r-root (make-object % s-root)])
(set! super-root s-root)
(set! rest-panel r-root)
r-root))
(define info-canvas #f)
(define/public (get-info-canvas) info-canvas)
(define/public (set-info-canvas c) (set! info-canvas c))
(define/public (get-info-editor)
(and info-canvas
(send info-canvas get-editor)))
(define/public (determine-width string canvas edit)
(send edit set-autowrap-bitmap #f)
(send canvas call-as-primary-owner
(λ ()
(let ([lb (box 0)]
[rb (box 0)])
(send edit erase)
(send edit insert string)
(send edit position-location
(send edit last-position)
rb)
(send edit position-location 0 lb)
(send canvas min-width
(+ (get-client-width/view-delta edit canvas)
(- (inexact->exact (floor (unbox rb)))
(inexact->exact (floor (unbox lb))))))))))
(define outer-info-panel 'top-info-panel-uninitialized)
;; this flag is specific to this frame
;; the true state of the info panel is
;; the combination of this flag and the
;; 'framework:show-status-line preference
;; as shown in update-info-visibility
(define info-hidden? #f)
(define/public (hide-info)
(set! info-hidden? #t)
(update-info-visibility (preferences:get 'framework:show-status-line)))
(define/public (show-info)
(set! info-hidden? #f)
(update-info-visibility (preferences:get 'framework:show-status-line)))
(define/public (is-info-hidden?) info-hidden?)
(define/private (update-info-visibility pref-value)
(cond
[(or info-hidden? (not pref-value))
(send super-root change-children
(λ (l)
(if (memq outer-info-panel l)
(begin (unregister-collecting-blit gc-canvas)
(unregister-pref-save-callback)
(list rest-panel))
l)))]
[else
(send super-root change-children
(λ (l)
(if (memq outer-info-panel l)
l
(begin
(register-gc-blit)
(register-pref-save-callback)
(list rest-panel outer-info-panel)))))]))
[define close-panel-callback
(preferences:add-callback
'framework:show-status-line
(λ (p v)
(update-info-visibility v)))]
(define memory-cleanup void) ;; only for checkouts and nightly build users; used with memory-text
(define/augment (on-close)
(unregister-collecting-blit gc-canvas)
(unregister-pref-save-callback)
(close-panel-callback)
(memory-cleanup)
(inner (void) on-close))
(define icon-currently-locked? 'uninit)
(define/public (lock-status-changed)
(let ([info-edit (get-info-editor)])
(cond
[(not (object? lock-canvas))
(void)]
[(is-a? info-edit editor:file<%>)
(unless (send lock-canvas is-shown?)
(send lock-canvas show #t))
(let ([locked-now? (not (send info-edit get-read-write?))])
(unless (eq? locked-now? icon-currently-locked?)
(set! icon-currently-locked? locked-now?)
(when (object? lock-canvas)
(send lock-canvas set-locked locked-now?))))]
[else
(when (send lock-canvas is-shown?)
(send lock-canvas show #f))])))
(define/public (update-info) (lock-status-changed))
(super-new)
(set! outer-info-panel (make-object horizontal-panel% super-root))
(send outer-info-panel stretchable-height #f)
(define info-panel (new horizontal-panel% [parent outer-info-panel]))
(new grow-box-spacer-pane% [parent outer-info-panel])
(define/public (get-info-panel) info-panel)
(define/public (update-memory-text)
(for ([memory-canvas (in-list memory-canvases)])
(send memory-canvas set-str (format-number (current-memory-use)))))
(define/private (format-number n)
(let* ([mbytes (/ n 1024 1024)]
[before-decimal (floor mbytes)]
[after-decimal (modulo (floor (* mbytes 100)) 100)])
(string-append
(number->string before-decimal)
"."
(cond
[(<= after-decimal 9) (format "0~a" after-decimal)]
[else (number->string after-decimal)])
" MB")))
(define/private (pad-to-3 n)
(cond
[(<= n 9) (format "00~a" n)]
[(<= n 99) (format "0~a" n)]
[else (number->string n)]))
(define pref-save-canvas #f)
(set! pref-save-canvas (new pref-save-canvas% [parent (get-info-panel)]))
[define lock-canvas (make-object lock-canvas% (get-info-panel))]
; set up the memory use display in the status line
(let* ([panel (new horizontal-panel%
[parent (get-info-panel)]
[stretchable-width #f]
[stretchable-height #f])]
[ec (new position-canvas%
[parent panel]
[button-up
(λ (evt)
(cond
[(or (send evt get-alt-down)
(send evt get-control-down))
(dynamic-require 'framework/private/follow-log #f)]
[else
(collect-garbage)
(update-memory-text)]))]
[init-width "99.99 MB"])])
(set! memory-canvases (cons ec memory-canvases))
(update-memory-text)
(set! memory-cleanup
(λ ()
(set! memory-canvases (remq ec memory-canvases))))
(send panel stretchable-width #f))
(define gc-canvas (new bday-click-canvas% [parent (get-info-panel)] [style '(border no-focus)]))
(define/private (register-gc-blit)
(let ([onb (icon:get-gc-on-bitmap)]
[offb (icon:get-gc-off-bitmap)])
(when (and (send onb ok?)
(send offb ok?))
(register-collecting-blit gc-canvas
0 0
(send onb get-width)
(send onb get-height)
onb offb))))
(define pref-save-callback-registration #f)
(inherit get-eventspace)
(define/private (register-pref-save-callback)
(when pref-save-canvas
(set! pref-save-callback-registration
(preferences:register-save-callback
(λ (start?)
(cond
[(eq? (current-thread) (eventspace-handler-thread (get-eventspace)))
(send pref-save-canvas set-on? start?)]
[else
(queue-callback
(λ ()
(send pref-save-canvas set-on? start?)))]))))))
(define/private (unregister-pref-save-callback)
(when pref-save-callback-registration
(preferences:unregister-save-callback pref-save-callback-registration)))
(register-pref-save-callback)
(unless (preferences:get 'framework:show-status-line)
(send super-root change-children
(λ (l)
(list rest-panel))))
(register-gc-blit)
(let* ([gcb (icon:get-gc-on-bitmap)]
[gc-width (if (send gcb ok?)
(send gcb get-width)
10)]
[gc-height (if (send gcb ok?)
(send gcb get-height)
10)])
(send* gc-canvas
(min-client-width (max (send gc-canvas min-width) gc-width))
(min-client-height (max (send gc-canvas min-height) gc-height))
(stretchable-width #f)
(stretchable-height #f)))
(send* (get-info-panel)
(set-alignment 'right 'center)
(stretchable-height #f)
(spacing 3)
(border 3))))
(define (ensure-enough-width editor-canvas text)
(send editor-canvas call-as-primary-owner
(λ ()
(let ([delta (get-client-width/view-delta text editor-canvas)]
[lb (box 0)]
[rb (box 0)])
(send text position-location
(send text last-position)
rb)
(send text position-location 0 lb)
(let ([nw
(+ delta (- (inexact->exact (floor (unbox rb)))
(inexact->exact (floor (unbox lb)))))])
(when (< (send editor-canvas min-client-width) nw)
(send editor-canvas min-client-width nw)))))))
(define (get-client-width/view-delta position-edit position-canvas)
(let ([admin (send position-edit get-admin)]
[wb (box 0)])
(send admin get-view #f #f wb #f)
(let-values ([(cw ch) (send position-canvas get-client-size)])
(inexact->exact (floor (- cw (unbox wb)))))))
(define position-canvas%
(class canvas%
(inherit min-client-height min-client-width get-dc get-client-size refresh)
(init init-width)
(init-field [button-up #f])
(init-field [char-typed void])
(define str "")
(define/public (set-str _str)
(set! str _str)
(update-client-width str)
(refresh))
(define/private (update-client-width str)
(let ([dc (get-dc)])
(let-values ([(cw _4) (get-client-size)]
[(tw _1 _2 _3) (send dc get-text-extent str normal-control-font)])
(when (< cw tw)
(min-client-width (inexact->exact (ceiling tw)))))))
(define/override (on-paint)
(let ([dc (get-dc)])
(send dc set-font normal-control-font)
(let-values ([(cw ch) (get-client-size)]
[(tw th _1 _2) (send dc get-text-extent str)])
(send dc draw-text str 0 (/ (- ch th) 2)))))
(define/override (on-event evt)
(when button-up
(when (send evt button-up?)
(let-values ([(cw ch) (get-client-size)])
(when (and (<= (send evt get-x) cw)
(<= (send evt get-y) ch))
(if (procedure-arity-includes? button-up 1)
(button-up evt)
(button-up)))))))
(define/override (on-char evt)
(char-typed evt))
(super-new (style '(transparent no-focus)))
(let ([dc (get-dc)])
(let-values ([(_1 th _2 _3) (send dc get-text-extent str)])
(min-client-height (inexact->exact (floor th)))))
(update-client-width init-width)))
(define text-info<%> frame:text-info<%>)
(define text-info-mixin
(mixin (info<%>) (text-info<%>)
(inherit get-info-editor)
(define remove-first
(preferences:add-callback
'framework:col-offsets
(λ (p v)
(editor-position-changed-offset/numbers
v
(preferences:get 'framework:display-line-numbers))
#t)))
(define remove-second
(preferences:add-callback
'framework:display-line-numbers
(λ (p v)
(editor-position-changed-offset/numbers
(preferences:get 'framework:col-offsets)
v)
#t)))
(define/augment (on-close)
(remove-first)
(remove-second)
(inner (void) on-close))
[define last-start #f]
[define last-end #f]
[define last-params #f]
(define/private (editor-position-changed-offset/numbers offset? line-numbers?)
(let* ([edit (get-info-editor)]
[make-one
(λ (pos)
(if line-numbers?
(let* ([line (send edit position-paragraph pos)]
[col (find-col edit line pos)])
(format "~a:~a"
(add1 line)
(if offset?
(add1 col)
col)))
(format "~a" pos)))])
(cond
[(not (object? position-canvas))
(void)]
[edit
(unless (send position-canvas is-shown?)
(send position-canvas show #t))
(let ([start (send edit get-start-position)]
[end (send edit get-end-position)])
(unless (and last-start
(equal? last-params (list offset? line-numbers?))
(= last-start start)
(= last-end end))
(set! last-params (list offset? line-numbers?))
(set! last-start start)
(set! last-end end)
(when (object? position-canvas)
(change-position-edit-contents
(if (= start end)
(make-one start)
(string-append (make-one start)
"-"
(make-one end)))))))]
[else
(when (send position-canvas is-shown?)
(send position-canvas show #f))])))
;; find-col : text number number -> number
;; given a line number and a position, finds the
;; column number for that position
(define/private (find-col text line pos)
(let ([line-start (send text paragraph-start-position line)])
(if (= line-start pos)
0
(let loop ([col 0]
[snip (send text find-snip line-start 'after-or-none)])
(cond
[(and snip (is-a? snip tab-snip%))
;; assume cursor isn't in the middle of the tab snip
;; and that there is no tab array
(let ([twb (box 0)])
(send text get-tabs #f twb #f)
(let ([tw (floor (inexact->exact (unbox twb)))])
(loop (+ col (- tw (modulo col tw)))
(send snip next))))]
[snip
(let ([snip-position (send text get-snip-position snip)]
[snip-length (send snip get-count)])
(if (<= snip-position pos (+ snip-position snip-length))
(+ col (- pos snip-position))
(loop (+ col snip-length)
(send snip next))))]
[else
col])))))
[define anchor-last-state? #f]
[define overwrite-last-state? #f]
(define/private (update-ascii-art-enlarge-msg)
(define ascii-art-enlarge-mode?
(let ([e (get-info-editor)])
(and (is-a? e text:ascii-art-enlarge-boxes<%>)
(send e get-ascii-art-enlarge))))
(unless (eq? (and (member ascii-art-enlarge-mode-msg (send uncommon-parent get-children)) #t)
ascii-art-enlarge-mode?)
(if ascii-art-enlarge-mode?
(add-uncommon-child ascii-art-enlarge-mode-msg)
(remove-uncommon-child ascii-art-enlarge-mode-msg))))
;; this callback is kind of a hack. we know that when the set-ascii-art-enlarge
;; method of text:ascii-art-enlarge<%> is called that it changes the preferences
;; value so we will get called back here; it would be better if we could just
;; have the callback happen directly by overriding that method, but that causes
;; backwards incompatibility problems.
(define callback (λ (p v)
(queue-callback
(λ () (update-ascii-art-enlarge-msg))
#f)))
(preferences:add-callback 'framework:ascii-art-enlarge callback #t)
(field (macro-recording? #f))
(define/private (update-macro-recording-icon)
(unless (eq? (send macro-recording-message is-shown?)
macro-recording?)
(if macro-recording?
(add-uncommon-child macro-recording-message)
(remove-uncommon-child macro-recording-message))))
(define/public (set-macro-recording on?)
(set! macro-recording? on?)
(update-macro-recording-icon))
(define/public (anchor-status-changed)
(let ([info-edit (get-info-editor)]
[failed
(λ ()
(unless (eq? anchor-last-state? #f)
(set! anchor-last-state? #f)
(remove-uncommon-child anchor-message)))])
(cond
[info-edit
(let ([anchor-now? (send info-edit get-anchor)])
(unless (eq? anchor-now? anchor-last-state?)
(cond
[(object? anchor-message)
(if anchor-now?
(add-uncommon-child anchor-message)
(remove-uncommon-child anchor-message))
(set! anchor-last-state? anchor-now?)]
[else (failed)])))]
[else
(failed)])))
(define/public (editor-position-changed)
(editor-position-changed-offset/numbers
(preferences:get 'framework:col-offsets)
(preferences:get 'framework:display-line-numbers)))
(define/public (overwrite-status-changed)
(let ([info-edit (get-info-editor)]
[failed
(λ ()
(set! overwrite-last-state? #f)
(remove-uncommon-child overwrite-message))])
(cond
[info-edit
(let ([overwrite-now? (send info-edit get-overwrite-mode)])
(unless (eq? overwrite-now? overwrite-last-state?)
(cond
[(object? overwrite-message)
(if overwrite-now?
(add-uncommon-child overwrite-message)
(remove-uncommon-child overwrite-message))
(set! overwrite-last-state? overwrite-now?)]
[else
(failed)])))]
[else
(failed)])))
(define/public (add-line-number-menu-items menu)
(void))
(define/public (use-file-text-mode-changed)
(when (object? file-text-mode-msg)
(define ed (get-info-editor))
(send file-text-mode-msg-parent change-children
(λ (l)
(if (and (is-a? ed text:info<%>)
(eq? (system-type) 'windows)
(send ed use-file-text-mode))
(list file-text-mode-msg)
'())))))
(define/override (update-info)
(super update-info)
(update-macro-recording-icon)
(update-ascii-art-enlarge-msg)
(overwrite-status-changed)
(anchor-status-changed)
(editor-position-changed)
(use-file-text-mode-changed))
(super-new)
(inherit get-info-panel)
(define position-parent (new click-pref-panel%
[border 2]
[parent (get-info-panel)]
[stretchable-width #f]
[stretchable-height #f]
[extra-menu-items (λ (menu) (add-line-number-menu-items menu))]))
(define position-canvas (new position-canvas%
[parent position-parent]
[init-width "1:1"]))
(define/private (change-position-edit-contents str)
(send position-canvas set-str str))
(send (get-info-panel) change-children
(λ (l)
(cons position-parent (remq position-parent l))))
(define file-text-mode-msg-parent (new horizontal-panel%
[stretchable-width #f]
[stretchable-height #f]
[parent (get-info-panel)]))
(define file-text-mode-msg (new file-text-mode-msg% [parent file-text-mode-msg-parent]))
(send file-text-mode-msg-parent change-children (λ (l) '()))
(send (get-info-panel) change-children
(λ (l)
(cons file-text-mode-msg-parent (remq file-text-mode-msg-parent l))))
(define uncommon-parent (new horizontal-panel%
[parent (get-info-panel)]
[stretchable-width #f]))
(send (get-info-panel) change-children
(λ (l) (cons uncommon-parent (remq uncommon-parent l))))
(define ascii-art-enlarge-mode-msg (new message%
[parent uncommon-parent]
[label "╠╬╣"]
[auto-resize #t]))
(define anchor-message
(new message%
[font small-control-font]
[label (string-constant auto-extend-selection)]
[parent uncommon-parent]))
(define overwrite-message
(new message%
[font small-control-font]
[label (string-constant overwrite)]
[parent uncommon-parent]))
(define macro-recording-message
(new message%
[label "c-x;("]
[font small-control-font]
[parent uncommon-parent]))
(define/private (remove-uncommon-child c)
(send uncommon-parent change-children
(λ (l) (remq c l))))
(define/private (add-uncommon-child c)
(define (child->num c)
(cond
[(eq? c ascii-art-enlarge-mode-msg) -1]
[(eq? c anchor-message) 0]
[(eq? c overwrite-message) 1]
[(eq? c macro-recording-message) 2]))
(send uncommon-parent change-children
(λ (l) (sort (cons c (remq c l))
<
#:key child->num))))
(inherit determine-width)
(send uncommon-parent change-children (λ (l) '()))
(editor-position-changed)
(use-file-text-mode-changed)))
(define crlf-string "CRLF")
(define file-text-mode-msg%
(class canvas%
(inherit min-width min-height get-dc refresh)
(define/override (on-paint)
(define dc (get-dc))
(send dc set-pen "black" 1 'transparent)
(send dc set-brush "orange" 'solid)
(define-values (w h d a) (send dc get-text-extent crlf-string))
(send dc draw-rectangle 0 0 (+ w 4) h)
(send dc draw-text crlf-string 2 0))
(super-new)
(inherit stretchable-width)
(stretchable-width #f)
(send (get-dc) set-font small-control-font)
(let ()
(define-values (w h d a) (send (get-dc) get-text-extent crlf-string))
(min-width (inexact->exact (ceiling (+ w 4))))
(min-height (inexact->exact (ceiling h))))))
(define click-pref-panel%
(class horizontal-panel%
(init-field extra-menu-items)
(inherit popup-menu)
(define/override (on-subwindow-event receiver evt)
(cond
[(send evt button-down?)
(let ([menu (new popup-menu%)]
[line-numbers? (preferences:get 'framework:display-line-numbers)])
(new checkable-menu-item%
[parent menu]
[label (string-constant show-line-and-column-numbers)]
[callback (λ (x y) (preferences:set 'framework:display-line-numbers #t))]
[checked line-numbers?])
(new checkable-menu-item%
[parent menu]
[label (string-constant show-character-offsets)]
[callback (λ (x y) (preferences:set 'framework:display-line-numbers #f))]
[checked (not line-numbers?)])
(extra-menu-items menu)
(popup-menu menu
(+ 1 (send evt get-x))
(+ 1 (send evt get-y))))
#t]
[else
(super on-subwindow-event receiver evt)]))
(super-new)))
(define pasteboard-info<%> (interface (info<%>)))
(define pasteboard-info-mixin
(mixin (basic<%>) (pasteboard-info<%>)
(super-new)))
(define standard-menus<%> frame:standard-menus<%>)
(generate-standard-menus-code)
(define -editor<%> (interface (standard-menus<%>)
get-entire-label
get-label-prefix
set-label-prefix
get-canvas%
get-canvas<%>
get-editor%
get-editor<%>
make-editor
revert
save
save-as
get-canvas
get-editor))
(define editor-mixin
(mixin (standard-menus<%>) (-editor<%>)
(init (filename #f))
(init-field (editor% #f))
(inherit get-area-container get-client-size
show get-edit-target-window get-edit-target-object)
(define/override get-filename
(case-lambda
[() (get-filename #f)]
[(b)
(let ([e (get-editor)])
(and e (send e get-filename b)))]))
(define/override (editing-this-file? filename)
(let ([path-equal?
(λ (x y)
(with-handlers ((exn:fail? (λ (x) #f)))
(equal? (normal-case-path (normalize-path x))
(normal-case-path (normalize-path y)))))])
(let ([this-fn (get-filename)])
(and this-fn
(path-equal? filename (get-filename))))))
(define/augment (on-close)
(send (get-editor) on-close)
(inner (void) on-close))
(define/augment (can-close?)
(and (send (get-editor) can-close?)
(inner #t can-close?)))
[define label ""]
[define label-prefix (application:current-app-name)]
(define/private (do-label)
(super set-label (gui-utils:trim-string (get-entire-label) 200))
(send (group:get-the-frame-group) frame-label-changed this))
(define/public (get-entire-label)
(cond
[(string=? "" label)
label-prefix]
[(string=? "" label-prefix)
label]
[else
(string-append label " - " label-prefix)]))
(define/public (get-label-prefix) label-prefix)
(define/public (set-label-prefix s)
(when (and (string? s)
(not (string=? s label-prefix)))
(set! label-prefix s)
(do-label)))
[define/override get-label (λ () label)]
[define/override set-label
(λ (t)
(when (and (string? t)
(not (string=? t label)))
(set! label t)
(do-label)))]
(define/public (get-canvas%) editor-canvas%)
(define/public (get-canvas<%>) (class->interface editor-canvas%))
(define/public (make-canvas)
(let ([% (get-canvas%)]
[<%> (get-canvas<%>)])
(unless (implementation? % <%>)
(error 'frame:editor%
"result of get-canvas% method must match ~e interface; got: ~e"
<%> %))
(instantiate % () (parent (get-area-container)))))
(define/public (get-editor%)
(or editor%
(error 'editor-frame% "abstract method: no editor% class specified")))
(define/public (get-editor<%>)
editor:basic<%>)
(define/public (make-editor)
(let ([% (get-editor%)]
[<%> (get-editor<%>)])
(unless (implementation? % <%>)
(error 'frame:editor%
"result of get-editor% method must match ~e interface; got: ~e"
<%> %))
(make-object %)))
(define/public save
(lambda ([format 'same])
(let* ([ed (get-editor)]
[filename (send ed get-filename)])
(if filename
(send ed save-file/gui-error filename format)
(save-as format)))))
(define/public save-as
(lambda ([format 'same])
(let* ([editor (get-editor)]
[name (send editor get-filename)])
(let-values ([(base name)
(if name
(let-values ([(base name dir?) (split-path name)])
(values base name))
(values #f #f))])
(let ([file (send editor put-file base name)])
(if file
(send editor save-file/gui-error file format)
#f))))))
(define/private (basename str)
(let-values ([(base name dir?) (split-path str)])
base))
(inherit get-checkable-menu-item% get-menu-item%)
(define/override (file-menu:open-callback item evt)
(let* ([e (get-editor)]
[fn (and e (send e get-filename))]
[dir (and fn
(let-values ([(base name dir) (split-path fn)])
base))])
(handler:open-file dir)))
(define/override (file-menu:revert-on-demand item)
(send item enable (not (send (get-editor) is-locked?))))
(define/override file-menu:revert-callback
(λ (item control)
(let* ([edit (get-editor)]
[b (box #f)]
[filename (send edit get-filename b)])
(if (or (not filename)
(unbox b))
(bell)
(when (or (not (send (get-editor) is-modified?))
(gui-utils:get-choice
(string-constant are-you-sure-revert)
(string-constant yes)
(string-constant no)
(string-constant are-you-sure-revert-title)
#f
this))
(revert))))
#t))
(define/public (revert)
(let* ([edit (get-editor)]
[b (box #f)]
[filename (send edit get-filename b)])
(when (and filename
(not (unbox b)))
(let ([start
(if (is-a? edit text%)
(send edit get-start-position)
#f)])
(send edit begin-edit-sequence)
(let ([status (send edit load-file/gui-error
filename
'guess
#f)])
(if status
(begin
(when (is-a? edit text%)
(send edit set-position start start))
(send edit end-edit-sequence))
(send edit end-edit-sequence)))))))
(define/override file-menu:create-revert? (λ () #t))
(define/override file-menu:save-callback
(λ (item control)
(save)
#t))
(define/override file-menu:create-save? (λ () #t))
(define/override file-menu:save-as-callback (λ (item control) (save-as) #t))
(define/override file-menu:create-save-as? (λ () #t))
(define/override file-menu:print-callback (λ (item control)
(send (get-editor) print
#t
#t
(preferences:get 'framework:print-output-mode))
#t))
(define/override file-menu:create-print? (λ () #t))
(inherit get-top-level-window)
(define/override (file-menu:between-save-as-and-print file-menu)
(when (and (can-get-page-setup-from-user?) (file-menu:create-print?))
(new menu-item%
[parent file-menu]
[label (string-constant page-setup-menu-item)]
[help-string (string-constant page-setup-info)]
[callback
(lambda (item event)
(let ([s (get-page-setup-from-user #f (get-top-level-window))])
(when s
(send (current-ps-setup) copy-from s))))])))
(define/override (edit-menu:between-select-all-and-find edit-menu)
(define c% (get-checkable-menu-item%))
(define (on-demand menu-item)
(define edit (get-edit-target-object))
(cond
[(and edit (is-a? edit editor<%>))
(send menu-item enable #t)
(send menu-item check (send edit auto-wrap))]
[else
(send menu-item check #f)
(send menu-item enable #f)]))
(define (callback item event)
(define edit (get-edit-target-object))
(when (and edit
(is-a? edit editor<%>))
(define new-pref (not (send edit auto-wrap)))
(preferences:set 'framework:auto-set-wrap? new-pref)
(send edit auto-wrap new-pref)))
(new c%
[label (string-constant wrap-text-item)]
[parent edit-menu]
[callback callback]
[demand-callback on-demand])
(make-object separator-menu-item% edit-menu))
(define/override help-menu:about-callback
(λ (menu evt)
(message-box (application:current-app-name)
(format (string-constant welcome-to-something)
(application:current-app-name))
#f
'(ok))))
(define/override help-menu:about-string (λ () (application:current-app-name)))
(define/override help-menu:create-about? (λ () #t))
(super-new (label (get-entire-label)))
(define canvas #f)
(define editor #f)
(public get-canvas get-editor)
(define get-canvas
(λ ()
(unless canvas
(set! canvas (make-canvas))
(send canvas set-editor (get-editor)))
canvas))
(define get-editor
(λ ()
(unless editor
(set! editor (make-editor))
(send (get-canvas) set-editor editor))
editor))
(cond
[(and filename (file-exists? filename))
(let ([ed (get-editor)])
(send ed begin-edit-sequence)
(send ed load-file/gui-error filename 'guess)
(send ed end-edit-sequence))]
[filename
(send (get-editor) set-filename filename)]
[else (void)])
(let ([ed-fn (send (get-editor) get-filename)])
(set! label (or (and ed-fn
(path->string (file-name-from-path ed-fn)))
(send (get-editor) get-filename/untitled-name))))
(do-label)
(let ([canvas (get-canvas)])
(when (is-a? canvas editor-canvas%)
;; when get-canvas is overridden,
;; it might not yet be implemented
(send canvas focus)))))
(define text<%> (interface (-editor<%>)))
(define text-mixin
(mixin (-editor<%>) (text<%>)
(define/override (get-editor<%>) (class->interface text%))
(init (filename #f) (editor% text:keymap%))
(inherit get-editor)
(define/override (edit-menu:between-find-and-preferences edit-menu)
(when (add-find-longest-line-menu-item?)
(new menu-item%
[parent edit-menu]
[label (string-constant find-longest-line)]
[callback
(λ (x y)
(define ed (get-editor))
(define (line-len p)
(define ans
(- (send ed paragraph-end-position p #f)
(send ed paragraph-start-position p #f)))
ans)
(when ed
(let loop ([p (- (send ed last-paragraph) 1)]
[longest-size (line-len (send ed last-paragraph))]
[longest (send ed last-paragraph)])
(cond
[(= p -1)
(send ed set-position
(send ed paragraph-start-position longest #f)
(send ed paragraph-end-position longest #f))]
[else
(define this-size (line-len p))
(cond
[(<= longest-size this-size)
(loop (- p 1) this-size p)]
[else
(loop (- p 1) longest-size longest)])]))))]))
(super edit-menu:between-find-and-preferences edit-menu))
(define/public (add-find-longest-line-menu-item?) #t)
(super-new (filename filename) (editor% editor%))))
(define pasteboard<%> (interface (-editor<%>)))
(define pasteboard-mixin
(mixin (-editor<%>) (pasteboard<%>)
(define/override get-editor<%> (λ () (class->interface pasteboard%)))
(init (filename #f) (editor% pasteboard:keymap%))
(super-new (filename filename) (editor% editor%))))
(define delegate<%>
(interface (status-line<%> text<%>)
delegated-text-shown?
hide-delegated-text
show-delegated-text
delegate-moved
set-delegated-text
get-delegated-text))
(define delegatee-editor-canvas%
(class (canvas:color-mixin canvas:basic%)
(init-field delegate-frame)
(inherit get-editor get-dc)
(define/override (on-event evt)
(super on-event evt)
(when delegate-frame
(let ([text (get-editor)])
(when (and (is-a? text text%)
(send delegate-frame delegated-text-shown?))
(cond
[(send evt button-down?)
(let-values ([(editor-x editor-y)
(send text dc-location-to-editor-location
(send evt get-x)
(send evt get-y))])
(send delegate-frame click-in-overview
(send text find-position editor-x editor-y)))])))))
(super-new)))
(define (at-most-200 s)
(cond
[(<= (string-length s) 200)
s]
[else (substring s 0 200)]))
(define delegatee-text<%>
(interface ()
set-start/end-para))
(define delegatee-text%
(class* text:basic% (delegatee-text<%>)
(define start-para #f)
(define end-para #f)
(define view-x-b (box 0))
(define view-width-b (box 0))
(inherit get-admin
paragraph-start-position paragraph-end-position
position-location invalidate-bitmap-cache scroll-to-position
get-visible-position-range position-paragraph
last-position)
(define/override (on-new-string-snip)
(instantiate text:1-pixel-string-snip% ()))
(define/override (on-new-tab-snip)
(instantiate text:1-pixel-tab-snip% ()))
;; set-start/end-para : (union (#f #f -> void) (number number -> void))
(define/public (set-start/end-para _start-para _end-para)
(unless (and (equal? _start-para start-para)
(equal? _end-para end-para))
(let ([old-start-para start-para]
[old-end-para end-para])
(cond
[else
(set! start-para _start-para)
(set! end-para _end-para)])
(when (and start-para end-para)
(let-values ([(v-start v-end) (let ([bs (box 0)]
[bf (box 0)])
(get-visible-position-range bs bf)
(values (unbox bs)
(unbox bf)))])
(let ([v-start-para (position-paragraph v-start)]
[v-end-para (position-paragraph v-end)])
(cond
[(v-start-para . >= . start-para)
(scroll-to-position (paragraph-start-position start-para))]
[(v-end-para . <= . end-para)
(scroll-to-position (paragraph-end-position end-para))]
[else (void)]))))
(define the-l #f)
(define the-t #f)
(define the-r #f)
(define the-b #f)
(when (and old-start-para old-end-para)
(let-values ([(x y w h) (get-rectangle old-start-para old-end-para)])
(when x
(set! the-l x)
(set! the-t y)
(set! the-r (+ x w))
(set! the-b (+ y h)))))
(when (and start-para end-para)
(let-values ([(x y w h) (get-rectangle start-para end-para)])
(cond
[(and x the-l)
(set! the-l (min x the-l))
(set! the-t (min y the-t))
(set! the-r (max the-r (+ x w)))
(set! the-b (max the-b (+ y h)))]
[x
(set! the-l x)
(set! the-t y)
(set! the-r (+ x w))
(set! the-b (+ y h))])))
(when the-l
(invalidate-bitmap-cache the-l the-t (- the-r the-l) (- the-b the-t))))))
(define/override (on-paint before? dc left top right bottom dx dy draw-caret)
(when (and before?
start-para
end-para)
(let ([old-pen (send dc get-pen)]
[old-brush (send dc get-brush)])
(send dc set-pen
(send the-pen-list find-or-create-pen
(preferences:get 'framework:delegatee-overview-color)
1
'solid))
(send dc set-brush
(send the-brush-list find-or-create-brush
(preferences:get 'framework:delegatee-overview-color)
'solid))
(let-values ([(x y w h) (get-rectangle start-para end-para)])
(when x
(send dc draw-rectangle
(+ dx x)
(+ dy y)
w
h)))
(send dc set-pen old-pen)
(send dc set-brush old-brush)))
(super on-paint before? dc left top right bottom dx dy draw-caret))
;; get-rectangle : number number ->
;; (values (union #f number) (union #f number) (union #f number) (union #f number))
;; computes the rectangle corresponding the input paragraphs
(define/private (get-rectangle start-para end-para)
(let ([start (get-line-y start-para #t)]
[end (get-line-y end-para #f)]
[admin (get-admin)])
(cond
[(not admin)
(values #f #f #f #f)]
[(= 0 (last-position))
(values #f #f #f #f)]
[else
(send admin get-view view-x-b #f view-width-b #f)
(send admin get-view view-x-b #f view-width-b #f)
(values (unbox view-x-b)
start
(unbox view-width-b)
(- end start))])))
(define/private (get-line-y para top?)
(let ([pos (paragraph-start-position para)]
[b (box 0)])
(position-location pos #f b top? #f #t)
(unbox b)))
(super-new)
(inherit set-cursor)
(set-cursor (make-object cursor% 'arrow))
(inherit set-line-spacing)
(set-line-spacing 0)))
(define delegate-mixin
(mixin (status-line<%> text<%>) (delegate<%>)
(define delegated-text #f)
(define/public-final (get-delegated-text) delegated-text)
(define/public-final (set-delegated-text t)
(unless (or (not t) (is-a? t text:delegate<%>))
(error 'set-delegated-text
"expected either #f or a text:delegate<%> object, got ~e"
t))
(unless (eq? delegated-text t)
(set! delegated-text t)
(when shown?
(unless (send (get-delegated-text) get-delegate)
(send (get-delegated-text) set-delegate
(new delegatee-text%)))
(send delegate-ec set-editor (send (get-delegated-text) get-delegate)))))
[define rest-panel 'uninitialized-root]
[define super-root 'uninitialized-super-root]
[define/override make-root-area-container
(λ (% parent)
(let* ([s-root (super make-root-area-container
horizontal-panel%
parent)]
[r-root (make-object % s-root)])
(set! super-root s-root)
(set! rest-panel r-root)
r-root))]
(define/override (get-editor<%>)
text:delegate<%>)
(define/override (get-editor%)
(text:delegate-mixin (super get-editor%)))
(field (shown? (preferences:get 'framework:show-delegate?)))
(define/public (delegated-text-shown?)
shown?)
(inherit close-status-line open-status-line)
(define/public (hide-delegated-text)
(set! shown? #f)
(when delegated-text
(send delegated-text set-delegate #f))
(send delegate-ec set-editor #f)
(send super-root change-children
(λ (l) (list rest-panel))))
(define/public (show-delegated-text)
(set! shown? #t)
(when delegated-text
(unless (send delegated-text get-delegate)
(send delegated-text set-delegate
(new delegatee-text%)))
(send delegate-ec set-editor (send (get-delegated-text) get-delegate)))
(send super-root change-children
(λ (l) (list rest-panel delegate-ec))))
(define/public (click-in-overview pos)
(when shown?
(let* ([d-canvas (send delegated-text get-canvas)]
[bx (box 0)]
[by (box 0)])
(let-values ([(cw ch) (send d-canvas get-client-size)])
(send delegated-text position-location pos bx by)
(send d-canvas focus)
(send d-canvas scroll-to
(- (unbox bx) (/ cw 2))
(- (unbox by) (/ ch 2))
cw
ch
#t)
(send delegated-text set-position pos)))))
(define/public (delegate-moved)
(define delegatee (send delegate-ec get-editor))
(when delegatee
(let ([startb (box 0)]
[endb (box 0)])
(send delegated-text get-visible-position-range startb endb #f)
(send delegatee set-start/end-para
(send delegated-text position-paragraph (unbox startb))
(send delegated-text position-paragraph (unbox endb))))))
(define/public (get-delegatee) (send delegate-ec get-editor))
(super-new)
(define delegate-ec (new delegatee-editor-canvas%
[parent super-root]
[delegate-frame this]
[min-width 150]
[stretchable-width #f]))
(inherit get-editor)
(if (preferences:get 'framework:show-delegate?)
(show-delegated-text)
(hide-delegated-text))))
(define searchable<%> (interface (basic<%>)
search
search-replace
replace-all
get-text-to-search
set-text-to-search
search-hidden?
hide-search
unhide-search
get-case-sensitive-search?
search-hits-changed
))
(define old-search-highlight void)
(define (clear-search-highlight)
(old-search-highlight)
(set! old-search-highlight void))
(define find/replace-text%
(class text:keymap%
(init-field pref-sym)
(inherit get-canvas get-text last-position insert find-first-snip
get-admin invalidate-bitmap-cache
begin-edit-sequence end-edit-sequence get-top-level-window)
(define/augment (after-insert x y)
(update-prefs)
(inner (void) after-insert x y))
(define/augment (after-delete x y)
(update-prefs)
(inner (void) after-delete x y))
(define timer #f)
(define/private (update-prefs)
(unless timer
(set! timer (new timer%
[notify-callback
(λ ()
(preferences:set pref-sym
(let loop ([snip (find-first-snip)])
(cond
[(not snip) '()]
[(is-a? snip string-snip%)
(cons (send snip get-text 0 (send snip get-count))
(loop (send snip next)))]
[else (cons snip (loop (send snip next)))]))))])))
(send timer stop)
(send timer start 150 #t))
(define/override (get-keymaps)
(editor:add-after-user-keymap search/replace-keymap (super get-keymaps)))
(super-new)
(inherit set-styles-fixed)
(set-styles-fixed #t)
(begin-edit-sequence)
(for-each
(λ (x) (insert x (last-position) (last-position)))
(preferences:get pref-sym))
(end-edit-sequence)
(define pref-callback
(λ (p v)
(let ([c (get-canvas)])
(when (and c (send c get-line-count))
(send c set-editor (send c get-editor))))))
(preferences:add-callback 'framework:standard-style-list:font-size pref-callback #t)))
(define find-text%
(class find/replace-text%
(inherit get-canvas get-text last-position insert find-first-snip
get-admin invalidate-bitmap-cache run-after-edit-sequence
begin-edit-sequence end-edit-sequence get-top-level-window)
(define/private (get-case-sensitive-search?)
(let ([frame (get-top-level-window)])
(and frame
(send frame get-case-sensitive-search?))))
(define/override (on-focus on?)
(let ([frame (get-top-level-window)])
(when frame
(let ([text-to-search (send frame get-text-to-search)])
(when text-to-search
(when on?
(send text-to-search set-search-anchor (send text-to-search get-start-position)))))))
(super on-focus on?))
(define/augment (after-insert x y)
(update-searching-str/trigger-jump)
(inner (void) after-insert x y))
(define/augment (after-delete x y)
(update-searching-str/trigger-jump)
(inner (void) after-delete x y))
(define/private (update-searching-str/trigger-jump)
(let ([tlw (get-top-level-window)])
(when tlw
(send tlw search-string-changed)))
;; trigger-jump
(when (preferences:get 'framework:anchored-search)
(let ([frame (get-top-level-window)])
(when frame
(let ([text-to-search (send frame get-text-to-search)])
(when text-to-search
(let ([anchor-pos (send text-to-search get-anchor-pos)])
(when anchor-pos
(cond
[(equal? "" (get-text))
(send text-to-search set-position anchor-pos anchor-pos)]
[else
(search 'forward #t #t #f anchor-pos)])))))))))
(define/private (get-searching-text)
(let ([frame (get-top-level-window)])
(and frame
(send frame get-text-to-search))))
(define/public (search [searching-direction 'forward]
[beep? #t]
[wrap? #t]
[move-anchor? #t]
[search-start-position #f])
(let* ([string (get-text)]
[top-searching-edit (get-searching-text)])
(when top-searching-edit
(let ([searching-edit
(let loop ([txt top-searching-edit])
(define focus-snip (send txt get-focus-snip))
(cond
[(and focus-snip (is-a? focus-snip editor-snip%))
(loop (send focus-snip get-editor))]
[else txt]))]
[not-found
(λ (found-edit skip-beep?)
(when (and beep?
(not skip-beep?))
(bell))
#f)]
[found
(λ (text first-pos)
(define (thunk)
(define last-pos ((if (eq? searching-direction 'forward) + -)
first-pos (string-length string)))
(define start-pos (min first-pos last-pos))
(define end-pos (max first-pos last-pos))
(send text begin-edit-sequence #t #f)
(send text set-caret-owner #f 'display)
(send text set-position start-pos end-pos #f #f 'local)
;; scroll to the middle if the search result isn't already visible
(let ([search-result-line (send text position-line (send text get-start-position))]
[bt (box 0)]
[bb (box 0)])
(send text get-visible-line-range bt bb #f)
(unless (< (unbox bt) search-result-line (unbox bb))
(let* ([half (sub1 (quotient (- (unbox bb) (unbox bt)) 2))]
[last-pos (send text position-line (send text last-position))]
[top-pos (send text line-start-position
(max (min (- search-result-line half) last-pos) 0))]
[bottom-pos (send text line-start-position
(max 0
(min (+ search-result-line half)
last-pos)))])
(send text scroll-to-position
top-pos
#f
bottom-pos))))
(when move-anchor?
(when (is-a? text text:searching<%>)
(send text set-search-anchor
(if (eq? searching-direction 'forward)
end-pos
start-pos))))
(send text end-edit-sequence))
(define owner (or (send text get-active-canvas)
(send text get-canvas)))
(if owner
(send owner call-as-primary-owner thunk)
(thunk))
#t)])
(if (string=? string "")
(not-found top-searching-edit #t)
(let-values ([(found-edit first-pos)
(find-string-embedded
(if search-start-position
top-searching-edit
searching-edit)
string
searching-direction
(or search-start-position
(if (eq? 'forward searching-direction)
(send searching-edit get-end-position)
(send searching-edit get-start-position)))
'eof #t
(get-case-sensitive-search?)
#t)])
(cond
[(not first-pos)
(if wrap?
(begin
(let-values ([(found-edit pos)
(find-string-embedded
top-searching-edit
string
searching-direction
(if (eq? 'forward searching-direction)
0
(send searching-edit last-position))
'eof #t
(get-case-sensitive-search?)
#f)])
(if (not pos)
(not-found found-edit #f)
(found found-edit pos))))
(not-found found-edit #f))]
[else
(found found-edit first-pos)])))))))
(define/override (on-paint before dc left top right bottom dx dy draw-caret?)
(super on-paint before dc left top right bottom dx dy draw-caret?)
(when before
(let ([canvas (get-canvas)])
(when (and canvas
(send canvas is-red?))
(let-values ([(view-x view-y view-width view-height)
(let ([b1 (box 0)]
[b2 (box 0)]
[b3 (box 0)]
[b4 (box 0)])
(send (get-admin) get-view b1 b2 b3 b4)
(values (unbox b1)
(unbox b2)
(unbox b3)
(unbox b4)))])
(let ([pen (send dc get-pen)]
[brush (send dc get-brush)])
(send dc set-pen "black" 1 'transparent)
(send dc set-brush "pink" 'solid)
(send dc draw-rectangle (+ dx view-x) (+ view-y dy) view-width view-height)
(send dc set-pen pen)
(send dc set-brush brush)))))))
(super-new [pref-sym 'framework:search-string])))
(define replace-text%
(class find/replace-text%
(inherit set-styles-fixed)
(super-new [pref-sym 'framework:replace-string])
(define/override (get-keymaps)
(editor:add-after-user-keymap search/replace-keymap (super get-keymaps)))
(set-styles-fixed #t)))
(define search/replace-keymap (new keymap%))
(send search/replace-keymap map-function "tab" "switch-between-search-and-replace")
(send search/replace-keymap add-function "switch-between-search-and-replace"
(λ (text evt)
(let ([find-txt (send (send text get-top-level-window) get-find-edit)]
[replace-txt (send (send text get-top-level-window) get-replace-edit)])
(cond
[(eq? find-txt text)
(send replace-txt set-position 0 (send replace-txt last-position))
(send (send replace-txt get-canvas) focus)]
[(eq? replace-txt text)
(send find-txt set-position 0 (send find-txt last-position))
(send (send find-txt get-canvas) focus)]))))
(send search/replace-keymap map-function "return" "next")
(send search/replace-keymap add-function "next"
(λ (text evt)
(send (send text get-top-level-window) search 'forward)))
(send search/replace-keymap map-function "s:return" "prev")
(send search/replace-keymap add-function "prev"
(λ (text evt)
(send (send text get-top-level-window) search 'backward)))
(send search/replace-keymap map-function "c:return" "insert-return")
(send search/replace-keymap map-function "a:return" "insert-return")
(send search/replace-keymap add-function "insert-return"
(λ (text evt)
(send text insert "\n")))
(send search/replace-keymap map-function "esc" "hide-search")
(send search/replace-keymap add-function "hide-search"
(λ (text evt)
(let ([tlw (send text get-top-level-window)])
(when tlw
(send tlw hide-search)))))
(send search/replace-keymap map-function "c:g" "hide-search-and-snap-back")
(send search/replace-keymap add-function "hide-search-and-snap-back"
(λ (text evt)
(let ([tlw (send text get-top-level-window)])
(when tlw
(when (preferences:get 'framework:anchored-search)
(let ([text-to-search (send tlw get-text-to-search)])
(when text-to-search
(let ([anchor-pos (send text-to-search get-anchor-pos)])
(when anchor-pos
(send text-to-search set-position anchor-pos))))))
(send tlw hide-search)))))
(send search/replace-keymap map-function "f3" "unhide-search-and-toggle-focus")
(send search/replace-keymap add-function "unhide-search-and-toggle-focus"
(λ (text evt)
(let ([tlw (send text get-top-level-window)])
(when tlw
(send tlw unhide-search-and-toggle-focus)))))
(define searchable-canvas%
(class editor-canvas%
(inherit refresh get-dc get-client-size)
(define red? #f)
(define/public (is-red?) red?)
(define/public (set-red r?)
(unless (equal? red? r?)
(set! red? r?)
(refresh)))
(define/override (on-paint)
(when red?
(let ([dc (get-dc)])
(let-values ([(cw ch) (get-client-size)])
(let ([pen (send dc get-pen)]
[brush (send dc get-brush)])
(send dc set-pen "black" 1 'transparent)
(send dc set-brush "pink" 'solid)
(send dc draw-rectangle 0 0 cw ch)
(send dc set-pen pen)
(send dc set-brush brush)))))
(super on-paint))
(super-new)))
(define-local-member-name
update-matches
get-find-edit
get-replace-edit
search-string-changed)
(define searchable-mixin
(mixin (standard-menus<%>) (searchable<%>)
(inherit edit-menu:get-show/hide-replace-item)
(define super-root 'unitiaialized-super-root)
(define case-sensitive-search? (preferences:get 'framework:case-sensitive-search?))
(define/public (get-case-sensitive-search?) case-sensitive-search?)
(define replace-visible? (preferences:get 'framework:replace-visible?))
(define/override (edit-menu:find-callback menu evt) (unhide-search-and-toggle-focus) #t)
(define/override (edit-menu:create-find?) #t)
(define/override (edit-menu:find-from-selection-callback menu evt)
(unhide-search-and-toggle-focus #:new-search-string-from-selection? #t)
#t)
(define/override (edit-menu:find-from-selection-on-demand item)
(define t (get-text-to-search))
(send item enable (and t (not (= (send t get-start-position)
(send t get-end-position))))))
(define/override (edit-menu:create-find-from-selection?) #t)
(define/override (edit-menu:find-next-callback menu evt) (search 'forward) #t)
(define/override (edit-menu:create-find-next?) #t)
(define/override (edit-menu:find-previous-callback menu evt) (search 'backward) #t)
(define/override (edit-menu:create-find-previous?) #t)
(define/override (edit-menu:create-show/hide-replace?) #t)
(define/override (edit-menu:show/hide-replace-callback a b)
(cond
[hidden?
(unhide-search #f)
(set-replace-visible? #t)
(send replace-edit set-position 0 (send replace-edit last-position))
(send (send replace-edit get-canvas) focus)]
[else
(set-replace-visible? (not replace-visible?))
(when replace-visible?
(send replace-edit set-position 0 (send replace-edit last-position))
(send (send replace-edit get-canvas) focus))])
#t)
(define/override (edit-menu:show/hide-replace-string)
(if replace-visible?
(string-constant hide-replace-menu-item)
(string-constant show-replace-menu-item)))
(define/override (edit-menu:show/hide-replace-on-demand item)
(send item set-label
(if (and (not hidden?) replace-visible?)
(string-constant hide-replace-menu-item)
(string-constant show-replace-menu-item))))
(define/override (edit-menu:replace-callback a b) (search-replace) #t)
(define/override (edit-menu:create-replace?) #t)
(define/override (edit-menu:replace-on-demand item)
(send item enable (and (not hidden?) replace-visible?)))
(define/override (edit-menu:find-case-sensitive-callback menu evt)
(set! case-sensitive-search? (not case-sensitive-search?))
(preferences:set 'framework:case-sensitive-search? case-sensitive-search?)
(when find-edit
(unless hidden?
(search-string-changed))))
(define/override (edit-menu:find-case-sensitive-on-demand item) (send item check case-sensitive-search?))
(define/override (edit-menu:create-find-case-sensitive?) #t)
(define/override (edit-menu:replace-all-callback menu evt) (replace-all) #t)
(define/override (edit-menu:replace-all-on-demand item) (send item enable (not hidden?)))
(define/override (edit-menu:create-replace-all?) #t)
(define/override make-root-area-container
(λ (% parent)
(let* ([s-root (super make-root-area-container
vertical-panel%
parent)]
[root (make-object % s-root)])
(set! super-root s-root)
root)))
(define text-to-search #f)
(define/public-final (get-text-to-search) text-to-search)
(define/public-final (set-text-to-search new)
(unless (or (not new) (is-a? new text:searching<%>))
(raise-argument-error 'set-text-to-search
(format "~s" '(or/c (is-a?/c text:searching<%>) #f))
new))
(unless (eq? new text-to-search)
(let ([old text-to-search])
(set! text-to-search new)
(unless hidden?
(when find-edit
(when old
(send old set-searching-state #f #f #f #f)
(send old set-search-anchor #f))
(when new
(send new set-search-anchor (send new get-start-position))
(search-parameters-changed)))))))
;; called by the text-to-search when it finishes the search
(define/public-final (search-hits-changed)
(when find-edit
(when text-to-search
(let-values ([(before-caret-new-hits new-hits) (send text-to-search get-search-hit-count)])
(update-matches before-caret-new-hits new-hits)
(let ([is-red? (and (zero? new-hits)
(not (zero? (send find-edit last-position))))])
(send find-canvas set-red is-red?))))))
(define/public-final (search-string-changed) (search-parameters-changed))
(define/private (search-parameters-changed)
(let ([str (send find-edit get-text)])
(when text-to-search
(send text-to-search set-searching-state
(if (equal? str "") #f str)
case-sensitive-search?
replace-visible?
#t))))
(define/public (search-hidden?) hidden?)
(define/public (hide-search)
(set! hidden? #t)
(when search-gui-built?
(when text-to-search
(send text-to-search set-searching-state #f #f #f #f))
(send super-root change-children
(λ (l)
(remove search/replace-panel l)))
(clear-search-highlight)
(when text-to-search
(send text-to-search set-search-anchor #f)
(let ([canvas (send text-to-search get-canvas)])
(when canvas
(send canvas focus))))))
(define/public (unhide-search focus? #:new-search-string-from-selection? [new-search-string-from-selection? #f])
(when hidden?
(set! hidden? #f)
(build-search-gui-in-frame)
(unless (memq search/replace-panel (send super-root get-children))
(send super-root add-child search/replace-panel))
(search-parameters-changed)
(send find-edit begin-edit-sequence #t #f)
(when new-search-string-from-selection?
(update-search-string-from-selection))
(when focus?
(send find-edit set-position 0 (send find-edit last-position))
(send (send find-edit get-canvas) focus))
(let ([c (send find-edit get-canvas)])
(when (and c (send c get-line-count))
;; try to update the canvas so that the font size is correctly accounted for
(send c set-editor (send c get-editor))))
(send find-edit end-edit-sequence)))
(define/public (unhide-search-and-toggle-focus #:new-search-string-from-selection? [new-search-string-from-selection? #f])
(if hidden?
(unhide-search #t #:new-search-string-from-selection? new-search-string-from-selection?)
(let ([canvas (and text-to-search (send text-to-search get-canvas))])
(cond
[(or (not text-to-search) (and canvas (send canvas has-focus?)))
(send find-edit begin-edit-sequence #t #f)
(when new-search-string-from-selection?
(update-search-string-from-selection))
(send find-edit set-position 0 (send find-edit last-position))
(send find-canvas focus)
(send find-edit end-edit-sequence)]
[canvas
(send canvas focus)]))))
(define/private (update-search-string-from-selection)
(when (and text-to-search
(not (= (send text-to-search get-start-position)
(send text-to-search get-end-position))))
(send find-edit delete 0 (send find-edit last-position))
(send text-to-search move/copy-to-edit
find-edit
(send text-to-search get-start-position)
(send text-to-search get-end-position)
0
#:try-to-move? #f)))
(define/public (search searching-direction)
(unhide-search #f)
(send find-edit search searching-direction #t))
(define/public (search-replace)
(define text-to-search (get-text-to-search))
(when text-to-search
(define replacee-start (send text-to-search get-replace-search-hit))
(when replacee-start
(define replacee-end (+ replacee-start (send find-edit last-position)))
(send text-to-search begin-edit-sequence)
(send text-to-search set-position replacee-end replacee-end)
(send text-to-search delete replacee-start replacee-end)
(copy-over replace-edit 0 (send replace-edit last-position) text-to-search replacee-start)
(search 'forward)
(send text-to-search finish-pending-search-work)
(send text-to-search end-edit-sequence))))
(define/private (copy-over src-txt src-start src-end dest-txt dest-pos)
(send src-txt split-snip src-start)
(send src-txt split-snip src-end)
(let loop ([snip (send src-txt find-snip src-end 'before)])
(cond
[(or (not snip) (< (send src-txt get-snip-position snip) src-start))
(void)]
[else
(let ([prev (send snip previous)])
(send dest-txt insert (send snip copy) dest-pos dest-pos)
(loop prev))])))
(define/public (replace-all)
(unhide-search #f)
(let ([txt (get-text-to-search)])
(when txt
(let ([search-str (send find-edit get-text)]
[ht (make-hasheq)])
(send txt begin-edit-sequence)
(hash-set! ht txt #t)
(let loop ([txt (pop-all-the-way-out txt)]
[pos 0])
(let-values ([(found-txt found-pos) (find-string-embedded txt
search-str
'forward
pos
'eof
#f
case-sensitive-search?
#t)])
(when found-pos
(unless (hash-ref ht found-txt #f)
(hash-set! ht found-txt #t)
(send found-txt begin-edit-sequence))
(let ([start (- found-pos (send find-edit last-position))])
(send found-txt delete start found-pos)
(copy-over replace-edit 0 (send replace-edit last-position) found-txt start)
(loop found-txt (+ start (send replace-edit last-position)))))))
(hash-for-each ht (λ (txt _) (send txt end-edit-sequence)))))))
(define/private (pop-all-the-way-out txt)
(let ([admin (send txt get-admin)])
(if (is-a? admin editor-snip-editor-admin<%>)
(let* ([snip (send admin get-snip)]
[edit-above (send (send snip get-admin) get-editor)])
(pop-all-the-way-out edit-above))
txt)))
(define/private (find-embedded-focus-editor editor)
(let loop ([editor editor])
(let ([s (send editor get-focus-snip)])
(cond
[(and s (is-a? s editor-snip%))
(let ([next-ed (send s get-editor)])
(if next-ed
(loop next-ed)
editor))]
[else editor]))))
(define search-gui-built? #f)
(define search/replace-panel #f)
(define find-canvas #f)
(define replace-canvas #f)
(define hidden? #t)
(field [update-matches void])
(define find-edit #f)
(define replace-edit #f)
(define/public (get-find-edit) find-edit)
(define/public (get-replace-edit) replace-edit)
(inherit begin-container-sequence end-container-sequence)
(define/public (get-replace-visible?) replace-visible?)
(define/public (set-replace-visible? r?)
(unless (equal? replace-visible? r?)
(set! replace-visible? r?)
(preferences:set 'framework:replace-visible? r?)
(show/hide-replace)
(send (edit-menu:get-show/hide-replace-item) set-label
(if replace-visible?
(string-constant hide-replace-menu-item)
(string-constant show-replace-menu-item)))
(search-parameters-changed)))
(define show/hide-replace void)
(define/private (build-search-gui-in-frame)
(unless search-gui-built?
(set! search-gui-built? #t)
(begin-container-sequence)
(set! find-edit (new find-text%))
(set! replace-edit (new replace-text%))
(set! search/replace-panel (new horizontal-panel%
[parent super-root]
[stretchable-height #f]))
(define search-panel
(new horizontal-panel%
[parent search/replace-panel]
[stretchable-height #f]))
(define replace-panel
(new horizontal-panel%
[parent search/replace-panel]
[stretchable-height #f]))
(set! find-canvas (new searchable-canvas%
[style '(hide-hscroll hide-vscroll)]
[vertical-inset 2]
[parent search-panel]
[editor find-edit]
[line-count 1]
[stretchable-height #f]
[stretchable-width #t]))
(set! replace-canvas (new searchable-canvas%
[style '(hide-hscroll hide-vscroll)]
[vertical-inset 2]
[parent replace-panel]
[editor replace-edit]
[line-count 1]
[stretchable-height #f]
[stretchable-width #t]))
(define search-button (new button%
[label (string-constant search-next)]
[vert-margin 0]
[parent search-panel]
[callback (λ (x y) (search 'forward))]
[font small-control-font]))
(define search-prev-button (new button%
[label (string-constant search-previous)]
[vert-margin 0]
[parent search-panel]
[callback (λ (x y) (search 'backward))]
[font small-control-font]))
(define hits-panel (new vertical-panel%
[parent search-panel]
[alignment '(left center)]
[stretchable-height #f]
[stretchable-width #f]))
(define num-msg (new message%
[label "0"]
[vert-margin 0]
[auto-resize #t]
[font tiny-control-font]
[parent hits-panel]))
(define matches-msg (new message%
[label (string-constant search-matches)]
[vert-margin 0]
[font tiny-control-font]
[parent hits-panel]))
(define _6 (set! update-matches
(λ (before-caret-m m)
(cond
[(zero? m)
(send num-msg set-label "0")]
[else
(let ([number (number->str/comma m)]
[bc-number (number->str/comma before-caret-m)])
(send num-msg set-label (format "~a/~a" bc-number number)))])
(send matches-msg set-label (if (= m 1)
(string-constant search-match)
(string-constant search-matches))))))
(define replace-button
(new button%
[label (string-constant search-replace)]
[vert-margin 0]
[parent replace-panel]
[font small-control-font]
[callback (λ (x y) (search-replace))]))
(define skip-button
(new button%
[label (string-constant search-skip)]
[vert-margin 0]
[parent replace-panel]
[font small-control-font]
[callback (λ (x y) (search 'forward))]))
(define show-replace-button
(new button%
[label (string-constant search-show-replace)]
[font small-control-font]
[callback (λ (a b) (set-replace-visible? #t))]
[parent replace-panel]))
(define hide-replace-button
(new button%
[label (string-constant search-hide-replace)]
[font small-control-font]
[callback (λ (a b) (set-replace-visible? #f))]
[parent replace-panel]))
(set! show/hide-replace
(λ ()
(send replace-panel begin-container-sequence)
(cond
[replace-visible?
(send replace-panel change-children (λ (l) all-replace-children))
(send replace-panel stretchable-width #t)]
[else
(send replace-panel change-children (λ (l) (list show-replace-button)))
(send replace-panel stretchable-width #f)])
(send replace-panel end-container-sequence)))
(define all-replace-children
(list replace-canvas
replace-button
skip-button
hide-replace-button))
(define hide-button
(new close-icon%
[callback (λ () (hide-search))]
[vertical-pad 0]
[parent search/replace-panel]))
(show/hide-replace)
(end-container-sequence)))
(super-new)))
(define (number->str/comma m)
(list->string
(reverse
(let loop ([chars (reverse (string->list (format "~a" m)))])
(cond
[(<= (length chars) 3)
chars]
[else (list* (list-ref chars 0)
(list-ref chars 1)
(list-ref chars 2)
#\,
(loop (cdddr chars)))])))))
(define searchable-text<%> (interface (searchable<%> text<%>)))
(define searchable-text-mixin
(mixin (text<%> searchable<%>) (searchable-text<%>)
(inherit get-editor)
(define/override (get-editor<%>) text:searching<%>)
(define/override (get-editor%) (text:searching-mixin (super get-editor%)))
(super-new)))
(define memory-canvases '())
(define bday-click-canvas%
(class canvas%
(define/override (on-event evt)
(cond
[(and (mrf-bday?)
(send evt button-up?))
(message-box (string-constant drscheme)
(string-constant happy-birthday-matthew))]
[else (super on-event evt)]))
(super-new)))
(define pref-save-canvas%
(class canvas%
(define on? #f)
(define mouse-over? #f)
(define mouse-down? #f)
(define/private (update-mouse-over? mo?)
(unless (eq? mouse-over? mo?)
(set! mouse-over? mo?)
(refresh)))
(define/private (update-mouse-down? md?)
(unless (eq? mouse-down? md?)
(set! mouse-down? md?)
(refresh)))
(define indicator "P")
(inherit refresh)
(define/override (on-event evt)
(cond
[(send evt entering?)
(update-mouse-over? #t)]
[(send evt leaving?)
(update-mouse-over? #f)])
(cond
[(send evt button-down?)
(update-mouse-down? #t)]
[(send evt button-up?)
(update-mouse-down? #f)
(let-values ([(cw ch) (get-client-size)])
(when (and (<= 0 (send evt get-x) cw)
(<= 0 (send evt get-y) ch))
(show-prefs-stats)))]))
(define/private (show-prefs-stats)
(define f (new frame%
[label (format "~a - Preferences Stats" (string-constant drscheme))]
[width 600]
[height 400]))
(define t (new text%))
(define ec (new editor-canvas% [parent f] [editor t]))
(send f reflow-container)
(send t begin-edit-sequence)
(define tp (open-output-text-editor t))
(define prefs-file (find-system-path 'pref-file))
(fprintf tp "prefs file:\n ~a\n\n" (path->string prefs-file))
(fprintf tp "setting a preference:\n ")
(preferences:set 'drracket:prefs-debug #f)
(parameterize ([current-output-port tp])
(time (preferences:set 'drracket:prefs-debug #t)))
(define file-contents (call-with-input-file prefs-file read))
(fprintf tp "\n~s preference keys\n\n" (length file-contents))
(fprintf tp "preferences taking the most space:\n")
(define sizes (map
(λ (x)
(list
(car x)
(bytes-length (string->bytes/utf-8 (format "~s" x)))))
file-contents))
(for ([frame (in-list (sort sizes > #:key cadr))]
[x (in-range 0 10)])
(define key (list-ref frame 0))
(define size (list-ref frame 1))
(fprintf tp " ~s (~s bytes)\n" key size))
(send t auto-wrap #t)
(send t set-position 0 0)
(send t lock #t)
(send t end-edit-sequence)
(send f show #t))
(define/override (on-paint)
(define-values (cw ch) (get-client-size))
(define dc (get-dc))
(define (draw-p)
(send dc set-font small-control-font)
(send dc draw-text indicator
(- (/ cw 2) (/ indicator-width 2))
(- (/ ch 2) (/ indicator-height 2))))
(cond
[on?
(send dc set-text-foreground (send the-color-database find-color "black"))
(draw-p)]
[mouse-over?
(send dc set-brush (if mouse-down? "blue" "skyblue") 'solid)
(send dc set-pen "black" 1 'transparent)
(send dc draw-rectangle 0 0 cw ch)
(send dc set-text-foreground (send the-color-database find-color "white"))
(draw-p)]))
(define/public (set-on? new-on?)
(set! on? new-on?)
(send (get-dc) erase)
(on-paint)
(flush))
(inherit get-dc flush get-client-size min-width min-height)
(super-new [stretchable-width #f]
[stretchable-height #f]
[style '(transparent no-focus)])
(send (get-dc) set-smoothing 'smoothed)
(define-values (indicator-width indicator-height)
(let-values ([(tw th _1 _2) (send (get-dc) get-text-extent indicator small-control-font)])
(values tw th)))
(min-width (+ (inexact->exact (ceiling indicator-width)) 4))
(min-height (+ (inexact->exact (ceiling indicator-height)) 4))))
(define basic% (focus-table-mixin (register-group-mixin (basic-mixin frame%))))
(define size-pref% (size-pref-mixin basic%))
(define info% (info-mixin basic%))
(define text-info% (text-info-mixin info%))
(define pasteboard-info% (pasteboard-info-mixin text-info%))
(define status-line% (status-line-mixin text-info%))
(define standard-menus% (standard-menus-mixin status-line%))
(define editor% (editor-mixin standard-menus%))
(define -text% (text-mixin editor%))
(define searchable% (searchable-text-mixin (searchable-mixin -text%)))
(define delegate% (delegate-mixin searchable%))
(define -pasteboard% (pasteboard-mixin editor%))