#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] (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) (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 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 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 ([focus-snip (send top-searching-edit get-focus-snip)]) (if (and focus-snip (is-a? focus-snip editor-snip%)) (send focus-snip get-editor) top-searching-edit))] [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 (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)]) (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)) (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%))