diff --git a/collects/drscheme/private/unit.ss b/collects/drscheme/private/unit.ss index f2fd266186..12dc905d0d 100644 --- a/collects/drscheme/private/unit.ss +++ b/collects/drscheme/private/unit.ss @@ -28,7 +28,8 @@ module browser threading seems wrong. "auto-language.ss" "insert-large-letters.ss" mrlib/switchable-button - + mrlib/cache-image-snip + (prefix-in drscheme:arrow: "../arrow.ss") mred @@ -112,60 +113,116 @@ module browser threading seems wrong. (or (is-a? text (get-definitions-text%)) (is-a? text drscheme:rep:text%)) (is-a? event mouse-event%)) - (let* ([end (send text get-end-position)] - [start (send text get-start-position)]) - (unless (= 0 (send text last-position)) - (let ([str (if (= end start) - (find-symbol - text - (call-with-values - (λ () - (send text dc-location-to-editor-location - (send event get-x) - (send event get-y))) - (λ (x y) - (send text find-position x y)))) - (send text get-text start end))] - [language - (let ([canvas (send text get-canvas)]) - (and canvas - (let ([tlw (send canvas get-top-level-window)]) - (and (is-a? tlw -frame<%>) - (send (send tlw get-definitions-text) - get-next-settings)))))]) - (unless (string=? str "") - (make-object separator-menu-item% menu) - (make-object menu-item% - (gui-utils:format-literal-label (string-constant search-help-desk-for) - (shorten-str - str - (- 200 (string-length (string-constant search-help-desk-for))))) - menu - (λ x (help-desk:help-desk str))) - (void))))))))) + + (let ([add-sep + (let ([added? #f]) + (λ () + (unless added? + (set! added? #t) + (new separator-menu-item% [parent menu]))))]) + + (let* ([end (send text get-end-position)] + [start (send text get-start-position)]) + (unless (= 0 (send text last-position)) + (let ([str (if (= end start) + (find-symbol + text + (call-with-values + (λ () + (send text dc-location-to-editor-location + (send event get-x) + (send event get-y))) + (λ (x y) + (send text find-position x y)))) + (send text get-text start end))] + [language + (let ([canvas (send text get-canvas)]) + (and canvas + (let ([tlw (send canvas get-top-level-window)]) + (and (is-a? tlw -frame<%>) + (send (send tlw get-definitions-text) + get-next-settings)))))]) + (unless (string=? str "") + (add-sep) + (make-object menu-item% + (gui-utils:format-literal-label + (string-constant search-help-desk-for) + (shorten-str + str + (- 200 (string-length (string-constant search-help-desk-for))))) + menu + (λ x (help-desk:help-desk str))))))) + + (when (is-a? text editor:basic<%>) + (let-values ([(pos text) (send text get-pos/text event)]) + (when (and pos (is-a? text text%)) + (send text split-snip pos) + (send text split-snip (+ pos 1)) + (let ([snip (send text find-snip pos 'after-or-none)]) + (when (or (is-a? snip image-snip%) + (is-a? snip cache-image-snip%)) + (add-sep) + (new menu-item% + [parent menu] + [label (string-constant save-image)] + [callback + (λ (_1 _2) + (let ([fn (put-file #f + (send text get-top-level-window) + #f "untitled.png" "png")]) + (when fn + (let ([kind (filename->kind fn)]) + (cond + [kind + (send (send snip get-bitmap) save-file fn kind)] + [else + (message-box + (string-constant drscheme) + "Must choose a filename that ends with either .png, .jpg, .xbm, or .xpm")])))))])))))) + + (void)))))) + + (define (filename->kind fn) + (let ([ext (filename-extension fn)]) + (and ext + (let ([sym (string->symbol (bytes->string/utf-8 ext))]) + (ormap (λ (pr) (and (eq? sym (car pr)) (cadr pr))) + allowed-extensions))))) + + (define allowed-extensions '((png png) + (jpg jpeg) + (xbm xbm) + (xpm xpm))) + + ;; find-symbol : number -> string ;; finds the symbol around the position `pos' (approx) (define (find-symbol text pos) - (let* ([before - (let loop ([i (- pos 1)] - [chars null]) - (if (< i 0) - chars - (let ([char (send text get-character i)]) - (if (non-letter? char) + (send text split-snip pos) + (send text split-snip (+ pos 1)) + (let ([snip (send text find-snip pos 'after-or-none)]) + (if (is-a? snip string-snip%) + (let* ([before + (let loop ([i (- pos 1)] + [chars null]) + (if (< i 0) chars - (loop (- i 1) - (cons char chars))))))] - [after - (let loop ([i pos]) - (if (< i (send text last-position)) - (let ([char (send text get-character i)]) - (if (non-letter? char) - null - (cons char (loop (+ i 1))))) - null))]) - (apply string (append before after)))) + (let ([char (send text get-character i)]) + (if (non-letter? char) + chars + (loop (- i 1) + (cons char chars))))))] + [after + (let loop ([i pos]) + (if (< i (send text last-position)) + (let ([char (send text get-character i)]) + (if (non-letter? char) + null + (cons char (loop (+ i 1))))) + null))]) + (apply string (append before after))) + ""))) ;; non-letter? : char -> boolean ;; returns #t if the character belongs in a symbol (approx) and #f it is @@ -293,7 +350,7 @@ module browser threading seems wrong. (let ([interactions-text (send f get-interactions-text)]) (when (object? interactions-text) (send interactions-text reset-highlighting)))))) - + (define/augment (after-insert x y) (reset-highlighting) (inner (void) after-insert x y)) diff --git a/collects/drscheme/syncheck.ss b/collects/drscheme/syncheck.ss index a7b4ba3fac..e5fe29a1a5 100644 --- a/collects/drscheme/syncheck.ss +++ b/collects/drscheme/syncheck.ss @@ -220,7 +220,7 @@ If the namespace does not, they are colored the unbound color. (let* ([cursor-arrow (make-object cursor% 'arrow)]) (class* super% (syncheck-text<%>) (inherit set-cursor get-admin invalidate-bitmap-cache set-position - position-location + get-pos/text position-location get-canvas last-position dc-location-to-editor-location find-position begin-edit-sequence end-edit-sequence highlight-range unhighlight-range) @@ -627,35 +627,6 @@ If the namespace does not, they are colored the unbound color. (for-each-tail-arrows/to/from tail-arrow-from-pos tail-arrow-from-text tail-arrow-to-pos tail-arrow-to-text)) - ;; get-pos/text : event -> (values (union #f text%) (union number #f)) - ;; returns two #fs to indicate the event doesn't correspond to - ;; a position in an editor, or returns the innermost text - ;; and position in that text where the event is. - (define/private (get-pos/text event) - (let ([event-x (send event get-x)] - [event-y (send event get-y)] - [on-it? (box #f)]) - (let loop ([editor this]) - (let-values ([(x y) (send editor dc-location-to-editor-location event-x event-y)]) - (cond - [(is-a? editor text%) - (let ([pos (send editor find-position x y #f on-it?)]) - (cond - [(not (unbox on-it?)) (values #f #f)] - [else - (let ([snip (send editor find-snip pos 'after-or-none)]) - (if (and snip - (is-a? snip editor-snip%)) - (loop (send snip get-editor)) - (values pos editor)))]))] - [(is-a? editor pasteboard%) - (let ([snip (send editor find-snip x y)]) - (if (and snip - (is-a? snip editor-snip%)) - (loop (send snip get-editor)) - (values #f #f)))] - [else (values #f #f)]))))) - (define/override (on-event event) (if arrow-vectors (cond @@ -674,7 +645,7 @@ If the namespace does not, they are colored the unbound color. (send event entering?)) (let-values ([(pos text) (get-pos/text event)]) (cond - [(and pos text) + [(and pos (is-a? text text%)) (unless (and (equal? pos cursor-location) (eq? cursor-text text)) (set! cursor-location pos) @@ -707,7 +678,7 @@ If the namespace does not, they are colored the unbound color. (super on-event event)] [(send event button-down? 'right) (let-values ([(pos text) (get-pos/text event)]) - (if (and pos text) + (if (and pos (is-a? text text%)) (let ([arrow-vector (hash-ref arrow-vectors text (λ () #f))]) (when arrow-vector (let ([vec-ents (vector-ref arrow-vector pos)]) diff --git a/collects/framework/main.ss b/collects/framework/main.ss index f61cf6b7e9..91615efd78 100644 --- a/collects/framework/main.ss +++ b/collects/framework/main.ss @@ -909,7 +909,10 @@ (parameter-doc keymap:add-to-right-button-menu (parameter/c - (-> (is-a?/c popup-menu%) (is-a?/c editor<%>) (is-a?/c event%) void?)) + (-> (is-a?/c popup-menu%) + (is-a?/c editor<%>) + (is-a?/c event%) + void?)) proc @{When the keymap that @scheme[keymap:get-global] returns is installed into an editor, this parameter's value is used for right button diff --git a/collects/framework/private/editor.ss b/collects/framework/private/editor.ss index e3b9283e32..d4887ea9d8 100644 --- a/collects/framework/private/editor.ss +++ b/collects/framework/private/editor.ss @@ -38,7 +38,9 @@ on-close can-close? close - get-filename/untitled-name)) + get-filename/untitled-name + + get-pos/text)) (define basic-mixin (mixin (editor<%>) (basic<%>) @@ -49,6 +51,31 @@ (begin (on-close) #t) #f)) + (define/public (get-pos/text event) + (let ([event-x (send event get-x)] + [event-y (send event get-y)] + [on-it? (box #f)]) + (let loop ([editor this]) + (let-values ([(x y) (send editor dc-location-to-editor-location event-x event-y)]) + (cond + [(is-a? editor text%) + (let ([pos (send editor find-position x y #f on-it?)]) + (cond + [(not (unbox on-it?)) (values #f #f)] + [else + (let ([snip (send editor find-snip pos 'after-or-none)]) + (if (and snip + (is-a? snip editor-snip%)) + (loop (send snip get-editor)) + (values pos editor)))]))] + [(is-a? editor pasteboard%) + (let ([snip (send editor find-snip x y)]) + (if (and snip + (is-a? snip editor-snip%)) + (loop (send snip get-editor)) + (values editor #f)))] + [else (values #f #f)]))))) + ;; get-filename/untitled-name : -> string ;; returns a string representing the visible name for this file, ;; or "Untitled " for some n. @@ -291,8 +318,7 @@ (get-top-level-window)]) (finder:put-file f d))) - - (super-instantiate ()))) + (super-new))) (define standard-style-list (new style-list%)) (define (get-standard-style-list) standard-style-list) diff --git a/collects/scribblings/framework/editor.scrbl b/collects/scribblings/framework/editor.scrbl index ea0bd0f214..04755375b0 100644 --- a/collects/scribblings/framework/editor.scrbl +++ b/collects/scribblings/framework/editor.scrbl @@ -116,6 +116,21 @@ returns a symbolic name (something like "Untitled"). } + + @defmethod[(get-pos/text [event (is-a?/c mouse-event%)]) + (values (or/c false/c (is-a?/c editor<%>)) + (or/c false/c number?))]{ + + This method's first result is @scheme[#f] when the mouse + event does not correspond to a location in the editor. + + If the first result is an @scheme[text%] object, then the + second result will be a position in the editor and + otherwise the second result will be @scheme[#f]. + + The @scheme[editor<%>] object will always be the nearest + enclosing editor containing the mouse click. + } } @defmixin[editor:basic-mixin (editor<%>) (editor:basic<%>)]{ This provides the basic editor services required by the rest of the diff --git a/collects/string-constants/english-string-constants.ss b/collects/string-constants/english-string-constants.ss index 5e16c70dcd..d34411e73c 100644 --- a/collects/string-constants/english-string-constants.ss +++ b/collects/string-constants/english-string-constants.ss @@ -391,6 +391,9 @@ please adhere to these guidelines: (yes "Yes") (no "No") + ;; saving image (right click on an image to see the text) + (save-image "Save image...") + ;;; preferences (preferences "Preferences") (error-saving-preferences "Error saving preferences: ~a")