diff --git a/collects/framework/gui-utils.ss b/collects/framework/gui-utils.ss index bf00eb0f..f3e6281f 100644 --- a/collects/framework/gui-utils.ss +++ b/collects/framework/gui-utils.ss @@ -1,273 +1,271 @@ +#reader scribble/reader +#lang scheme/gui -(module gui-utils mzscheme - (require mzlib/class - mred - mzlib/etc - mzlib/contract - string-constants) - - (define-syntax (provide/contract/docs stx) - (syntax-case stx () - [(_ (name contract docs ...) ...) - (syntax (provide/contract (name contract) ...))])) - - - (define (trim-string str size) - (let ([str-size (string-length str)]) - (cond - [(<= str-size size) - str] - [else - (let* ([between "..."] - [pre-length (- (quotient size 2) - (quotient (string-length between) 2))] - [post-length (- size - pre-length - (string-length between))]) - (cond - [(or (<= pre-length 0) - (<= post-length 0)) - (substring str 0 size)] - [else - (string-append - (substring str 0 pre-length) - between - (substring str - (- str-size post-length) - str-size))]))]))) - - - (define maximum-string-label-length 200) - - ;; format-literal-label: string any* -> string - (define (format-literal-label format-str . args) - (quote-literal-label (apply format format-str args))) - - ;; quote-literal-label: string -> string - (define (quote-literal-label a-str) - (trim-string (regexp-replace* #rx"(&)" a-str "\\1\\1") - maximum-string-label-length)) - +(require string-constants) - - ;; selected-text-color : color - (define selected-text-color (send the-color-database find-color "black")) +(require scribble/srcdoc) +(require/doc scheme/base scribble/manual) - ;; unselected-text-color : color - (define unselected-text-color (case (system-type) - [(macosx) (make-object color% 75 75 75)] - [else (send the-color-database find-color "black")])) - - ;; selected-brush : brush - (define selected-brush (send the-brush-list find-or-create-brush "WHITE" 'solid)) - - ;; unselected-brush : brush - (define unselected-brush (send the-brush-list find-or-create-brush (get-panel-background) 'solid)) - - ;; button-down/over-brush : brush - (define button-down/over-brush - (case (system-type) - [(macosx) (send the-brush-list find-or-create-brush - "light blue" - 'solid)] +(define (trim-string str size) + (let ([str-size (string-length str)]) + (cond + [(<= str-size size) + str] [else - (send the-brush-list find-or-create-brush - (make-object color% 225 225 255) - 'solid)])) + (let* ([between "..."] + [pre-length (- (quotient size 2) + (quotient (string-length between) 2))] + [post-length (- size + pre-length + (string-length between))]) + (cond + [(or (<= pre-length 0) + (<= post-length 0)) + (substring str 0 size)] + [else + (string-append + (substring str 0 pre-length) + between + (substring str + (- str-size post-length) + str-size))]))]))) - - ;; name-box-pen : pen - ;; this pen draws the lines around each individual item - (define name-box-pen (send the-pen-list find-or-create-pen "black" 1 'solid)) - ;; background-brush : brush - ;; this brush is set when drawing the background for the control - (define background-brush - (case (system-type) - [(macosx) (send the-brush-list find-or-create-brush (get-panel-background) 'panel)] - [else (send the-brush-list find-or-create-brush "white" 'solid)])) - - ;; background-pen : pen - ;; this pen is set when drawing the background for the control - (define background-pen (send the-pen-list find-or-create-pen "black" 1 'transparent)) - - ;; label-font : font - (define label-font (send the-font-list find-or-create-font - (if (eq? (system-type) 'windows) 10 12) - 'system 'normal - (if (eq? (system-type) 'macosx) 'bold 'normal) - #f)) +(define maximum-string-label-length 200) - ;; name-gap : number - ;; the space between each name - (define name-gap 4) - - ;; hang-over : number - ;; the amount of space a single entry "slants" over - (define hang-over 8) - - ;; top-space : number - ;; the gap at the top of the canvas, above all the choices - (define top-space 4) - - ;; bottom-space : number - ;; the extra space below the words - (define bottom-space 2) - - ;; end choices-canvas% - - (define (cancel-on-right?) (eq? (system-type) 'windows)) - - (define ok/cancel-buttons - (opt-lambda (parent - confirm-callback - cancel-callback - [confirm-str (string-constant ok)] - [cancel-str (string-constant cancel)]) - (let ([confirm (λ () - (instantiate button% () - (parent parent) - (callback confirm-callback) - (label confirm-str) - (style '(border))))] - [cancel (λ () - (instantiate button% () - (parent parent) - (callback cancel-callback) - (label cancel-str)))]) - (let-values ([(b1 b2) - (cond - [(cancel-on-right?) - (values (confirm) (cancel))] - [else - (values (cancel) (confirm))])]) - (let ([w (max (send b1 get-width) - (send b2 get-width))]) - (send b1 min-width w) - (send b2 min-width w) - (if (cancel-on-right?) - (values b1 b2) - (values b2 b1))))))) +;; format-literal-label: string any* -> string +(define (format-literal-label format-str . args) + (quote-literal-label (apply format format-str args))) - - (define clickback-delta (make-object style-delta% 'change-underline #t)) - (define white-on-black-clickback-delta (make-object style-delta% 'change-underline #t)) +;; quote-literal-label: string -> string +(define (quote-literal-label a-str) + (trim-string (regexp-replace* #rx"(&)" a-str "\\1\\1") + maximum-string-label-length)) + + + +;; selected-text-color : color +(define selected-text-color (send the-color-database find-color "black")) + +;; unselected-text-color : color +(define unselected-text-color (case (system-type) + [(macosx) (make-object color% 75 75 75)] + [else (send the-color-database find-color "black")])) + +;; selected-brush : brush +(define selected-brush (send the-brush-list find-or-create-brush "WHITE" 'solid)) + +;; unselected-brush : brush +(define unselected-brush (send the-brush-list find-or-create-brush (get-panel-background) 'solid)) + +;; button-down/over-brush : brush +(define button-down/over-brush + (case (system-type) + [(macosx) (send the-brush-list find-or-create-brush + "light blue" + 'solid)] + [else + (send the-brush-list find-or-create-brush + (make-object color% 225 225 255) + 'solid)])) + + +;; name-box-pen : pen +;; this pen draws the lines around each individual item +(define name-box-pen (send the-pen-list find-or-create-pen "black" 1 'solid)) + +;; background-brush : brush +;; this brush is set when drawing the background for the control +(define background-brush + (case (system-type) + [(macosx) (send the-brush-list find-or-create-brush (get-panel-background) 'panel)] + [else (send the-brush-list find-or-create-brush "white" 'solid)])) + +;; background-pen : pen +;; this pen is set when drawing the background for the control +(define background-pen (send the-pen-list find-or-create-pen "black" 1 'transparent)) + +;; label-font : font +(define label-font (send the-font-list find-or-create-font + (if (eq? (system-type) 'windows) 10 12) + 'system 'normal + (if (eq? (system-type) 'macosx) 'bold 'normal) + #f)) + +;; name-gap : number +;; the space between each name +(define name-gap 4) + +;; hang-over : number +;; the amount of space a single entry "slants" over +(define hang-over 8) + +;; top-space : number +;; the gap at the top of the canvas, above all the choices +(define top-space 4) + +;; bottom-space : number +;; the extra space below the words +(define bottom-space 2) + +;; end choices-canvas% + +(define (cancel-on-right?) (eq? (system-type) 'windows)) + +(define ok/cancel-buttons + (lambda (parent + confirm-callback + cancel-callback + [confirm-str (string-constant ok)] + [cancel-str (string-constant cancel)]) + (let ([confirm (λ () + (instantiate button% () + (parent parent) + (callback confirm-callback) + (label confirm-str) + (style '(border))))] + [cancel (λ () + (instantiate button% () + (parent parent) + (callback cancel-callback) + (label cancel-str)))]) + (let-values ([(b1 b2) + (cond + [(cancel-on-right?) + (values (confirm) (cancel))] + [else + (values (cancel) (confirm))])]) + (let ([w (max (send b1 get-width) + (send b2 get-width))]) + (send b1 min-width w) + (send b2 min-width w) + (if (cancel-on-right?) + (values b1 b2) + (values b2 b1))))))) + + +(define clickback-delta (make-object style-delta% 'change-underline #t)) +(define white-on-black-clickback-delta (make-object style-delta% 'change-underline #t)) +(let () (send clickback-delta set-delta-foreground "BLUE") (send white-on-black-clickback-delta set-delta-foreground "deepskyblue") - (define get-clickback-delta - (opt-lambda ([white-on-black? #f]) - (if white-on-black? - white-on-black-clickback-delta - clickback-delta))) - - (define clicked-clickback-delta (make-object style-delta%)) - (define white-on-black-clicked-clickback-delta (make-object style-delta%)) + (void)) +(define get-clickback-delta + (lambda ([white-on-black? #f]) + (if white-on-black? + white-on-black-clickback-delta + clickback-delta))) + +(define clicked-clickback-delta (make-object style-delta%)) +(define white-on-black-clicked-clickback-delta (make-object style-delta%)) +(let () (send clicked-clickback-delta set-delta-background "BLACK") (send white-on-black-clicked-clickback-delta set-delta-background "white") - (define get-clicked-clickback-delta - (opt-lambda ([white-on-black? #f]) - (if white-on-black? - white-on-black-clicked-clickback-delta - clicked-clickback-delta))) - - (define next-untitled-name - (let ([n 1]) + (void)) +(define get-clicked-clickback-delta + (lambda ([white-on-black? #f]) + (if white-on-black? + white-on-black-clicked-clickback-delta + clicked-clickback-delta))) + +(define next-untitled-name + (let ([n 1]) + (λ () + (begin0 + (cond + [(= n 1) (string-constant untitled)] + [else (format (string-constant untitled-n) n)]) + (set! n (+ n 1)))))) + +(define cursor-delay + (let ([x 0.25]) + (case-lambda + [() x] + [(v) (set! x v) x]))) + +(define show-busy-cursor + (lambda (thunk [delay (cursor-delay)]) + (local-busy-cursor #f thunk delay))) + +(define delay-action + (λ (delay-time open close) + (let ([semaphore (make-semaphore 1)] + [open? #f] + [skip-it? #f]) + (thread + (λ () + (sleep delay-time) + (semaphore-wait semaphore) + (unless skip-it? + (set! open? #t) + (open)) + (semaphore-post semaphore))) (λ () - (begin0 - (cond - [(= n 1) (string-constant untitled)] - [else (format (string-constant untitled-n) n)]) - (set! n (+ n 1)))))) - - (define cursor-delay - (let ([x 0.25]) - (case-lambda - [() x] - [(v) (set! x v) x]))) - - (define show-busy-cursor - (opt-lambda (thunk [delay (cursor-delay)]) - (local-busy-cursor #f thunk delay))) - - (define delay-action - (λ (delay-time open close) - (let ([semaphore (make-semaphore 1)] - [open? #f] - [skip-it? #f]) - (thread - (λ () - (sleep delay-time) - (semaphore-wait semaphore) - (unless skip-it? - (set! open? #t) - (open)) - (semaphore-post semaphore))) - (λ () - (semaphore-wait semaphore) - (set! skip-it? #t) - (when open? - (close)) - (semaphore-post semaphore))))) - - (define local-busy-cursor - (let ([watch (make-object cursor% 'watch)]) - (case-lambda - [(win thunk) (local-busy-cursor win thunk (cursor-delay))] - [(win thunk delay) - (let* ([old-cursor #f] - [cursor-off void]) - (dynamic-wind - (λ () - (set! cursor-off - (delay-action - delay - (λ () - (if win - (begin (set! old-cursor (send win get-cursor)) - (send win set-cursor watch)) - (begin-busy-cursor))) - (λ () - (if win - (send win set-cursor old-cursor) - (end-busy-cursor)))))) - (λ () (thunk)) - (λ () (cursor-off))))]))) - - (define unsaved-warning - (opt-lambda (filename action-anyway (can-save-now? #f) (parent #f)) - (let ([mb-res (message-box/custom - (string-constant warning) - (format (string-constant file-is-not-saved) filename) - (string-constant save) - (string-constant cancel) - action-anyway - parent - (if can-save-now? - '(default=1 caution) - '(default=2 caution)) - 2)]) - (case mb-res - [(1) 'save] - [(2) 'cancel] - [(3) 'continue])))) - - (define get-choice - (opt-lambda (message - true-choice - false-choice - (title (string-constant warning)) - (default-result 'disallow-close) - (parent #f) - (style 'app) - (checkbox-proc #f) - (checkbox-label (string-constant dont-ask-again))) - (let* ([check? (and checkbox-proc (checkbox-proc))] - [style (if (eq? style 'app) `(default=1) `(default=1 ,style))] - [style (if (eq? 'disallow-close default-result) + (semaphore-wait semaphore) + (set! skip-it? #t) + (when open? + (close)) + (semaphore-post semaphore))))) + +(define local-busy-cursor + (let ([watch (make-object cursor% 'watch)]) + (case-lambda + [(win thunk) (local-busy-cursor win thunk (cursor-delay))] + [(win thunk delay) + (let* ([old-cursor #f] + [cursor-off void]) + (dynamic-wind + (λ () + (set! cursor-off + (delay-action + delay + (λ () + (if win + (begin (set! old-cursor (send win get-cursor)) + (send win set-cursor watch)) + (begin-busy-cursor))) + (λ () + (if win + (send win set-cursor old-cursor) + (end-busy-cursor)))))) + (λ () (thunk)) + (λ () (cursor-off))))]))) + +(define unsaved-warning + (lambda (filename action-anyway (can-save-now? #f) (parent #f)) + (let ([mb-res (message-box/custom + (string-constant warning) + (format (string-constant file-is-not-saved) filename) + (string-constant save) + (string-constant cancel) + action-anyway + parent + (if can-save-now? + '(default=1 caution) + '(default=2 caution)) + 2)]) + (case mb-res + [(1) 'save] + [(2) 'cancel] + [(3) 'continue])))) + +(define get-choice + (lambda (message + true-choice + false-choice + (title (string-constant warning)) + (default-result 'disallow-close) + (parent #f) + (style 'app) + (checkbox-proc #f) + (checkbox-label (string-constant dont-ask-again))) + (let* ([check? (and checkbox-proc (checkbox-proc))] + [style (if (eq? style 'app) `(default=1) `(default=1 ,style))] + [style (if (eq? 'disallow-close default-result) (cons 'disallow-close style) style)] - [style (if check? (cons 'checked style) style)] - [return (λ (mb-res) (case mb-res [(1) #t] [(2) #f] [else mb-res]))]) - (if checkbox-proc + [style (if check? (cons 'checked style) style)] + [return (λ (mb-res) (case mb-res [(1) #t] [(2) #f] [else mb-res]))]) + (if checkbox-proc (let-values ([(mb-res checked) (message+check-box/custom title message checkbox-label true-choice false-choice #f @@ -276,293 +274,287 @@ (return mb-res)) (return (message-box/custom title message true-choice false-choice #f parent style default-result)))))) + +;; manual renaming +(define gui-utils:trim-string trim-string) +(define gui-utils:quote-literal-label quote-literal-label) +(define gui-utils:format-literal-label format-literal-label) +(define gui-utils:next-untitled-name next-untitled-name) +(define gui-utils:show-busy-cursor show-busy-cursor) +(define gui-utils:delay-action delay-action) +(define gui-utils:local-busy-cursor local-busy-cursor) +(define gui-utils:unsaved-warning unsaved-warning) +(define gui-utils:get-choice get-choice) +(define gui-utils:get-clicked-clickback-delta get-clicked-clickback-delta) +(define gui-utils:get-clickback-delta get-clickback-delta) +(define gui-utils:ok/cancel-buttons ok/cancel-buttons) +(define gui-utils:cancel-on-right? cancel-on-right?) +(define gui-utils:cursor-delay cursor-delay) + + +(provide/doc + (proc-doc + gui-utils:trim-string + (->d ([str string?][size (and/c number? positive?)]) + () + [_ (and/c string? + (λ (str) + ((string-length str) . <= . size)))]) + @{Constructs a string whose size is less + than @scheme[size] by trimming the @scheme[str] + and inserting an ellispses into it.}) + + (proc-doc + gui-utils:quote-literal-label + (->d ([str string?]) + () + [_ (and/c string? + (lambda (str) + ((string-length str) . <= . 200)))]) + @{Constructs a string whose ampersand characters are + escaped; the label is also trimmed to <= 200 + characters.}) + + (proc-doc + gui-utils:format-literal-label + (->d ([str string?]) + () + #:rest rest (listof any/c) + [_ (and/c string? + (lambda (str) + ((string-length str) . <= . 200)))]) + @{Formats a string whose ampersand characters are + escaped; the label is also trimmed to <= 200 + characters.}) + + (proc-doc/names + gui-utils:cancel-on-right? + (-> boolean?) + () + @{Returns @scheme[#t] if cancel should be on the right-hand side (or below) + in a dialog and @scheme[#f] otherwise. + + See also @scheme[gui-utils:ok/cancel-buttons].}) + (proc-doc/names + gui-utils:ok/cancel-buttons + (->* ((is-a?/c area-container<%>) + ((is-a?/c button%) (is-a?/c event%) . -> . any) + ((is-a?/c button%) (is-a?/c event%) . -> . any)) + (string? + string?) + (values (is-a?/c button%) + (is-a?/c button%))) + ((parent + confirm-callback + cancel-callback) + ((confirm-label (string-constant ok)) + (cancel-label (string-constant cancel)))) + @{Adds an Ok and a cancel button to a panel, changing the order + to suit the platform. Under Mac OS X and unix, the confirmation action + is on the right (or bottom) and under Windows, the canceling action is on the + right (or bottom). + The confirmation action button has the @scheme['(border)] style. + The buttons are also sized to be the same width. + + The first result is be the OK button and the second is + the cancel button. + + See also @scheme[gui-utils:cancel-on-right?].}) + + (proc-doc/names + gui-utils:next-untitled-name + (-> string?) + () + @{Returns a name for the next opened untitled frame. The first + name is ``Untitled'', the second is ``Untitled 2'', + the third is ``Untitled 3'', and so forth.}) + (proc-doc/names + gui-utils:cursor-delay + (case-> + (-> real?) + (real? . -> . void?)) + (() (new-delay)) + @{This function is @italic{not} a parameter. + Instead, the state is just stored in the closure. + + The first case in the case lambda + returns the current delay in seconds before a watch cursor is shown, + when either @scheme[gui-utils:local-busy-cursor] or + @scheme[gui-utils:show-busy-cursor] is called. + + The second case in the case lambda + Sets the delay, in seconds, before a watch cursor is shown, when + either @scheme[gui-utils:local-busy-cursor] or + @scheme[gui-utils:show-busy-cursor] is called.}) + (proc-doc/names + gui-utils:show-busy-cursor + (->* ((-> any/c)) + (integer?) + any/c) + ((thunk) + ((delay (gui-utils:cursor-delay)))) + @{Evaluates @scheme[(thunk)] with a watch cursor. The argument + @scheme[delay] specifies the amount of time before the watch cursor is + opened. Use @scheme[gui-utils:cursor-delay] to set this value + to all calls. + + This function returns the result of @scheme[thunk].}) + (proc-doc/names + gui-utils:delay-action + (real? + (-> void?) + (-> void?) + . -> . + void?) + (delay-time open close) + @{Use this function to delay an action for some period of time. It also + supports cancelling the action before the time period elapses. For + example, if you want to display a watch cursor, but you only want it + to appear after 2 seconds and the action may or may not take more than + two seconds, use this pattern: + + @schemeblock[ + (let ([close-down + (gui-utils:delay-action + 2 + (λ () .. init watch cursor ...) + (λ () .. close watch cursor ...))]) + ;; .. do action ... + (close-down))] + + Creates a thread that waits @scheme[delay-time]. After @scheme[delay-time] + has elapsed, if the result thunk has @italic{not} been called, call + @scheme[open]. Then, when the result thunk is called, call + @scheme[close]. The function @scheme[close] will only be called if + @scheme[open] has been called.}) + + (proc-doc/names + gui-utils:local-busy-cursor + (->* + ((is-a?/c window<%>) + (-> any/c)) + (integer?) + any/c) + ((window thunk) + ((delay (gui-utils:cursor-delay)))) + @{Evaluates @scheme[(thunk)] with a watch cursor in @scheme[window]. If + @scheme[window] is @scheme[#f], the watch cursor is turned on globally. The + argument @scheme[delay] specifies the amount of time before the watch + cursor is opened. Use + @scheme[gui-utils:cursor-delay] + to set this value for all uses of this function. + + The result of this function is the result of @scheme[thunk].}) + + (proc-doc/names + gui-utils:unsaved-warning + (->* + (string? + string?) + (boolean? + (or/c false/c + (is-a?/c frame%) + (is-a?/c dialog%))) + (symbols 'continue 'save 'cancel)) + ((filename action) + ((can-save-now? #f) + (parent #f))) - ;; manual renaming - (define gui-utils:trim-string trim-string) - (define gui-utils:quote-literal-label quote-literal-label) - (define gui-utils:format-literal-label format-literal-label) - (define gui-utils:next-untitled-name next-untitled-name) - (define gui-utils:show-busy-cursor show-busy-cursor) - (define gui-utils:delay-action delay-action) - (define gui-utils:local-busy-cursor local-busy-cursor) - (define gui-utils:unsaved-warning unsaved-warning) - (define gui-utils:get-choice get-choice) - (define gui-utils:get-clicked-clickback-delta get-clicked-clickback-delta) - (define gui-utils:get-clickback-delta get-clickback-delta) - (define gui-utils:ok/cancel-buttons ok/cancel-buttons) - (define gui-utils:cancel-on-right? cancel-on-right?) - (define gui-utils:cursor-delay cursor-delay) + @{This displays a dialog that warns the user of a unsaved file. + + The string, @scheme[action], indicates what action is about to + take place, without saving. For example, if the application + is about to close a file, a good action is @scheme["Close" "Anyway"]. + The result symbol indicates the user's choice. If + @scheme[can-save-now?] is @scheme[#f], this function does not + give the user the ``Save'' option and thus will not return + @scheme['save].}) + + (proc-doc/names + gui-utils:get-choice + (->* (string? + string? + string?) + (string? + any/c + (or/c false/c (is-a?/c frame%) (is-a?/c dialog%)) + (symbols 'app 'caution 'stop) + (or/c false/c (case-> (boolean? . -> . void?) + (-> boolean?))) + string?) + any/c) + ((message true-choice false-choice) + ((title (string-constant warning)) + (default-result 'disallow-close) + (parent #f) + (style 'app) + (checkbox-proc #f) + (checkbox-label (string-constant dont-ask-again)))) - - (provide/contract/docs - - (gui-utils:trim-string - (string? - (and/c number? positive?) - . ->d . - (λ (str size) - (and/c string? - (λ (str) - ((string-length str) . <= . size))))) - (str size) - "Constructs a string whose size is less" - "than \\var{size} by trimming the \\var{str}" - "and inserting an ellispses into it.") - - (gui-utils:quote-literal-label - (string? - . ->d . - (lambda (str) - (and/c string? - (lambda (str) - ((string-length str) . <= . 200))))) - "Constructs a string whose ampersand characters are" - "escaped; the label is also trimmed to <= 200" - "characters.") - - (gui-utils:format-literal-label - ((string?) - (listof any/c) - . ->d* . - (lambda (str . rest) - (and/c string? - (lambda (str) - ((string-length str) . <= . 200))))) - "Formats a string whose ampersand characters are" - "escaped; the label is also trimmed to <= 200" - "characters.") - - (gui-utils:cancel-on-right? - (-> boolean?) - () - "Returns \\scheme{#t} if cancel should be on the right-hand side (or below)" - "in a dialog and \\scheme{#f} otherwise." - "" - "See also" - "@flink gui-utils:ok/cancel-buttons %" - ".") - (gui-utils:ok/cancel-buttons - (opt->* - ((is-a?/c area-container<%>) - ((is-a?/c button%) (is-a?/c event%) . -> . any) - ((is-a?/c button%) (is-a?/c event%) . -> . any)) - (string? - string?) - ((is-a?/c button%) - (is-a?/c button%))) - ((parent - confirm-callback - cancel-callback) - ((confirm-label (string-constant ok)) - (cancel-label (string-constant cancel)))) - "Adds an Ok and a cancel button to a panel, changing the order" - "to suit the platform. Under \\MacOSBoth{} and unix, the confirmation action" - "is on the right (or bottom) and under Windows, the canceling action is on the" - "right (or bottom)." - "The confirmation action button has the \\scheme|'(border)| style." - "The buttons are also sized to be the same width." - "" - "The first result is be the OK button and the second is" - "the cancel button." - "" - "See also" - "@flink gui-utils:cancel-on-right? %" - ".") - - (gui-utils:next-untitled-name - (-> string?) - () - "Returns a name for the next opened untitled frame. The first" - "name is ``Untitled'', the second is ``Untitled 2''," - "the third is ``Untitled 3'', and so forth.") - (gui-utils:cursor-delay - (case-> - (-> real?) - (real? . -> . void?)) - (() (new-delay)) - "This function is {\\em not\\/} a parameter." - "Instead, the state is just stored in the closure." - "" - "The first case in the case lambda" - "returns the current delay in seconds before a watch cursor is shown," - "when either \\iscmprocedure{gui-utils:local-busy-cursor} or" - "\\iscmprocedure{gui-utils:show-busy-cursor} is called." - - "The second case in the case lambda" - "Sets the delay, in seconds, before a watch cursor is shown, when" - "either \\iscmprocedure{gui-utils:local-busy-cursor} or" - "\\iscmprocedure{gui-utils:show-busy-cursor} is called.") - (gui-utils:show-busy-cursor - (opt-> - ((-> any/c)) - (integer?) - any/c) - ((thunk) - ((delay (gui-utils:cursor-delay)))) - "Evaluates \\rawscm{(\\var{thunk})} with a watch cursor. The argument" - "\\var{delay} specifies the amount of time before the watch cursor is" - "opened. Use \\iscmprocedure{gui-utils:cursor-delay} to set this value" - "to all calls." - "" - "This function returns the result of \\var{thunk}.") - (gui-utils:delay-action - (real? - (-> void?) - (-> void?) - . -> . - void?) - (delay-time open close) - "Use this function to delay an action for some period of time. It also" - "supports cancelling the action before the time period elapses. For" - "example, if you want to display a watch cursor, but you only want it" - "to appear after 2 seconds and the action may or may not take more than" - "two seconds, use this pattern:" - "" - "\\begin{schemedisplay}" - "(let ([close-down" - " (gui-utils:delay-action" - " 2" - " (λ () .. init watch cursor ...)" - " (λ () .. close watch cursor ...))])" - " ;; .. do action ..." - " (close-down))" - "\\end{schemedisplay}" - "" - "Creates a thread that waits \\var{delay-time}. After \\var{delay-time}" - "has elapsed, if the result thunk has {\\em not} been called, call" - "\\var{open}. Then, when the result thunk is called, call" - "\\var{close}. The function \\var{close} will only be called if" - "\\var{open} has been called.") - - (gui-utils:local-busy-cursor - (opt-> - ((is-a?/c window<%>) - (-> any/c)) - (integer?) - any/c) - ((window thunk) - ((delay (gui-utils:cursor-delay)))) - "Evaluates \\rawscm{(\\var{thunk})} with a watch cursor in \\var{window}. If" - "\\var{window} is \\rawscm{\\#f}, the watch cursor is turned on globally. The" - "argument \\var{delay} specifies the amount of time before the watch" - "cursor is opened. Use " - "@flink gui-utils:cursor-delay " - "to set this value for all uses of this function." - "" - "The result of this function is the result of \\var{thunk}.") - - (gui-utils:unsaved-warning - (opt-> - (string? - string?) - (boolean? - (or/c false/c - (is-a?/c frame%) - (is-a?/c dialog%))) - (symbols 'continue 'save 'cancel)) - ((filename action) - ((can-save-now? #f) - (parent #f))) - - "This displays a dialog that warns the user of a unsaved file." - "" - "The string, \\var{action}, indicates what action is about to" - "take place, without saving. For example, if the application" - "is about to close a file, a good action is \\rawscm{\"Close" - "Anyway\"}. The result symbol indicates the user's choice. If" - "\\var{can-save-now?} is \\rawscm{\\#f}, this function does not" - "give the user the ``Save'' option and thus will not return" - "\\rawscm{'save}.") - - (gui-utils:get-choice - (opt-> - (string? - string? - string?) - (string? - any/c - (or/c false/c (is-a?/c frame%) (is-a?/c dialog%)) - (symbols 'app 'caution 'stop) - (or/c false/c (case-> (boolean? . -> . void?) - (-> boolean?))) - string?) - any/c) - ((message true-choice false-choice) - ((title (string-constant warning)) - (default-result 'disallow-close) - (parent #f) - (style 'app) - (checkbox-proc #f) - (checkbox-label (string-constant dont-ask-again)))) - - "Opens a dialog that presents a binary choice to the user. The user is forced" - "to choose between these two options, ie cancelling or closing the dialog" - "opens a message box asking the user to actually choose one of the two options." - "" - "The dialog will contain the string \\var{message} and two buttons," - "labeled with the \\var{true-choice} and the \\var{false-choice}. If the" - "user clicks on \\var{true-choice} \\rawscm{\\#t} is returned. If the user" - "clicks on \\var{false-choice}, \\rawscm{\\#f} is returned." - "" - "The argument \\var{default-result} determines how closing the window is" - "treated. If the argument is \\rawscm{'disallow-close}, closing the window" - "is not allowed. If it is anything else, that value is returned when" - "the user closes the window." - "" - "If " - "@flink gui-utils:cancel-on-right?" - "returns \\scheme|#t|, the false choice is on the right." - "Otherwise, the true choice is on the right." - "" - "The \\var{style} parameter is (eventually) passed to" - "@link message" - "as an icon in the dialog." - "" - "If \\var{checkbox-proc} is given, it should be a procedure that behaves" - "like a parameter for getting/setting a boolean value. The intention for" - "this value is that it can be used to disable the dialog. When it is" - "given, a checkbox will appear with a \\var{checkbox-label} label" - "(defaults to the \\rawscm{dont-ask-again} string constant), and that" - "checkbox value will be sent to the \\var{checkbox-proc} when the dialog" - "is closed. Note that the dialog will always pop-up --- it is the" - "caller's responsibility to avoid the dialog if not needed.") - - (gui-utils:get-clicked-clickback-delta - (opt-> - () - (boolean?) - (is-a?/c style-delta%)) - (() - ((white-on-black? #f))) - "This delta is designed for use with" - "@link text set-clickback %" - ". Use it as one of the \\iscmclass{style-delta} argument to" - "@link text set-clickback %" - "." - "" - "If \\var{white-on-black?} is true, the function returns" - "a delta suitable for use on a black background." - "" - "See also" - "@flink gui-utils:get-clickback-delta %" - ".") - - (gui-utils:get-clickback-delta - (opt-> - () - (boolean?) - (is-a?/c style-delta%)) - (() - ((white-on-black? #f))) - "This delta is designed for use with" - "@link text set-clickback %" - ". Use the result of this function as the style" - "for the region" - "text where the clickback is set." - "" - "If \\var{white-on-black?} is true, the function returns" - "a delta suitable for use on a black background." - "" - "See also" - "@flink gui-utils:get-clicked-clickback-delta %" - "."))) + @{Opens a dialog that presents a binary choice to the user. The user is forced + to choose between these two options, ie cancelling or closing the dialog + opens a message box asking the user to actually choose one of the two options. + + The dialog will contain the string @scheme[message] and two buttons, + labeled with the @scheme[true-choice] and the @scheme[false-choice]. If the + user clicks on @scheme[true-choice] @scheme[#t] is returned. If the user + clicks on @scheme[false-choice], @scheme[#f] is returned. + + The argument @scheme[default-result] determines how closing the window is + treated. If the argument is @scheme['disallow-close], closing the window + is not allowed. If it is anything else, that value is returned when + the user closes the window. + + If + @scheme[gui-utils:cancel-on-right?] + returns @scheme[#t], the false choice is on the right. + Otherwise, the true choice is on the right. + + The @scheme[style] parameter is (eventually) passed to + @scheme[message] + as an icon in the dialog. + + If @scheme[checkbox-proc] is given, it should be a procedure that behaves + like a parameter for getting/setting a boolean value. The intention for + this value is that it can be used to disable the dialog. When it is + given, a checkbox will appear with a @scheme[checkbox-label] label + (defaults to the @scheme[dont-ask-again] string constant), and that + checkbox value will be sent to the @scheme[checkbox-proc] when the dialog + is closed. Note that the dialog will always pop-up --- it is the + caller's responsibility to avoid the dialog if not needed.}) + + (proc-doc/names + gui-utils:get-clicked-clickback-delta + (->* () + (boolean?) + (is-a?/c style-delta%)) + (() + ((white-on-black? #f))) + @{This delta is designed for use with + @method[text set-clickback]. + Use it as one of the @scheme[style-delta%] argument to + @method[text% set-clickback]. + + If @scheme[white-on-black?] is true, the function returns + a delta suitable for use on a black background. + + See also @scheme[gui-utils:get-clickback-delta].}) + + (proc-doc/names + gui-utils:get-clickback-delta + (->* () + (boolean?) + (is-a?/c style-delta%)) + (() + ((white-on-black? #f))) + @{This delta is designed for use with + @method[text% set-clickback]. + Use the result of this function as the style + for the region + text where the clickback is set. + + If @scheme[white-on-black?] is true, the function returns + a delta suitable for use on a black background. + + See also + @scheme[gui-utils:get-clicked-clickback-delta].})) \ No newline at end of file diff --git a/collects/framework/preferences.ss b/collects/framework/preferences.ss index dd1dd42e..f57fa585 100644 --- a/collects/framework/preferences.ss +++ b/collects/framework/preferences.ss @@ -1,4 +1,5 @@ - +#reader scribble/reader +#lang scheme/gui #| There are three attributes for each preference: @@ -26,443 +27,438 @@ the state transitions / contracts are: |# -(module preferences mzscheme - (require mzlib/file - mzlib/etc - mzlib/contract) - - (provide exn:struct:unknown-preference) - - (define-struct (exn:unknown-preference exn) ()) - - ;; these two names are for consistency - (define exn:make-unknown-preference make-exn:unknown-preference) - (define exn:struct:unknown-preference struct:exn:unknown-preference) - - (define-syntax (provide/contract/docs stx) - (syntax-case stx () - [(_ (name contract docs ...) ...) - (syntax (provide/contract (name contract) ...))])) +(require scribble/srcdoc) +(require/doc scheme/base scribble/manual) - - (define old-preferences-symbol 'plt:framework-prefs) - (define old-preferences (make-hash-table)) - (let ([old-prefs (get-preference old-preferences-symbol (λ () '()))]) - (for-each - (λ (line) (hash-table-put! old-preferences (car line) (cadr line))) - old-prefs)) - - (define (add-pref-prefix p) (string->symbol (format "plt:framework-pref:~a" p))) - - ;; preferences : hash-table[sym -o> any] - ;; the current values of the preferences - (define preferences (make-hash-table)) - - ;; marshalled : hash-table[sym -o> any] - ;; the values of the preferences, as read in from the disk - ;; each symbol will only be mapped in one of the preferences - ;; hash-table and this hash-table, but not both. - (define marshalled (make-hash-table)) - - ;; marshall-unmarshall : sym -o> un/marshall - (define marshall-unmarshall (make-hash-table)) - - ;; callbacks : sym -o> (listof (sym TST -> boolean)) - (define callbacks (make-hash-table)) - - ;; defaults : hash-table[sym -o> default] - (define defaults (make-hash-table)) - - ;; these four functions determine the state of a preference - (define (pref-un/marshall-set? pref) (hash-table-bound? marshall-unmarshall pref)) - (define (pref-default-set? pref) (hash-table-bound? defaults pref)) - (define (pref-can-init? pref) - (and (not snapshot-grabbed?) - (not (hash-table-bound? preferences pref)))) - - ;; type un/marshall = (make-un/marshall (any -> prinable) (printable -> any)) - (define-struct un/marshall (marshall unmarshall)) - - ;; type pref = (make-pref any) - (define-struct pref (value)) - - ;; type default = (make-default any (any -> bool)) - (define-struct default (value checker)) - - ;; pref-callback : (make-pref-callback (union (weak-box (sym tst -> void)) (sym tst -> void))) - ;; this is used as a wrapped to deal with the problem that different procedures might be eq?. - (define-struct pref-callback (cb)) - - ;; get : symbol -> any - ;; return the current value of the preference `p' - ;; exported - (define (preferences:get p) - (cond - [(pref-default-set? p) - - ;; unmarshall, if required - (when (hash-table-bound? marshalled p) - ;; if `preferences' is already bound, that means the unmarshalled value isn't useful. - (unless (hash-table-bound? preferences p) - (hash-table-put! preferences p (unmarshall-pref p (hash-table-get marshalled p)))) - (hash-table-remove! marshalled p)) - - ;; if there is no value in the preferences table, but there is one - ;; in the old version preferences file, take that: +(provide exn:struct:unknown-preference) + +(define-struct (exn:unknown-preference exn) ()) + +;; these two names are for consistency +(define exn:make-unknown-preference make-exn:unknown-preference) +(define exn:struct:unknown-preference struct:exn:unknown-preference) + +(define old-preferences-symbol 'plt:framework-prefs) +(define old-preferences (make-hasheq)) +(let ([old-prefs (get-preference old-preferences-symbol (λ () '()))]) + (for-each + (λ (line) (hash-set! old-preferences (car line) (cadr line))) + old-prefs)) + +(define (add-pref-prefix p) (string->symbol (format "plt:framework-pref:~a" p))) + +;; preferences : hash-table[sym -o> any] +;; the current values of the preferences +(define preferences (make-hasheq)) + +;; marshalled : hash-table[sym -o> any] +;; the values of the preferences, as read in from the disk +;; each symbol will only be mapped in one of the preferences +;; hash-table and this hash-table, but not both. +(define marshalled (make-hasheq)) + +;; marshall-unmarshall : sym -o> un/marshall +(define marshall-unmarshall (make-hasheq)) + +;; callbacks : sym -o> (listof (sym TST -> boolean)) +(define callbacks (make-hasheq)) + +;; defaults : hash-table[sym -o> default] +(define defaults (make-hasheq)) + +;; these four functions determine the state of a preference +(define (pref-un/marshall-set? pref) (hash-table-bound? marshall-unmarshall pref)) +(define (pref-default-set? pref) (hash-table-bound? defaults pref)) +(define (pref-can-init? pref) + (and (not snapshot-grabbed?) + (not (hash-table-bound? preferences pref)))) + +;; type un/marshall = (make-un/marshall (any -> prinable) (printable -> any)) +(define-struct un/marshall (marshall unmarshall)) + +;; type pref = (make-pref any) +(define-struct pref (value)) + +;; type default = (make-default any (any -> bool)) +(define-struct default (value checker)) + +;; pref-callback : (make-pref-callback (union (weak-box (sym tst -> void)) (sym tst -> void))) +;; this is used as a wrapped to deal with the problem that different procedures might be eq?. +(define-struct pref-callback (cb)) + +;; get : symbol -> any +;; return the current value of the preference `p' +;; exported +(define (preferences:get p) + (cond + [(pref-default-set? p) + + ;; unmarshall, if required + (when (hash-table-bound? marshalled p) + ;; if `preferences' is already bound, that means the unmarshalled value isn't useful. (unless (hash-table-bound? preferences p) - (when (hash-table-bound? old-preferences p) - (hash-table-put! preferences p (unmarshall-pref p (hash-table-get old-preferences p))))) - - ;; clear the pref from the old table (just in case it was taking space -- we don't need it anymore) + (hash-set! preferences p (unmarshall-pref p (hash-ref marshalled p)))) + (hash-remove! marshalled p)) + + ;; if there is no value in the preferences table, but there is one + ;; in the old version preferences file, take that: + (unless (hash-table-bound? preferences p) (when (hash-table-bound? old-preferences p) - (hash-table-remove! old-preferences p)) - - ;; if it still isn't set, take the default value - (unless (hash-table-bound? preferences p) - (hash-table-put! preferences p (default-value (hash-table-get defaults p)))) - - (hash-table-get preferences p)] - [(not (pref-default-set? p)) - (raise-unknown-preference-error - 'preferences:get - "tried to get a preference but no default set for ~e" - p)])) - - ;; set : symbol any -> void - ;; updates the preference - ;; exported - (define (preferences:set p value) (multi-set (list p) (list value))) - - ;; set : symbol any -> void - ;; updates the preference - ;; exported + (hash-set! preferences p (unmarshall-pref p (hash-ref old-preferences p))))) + + ;; clear the pref from the old table (just in case it was taking space -- we don't need it anymore) + (when (hash-table-bound? old-preferences p) + (hash-remove! old-preferences p)) + + ;; if it still isn't set, take the default value + (unless (hash-table-bound? preferences p) + (hash-set! preferences p (default-value (hash-ref defaults p)))) + + (hash-ref preferences p)] + [(not (pref-default-set? p)) + (raise-unknown-preference-error + 'preferences:get + "tried to get a preference but no default set for ~e" + p)])) - (define (multi-set ps values) - (for-each - (λ (p value) - (cond - [(pref-default-set? p) - (let ([default (hash-table-get defaults p)]) - (unless ((default-checker default) value) - (error 'preferences:set - "tried to set preference ~e to ~e but it does not meet test from preferences:set-default" - p value)) - (check-callbacks p value) - (hash-table-put! preferences p value) - (void))] - [(not (pref-default-set? p)) - (raise-unknown-preference-error - 'preferences:set "tried to set the preference ~e to ~e, but no default is set" - p - value)])) - ps values) - ((preferences:low-level-put-preferences) - (map add-pref-prefix ps) - (map (λ (p value) (marshall-pref p value)) - ps - values)) - - (void)) - - (define preferences:low-level-put-preferences (make-parameter put-preferences)) - - (define (raise-unknown-preference-error sym fmt . args) - (raise (exn:make-unknown-preference - (string-append (format "~a: " sym) (apply format fmt args)) - (current-continuation-marks)))) +;; set : symbol any -> void +;; updates the preference +;; exported +(define (preferences:set p value) (multi-set (list p) (list value))) - ;; unmarshall-pref : symbol marshalled -> any - ;; unmarshalls a preference read from the disk - (define (unmarshall-pref p data) - (let* ([un/marshall (hash-table-get marshall-unmarshall p #f)] - [result (if un/marshall - ((un/marshall-unmarshall un/marshall) data) - data)] - [default (hash-table-get defaults p)]) - (if ((default-checker default) result) - result - (default-value default)))) - - ;; add-callback : sym (-> void) -> void - (define preferences:add-callback - (opt-lambda (p callback [weak? #f]) - (let ([new-cb (make-pref-callback (if weak? - (make-weak-box callback) - callback))]) - (hash-table-put! callbacks - p - (append - (hash-table-get callbacks p (λ () null)) - (list new-cb))) - (λ () - (hash-table-put! - callbacks - p - (let loop ([callbacks (hash-table-get callbacks p (λ () null))]) - (cond - [(null? callbacks) null] - [else - (let ([callback (car callbacks)]) - (cond - [(eq? callback new-cb) - (loop (cdr callbacks))] - [else - (cons (car callbacks) (loop (cdr callbacks)))]))]))))))) - - ;; check-callbacks : sym val -> void - (define (check-callbacks p value) - (let ([new-callbacks - (let loop ([callbacks (hash-table-get callbacks p (λ () null))]) - (cond - [(null? callbacks) null] - [else - (let* ([callback (car callbacks)] - [cb (pref-callback-cb callback)]) - (cond - [(weak-box? cb) - (let ([v (weak-box-value cb)]) - (if v - (begin - (v p value) - (cons callback (loop (cdr callbacks)))) - (loop (cdr callbacks))))] - [else - (cb p value) - (cons callback (loop (cdr callbacks)))]))]))]) - (if (null? new-callbacks) - (hash-table-remove! callbacks p) - (hash-table-put! callbacks p new-callbacks)))) - - (define (preferences:set-un/marshall p marshall unmarshall) - (cond - [(and (pref-default-set? p) - (not (pref-un/marshall-set? p)) - (pref-can-init? p)) - (hash-table-put! marshall-unmarshall p (make-un/marshall marshall unmarshall))] - [(not (pref-default-set? p)) - (error 'preferences:set-un/marshall - "must call set-default for ~s before calling set-un/marshall for ~s" - p p)] - [(pref-un/marshall-set? p) - (error 'preferences:set-un/marshall - "already set un/marshall for ~e" - p)] - [(not (pref-can-init? p)) - (error 'preferences:set-un/marshall "the preference ~e cannot be configured any more" p)])) - - (define (hash-table-bound? ht s) - (let/ec k - (hash-table-get ht s (λ () (k #f))) - #t)) - - (define (preferences:restore-defaults) - (hash-table-for-each - defaults - (λ (p def) (preferences:set p (default-value def))))) - - ;; set-default : (sym TST (TST -> boolean) -> void - (define (preferences:set-default p default-value checker) - (cond - [(and (not (pref-default-set? p)) - (pref-can-init? p)) - (let ([default-okay? (checker default-value)]) - (unless default-okay? - (error 'set-default "~s: checker (~s) returns ~s for ~s, expected #t~n" - p checker default-okay? default-value)) - (hash-table-put! defaults p (make-default default-value checker)) - (let/ec k - (let ([m (get-preference (add-pref-prefix p) (λ () (k (void))))]) - ;; if there is no preference saved, we just don't do anything. - ;; `get' notices this case. - (hash-table-put! marshalled p m))))] - [(not (pref-can-init? p)) - (error 'preferences:set-default - "tried to call set-default for preference ~e but it cannot be configured any more" - p)] - [(pref-default-set? p) - (error 'preferences:set-default - "preferences default already set for ~e" p)] - [(not (pref-can-init? p)) - (error 'preferences:set-default - "can no longer set the default for ~e" p)])) - - ;; marshall-pref : symbol any -> (list symbol printable) - (define (marshall-pref p value) - (let/ec k - (let* ([marshaller - (un/marshall-marshall - (hash-table-get marshall-unmarshall p (λ () (k value))))]) - (marshaller value)))) - - (define-struct preferences:snapshot (x)) - (define snapshot-grabbed? #f) - (define (preferences:get-prefs-snapshot) - (set! snapshot-grabbed? #t) - (make-preferences:snapshot (hash-table-map defaults (λ (k v) (cons k (preferences:get k)))))) - - (define (preferences:restore-prefs-snapshot snapshot) - (multi-set (map car (preferences:snapshot-x snapshot)) - (map cdr (preferences:snapshot-x snapshot))) - (void)) - - - (provide/contract/docs - (preferences:snapshot? - (-> any/c boolean?) - (arg) - "Determines if its argument is a preferences snapshot." - "" - "See also " - "@flink preferences:get-prefs-snapshot" - " and " - "@flink preferences:restore-prefs-snapshot %" - ".") - (preferences:restore-prefs-snapshot - (-> preferences:snapshot? void?) - (snapshot) - "Restores the preferences saved in \\var{snapshot}." - "" - "See also " - "@flink preferences:get-prefs-snapshot %" - ".") - - (preferences:get-prefs-snapshot - (-> preferences:snapshot?) - () - "Caches all of the current values of the preferences and returns them." - "" - "See also " - "@flink preferences:restore-prefs-snapshot %" - ".") - - (exn:make-unknown-preference - (string? continuation-mark-set? . -> . exn:unknown-preference?) - (message continuation-marks) - "Creates an unknown preference exception.") - (exn:unknown-preference? - (any/c . -> . boolean?) - (exn) - "Determines if a value is an unknown preference exn.") +;; set : symbol any -> void +;; updates the preference +;; exported - (preferences:low-level-put-preferences - (parameter/c (-> (listof symbol?) (listof any/c) any)) - () - "This is a parameter (see " - "\\Mzhyperref{parameters}{mz:parameters} for information about parameters)" - "which is called when a preference is saved. Its interface should " - "be just like mzlib's \\scheme|put-preference|.") - - (preferences:get - (symbol? . -> . any/c) - (symbol) - "See also" - "@flink preferences:set-default %" - "." - "" - "\\rawscm{preferences:get} returns the value for the preference" - "\\var{symbol}. It raises" - "\\scmindex{exn:unknown-preference}\\rawscm{exn:unknown-preference}" - "if the preference's default has not been set.") - (preferences:set - (symbol? any/c . -> . void?) - (symbol value) - "See also" - "@flink preferences:set-default %" - "." - "" - "\\rawscm{preferences:set-preference} sets the preference" - "\\var{symbol} to \\var{value}. This should be called when the" - "users requests a change to a preference." - "" - "This function immediately writes the preference value to disk." - "" - "It raises" - "\\scmindex{exn:unknown-preference}\\rawscm{exn:unknown-preference}" - "if the preference's default has not been set.") - (preferences:add-callback - (opt-> (symbol? - - ;; important that this arg only has a flat contract - ;; so that no wrapper is created, so that - ;; the weak box stuff works ... - (λ (x) (and (procedure? x) (procedure-arity-includes? x 2)))) - (boolean?) - (-> void?)) - ((p f) - ((weak? #f))) - "This function adds a callback which is called with a symbol naming a" - "preference and it's value, when the preference changes." - "\\rawscm{preferences:add-callback} returns a thunk, which when" - "invoked, removes the callback from this preference." - "" - "If \\var{weak?} is true, the preferences system will only hold on to" - "the callback weakly." - "" - "The callbacks will be called in the order in which they were added." - "" - "If you are adding a callback for a preference that requires" - "marshalling and unmarshalling, you must set the marshalling and" - "unmarshalling functions by calling" - "\\iscmprocedure{preferences:set-un/marshall} before adding a callback." - "" - "This function raises" - "\\scmindex{exn:unknown-preference}\\rawscm{exn:unknown-preference}" - "if the preference has not been set.") - (preferences:set-default - (symbol? any/c (any/c . -> . any) . -> . void?) - (symbol value test) - "This function must be called every time your application starts up, before any call to" - "@flink preferences:get %" - ", " - "@flink preferences:set" - "(for any given preference)." - "" - "If you use" - "@flink preferences:set-un/marshall %" - ", you must call this function before calling it." - "" - "This sets the default value of the preference \\var{symbol} to" - "\\var{value}. If the user has chosen a different setting," - "the user's setting" - "will take precedence over the default value." - "" - "The last argument, \\var{test} is used as a safeguard. That function is" - "called to determine if a preference read in from a file is a valid" - "preference. If \\var{test} returns \\rawscm{\\#t}, then the preference is" - "treated as valid. If \\var{test} returns \\rawscm{\\#f} then the default is" - "used.") - (preferences:set-un/marshall - (symbol? (any/c . -> . printable/c) (printable/c . -> . any/c) . -> . void?) - (symbol marshall unmarshall) - "\\rawscm{preference:set-un/marshall} is used to specify marshalling and" - "unmarshalling functions for the preference" - "\\var{symbol}. \\var{marshall} will be called when the users saves their" - "preferences to turn the preference value for \\var{symbol} into a" - "printable value. \\var{unmarshall} will be called when the user's" - "preferences are read from the file to transform the printable value" - "into it's internal representation. If \\rawscm{preference:set-un/marshall}" - "is never called for a particular preference, the values of that" - "preference are assumed to be printable." - "" - "If the unmarshalling function returns a value that does not meet the" - "guard passed to " - "@flink preferences:set-default" - "for this preference, the default value is used." - "" - "The \\var{marshall} function might be called with any value returned" - "from \\scheme{read} and it must not raise an error (although it" - "can return arbitrary results if it gets bad input). This might" - "happen when the preferences file becomes corrupted, or is edited" - "by hand." - "" - "\\rawscm{preference:set-un/marshall} must be called before calling" - "@flink preferences:get %" - ", " - "@flink preferences:set %" - ".") - - (preferences:restore-defaults - (-> void?) - () - "\\rawscm{(preferences:restore-defaults)} restores the users's configuration to the" - "default preferences."))) +(define (multi-set ps values) + (for-each + (λ (p value) + (cond + [(pref-default-set? p) + (let ([default (hash-ref defaults p)]) + (unless ((default-checker default) value) + (error 'preferences:set + "tried to set preference ~e to ~e but it does not meet test from preferences:set-default" + p value)) + (check-callbacks p value) + (hash-set! preferences p value) + (void))] + [(not (pref-default-set? p)) + (raise-unknown-preference-error + 'preferences:set "tried to set the preference ~e to ~e, but no default is set" + p + value)])) + ps values) + ((preferences:low-level-put-preferences) + (map add-pref-prefix ps) + (map (λ (p value) (marshall-pref p value)) + ps + values)) + + (void)) + +(define preferences:low-level-put-preferences (make-parameter put-preferences)) + +(define (raise-unknown-preference-error sym fmt . args) + (raise (exn:make-unknown-preference + (string-append (format "~a: " sym) (apply format fmt args)) + (current-continuation-marks)))) + +;; unmarshall-pref : symbol marshalled -> any +;; unmarshalls a preference read from the disk +(define (unmarshall-pref p data) + (let* ([un/marshall (hash-ref marshall-unmarshall p #f)] + [result (if un/marshall + ((un/marshall-unmarshall un/marshall) data) + data)] + [default (hash-ref defaults p)]) + (if ((default-checker default) result) + result + (default-value default)))) + +;; add-callback : sym (-> void) -> void +(define preferences:add-callback + (lambda (p callback [weak? #f]) + (let ([new-cb (make-pref-callback (if weak? + (make-weak-box callback) + callback))]) + (hash-set! callbacks + p + (append + (hash-ref callbacks p (λ () null)) + (list new-cb))) + (λ () + (hash-set! + callbacks + p + (let loop ([callbacks (hash-ref callbacks p (λ () null))]) + (cond + [(null? callbacks) null] + [else + (let ([callback (car callbacks)]) + (cond + [(eq? callback new-cb) + (loop (cdr callbacks))] + [else + (cons (car callbacks) (loop (cdr callbacks)))]))]))))))) + +;; check-callbacks : sym val -> void +(define (check-callbacks p value) + (let ([new-callbacks + (let loop ([callbacks (hash-ref callbacks p (λ () null))]) + (cond + [(null? callbacks) null] + [else + (let* ([callback (car callbacks)] + [cb (pref-callback-cb callback)]) + (cond + [(weak-box? cb) + (let ([v (weak-box-value cb)]) + (if v + (begin + (v p value) + (cons callback (loop (cdr callbacks)))) + (loop (cdr callbacks))))] + [else + (cb p value) + (cons callback (loop (cdr callbacks)))]))]))]) + (if (null? new-callbacks) + (hash-remove! callbacks p) + (hash-set! callbacks p new-callbacks)))) + +(define (preferences:set-un/marshall p marshall unmarshall) + (cond + [(and (pref-default-set? p) + (not (pref-un/marshall-set? p)) + (pref-can-init? p)) + (hash-set! marshall-unmarshall p (make-un/marshall marshall unmarshall))] + [(not (pref-default-set? p)) + (error 'preferences:set-un/marshall + "must call set-default for ~s before calling set-un/marshall for ~s" + p p)] + [(pref-un/marshall-set? p) + (error 'preferences:set-un/marshall + "already set un/marshall for ~e" + p)] + [(not (pref-can-init? p)) + (error 'preferences:set-un/marshall "the preference ~e cannot be configured any more" p)])) + +(define (hash-table-bound? ht s) + (let/ec k + (hash-ref ht s (λ () (k #f))) + #t)) + +(define (preferences:restore-defaults) + (hash-for-each + defaults + (λ (p def) (preferences:set p (default-value def))))) + +;; set-default : (sym TST (TST -> boolean) -> void +(define (preferences:set-default p default-value checker) + (cond + [(and (not (pref-default-set? p)) + (pref-can-init? p)) + (let ([default-okay? (checker default-value)]) + (unless default-okay? + (error 'set-default "~s: checker (~s) returns ~s for ~s, expected #t~n" + p checker default-okay? default-value)) + (hash-set! defaults p (make-default default-value checker)) + (let/ec k + (let ([m (get-preference (add-pref-prefix p) (λ () (k (void))))]) + ;; if there is no preference saved, we just don't do anything. + ;; `get' notices this case. + (hash-set! marshalled p m))))] + [(not (pref-can-init? p)) + (error 'preferences:set-default + "tried to call set-default for preference ~e but it cannot be configured any more" + p)] + [(pref-default-set? p) + (error 'preferences:set-default + "preferences default already set for ~e" p)] + [(not (pref-can-init? p)) + (error 'preferences:set-default + "can no longer set the default for ~e" p)])) + +;; marshall-pref : symbol any -> (list symbol printable) +(define (marshall-pref p value) + (let/ec k + (let* ([marshaller + (un/marshall-marshall + (hash-ref marshall-unmarshall p (λ () (k value))))]) + (marshaller value)))) + +(define-struct preferences:snapshot (x)) +(define snapshot-grabbed? #f) +(define (preferences:get-prefs-snapshot) + (set! snapshot-grabbed? #t) + (make-preferences:snapshot (hash-map defaults (λ (k v) (cons k (preferences:get k)))))) + +(define (preferences:restore-prefs-snapshot snapshot) + (multi-set (map car (preferences:snapshot-x snapshot)) + (map cdr (preferences:snapshot-x snapshot))) + (void)) + + +(provide/doc + (proc-doc/names + preferences:snapshot? + (-> any/c boolean?) + (arg) + @{Determines if its argument is a preferences snapshot. + + See also + @scheme[preferences:get-prefs-snapshot] and + @scheme[preferences:restore-prefs-snapshot].}) + (proc-doc/names + preferences:restore-prefs-snapshot + (-> preferences:snapshot? void?) + (snapshot) + @{Restores the preferences saved in @scheme[snapshot]. + + See also @scheme[preferences:get-prefs-snapshot].}) + + (proc-doc/names + preferences:get-prefs-snapshot + (-> preferences:snapshot?) + () + @{Caches all of the current values of the preferences and returns them. + + See also + @scheme[preferences:restore-prefs-snapshot].}) + + + (proc-doc/names + exn:make-unknown-preference + (string? continuation-mark-set? . -> . exn:unknown-preference?) + (message continuation-marks) + @{Creates an unknown preference exception.}) + (proc-doc/names + exn:unknown-preference? + (any/c . -> . boolean?) + (exn) + @{Determines if a value is an unknown preference exn.}) + + (parameter-doc + preferences:low-level-put-preferences + (parameter/c (-> (listof symbol?) (listof any/c) any)) + put-preference + @{This parameter's value + is called when to save preference the preferences. Its interface should + be just like mzlib's @scheme[put-preference].}) + + (proc-doc/names + preferences:get + (symbol? . -> . any/c) + (symbol) + @{See also @scheme[preferences:set-default]. + + @scheme[preferences:get] returns the value for the preference + @scheme[symbol]. It raises + @index['("exn:unknown-preference")]{@scheme[exn:unknown-preference]} + @scheme[exn:unknown-preference] + if the preference's default has not been set.}) + (proc-doc/names + preferences:set + (symbol? any/c . -> . void?) + (symbol value) + @{See also @scheme[preferences:set-default]. + + @scheme[preferences:set-preference] sets the preference + @scheme[symbol] to @scheme[value]. This should be called when the + users requests a change to a preference. + + This function immediately writes the preference value to disk. + + It raises + @index['("exn:unknown-preference")]{@scheme[exn:unknown-preference]} + if the preference's default has not been set.}) + (proc-doc/names + preferences:add-callback + (->* (symbol? + + ;; important that this arg only has a flat contract + ;; so that no wrapper is created, so that + ;; the weak box stuff works ... + (λ (x) (and (procedure? x) (procedure-arity-includes? x 2)))) + (boolean?) + (-> void?)) + ((p f) + ((weak? #f))) + @{This function adds a callback which is called with a symbol naming a + preference and it's value, when the preference changes. + @scheme[preferences:add-callback] returns a thunk, which when + invoked, removes the callback from this preference. + + If @scheme[weak?] is true, the preferences system will only hold on to + the callback weakly. + + The callbacks will be called in the order in which they were added. + + If you are adding a callback for a preference that requires + marshalling and unmarshalling, you must set the marshalling and + unmarshalling functions by calling + @scheme[preferences:set-un/marshall] before adding a callback. + + This function raises + @index['("exn:unknown-preference")]{@scheme[exn:unknown-preference]} + @scheme[exn:unknown-preference] + if the preference has not been set.}) + (proc-doc/names + preferences:set-default + (symbol? any/c (any/c . -> . any) . -> . void?) + (symbol value test) + @{This function must be called every time your application starts up, before any call to + @scheme[preferences:get] or + @scheme[preferences:set] + (for any given preference). + + If you use + @scheme[preferences:set-un/marshall], + you must call this function before calling it. + + This sets the default value of the preference @scheme[symbol] to + @scheme[value]. If the user has chosen a different setting, + the user's setting + will take precedence over the default value. + + The last argument, @scheme[test] is used as a safeguard. That function is + called to determine if a preference read in from a file is a valid + preference. If @scheme[test] returns @scheme[#t], then the preference is + treated as valid. If @scheme[test] returns @scheme[#f] then the default is + used.}) + (proc-doc/names + preferences:set-un/marshall + (symbol? (any/c . -> . printable/c) (printable/c . -> . any/c) . -> . void?) + (symbol marshall unmarshall) + @{@scheme[preference:set-un/marshall] is used to specify marshalling and + unmarshalling functions for the preference + @scheme[symbol]. @scheme[marshall] will be called when the users saves their + preferences to turn the preference value for @scheme[symbol] into a + printable value. @scheme[unmarshall] will be called when the user's + preferences are read from the file to transform the printable value + into it's internal representation. If @scheme[preference:set-un/marshall] + is never called for a particular preference, the values of that + preference are assumed to be printable. + + If the unmarshalling function returns a value that does not meet the + guard passed to + @scheme[preferences:set-default] + for this preference, the default value is used. + + The @scheme[marshall] function might be called with any value returned + from @scheme[read] and it must not raise an error + (although it can return arbitrary results if it gets bad input). This might + happen when the preferences file becomes corrupted, or is edited + by hand. + + @scheme[preference:set-un/marshall] must be called before calling + @scheme[preferences:get], + @scheme[preferences:set].}) + + (proc-doc/names + preferences:restore-defaults + (-> void?) + () + @{@scheme[(preferences:restore-defaults)] + restores the users's configuration to the + default preferences.})) diff --git a/collects/framework/test.ss b/collects/framework/test.ss index b236328b..ce91d13b 100644 --- a/collects/framework/test.ss +++ b/collects/framework/test.ss @@ -4,11 +4,6 @@ (require scribble/srcdoc) (require/doc scheme/base scribble/manual) -(define-syntax (provide/contract/docs stx) - (syntax-case stx () - [(_ (name contract docs ...) ...) - (syntax (provide/contract (name contract) ...))])) - (define (test:top-level-focus-window-has? pred) (let ([tlw (get-top-level-focus-window)]) (and tlw diff --git a/collects/scribblings/framework/framework-application.scrbl b/collects/scribblings/framework/application.scrbl similarity index 100% rename from collects/scribblings/framework/framework-application.scrbl rename to collects/scribblings/framework/application.scrbl diff --git a/collects/scribblings/framework/framework-autosave.scrbl b/collects/scribblings/framework/autosave.scrbl similarity index 100% rename from collects/scribblings/framework/framework-autosave.scrbl rename to collects/scribblings/framework/autosave.scrbl diff --git a/collects/scribblings/framework/framework-canvas.scrbl b/collects/scribblings/framework/canvas.scrbl similarity index 100% rename from collects/scribblings/framework/framework-canvas.scrbl rename to collects/scribblings/framework/canvas.scrbl diff --git a/collects/scribblings/framework/framework-color-model.scrbl b/collects/scribblings/framework/color-model.scrbl similarity index 100% rename from collects/scribblings/framework/framework-color-model.scrbl rename to collects/scribblings/framework/color-model.scrbl diff --git a/collects/scribblings/framework/framework-color-prefs.scrbl b/collects/scribblings/framework/color-prefs.scrbl similarity index 100% rename from collects/scribblings/framework/framework-color-prefs.scrbl rename to collects/scribblings/framework/color-prefs.scrbl diff --git a/collects/scribblings/framework/framework-color.scrbl b/collects/scribblings/framework/color.scrbl similarity index 100% rename from collects/scribblings/framework/framework-color.scrbl rename to collects/scribblings/framework/color.scrbl diff --git a/collects/scribblings/framework/framework-comment-box.scrbl b/collects/scribblings/framework/comment-box.scrbl similarity index 100% rename from collects/scribblings/framework/framework-comment-box.scrbl rename to collects/scribblings/framework/comment-box.scrbl diff --git a/collects/scribblings/framework/framework-editor.scrbl b/collects/scribblings/framework/editor.scrbl similarity index 100% rename from collects/scribblings/framework/framework-editor.scrbl rename to collects/scribblings/framework/editor.scrbl diff --git a/collects/scribblings/framework/framework-exit.scrbl b/collects/scribblings/framework/exit.scrbl similarity index 100% rename from collects/scribblings/framework/framework-exit.scrbl rename to collects/scribblings/framework/exit.scrbl diff --git a/collects/scribblings/framework/framework-finder.scrbl b/collects/scribblings/framework/finder.scrbl similarity index 100% rename from collects/scribblings/framework/framework-finder.scrbl rename to collects/scribblings/framework/finder.scrbl diff --git a/collects/scribblings/framework/framework-frame.scrbl b/collects/scribblings/framework/frame.scrbl similarity index 100% rename from collects/scribblings/framework/framework-frame.scrbl rename to collects/scribblings/framework/frame.scrbl diff --git a/collects/scribblings/framework/framework.scrbl b/collects/scribblings/framework/framework.scrbl index f7652571..fc0bce0b 100644 --- a/collects/scribblings/framework/framework.scrbl +++ b/collects/scribblings/framework/framework.scrbl @@ -5,9 +5,13 @@ @title{@bold{Framework}: PLT GUI Application Framework} -The framework provides these libraries: @itemize{ +@item{Mode} +@item{``Cannot parse docs for handler:open-file''} +@item{Check indexing in preferences:get} +} +@itemize{ @item{@bold{Entire Framework} @itemize{ @@ -74,8 +78,7 @@ The precise set of exported names is: @scheme[preferences:restore-defaults]. }} -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -@section{Thanks} +@bold{Thanks} Thanks to Shriram Krishnamurthi, Cormac Flanagan, Matthias Felleisen, Ian Barland, Gann Bierner, Richard Cobbe, Dan @@ -84,33 +87,33 @@ Johnathan Franklin, Mark Krentel, Corky Cartwright, Michael Ernst, Kennis Koldewyn, Bruce Duba, and many others for their feedback and help. - -@include-section["framework-application.scrbl"] -@include-section["framework-autosave.scrbl"] -@include-section["framework-canvas.scrbl"] -@include-section["framework-color-model.scrbl"] -@include-section["framework-color-prefs.scrbl"] -@include-section["framework-color.scrbl"] -@include-section["framework-comment-box.scrbl"] -@include-section["framework-editor.scrbl"] -@include-section["framework-exit.scrbl"] -@include-section["framework-finder.scrbl"] -@include-section["framework-frame.scrbl"] -@include-section["framework-group.scrbl"] -@include-section["framework-handler.scrbl"] -@include-section["framework-icon.scrbl"] -@include-section["framework-keymap.scrbl"] -@;include-section["framework-main.scrbl"] -@include-section["framework-menu.scrbl"] -@;include-section["framework-mode.scrbl"] -@include-section["framework-number-snip.scrbl"] -@include-section["framework-panel.scrbl"] -@include-section["framework-pasteboard.scrbl"] -@include-section["framework-path-utils.scrbl"] -@include-section["framework-preferences.scrbl"] -@include-section["framework-scheme.scrbl"] -@include-section["framework-text.scrbl"] -@include-section["framework-test.scrbl"] -@include-section["framework-version.scrbl"] +@include-section["application.scrbl"] +@include-section["autosave.scrbl"] +@include-section["canvas.scrbl"] +@include-section["color-model.scrbl"] +@include-section["color-prefs.scrbl"] +@include-section["color.scrbl"] +@include-section["comment-box.scrbl"] +@include-section["editor.scrbl"] +@include-section["exit.scrbl"] +@include-section["finder.scrbl"] +@include-section["frame.scrbl"] +@include-section["group.scrbl"] +@include-section["gui-utils.scrbl"] +@include-section["handler.scrbl"] +@include-section["icon.scrbl"] +@include-section["keymap.scrbl"] +@include-section["menu.scrbl"] +@include-section["mode.scrbl"] +@include-section["number-snip.scrbl"] +@include-section["panel.scrbl"] +@include-section["pasteboard.scrbl"] +@include-section["path-utils.scrbl"] +@include-section["preferences.scrbl"] +@include-section["preferences-text.scrbl"] +@include-section["scheme.scrbl"] +@include-section["text.scrbl"] +@include-section["test.scrbl"] +@include-section["version.scrbl"] @index-section[] diff --git a/collects/scribblings/framework/framework-group.scrbl b/collects/scribblings/framework/group.scrbl similarity index 100% rename from collects/scribblings/framework/framework-group.scrbl rename to collects/scribblings/framework/group.scrbl diff --git a/collects/scribblings/framework/gui-utils.scrbl b/collects/scribblings/framework/gui-utils.scrbl new file mode 100644 index 00000000..89649225 --- /dev/null +++ b/collects/scribblings/framework/gui-utils.scrbl @@ -0,0 +1,10 @@ +#lang scribble/doc +@(require scribble/manual scribble/extract) +@(require (for-label framework/framework)) +@(require (for-label scheme/gui)) +@title{GUI Utilities} + +@(require framework/framework-docs) +@(defmodule framework/gui-utils) + +@(include-extracted (lib "gui-utils.ss" "framework")) diff --git a/collects/scribblings/framework/framework-handler.scrbl b/collects/scribblings/framework/handler.scrbl similarity index 100% rename from collects/scribblings/framework/framework-handler.scrbl rename to collects/scribblings/framework/handler.scrbl diff --git a/collects/scribblings/framework/framework-icon.scrbl b/collects/scribblings/framework/icon.scrbl similarity index 100% rename from collects/scribblings/framework/framework-icon.scrbl rename to collects/scribblings/framework/icon.scrbl diff --git a/collects/scribblings/framework/framework-keymap.scrbl b/collects/scribblings/framework/keymap.scrbl similarity index 100% rename from collects/scribblings/framework/framework-keymap.scrbl rename to collects/scribblings/framework/keymap.scrbl diff --git a/collects/scribblings/framework/framework-main.scrbl b/collects/scribblings/framework/main.scrbl similarity index 100% rename from collects/scribblings/framework/framework-main.scrbl rename to collects/scribblings/framework/main.scrbl diff --git a/collects/scribblings/framework/framework-menu.scrbl b/collects/scribblings/framework/menu.scrbl similarity index 100% rename from collects/scribblings/framework/framework-menu.scrbl rename to collects/scribblings/framework/menu.scrbl diff --git a/collects/scribblings/framework/framework-mode.scrbl b/collects/scribblings/framework/mode.scrbl similarity index 100% rename from collects/scribblings/framework/framework-mode.scrbl rename to collects/scribblings/framework/mode.scrbl diff --git a/collects/scribblings/framework/framework-number-snip.scrbl b/collects/scribblings/framework/number-snip.scrbl similarity index 100% rename from collects/scribblings/framework/framework-number-snip.scrbl rename to collects/scribblings/framework/number-snip.scrbl diff --git a/collects/scribblings/framework/framework-panel.scrbl b/collects/scribblings/framework/panel.scrbl similarity index 100% rename from collects/scribblings/framework/framework-panel.scrbl rename to collects/scribblings/framework/panel.scrbl diff --git a/collects/scribblings/framework/framework-pasteboard.scrbl b/collects/scribblings/framework/pasteboard.scrbl similarity index 100% rename from collects/scribblings/framework/framework-pasteboard.scrbl rename to collects/scribblings/framework/pasteboard.scrbl diff --git a/collects/scribblings/framework/framework-path-utils.scrbl b/collects/scribblings/framework/path-utils.scrbl similarity index 100% rename from collects/scribblings/framework/framework-path-utils.scrbl rename to collects/scribblings/framework/path-utils.scrbl diff --git a/collects/scribblings/framework/preferences-text.scrbl b/collects/scribblings/framework/preferences-text.scrbl new file mode 100644 index 00000000..0752338b --- /dev/null +++ b/collects/scribblings/framework/preferences-text.scrbl @@ -0,0 +1,10 @@ +#lang scribble/doc +@(require scribble/manual scribble/extract) +@(require (for-label framework/framework)) +@(require (for-label scheme/gui)) +@title{Preferences, Textual} + +@(require framework/framework-docs) +@(defmodule framework/preferences) + +@(include-extracted (lib "preferences.ss" "framework")) diff --git a/collects/scribblings/framework/framework-preferences.scrbl b/collects/scribblings/framework/preferences.scrbl similarity index 100% rename from collects/scribblings/framework/framework-preferences.scrbl rename to collects/scribblings/framework/preferences.scrbl diff --git a/collects/scribblings/framework/framework-scheme.scrbl b/collects/scribblings/framework/scheme.scrbl similarity index 100% rename from collects/scribblings/framework/framework-scheme.scrbl rename to collects/scribblings/framework/scheme.scrbl diff --git a/collects/scribblings/framework/framework-test.scrbl b/collects/scribblings/framework/test.scrbl similarity index 100% rename from collects/scribblings/framework/framework-test.scrbl rename to collects/scribblings/framework/test.scrbl diff --git a/collects/scribblings/framework/framework-text.scrbl b/collects/scribblings/framework/text.scrbl similarity index 100% rename from collects/scribblings/framework/framework-text.scrbl rename to collects/scribblings/framework/text.scrbl diff --git a/collects/scribblings/framework/framework-version.scrbl b/collects/scribblings/framework/version.scrbl similarity index 100% rename from collects/scribblings/framework/framework-version.scrbl rename to collects/scribblings/framework/version.scrbl