Module: environment-tools Synopsis: Environment Tools Utilities Author: Andy Armstrong, Chris Page Copyright: Original Code is Copyright (c) 1995-2004 Functional Objects, Inc. All rights reserved. License: Functional Objects Library Public License Version 1.0 Dual-license: GNU Lesser General Public License Warranty: Distributed WITHOUT WARRANTY OF ANY KIND /// Message boxes // Each of these message functions can be made to produce alert dialogs // that look like any of the others by supplying the appropriate keyword // parameters. The main difference between each of them is the default // icon and set of buttons. // DUIM doesn't specify the specializer of exit-type, so we'll do it // ourselves to prevent errors in environment code. define constant = one-of(#"ok", #"yes", #"no", #"cancel"); // A little convenience function we use in each of the message functions. define function assert-owner-supplied (owner) => () assert(~unsupplied?(owner), "Required keyword parameter owner: is unsupplied"); end function assert-owner-supplied; define function environment-message (message :: , #rest keys, #key owner = $unsupplied, title :: false-or(), exit-style :: false-or()) => () assert-owner-supplied(owner); apply(notify-user, message, title: title | release-product-name(), style: #"information", keys); end function environment-message; define function environment-warning-message (message :: , #rest keys, #key owner = $unsupplied, title :: false-or(), exit-style :: false-or()) => () assert-owner-supplied(owner); apply(notify-user, message, title: title | release-product-name(), style: #"warning", keys); end function environment-warning-message; define function environment-error-message (message :: , #rest keys, #key owner = $unsupplied, title :: false-or(), exit-style :: false-or()) => () assert-owner-supplied(owner); apply(notify-user, message, title: title | release-product-name(), style: #"error", keys); end function environment-error-message; define function environment-question (message :: , #rest keys, #key owner = $unsupplied, title :: false-or(), style :: = #"information", exit-style :: false-or(), cancel :: ) => (ok? :: , exit-type :: ) assert-owner-supplied(owner); apply(notify-user, message, title: title | release-product-name(), exit-style: case exit-style => exit-style; cancel => #"yes-no-cancel"; otherwise => #"yes-no"; end, keys) end function environment-question; define function environment-action-unavailable (frame :: , message :: ) => () if (frame-status-bar(frame)) frame-status-message(frame) := message; beep(frame) else environment-error-message(message, owner: frame) end end function environment-action-unavailable; define function not-yet-implemented (#key message :: false-or(), owner = $unsupplied) => () assert-owner-supplied(owner); environment-error-message(message | "Not yet implemented!", owner: owner) end function not-yet-implemented; /// File filters define function environment-choose-file (#key title :: false-or(), owner = $unsupplied, directory :: false-or() = #f, default :: false-or() = directory, direction :: one-of(#"input", #"output") = #"input", filters :: false-or() = #f, filter :: false-or()) => (filename :: false-or(), filter :: false-or()) assert-owner-supplied(owner); let actual-filters = filters & apply(filters-for-file-types, filters); let (filename, filter-index) = choose-file(title: title, frame: owner, default: default & as(, default), direction: direction, filters: actual-filters, default-filter: if (filters) (filter & position(filters, filter)) | 0 end); values(filename & as(, filename), filters & filter-index & filters[filter-index]) end function environment-choose-file; define function make-filter (extension :: ) concatenate("*.", extension) end function make-filter; define class () constant slot %thunk :: , required-init-keyword: thunk:; end class ; // --- hughg, 1997/10/07: Needs to be "define method", as emulator // gets #all-keys wrong for "define function". define sealed method lazy-element (table :: , key, #rest keys, #key, #all-keys) => (element) let value = apply(element, table, key, keys); select (value by instance?) => let element = value.%thunk(); table[key] := element; otherwise => value end end method lazy-element; //---*** It would be cool to generalize this to work in functional-extensions define macro lazy-table-definer { define lazy-table ?table-name:name = { ?entries } } => { define constant ?table-name ::
= make(
); begin let the-table = ?table-name; ?entries end; } { define lazy-table ?table-name:name :: ?table-type:name = { ?entries } } => { define constant ?table-name :: ?table-type = make(?table-type); begin let the-table = ?table-name; ?entries end; } entries: { } => { } { ?key:expression => ?value:expression, ... } => { the-table[ ?key ] := make(, thunk: method () ?value end); ... } end macro lazy-table-definer; // cpage: 1997.02.04 Lazily initialize some filter values, in the same // way that lazy-table-definer initializes a table of filters. // We need this for filters that have the same file type // extension so we can get a handle on them to compare for // them later. define variable %filter-for-lid-as-hdp :: = #[]; define function filter-for-lid-as-hdp () => (filter :: ) if (%filter-for-lid-as-text ~= #[]) %filter-for-lid-as-hdp else %filter-for-lid-as-hdp := vector("Dylan Library Interchange Descriptions (import)", make-filter(lid-file-extension())) end if end function filter-for-lid-as-hdp; define variable %filter-for-lid-as-text :: = #[]; define function filter-for-lid-as-text () => (filter :: ) if (%filter-for-lid-as-text ~= #[]) %filter-for-lid-as-text else %filter-for-lid-as-text := vector("Dylan Library Interchange Descriptions (as text)", make-filter(lid-file-extension())) end if end function filter-for-lid-as-text; // This filter is for an internal hack to allow internal developers // to open LID files directly without importing them to project files. // Hopefully someday we'll all use project files and dispense with this. define variable %filter-for-lid-without-importing :: = #[]; define function filter-for-lid-without-importing () => (filter :: ) if (%filter-for-lid-without-importing ~= #[]) %filter-for-lid-without-importing else %filter-for-lid-without-importing := vector("Dylan LIDs (without importing)", make-filter(lid-file-extension())) end if end function filter-for-lid-without-importing; define lazy-table $file-type-filters = { #"common" => /* The common file types for opening files. */ vector("Common Files", make-filter(project-file-extension()), make-filter(dylan-file-extension()), "*.dyl", "*.spec", "*.rc", "*.txt", "*.text", "*.c", "*.cpp", "*.cxx", "*.h", "*.hpp", "*.hxx", "*.inl"), #"common-insert" => /* The common file types for inserting source files. */ vector("Common Files", make-filter(project-file-extension()), make-filter(dylan-file-extension()), "*.dyl", "*.rc", "*.spec", "*.txt", "*.text", "*.c", "*.cpp", "*.cxx", "*.h", "*.hpp", "*.hxx", "*.inl", "*.lib"), #"common-locate-project" => /* The common file types for locating project/LID files. */ vector("Common Files", make-filter(project-file-extension()), make-filter(lid-file-extension())), #"project" => vector("Functional Developer Projects", make-filter(project-file-extension())), #"lid" => vector("Dylan Library Interchange Descriptions", make-filter(lid-file-extension())), #"lid-as-hdp" => filter-for-lid-as-hdp(), #"lid-without-importing" => filter-for-lid-without-importing(), #"lid-as-text" => filter-for-lid-as-text(), #"dylan" => vector("Dylan Source Files", make-filter(dylan-file-extension()), "*.dyl"), #"tool-spec" => #["Functional Developer Tool Specifications", "*.spec"], #"executable" => #["Programs", "*.exe"], #"resource" => #["Resource Files", "*.rc"], #"build-script" => #["Jam build scripts", "*.jam"], #"text" => #["Text Files", "*.txt", "*.text"], #"c" => #["C Source Files", "*.c", "*.cpp", "*.cxx"], #"c-include" => #["C Includes", "*.h", "*.hpp", "*.hxx", "*.inl"], #"library" => #["C Libraries", "*.lib"], #"all" => #["All Files (*.*)", "*.*"] }; define function filter-for-file-type (type :: ) => (filter :: ) lazy-element($file-type-filters, type, default: #f) | error("Can't find filter for unrecognised file type %s", type) end function filter-for-file-type; define function filters-for-file-types (#rest types :: ) => (filters :: ) map(filter-for-file-type, types) end function filters-for-file-types; /// Percentages ///---*** should be somewhere more standard? define class () sealed constant slot percentage-value :: , init-keyword: value:; end class ; define method percentage (amount :: , total :: ) => (percentage :: ) let t = as(, total); make(, value: if (t = 0.0) 100.0 else (as(, amount) * 100) / t end) end method percentage; define method \= (object1 :: , object2 :: ) => (equal? :: ) object1.percentage-value = object2.percentage-value end method \=; define method \< (object1 :: , object2 :: ) => (less-than? :: ) object1.percentage-value < object2.percentage-value end method \<; define constant $percentage-suffix = "%"; define method percentage-label (percentage :: , #key decimal-points :: = 0) => (label :: ) let value = percentage.percentage-value; concatenate(float-to-string(value, decimal-points: decimal-points), $percentage-suffix) end method percentage-label; /// Some useful functions define method frame-undefined-callback (frame :: ) => () not-yet-implemented(owner: frame) end method frame-undefined-callback; define method frame-sort-items (frame :: , items :: , #key key = identity, label-key, test = \<) => (sorted-items :: ) let sorted-items = keyed-sort(items, key: if (key == identity) label-key | curry(frame-default-object-name, frame) else method (item) let object = item.key; if (label-key) label-key(object) else frame-default-object-name(frame, object) end end end, test: test); assert(sorted-items, "This has to be a sequence!"); sorted-items end method frame-sort-items; /*---*** This version messes up in the compiler! define method frame-sort-items (frame :: , items :: , #key key = identity, label-key, test = \<) => (sorted-items :: ) keyed-sort(items, key: if (key == identity) label-key | curry(frame-default-object-name, frame) else method (item) let object = item.key; if (label-key) label-key(object) else frame-default-object-name(frame, object) end end end, test: test) end method frame-sort-items; */ /// Labelling define function make-labels-layout (descriptions :: , #key prefix, y-spacing = 0, border = 0, foreground, background, text-style) => (layout :: ) let labels = make(, size: descriptions.size); for (description in descriptions, index from 0) let label = if (prefix) concatenate-as(, prefix, description) else description end; labels[index] := make(