Module: environment-tools Synopsis: Environment tools Author: Andy Armstrong 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 /// Displayers define open abstract class () constant slot displayer-state-object :: , required-init-keyword: object:; end class ; define open abstract class () constant slot displayer-extra-pane :: false-or() = #f, init-keyword: extra-pane:; slot displayer-enabled? :: = #t, setter: %enabled?-setter, init-keyword: enabled?:; slot displayer-valid? :: = #f; slot displayer-new-state? :: = #f; slot displayer-state :: false-or() = #f, setter: %state-setter, init-keyword: state:; constant slot %lock :: = make(); slot displayer-operation :: false-or() = #f; slot displayer-ratios :: false-or() = #f, init-keyword: ratios:; constant slot displayer-ratios-changed-callback :: false-or() = #f, init-keyword: ratios-changed-callback:; constant slot displayer-transaction-function :: false-or() = #f, init-keyword: transaction-function:; end class ; define open generic displayer-default-input-focus (displayer :: ) => (sheet :: false-or()); define open generic note-displayer-enabled-state-changed (displayer :: ) => (); define open generic update-displayer-state (displayer :: ) => (); define method displayer-default-input-focus (displayer :: ) => (sheet == #f) #f end method displayer-default-input-focus; define method displayer-object (displayer :: ) => (object :: false-or()) let state = displayer.displayer-state; state & state.displayer-state-object end method displayer-object; define method update-displayer-state (displayer :: ) => () #f end method update-displayer-state; define method displayer-current-state (displayer :: ) => (state :: false-or()) update-displayer-state(displayer); displayer.displayer-state end method displayer-current-state; define method displayer-state-setter (state :: false-or(), displayer :: ) => (state :: false-or()) unless (state == displayer.displayer-state) displayer.displayer-new-state? := #t; displayer.%state := state end; state end method displayer-state-setter; define method displayer-enabled?-setter (enabled? :: , displayer :: ) => (enabled? :: ) displayer.%enabled? := enabled?; note-displayer-enabled-state-changed(displayer); enabled? end method displayer-enabled?-setter; define method maybe-add-extra-pane (displayer :: , pane :: ) => (layout :: ) let extra-pane = displayer.displayer-extra-pane; if (extra-pane) make(, ratios: displayer.displayer-ratios, children: vector(pane, extra-pane), split-bar-moved-callback: method (splitter :: , #rest arguments) ignore(arguments); let callback = displayer.displayer-ratios-changed-callback; displayer.displayer-ratios := splitter.gadget-ratios; callback & callback(displayer) end) else pane end end method maybe-add-extra-pane; define open generic make-displayer-state (displayer :: , object) => (state :: ); define open generic note-displayer-state-updated (displayer :: , #key after-function, new-thread?, state :: false-or()) => (); define open generic refresh-displayer-state (state :: ) => (); define method note-displayer-state-updated (displayer :: , #key after-function, new-thread? = #f, state :: false-or() = #f) => () after-function & after-function() end method note-displayer-state-updated; define method note-displayer-enabled-state-changed (displayer :: ) => () #f end method note-displayer-enabled-state-changed; define method refresh-displayer-state (state :: ) => () #f end method refresh-displayer-state; define method execute-displayer-gadget-activate-callback (gadget :: ) => () let displayer = gadget-client(gadget); if (displayer) let values = gadget-value(gadget); let value = case ~instance?(values, ) => values; ~empty?(values) => values[0]; otherwise => #f; end; execute-displayer-activate-callback(displayer, value) end end method execute-displayer-gadget-activate-callback; define method execute-displayer-activate-callback (pane :: , object) => () let page = sheet-parent(pane); execute-displayer-activate-callback(page, object) end method execute-displayer-activate-callback; define method pane-sheet-with-selection (pane :: ) => (sheet :: false-or()) #f end method pane-sheet-with-selection; /// Displayer operations define constant = one-of(#"querying", #"cancelled", #"finished"); define class () sealed slot operation-state :: = #"querying"; end class ; define macro with-displayer-lock { with-displayer-lock (?displayer:expression) ?body:body end } => { with-lock (?displayer.%lock) ?body end } end macro with-displayer-lock; define macro with-displayer-operation { with-displayer-operation (?operation:name = ?displayer:expression) ?body:body end } => { with-displayer-lock (?displayer) let ?operation = ?displayer.displayer-operation; ?body end } end macro with-displayer-operation; /// Items caching define abstract class () end class ; define generic object-cached-items (object, cache :: ) => (items :: false-or()); define generic object-cached-items-setter (items :: , object, cache :: ) => (items :: ); define generic clear-items-cache (cache :: false-or()) => (); define method clear-items-cache (no-cache == #f) => () #f end method clear-items-cache; define class () slot cache-items :: false-or() = #f; end class ; define method object-cached-items (object, cache :: ) => (items :: false-or()) cache.cache-items end method object-cached-items; define method object-cached-items-setter (items :: , object, cache :: ) => (items :: ) cache.cache-items := items end method object-cached-items-setter; define method clear-items-cache (cache :: ) => () cache.cache-items := #f end method clear-items-cache; define class () constant slot cache-children-table :: = make(
); end class ; define method object-cached-items (object, cache :: ) => (items :: false-or()) element(cache.cache-children-table, object, default: #f) end method object-cached-items; define method object-cached-items-setter (items :: , object, cache :: ) => (items :: ) element(cache.cache-children-table, object) := items end method object-cached-items-setter; define method clear-items-cache (cache :: ) => () remove-all-keys!(cache.cache-children-table) end method clear-items-cache; /// Some standard collection displayers define class () slot displayer-state-items-cache :: false-or() = #f, init-keyword: items-cache:; constant slot displayer-state-labels-cache :: = make(); slot displayer-state-sorted-items-cache :: false-or() = #f, init-keyword: sorted-items-cache:; slot displayer-state-sort-order :: false-or() = #f, init-keyword: sort-order:; slot displayer-state-names-cached?-cache :: false-or() = #f; slot displayer-state-value :: = #f; slot displayer-state-expanded-objects :: false-or() = #f; end class ; define macro with-displayer-items-cache { with-displayer-items-cache (?displayer:expression, ?cache:name, ?object:expression) ?body:body end } => { begin let state = ?displayer.displayer-state; let cache = state.?cache | begin ?cache ## "-setter" (make-displayer-items-cache(?displayer), state) end; object-cached-items(?object, cache) | begin object-cached-items(?object, cache) := ?body end end } end macro with-displayer-items-cache; define method refresh-displayer-state (state :: ) => () next-method(); clear-items-cache(state.displayer-state-items-cache); clear-items-cache(state.displayer-state-sorted-items-cache); clear-items-cache(state.displayer-state-names-cached?-cache); remove-all-keys!(state.displayer-state-labels-cache) end method refresh-displayer-state; define class () constant slot displayer-element-label :: = $unknown, init-keyword: element-label:; constant slot displayer-value-changed-callback :: false-or() = #f, init-keyword: value-changed-callback:; constant slot displayer-activate-callback :: false-or() = #f, init-keyword: activate-callback:; constant slot displayer-items-changed-callback :: false-or() = #f, init-keyword: items-changed-callback:; constant slot displayer-information-available?-function :: false-or() = #f, init-keyword: information-available?-function:; constant slot displayer-children-generator :: , required-init-keyword: children-generator:; constant slot displayer-selection-mode :: = #"multiple", init-keyword: selection-mode:; constant slot displayer-test-function :: = \=, init-keyword: test:; constant slot displayer-label-key :: = identity, init-keyword: label-key:; constant slot displayer-value-key :: = identity, init-keyword: value-key:; constant slot displayer-icon-function :: false-or() = #f, init-keyword: icon-function:; constant slot displayer-sort-function :: = identity, init-keyword: sort-function:; constant slot displayer-sort-order :: false-or() = #f, init-keyword: sort-order:; constant slot displayer-always-show-selection? :: = #t, init-keyword: always-show-selection?:; end class ; define open generic displayer-collection-gadget (displayer :: ); //---*** andrewa: 'define frame' doesn't specify a return type // => (gadget :: ); define open generic displayer-item-count (displayer :: ) => (display-count :: false-or(), total-count :: false-or()); define open generic compute-displayer-items (displayer :: , object) => (items :: ); define open generic displayer-items (displayer :: , object) => (items :: ); define open generic displayer-display-items (displayer :: , object) => (items :: ); define open generic displayer-sorted-items (displayer :: , object) => (items :: ); define method displayer-default-input-focus (displayer :: ) => (sheet :: ) displayer.displayer-collection-gadget end method displayer-default-input-focus; define method note-displayer-enabled-state-changed (displayer :: ) => () next-method(); let gadget = displayer.displayer-collection-gadget; gadget.gadget-enabled? := displayer.displayer-enabled? end method note-displayer-enabled-state-changed; define method make-displayer-items-cache (displayer :: ) => (cache :: ) make() end method make-displayer-items-cache; define method displayer-project (displayer :: ) => (project :: ) //---*** Should displayers really know about projects? let frame = displayer.sheet-frame; frame.frame-project end method displayer-project; define method displayer-information-available? (displayer :: ) => (available? :: ) let test-value = displayer.displayer-state & begin let function = displayer.displayer-information-available?-function; if (function) function() else project-compiler-database(displayer.displayer-project) end end; test-value ~= #f end method displayer-information-available?; define method displayer-sorted-items (displayer :: , object) => (items :: ) let sort-function = displayer.displayer-sort-function; let items = if (sort-function == identity) displayer-display-items(displayer, object) else with-displayer-items-cache (displayer, displayer-state-sorted-items-cache, object) let state = displayer.displayer-state; let items = displayer-display-items(displayer, object); let sort-function = displayer.displayer-sort-function; let sort-order = state.displayer-state-sort-order; if (sort-order) sort-function(items, sort-order) else items end end end; //--- We do this so that the names are computed and then cached during //--- this slow operation, rather than when the items are about to be //--- displayed which needs to be fast. This makes the tree control update //--- instantly once the items are all computed. with-displayer-items-cache (displayer, displayer-state-names-cached?-cache, object) ensure-displayer-item-labels-cached(displayer, items); //--- Return empty list to fill the cache, since it has to be a sequence #() end; items end method displayer-sorted-items; define method displayer-display-items (displayer :: , object) => (items :: ) displayer-items(displayer, object) end method displayer-display-items; define method displayer-items (displayer :: , object) => (items :: ) with-displayer-items-cache (displayer, displayer-state-items-cache, object) compute-displayer-items(displayer, object); end end method displayer-items; define method compute-displayer-items (displayer :: , object) => (items :: ) displayer.displayer-children-generator(object) end method compute-displayer-items; define method displayer-item-label (displayer :: , item :: ) => (label :: ) let state = displayer.displayer-state; let cache = state.displayer-state-labels-cache; element(cache, item, default: #f) | begin let label = displayer.displayer-label-key(item); element(cache, item) := label end end method displayer-item-label; define method ensure-displayer-item-labels-cached (displayer :: , items :: ) => () let label-key = displayer.displayer-label-key; let state = displayer.displayer-state; let cache = state.displayer-state-labels-cache; for (item in items) unless (element(cache, item, default: #f)) element(cache, item) := label-key(item) end end; items end method ensure-displayer-item-labels-cached; define method displayer-item-count (displayer :: ) => (count :: false-or(), total :: false-or()) size(displayer-items(displayer, displayer.displayer-object)) end method displayer-item-count; define method pane-sheet-with-selection (pane :: ) => (sheet :: ) pane.displayer-collection-gadget end method pane-sheet-with-selection; define method execute-displayer-activate-callback (displayer :: , object) => () let activate-callback = displayer.displayer-activate-callback; if (activate-callback) let gadget = displayer.displayer-collection-gadget; activate-callback(object) else next-method() end end method execute-displayer-activate-callback; define method displayer-object-icon (displayer :: , object) => (icon) let icon-function = displayer.displayer-icon-function; if (icon-function) icon-function(object) else environment-object-icon(displayer.displayer-project, object) end end method displayer-object-icon; define method make-displayer-state (pane :: , object) => (class :: ) make(, object: object, sort-order: pane.displayer-sort-order) end method make-displayer-state; define method update-displayer-state (displayer :: ) => () next-method(); let state = displayer.displayer-state; if (state) let gadget = displayer.displayer-collection-gadget; state.displayer-state-value := gadget.gadget-value; end end method update-displayer-state; define method displayer-object-setter (object :: false-or(), displayer :: , #key clean? = #f, new-thread? = #t, after-function) => () let frame = sheet-frame(displayer); with-busy-cursor (frame) with-displayer-lock (displayer) let old-state = displayer.displayer-state; let new-state = case old-state & (old-state.displayer-state-object == object) => old-state; otherwise => let state = object & make-displayer-state(displayer, object); displayer.displayer-state := state end; let new-state? = displayer.displayer-new-state?; let clean? = ~displayer.displayer-valid? | clean?; if (clean? | new-state?) refresh-displayer (displayer, clean?: clean?, new-thread?: new-thread?, after-function: after-function) else //--- We need to do this to get the status bar to update, //--- but it doesn't feel quite right somehow... note-displayer-items-changed(displayer); after-function & after-function() end end end end method displayer-object-setter; // Some displayers want to establish a debugger transaction // around entire display operations to avoid the overhead // involved with establishing several debugger transactions // in a running application later on during the display define macro with-displayer-transaction { with-displayer-transaction (?displayer:expression) ?:body end } => { let transaction-function = ?displayer.displayer-transaction-function; if (transaction-function) transaction-function(method () ?body end) else ?body end } end macro; define macro with-displayer-transaction-method { with-displayer-transaction-method (?displayer:expression) ?body:body end } => { let transaction-function = ?displayer.displayer-transaction-function; let display-method = method () ?body end; if (transaction-function) method () transaction-function(display-method) end else display-method end } end macro; define method refresh-displayer (displayer :: , #key clean? = #t, new-thread? = #t, after-function) => () let frame = sheet-frame(displayer); let state = displayer.displayer-state; let gadget = displayer.displayer-collection-gadget; update-displayer-state(displayer); if (clean? & state) refresh-displayer-state(state) end; if (new-thread?) set-displayer-contents(displayer, #[]); with-frame-background-operation (frame, "Refreshing...") with-abort-restart () with-environment-handlers (frame) with-displayer-transaction (displayer) note-displayer-state-updated (displayer, after-function: after-function, new-thread?: #t, state: state) end end end end else with-busy-cursor (frame) with-displayer-transaction (displayer) note-displayer-state-updated (displayer, after-function: after-function, state: state) end end end end method refresh-displayer; define thread variable *handle-item-callbacks?* :: = #t; define method note-displayer-items-changed (displayer :: ) => () if (*handle-item-callbacks?*) let callback = displayer.displayer-items-changed-callback; if (callback) callback(displayer) else note-displayer-items-updated (displayer, displayer.displayer-element-label) end end end method note-displayer-items-changed; define method compute-displayer-contents (displayer :: ) => (sorted-items :: ) let state = displayer.displayer-state; if (displayer-information-available?(displayer)) let object = state & state.displayer-state-object; displayer-sorted-items(displayer, object) else #[] end end method compute-displayer-contents; define method set-displayer-contents (displayer :: , sorted-items :: , #key state :: false-or() = #f) => () let gadget = displayer.displayer-collection-gadget; with-atomic-redisplay (gadget) if (gadget-items(gadget) ~= sorted-items) gadget-items(gadget) := sorted-items else update-gadget(gadget) end; let value = if (state) state.displayer-state-value end; gadget-value(gadget) := case value => value; gadget-selection-mode(gadget) == #"multiple" => #[]; otherwise => #f; end end end method set-displayer-contents; define method note-displayer-state-updated (displayer :: , #key after-function, new-thread? = #f, state :: false-or() = #f) => () let frame = sheet-frame(displayer); if (new-thread?) let current-operation = with-displayer-operation (operation = displayer) if (operation) operation.operation-state := #"cancelled" end; displayer.displayer-operation := make() end; let sorted-items = compute-displayer-contents(displayer); call-in-frame(frame, with-displayer-transaction-method (displayer) with-displayer-operation (operation = displayer) if (operation & operation == current-operation & operation.operation-state ~== #"cancelled") displayer.displayer-operation := #f; set-displayer-contents (displayer, sorted-items, state: state); displayer.displayer-valid? := #t; displayer.displayer-new-state? := #f; note-displayer-items-changed(displayer); operation.operation-state := #"finished"; end end; after-function & after-function() end) else let sorted-items = compute-displayer-contents(displayer); call-in-frame (frame, with-displayer-transaction-method (displayer) set-displayer-contents(displayer, sorted-items, state: state); displayer.displayer-valid? := #t; displayer.displayer-new-state? := #f; note-displayer-items-changed(displayer); after-function & after-function(); end) end end method note-displayer-state-updated; define function displayer-toggle-sort-order (displayer :: , order) => () let state = displayer.displayer-state; if (state) with-busy-cursor (sheet-frame(displayer)) with-displayer-lock (displayer) let old-order = state.displayer-state-sort-order; state.displayer-state-sort-order := select (order by instance?) => let order-0 = order[0]; let order-1 = order[1]; if (old-order = order-0) order-1 else order-0 end; otherwise => if (old-order = order) #f else order end; end; clear-items-cache(state.displayer-state-sorted-items-cache); note-displayer-state-updated(displayer) end end end end function displayer-toggle-sort-order; define function project-not-built-message (project :: , #key message = $no-information-available) => (message :: ) concatenate(message, if (~project.project-compiler-database & project.project-can-be-built?) concatenate(" ", $project-not-built) else "" end) end function project-not-built-message; define method note-displayer-items-updated (displayer :: , name :: ) => () let frame = sheet-frame(displayer); let project = displayer.displayer-project; let (count, total) = displayer-information-available?(displayer) & displayer-item-count(displayer); let message = case total & total ~= count => format-to-string("%d %s (%d total)", count, string-pluralize(name, count: count), total); count => format-to-string("%d %s", count, string-pluralize(name, count: count)); otherwise => project-not-built-message(project); end; frame-status-message(frame) := message end method note-displayer-items-updated; /// Filtering displayers define pane () constant slot displayer-filter-function :: , required-init-keyword: filter-function:; constant slot displayer-filter-types :: , required-init-keyword: filter-types:; constant slot displayer-filter-type :: , init-keyword: filter-type:; constant slot displayer-filter-extra-types :: = #[], init-keyword: filter-extra-types:; constant slot displayer-filter-extra-type :: false-or() = #f, init-keyword: filter-extra-type:; constant slot displayer-filter-extra-text-label :: false-or() = #f, init-keyword: filter-extra-text-label:; constant slot displayer-filter-text-label :: = "with names containing", init-keyword: filter-text-label:; constant slot displayer-filter-type-only? :: = #f, init-keyword: filter-type-only?:; pane displayer-type-filter-pane (pane) make(, items: pane.displayer-filter-types, value: pane.displayer-filter-type, label-key: first, value-key: second, value-changed-callback: method (sheet) update-displayer-filter(pane) end, documentation: "Chooses the subset of objects to show.", fixed-width?: ~pane.displayer-filter-type-only?); pane displayer-extra-type-filter-pane (pane) make(, items: pane.displayer-filter-extra-types, value: pane.displayer-filter-extra-type, label-key: first, value-key: second, value-changed-callback: method (sheet) update-displayer-filter(pane) end, documentation: "Chooses the subset of objects to show.", fixed-width?: #t); pane displayer-substring-filter-pane (pane) make(, value-changed-callback: method (sheet) update-displayer-filter(pane) end, activate-callback: method (sheet) update-displayer-filter(pane) end, documentation: "Filters the subset of objects by substring.", //---*** What should the width really be? width: 100, fixed-width?: #t); pane displayer-filters-layout (pane) begin let extra-types? = ~empty?(pane.displayer-filter-extra-types); let extra-label = pane.displayer-filter-extra-text-label; let filter-type-only? = pane.displayer-filter-type-only?; let children = if (filter-type-only?) vector(pane.displayer-type-filter-pane) else vector(pane.displayer-type-filter-pane, extra-label & make(