Module: environment-framework Synopsis: Environment Framework 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 /// Searching commands define constant $asynchronous-timeout = 10; // Return whether the frame can issue Find/Replace commands at this time. // These are used to determine when the relavent commands should be enabled. define open generic frame-can-find? (frame :: ) => (can? :: ); define open generic frame-can-replace? (frame :: ) => (can? :: ); // Open a frame to display/change the Find/Replace strings and other options. define open generic frame-edit-search-options (frame :: ) => (); // Return the class to instantiate for the search options frame. This allows // client libraries to subclass the frame. define open generic frame-search-frame-class (frame :: ) => (class :: ); // Find the next/previous match, using the current search options. define open generic frame-find-next (frame :: ) => (); define open generic frame-find-previous (frame :: ) => (); // Find the next/previous match in the next/previous target. This skips over // any matches in the current target. define open generic frame-find-in-next-target (frame :: ) => (); define open generic frame-find-in-previous-target (frame :: ) => (); // Copy the selected text in the frame to the Find/Replace string. define open generic frame-copy-selection-to-search (frame :: ) => (); define open generic frame-copy-selection-to-replace (frame :: ) => (); // Copy the selected text in the frame to the Find/Replace string, then // search for the next/previous match. This is the same as calling // frame-copy-selection-to-search followed by frame-find-next/previous. define open generic frame-find-selection-next (frame :: ) => (); define open generic frame-find-selection-previous (frame :: ) => (); // Replace the selected text in the current target if it is a match. define open generic frame-replace-selection (frame :: ) => (); // Replace the selected text in the current target if it is a match. Then // search for the next/previous match. The search is performed whether or // not the current selection is replaced. define open generic frame-replace-and-find-next (frame :: ) => (); define open generic frame-replace-and-find-previous (frame :: ) => (); // Find and replace all matches. Depending on the current search options, // this may replace from the selection or throughout the current target, // and it may replace only in the current target, or throughout all targets. define open generic frame-replace-all (frame :: ) => (); // Notify the frame that the searching options have been changed. This is // usually used to update command enabling. define open generic note-frame-searching-updated (frame :: ) => (); // Default methods define method frame-can-find? (frame :: ) => (can? :: ) #f end method frame-can-find?; define method frame-can-replace? (frame :: ) => (can? :: ) #f end method frame-can-replace?; define method note-frame-searching-updated (frame :: ) => () // Do nothing end method note-frame-searching-updated; /// Searching in a sheet/gadget define open generic can-find-in-sheet? (sheet :: ) => (can? :: ); define open generic can-replace-in-sheet? (sheet :: ) => (can? :: ); define open generic find-in-sheet (sheet :: , search-string :: , #key from-selection? :: , backwards? :: , wrap? :: , match-case? :: , match-word? :: , match-regexp? :: , progress-callback :: false-or()) => (object :: ); define open generic find-all-in-sheet (sheet :: , register-object :: , search-string :: , #key match-case? :: , match-word? :: , match-regexp? :: , progress-callback :: false-or()) => (); define open generic replace-in-sheet (sheet :: , search-string :: , replace-string :: , #key match-case? :: , match-word? :: , match-regexp? :: , progress-callback :: false-or()) => (object :: ); define open generic replace-all-in-sheet (sheet :: , search-string :: , replace-string :: , #key from-selection? :: , backwards? :: , wrap? :: , match-case? :: , match-word? :: , match-regexp? :: , progress-callback :: false-or()) => (replace-count :: ); define open generic sheet-reveal-search-object (sheet :: , object :: ) => (revealed? :: ); define open generic sheet-search-object-label (sheet :: , object :: ) => (label :: false-or()); define open generic sheet-search-object-icon (sheet :: , object :: ) => (icon :: false-or()); // Default methods define method can-find-in-sheet? (sheet :: ) => (can-find? :: ) #f end method can-find-in-sheet?; define method can-replace-in-sheet? (sheet :: ) => (can-replace? :: ) #f end method can-replace-in-sheet?; /// Searching in a frame define open generic can-find-in-frame? (frame :: ) => (can? :: ); define open generic can-replace-in-frame? (frame :: ) => (can? :: ); define open generic find-in-frame (frame :: , search-string :: , #key from-selection? :: , backwards? :: , wrap? :: , match-case? :: , match-word? :: , match-regexp? :: , progress-callback :: false-or()) => (object :: ); define open generic find-all-in-frame (frame :: , register-object :: , search-string :: , #key match-case? :: , match-word? :: , match-regexp? :: , progress-callback :: false-or()) => (); define open generic replace-in-frame (frame :: , search-string :: , replace-string :: , #key match-case? :: , match-word? :: , match-regexp? :: , progress-callback :: false-or()) => (object :: ); define open generic replace-all-in-frame (frame :: , search-string :: , replace-string :: , #key from-selection? :: , backwards? :: , wrap? :: , match-case? :: , match-word? :: , match-regexp? :: , progress-callback :: false-or()) => (replace-count :: ); define open generic frame-reveal-search-object (frame :: , object :: ) => (revealed? :: ); define open generic frame-search-object-label (frame :: , object :: ) => (label :: false-or()); define open generic frame-search-object-icon (frame :: , object :: ) => (icon :: false-or()); // Default methods define method can-find-in-frame? (frame :: ) => (can-find? :: ) #f end method can-find-in-frame?; define method can-replace-in-frame? (frame :: ) => (can-replace? :: ) #f end method can-replace-in-frame?; /// Searching commands define abstract class () end class ; define abstract class (, ) end class ; define abstract class () end class ; define class () end class ; define method do-execute-command (frame :: , command :: ) => () frame-edit-search-options(frame) end method do-execute-command; define class () end class ; define method execute-command-for-focus (frame :: , command :: ) => () frame-find-next(frame) end method execute-command-for-focus; define class () end class ; define method execute-command-for-focus (frame :: , command :: ) => () frame-find-previous(frame) end method execute-command-for-focus; define class () end class ; define method execute-command-for-focus (frame :: , command :: ) => () frame-find-in-next-target(frame) end method execute-command-for-focus; define class () end class ; define method execute-command-for-focus (frame :: , command :: ) => () frame-find-in-previous-target(frame) end method execute-command-for-focus; define class () end class ; define method execute-command-for-focus (frame :: , command :: ) => () frame-copy-selection-to-replace(frame) end method execute-command-for-focus; define class () end class ; define method execute-command-for-focus (frame :: , command :: ) => () frame-copy-selection-to-replace(frame) end method execute-command-for-focus; define class () end class ; define method execute-command-for-focus (frame :: , command :: ) => () frame-find-selection-next(frame) end method execute-command-for-focus; define class () end class ; define method execute-command-for-focus (frame :: , command :: ) => () frame-find-selection-previous(frame) end method execute-command-for-focus; define class () end class ; define method execute-command-for-focus (frame :: , command :: ) => () frame-replace-selection(frame) end method execute-command-for-focus; define class () end class ; define method execute-command-for-focus (frame :: , command :: ) => () frame-replace-and-find-next(frame) end method execute-command-for-focus; define class () end class ; define method execute-command-for-focus (frame :: , command :: ) => () frame-replace-and-find-previous(frame) end method execute-command-for-focus; define class () end class ; define method execute-command-for-focus (frame :: , command :: ) => () frame-replace-all(frame) end method execute-command-for-focus; /// Searching within a domain define open abstract class () end class ; define open generic register-search-domain (domain :: ) => (); define open generic unregister-search-domain (domain :: ) => (); define open generic search-domain-label (domain :: ) => (label :: ); define open generic search-domain-targets (domain :: ) => (targets :: ); define open generic search-domain-target-label (domain :: , target :: ) => (label :: ); define open generic search-domain-target-kind-label (domain :: , target :: ) => (label :: ); define open generic search-domain-target-icon (domain :: , target :: ) => (icon :: false-or()); define open generic search-domain-target-can-find? (domain :: , target :: ) => (can-find? :: ); define open generic search-domain-target-can-replace? (domain :: , target :: ) => (can-replace? :: ); define open generic search-domain-find (domain :: , target :: , search-string :: , #key from-selection? :: , backwards? :: , wrap? :: , match-case? :: , match-word? :: , match-regexp? :: , progress-callback :: false-or()) => (object :: ); define open generic search-domain-find-all (domain :: , target :: , register-object :: , search-string :: , #key match-case? :: , match-word? :: , match-regexp? :: , progress-callback :: false-or()) => (); define open generic search-domain-replace-selection (domain :: , target :: , search-string :: , replace-string :: , #key match-case? :: , match-word? :: , match-regexp? :: , progress-callback :: false-or()) => (object :: ); define open generic search-domain-replace-all (domain :: , target :: , search-string :: , replace-string :: , #key from-selection? :: , backwards? :: , wrap? :: , match-case? :: , match-word? :: , match-regexp? :: , progress-callback :: false-or()) => (replace-count :: ); define open generic search-domain-reveal-search-object (domain :: , object :: ) => (revealed? :: ); define open generic search-domain-search-object-label (domain :: , object :: ) => (label :: false-or()); define open generic search-domain-search-object-icon (domain :: , object :: ) => (icon :: false-or()); // Default methods define method search-domain-target-can-find? (domain :: , target :: ) => (can-find? :: ) #f end method search-domain-target-can-find?; define method search-domain-target-can-replace? (domain :: , target :: ) => (can-replace? :: ) #f end method search-domain-target-can-replace?; /// : Frames that can be searched define open abstract class () end class ; // The current target frame define variable *current-search-target-frame* :: false-or() = #f; define function current-search-target-frame () => (target-frame :: false-or()) *current-search-target-frame* end function current-search-target-frame; define function current-search-target-frame-setter (target-frame :: false-or()) => (target-frame :: false-or()) when (*current-search-target-frame* ~== target-frame) *current-search-target-frame* := target-frame; let search-frame = current-search-options-frame(); search-frame & call-in-frame(search-frame, note-search-frame-current-search-target-frame-changed, search-frame); end; target-frame end function current-search-target-frame-setter; // Set the current search target frame when a target frame gets the focus define method handle-event (frame :: , event :: ) => () next-method(); //---*** Enabled for debugging only... debug-message("handle-event: Search target frame got the focus: %=", frame.frame-title); current-search-target-frame() := frame; end method handle-event; // Reset the current search target frame define function reset-current-search-target-frame (old-target-frame :: ) => () when (old-target-frame == current-search-target-frame()) //debug-message("handle-event: The current target frame was exited"); //--- cpage: 1998.08.11 Ideally, we could just reset this to #f and the next // frame to get the focus would set it. However, the next frame to // get the focus isn't necessarily a search target frame. This can // happen if the Find window, for example is the next frame in Z // order and the user closes the target frame. I suppose it might // be better to hang this behavior on for // all non-target frames, to prevent unnecessary work, but I // don't think there's an appropriate subclass to // specialize on. Perhaps we need to define a . // Search for the next available target frame let new-target-frame = block (return) do-frames(method (frame :: ) => () when (frame ~== old-target-frame & instance?(frame, )) return(frame) end end method, z-order: #"top-down"); end block; //debug-message("Resetting search target frame to: %=", // new-target-frame & new-target-frame.frame-title); current-search-target-frame() := new-target-frame; end; end function reset-current-search-target-frame; // Reset the current search target frame when the current frame exits define method handle-event (frame :: , event :: ) => () next-method(); //debug-message("handle-event: Search target frame was exited: %=", frame.frame-title); reset-current-search-target-frame(frame); end method handle-event; //---*** cpage: 1997.07.24 Andy believes we should use // instead. This is partly based upon the belief that iconized // windows are unmapped. Verify, then delete these methods. // BEGIN DELETE define method handle-event (frame :: , event :: ) => () next-method(); //debug-message("handle-event: Search target frame was unmapped: %=", frame.frame-title); reset-current-search-target-frame(frame); end method handle-event; define method handle-event (frame :: , event :: ) => () next-method(); //debug-message("handle-event: Search target frame was destroyed: %=", frame.frame-title); reset-current-search-target-frame(frame); end method handle-event; // END DELETE /// Searching in a sheet/gadget define method can-find-in-sheet? (gadget :: ) => (can-find? :: ) #t end method can-find-in-sheet?; define function string-contains? (string :: , pattern :: , match-word? :: ) => (contains? :: ) block (return) for (i from 1) let position = subsequence-position(string, pattern, count: i); when (position = #f) return(#f) end; unless (match-word?) return(#t); end; local method word-break? (c :: ) => (break? :: ) // Return whether a character is a word-break character //--- cpage: 1998.06.26 Do we have any library functions // for this type of thing? member?(c, #[' ',',','\'','"','#','(',')','[',']']) end method; let end-position = position + size(pattern) - 1; let end-string = size(string) - 1; let at-start? = position = 0; let at-end? = end-position = end-string; when ((at-start? | word-break?(string[position - 1])) & (at-end? | word-break?(string[end-position + 1]))) return(#t); end when end for end block end function string-contains?; define method gadget-find-string (gadget :: , string :: , #key start, wrap? = #f, match-case? = #t, match-word? = #f) //--- cpage: 1998.08.07 Add support for progress-callback if some collections // might take a while to search. => (index :: false-or()) when (~match-case?) string := as-lowercase(string); end; block (return) let items = gadget-items(gadget); let start = start | 0; for (i from start below size(items)) let label = gadget-label-key(gadget)(items[i]); when (~match-case?) label := as-lowercase(label); end; when (string-contains?(label, string, match-word?)) return(i) end; end for; when (wrap? & (start > 0)) gadget-find-string(gadget, string, wrap?: wrap?, match-case?: match-case?, match-word?: match-word?) end when end block end method gadget-find-string; define method gadget-find-previous-string (gadget :: , string :: , #key start, wrap? = #t, match-case? = #t, match-word? = #f) //--- cpage: 1998.08.07 Add support for progress-callback if some collections // might take a while to search. => (index :: false-or()) when (~match-case?) string := as-lowercase(string); end; block (return) let items = gadget-items(gadget); let last-index = size(items) - 1; let start = start | last-index; for (i from start to 0 by -1) let label = gadget-label-key(gadget)(items[i]); when (~match-case?) label := as-lowercase(label); end; when (string-contains?(label, string, match-word?)) return(i) end; end for; when (wrap? & (start < last-index)) gadget-find-previous-string(gadget, string, wrap?: wrap?, match-case?: match-case?, match-word?: match-word?) end when end block end method gadget-find-previous-string; define method find-in-sheet (gadget :: , search-string :: , #key from-selection? :: , backwards? :: , wrap? :: , match-case? :: , match-word? :: , match-regexp? :: , progress-callback :: false-or()) => (index :: false-or()) ignore(match-regexp?, progress-callback); let start = when (from-selection?) let old-selection = gadget-selection(gadget); unless (empty?(old-selection)) let index = old-selection[0]; if (backwards?) index - 1 else index + 1 end if; end unless; end when; if (backwards?) gadget-find-previous-string(gadget, search-string, start: start, wrap?: wrap?, match-case?: match-case?, match-word?: match-word?) else gadget-find-string(gadget, search-string, start: start, wrap?: wrap?, match-case?: match-case?, match-word?: match-word?) end if end method find-in-sheet; define method find-all-in-sheet (gadget :: , register-object :: , search-string :: , #key match-case? :: , match-word? :: , match-regexp? :: , progress-callback :: false-or()) => () ignore(match-regexp?, progress-callback); let index = 0; while (index ~== #f) index := gadget-find-string(gadget, search-string, start: index, wrap?: #f, match-case?: match-case?, match-word?: match-word?); index & register-object(index); end while; end find-all-in-sheet; define method call-in-frame-synchronously (frame :: , function :: , #rest args) => (#rest values) if (frame.frame-thread = current-thread()) apply(function, args) else let v :: = with-asynchronous-results (id, timeout: $asynchronous-timeout) call-in-frame(frame, method () => () let (#rest _v) = apply(function, args); provide-results(id, _v); end); end; apply(values, v) end if end method call-in-frame-synchronously; //--- cpage: 1998.09.17 Perhaps this could be changed to apply // call-in-frame-synchronously to share code. define method apply-in-frame-synchronously (frame :: , function :: , arg, #rest args) => (#rest values) if (frame.frame-thread = current-thread()) apply(apply, function, arg, args) else let v :: = with-asynchronous-results (id, timeout: $asynchronous-timeout) call-in-frame(frame, method () => () let (#rest _v) = apply(apply, function, arg, args); provide-results(id, _v); end); end; apply(values, v) end if end method apply-in-frame-synchronously; define method sheet-reveal-search-object (gadget :: , index :: ) => (revealed? :: ) let frame = sheet-frame(gadget); when (frame & (index < size(gadget.gadget-items))) call-in-frame-synchronously(frame, method () => () gadget-selection(gadget) := vector(index); note-frame-selection-updated(frame); end); #t end end method sheet-reveal-search-object; define method sheet-search-object-label (gadget :: , index :: ) => (label :: false-or()) let _gadget-label = gadget.gadget-label; let collection-label = _gadget-label & as(, _gadget-label); let object-label = when (index < size(gadget-items(gadget))) let item = gadget.gadget-items[index]; let label = gadget-item-label(gadget, item); label & as(, label) end; concatenate(collection-label | "?", ": ", object-label | "?") end method sheet-search-object-label; define method sheet-search-object-icon (sheet :: , index :: ) => (icon :: false-or()) ignore(sheet, index); //---*** cpage: 1998.08.20 How can we get a gadget item's icon? // There's a 'foo-icon-function' slot for several gadgets, // but there appears to be no generic protocol for getting // a 'gadget-item-icon'. #f end method sheet-search-object-icon; /// Searching in a frame define method can-find-in-frame? (frame :: ) => (can-find? :: ) // Search the sheet with the selection focus let sheet = frame-sheet-with-selection(frame); sheet & can-find-in-sheet?(sheet) end method can-find-in-frame?; define method find-in-frame (frame :: , search-string :: , #rest keys, #key from-selection? :: , backwards? :: , wrap? :: , match-case? :: , match-word? :: , match-regexp? :: , progress-callback :: false-or()) => (object :: ) let sheet = frame-sheet-with-selection(frame); let object = sheet & apply(find-in-sheet, sheet, search-string, keys); object & pair(sheet, object) end method find-in-frame; define method find-all-in-frame (frame :: , register-object :: , search-string :: , #rest keys, #key match-case? :: , match-word? :: , match-regexp? :: , progress-callback :: false-or()) => () let sheet = frame-sheet-with-selection(frame); when (sheet) local method register-sheet-object (object :: ) register-object(pair(sheet, object)) end; apply(find-all-in-sheet, sheet, register-sheet-object, search-string, keys); end when; end find-all-in-frame; //---*** cpage: 1998.08.20 Most of this should probably be moved into // search-domain-replace-all so it can be reused // in more places. define method replace-all-in-frame (frame :: , search-string :: , replace-string :: , #rest keys, #key from-selection? :: , backwards? :: , wrap? :: , match-case? :: , match-word? :: , match-regexp? :: , progress-callback :: false-or()) => (replace-count :: ) let replace-count :: = 0; // To prevent an infinite loop, if wrapping, just replace all from the start //---*** cpage: 1998.09.17 Ideally, we should add a protocol for getting some kind // of token representing the current selection, then we'd be able to // wrap to that point. Note that a simple fixed index or offset // wouldn't be adequate because replacements may cause the position // to move. Of course, we could just not allow wrapping with // "Replace All", which is what some other environments do, but I // think it's better to keep all search/replace operations as // uniform as possible. when (wrap?) from-selection? := #f; end; // First, replace the current selection if replacing from the selection when (from-selection?) when (replace-in-frame(frame, search-string, replace-string, match-case?: match-case?, match-word?: match-word?, match-regexp?: match-regexp?, progress-callback: progress-callback)) replace-count := replace-count + 1; end when; end when; // Find the next match, if any let object = apply(find-in-frame, frame, search-string, from-selection?: from-selection?, wrap?: #f, keys); while (object) // Select the found object, then replace it let revealed? = frame-reveal-search-object(frame, object); debug-assert(revealed?, "Unable to reveal found object %=", object); let replacement-object = replace-in-frame(frame, search-string, replace-string, match-case?: match-case?, match-word?: match-word?, match-regexp?: match-regexp?, progress-callback: progress-callback); // Reveal the replacement object, if any, so we can continue searching past it // (There may not be one if the target turns out to be read-only) when (replacement-object) revealed? := frame-reveal-search-object(frame, replacement-object); debug-assert(revealed?, "Unable to reveal replacement object %=", replacement-object); replace-count := replace-count + 1; end; // Find the next match object := apply(find-in-frame, frame, search-string, from-selection?: #t, wrap?: #f, keys); end while; replace-count end method replace-all-in-frame; define method frame-reveal-search-object (frame :: , object :: ) => (revealed? :: ) let sheet = object.head; when (sheet) call-in-frame-synchronously(frame, frame-input-focus-setter, sheet, frame); sheet-reveal-search-object(sheet, object.tail) end end method frame-reveal-search-object; define method frame-search-object-label (frame :: , object :: ) => (label :: false-or()) let frame-label = frame.frame-title; let sheet = object.head; let object-label = sheet & sheet-search-object-label(sheet, object.tail); concatenate(frame-label | "?", ": ", object-label | "?") end method frame-search-object-label; define method frame-search-object-icon (frame :: , object :: ) => (icon :: false-or()) let sheet = object.head; (sheet & sheet-search-object-icon(sheet, object.tail)) | frame.frame-icon end method frame-search-object-icon; /// : Searches the main text of frames define open abstract class () end class ; // Search domain for all frames define class () end class ; // Search domain for the "current" frame; the most recently activated frame. define class () end class ; define constant $current-frame-search-domain = make(); // The label for target kind define constant $frame-search-domain-kind-label = "window"; // Frame search domain label define constant $all-frames-search-domain-label = "All Windows"; // Base label for "Current Window" label define constant $current-frame = "Current Window"; // The label for when there is no current window define constant $no-current-frame = "(None)"; define method search-domain-label (domain :: ) => (label :: ) $all-frames-search-domain-label end method search-domain-label; define method search-domain-label (domain :: ) => (label :: ) let frame = current-search-target-frame(); concatenate($current-frame, " - ", (frame & search-domain-target-label(domain, frame)) | $no-current-frame) end method search-domain-label; define method search-domain-targets (domain :: ) => (targets :: ) ignore(domain); let targets = make(); do-frames(method (frame :: ) when (instance?(frame, )) add!(targets, frame) end end method); targets end method search-domain-targets; define method search-domain-targets (domain :: ) => (targets :: ) ignore(domain); vector(#"current-frame") end method search-domain-targets; /// UTILITY CODE COPIED FROM ENVIRONMENT-TOOLS UTILITIES.DYLAN //---*** cpage: 1998.04.17 We should unify this code and share it // between the framework and environment-tools. Of // course, we should have a simpler means for getting // the short name, without stripping off the suffix. define variable $frame-title-optional-suffix :: false-or() = #f; //---*** andrewa: it would be better to compute this directly, rather //---*** than stripping off the extra string all of the time. define method frame-short-title (frame :: ) => (title :: ) let suffix = $frame-title-optional-suffix | begin let suffix = concatenate(" - ", release-product-name()); $frame-title-optional-suffix := suffix end; let title = frame-title(frame); let title-size = size(title); let suffix-size = size($frame-title-optional-suffix); let title-includes-suffix? = (title-size > suffix-size) & ($frame-title-optional-suffix = copy-sequence(title, start: title-size - suffix-size)); if (title-includes-suffix?) copy-sequence(title, end: title-size - suffix-size) else title end end method frame-short-title; define method search-domain-target-label (domain :: , target :: ) => (label :: ) ignore(domain); frame-short-title(target) end method search-domain-target-label; define method search-domain-target-label (domain :: , target == #"current-frame") => (label :: ) ignore(domain, target); let frame = current-search-target-frame(); (frame & search-domain-target-label(domain, frame)) | $no-current-frame end method search-domain-target-label; define method search-domain-target-kind-label (domain :: , target :: ) => (label :: ) ignore(domain, target); $frame-search-domain-kind-label end method search-domain-target-kind-label; define method search-domain-target-icon (domain :: , target :: ) => (icon :: false-or()) ignore(domain); target.frame-icon end method search-domain-target-icon; define method search-domain-target-icon (domain :: , target == #"current-frame") => (icon :: false-or()) ignore(target); let frame = current-search-target-frame(); frame & search-domain-target-icon(domain, frame) end method search-domain-target-icon; define method search-domain-target-can-find? (domain :: , target :: ) => (can-find? :: ) ignore(domain); can-find-in-frame?(target) end method search-domain-target-can-find?; define method search-domain-target-can-find? (domain :: , target == #"current-frame") => (can-find? :: ) ignore(target); let frame = current-search-target-frame(); frame & search-domain-target-can-find?(domain, frame) end method search-domain-target-can-find?; define method search-domain-target-can-replace? (domain :: , target :: ) => (can-replace? :: ) ignore(domain); can-replace-in-frame?(target) end method search-domain-target-can-replace?; define method search-domain-target-can-replace? (domain :: , target == #"current-frame") => (can-find? :: ) ignore(target); let frame = current-search-target-frame(); frame & search-domain-target-can-replace?(domain, frame) end method search-domain-target-can-replace?; define method search-domain-find (domain :: , target :: , search-string :: , #rest keys, #key from-selection? :: , backwards? :: , wrap? :: , match-case? :: , match-word? :: , match-regexp? :: , progress-callback :: false-or()) => (object :: false-or()) ignore(domain); when (can-find-in-frame?(target)) let object = apply(find-in-frame, target, search-string, keys); object & pair(target, object) end end method search-domain-find; define method search-domain-find (domain :: , target == #"current-frame", search-string :: , #rest keys, #key from-selection? :: , backwards? :: , wrap? :: , match-case? :: , match-word? :: , match-regexp? :: , progress-callback :: false-or()) => (object :: ) ignore(target); let frame = current-search-target-frame(); frame & apply(search-domain-find, domain, frame, search-string, keys) end method search-domain-find; define method search-domain-find-all (domain :: , target :: , register-object :: , search-string :: , #rest keys, #key match-case? :: , match-word? :: , match-regexp? :: , progress-callback :: false-or()) => () ignore(domain); when (can-find-in-frame?(target)) local method register-frame-object (object :: ) => () register-object(pair(target, object)); end; apply(find-all-in-frame, target, register-frame-object, search-string, keys); end when; end method search-domain-find-all; define method search-domain-find-all (domain :: , target == #"current-frame", register-object :: , search-string :: , #rest keys, #key match-case? :: , match-word? :: , match-regexp? :: , progress-callback :: false-or()) => () ignore(target); let frame = current-search-target-frame(); frame & apply(search-domain-find-all, domain, frame, register-object, search-string, keys) end method search-domain-find-all; define method search-domain-replace-selection (domain :: , target :: , search-string :: , replace-string :: , #rest keys, #key match-case? :: , match-word? :: , match-regexp? :: , progress-callback :: false-or()) => (object :: false-or()) ignore(domain); when (can-replace-in-frame?(target)) let object = apply(replace-in-frame, target, search-string, replace-string, keys); object & pair(target, object) end end method search-domain-replace-selection; define method search-domain-replace-selection (domain :: , target == #"current-frame", search-string :: , replace-string :: , #rest keys, #key match-case? :: , match-word? :: , match-regexp? :: , progress-callback :: false-or()) => (object :: ) ignore(target); let frame = current-search-target-frame(); frame & apply(search-domain-replace-selection, domain, frame, search-string, replace-string, keys) end method search-domain-replace-selection; define method search-domain-replace-all (domain :: , target :: , search-string :: , replace-string :: , #rest keys, #key from-selection? :: , backwards? :: , wrap? :: , match-case? :: , match-word? :: , match-regexp? :: , progress-callback :: false-or()) => (replace-count :: ) ignore(domain); if (can-replace-in-frame?(target)) apply(replace-all-in-frame, target, search-string, replace-string, keys) else 0 end end method search-domain-replace-all; define method search-domain-replace-all (domain :: , target == #"current-frame", search-string :: , replace-string :: , #rest keys, #key from-selection? :: , backwards? :: , wrap? :: , match-case? :: , match-word? :: , match-regexp? :: , progress-callback :: false-or()) => (replace-count :: ) ignore(target); let frame = current-search-target-frame(); if (frame) apply(search-domain-replace-all, domain, frame, search-string, replace-string, keys) else 0 end end method search-domain-replace-all; define method search-domain-reveal-search-object (domain :: , object :: ) => (revealed? :: ) let frame = object.head; when (frame & frame.frame-state ~= #"destroyed") call-in-frame-synchronously(frame, method () => () deiconify-frame(frame); raise-frame(frame); end); frame-reveal-search-object(frame, object.tail) end end method search-domain-reveal-search-object; define method search-domain-search-object-label (domain :: , object :: ) => (label :: false-or()) let frame = first(object); when (frame & frame.frame-state ~= #"destroyed") let target-object = second(object); frame-search-object-label(frame, target-object) end end method search-domain-search-object-label; define method search-domain-search-object-icon (domain :: , object :: ) => (icon :: false-or()) let frame = first(object); when (frame & frame.frame-state ~= #"destroyed") let target-object = second(object); frame-search-object-icon(frame, target-object) end end method search-domain-search-object-icon; /// The set of registered search domains define constant $search-domains :: = make(, size: 1, fill: $current-frame-search-domain); define method do-search-domains (function :: , #key test :: false-or()) => (domain :: false-or()) block (return) for (domain :: in $search-domains) if (~test | test(domain)) function(domain) end end end end method do-search-domains; define method register-search-domain (domain :: ) => () add-new!($search-domains, domain); note-search-domain-registered(domain); end method register-search-domain; define method unregister-search-domain (domain :: ) => () remove!($search-domains, domain); note-search-domain-unregistered(domain); end method unregister-search-domain; // Update search frame UI define function note-search-domains-changed () => () let search-frame = current-search-options-frame(); //---*** cpage: 1998.08.24 The set of functions for updating gadgets and // search state are a bit convoluted. These names don't // quite jive with how we're using them here. We really need // to reorganize the set of functions and their names. // search-frame & note-search-frame-description-changed(search-frame); search-frame & call-in-frame(search-frame, note-search-frame-current-search-target-frame-changed, search-frame); end function note-search-domains-changed; // Update search frame UI to show new domain define function note-search-domain-registered (domain :: ) => () note-search-domains-changed(); end function note-search-domain-registered; // Select default domain if unregistering the current domain define function note-search-domain-unregistered (domain :: ) => () let description = *current-search-description*; when (domain == description.search-description-domain) let new-domain = $current-frame-search-domain; description.search-description-domain := new-domain; description.search-description-targets := search-domain-targets(new-domain); end; note-search-domains-changed(); end function note-search-domain-unregistered; //--- cpage: 1998.07.30 This domain is no longer needed since the // introduction of other, more specific, domains. Keep // this code around, though. In particular, it may be // desirable to let other domains call on this one to // handle some of the generic behavior of searching // through frames. /* define constant $all-frames-search-domain = make(); register-search-domain($all-frames-search-domain); */ /// Search Descriptions define class () slot search-description-domain :: , required-init-keyword: domain:; slot search-description-targets :: , required-init-keyword: targets:; slot search-description-search-string :: = "", init-keyword: search-string:; slot search-description-replace-string :: = "", init-keyword: replace-string:; slot search-description-batch? :: = #f, init-keyword: batch?:; slot search-description-wrap? :: = #f, init-keyword: wrap?:; slot search-description-boundaries? :: = #f, init-keyword: boundaries?:; slot search-description-match-case? :: = #f, init-keyword: match-case?:; slot search-description-match-word? :: = #f, init-keyword: match-word?:; slot search-description-match-regexp? :: = #f, init-keyword: match-regexp?:; end class ; // Maybe this is just me being paranoid, but let's be sure to make // copies when getting/setting the search description from outside // this library. We don't want anyone inadvertently changing the // description behind our back. define method shallow-copy (description :: ) => (description :: ) make(, domain: description.search-description-domain, targets: description.search-description-targets, search-string: description.search-description-search-string, replace-string: description.search-description-replace-string, batch?: description.search-description-batch?, wrap?: description.search-description-wrap?, boundaries?: description.search-description-boundaries?, match-case?: description.search-description-match-case?, match-word?: description.search-description-match-word?, match-regexp?: description.search-description-match-regexp?) end method shallow-copy; define open generic frame-search-description (frame :: ) => (description); define open generic frame-search-description-setter (description, frame :: ) => (description); // Global Search Description // This is the global search state. There is only one per application. //--- cpage: 1997.09.29 Eventually, we may add support for persistently // storing the search state. define variable *current-search-description* :: = make(, domain: $current-frame-search-domain, targets: search-domain-targets($current-frame-search-domain)); define variable *current-search-target-from-selection?* :: = #f; define variable *current-search-target-index* :: false-or() = #f; define function current-search-description () => (description :: ) shallow-copy(*current-search-description*) end function current-search-description; define function current-search-description-setter (description :: ) => (description :: ) *current-search-description* := shallow-copy(description); let search-frame = current-search-options-frame(); search-frame & note-search-frame-description-changed(search-frame); description end function current-search-description-setter; define method current-search-target-from-selection? () => (from-selection? :: ) *current-search-target-from-selection?* end method current-search-target-from-selection?; define method current-search-target-from-selection?-setter (from-selection? :: ) => (from-selection? :: ) //debug-message("*current-search-target-from-selection?* := %=, was = %=", // from-selection?, *current-search-target-from-selection?*); *current-search-target-from-selection?* := from-selection?; from-selection? end method current-search-target-from-selection?-setter; define method current-search-target-index () => (target-index :: false-or()) *current-search-target-index* end method current-search-target-index; define method current-search-target-index-setter (target-index :: false-or()) => (target-index :: false-or()) //---*** cpage: 1998.07.23 I think perhaps we shouldn't touch // *current-search-target-from-selection* here at all. // We should probably just change it in those places // where it needs to be changed. // Reset from-selection? only when the target is reset or is changing, // so that setting the target to the current target continues // searching from the selection. when ((target-index == #f) | (target-index ~== *current-search-target-index*)) current-search-target-from-selection?() := #f; end; *current-search-target-index* := target-index; let search-frame = current-search-options-frame(); search-frame & note-search-frame-status-changed(search-frame); target-index end method current-search-target-index-setter; // A utility function to reset the target index define function reset-current-search-target-index (domain :: ) => () // Reset to 0 for "Current Window", so that we continue searching // from the selection in the current target, else reset to #f for // all other search domains to start searching from the start of // the first target. current-search-target-index() := when (domain == $current-frame-search-domain) 0 end; end function reset-current-search-target-index; /// Search history // Maximum number of search/replace strings stored. define constant $max-search-history = 10; define variable *previous-search-strings* :: = make(); define variable *previous-replace-strings* :: = make(); define method record-search-string (string :: ) => () // Record a copy of the string, not the original string := shallow-copy(string); // If the element is already in the deque, 'move' it to the front remove!(*previous-search-strings*, string, test: \=); push(*previous-search-strings*, string); while (size(*previous-search-strings*) > $max-search-history) pop-last(*previous-search-strings*); end; let search-frame = current-search-options-frame(); when (search-frame) search-frame.search-frame-previous-search-strings := *previous-search-strings*; end; end method record-search-string; define method record-replace-string (string :: ) => () // Record a copy of the string, not the original string := shallow-copy(string); // If the element is already in the deque, 'move' it to the front remove!(*previous-replace-strings*, string, test: \=); push(*previous-replace-strings*, string); while (size(*previous-replace-strings*) > $max-search-history) pop-last(*previous-replace-strings*); end; // Update the UI let search-frame = current-search-options-frame(); when (search-frame) search-frame.search-frame-previous-replace-strings := *previous-replace-strings*; end; end method record-replace-string; define function previous-search-strings () => (strings :: ) // Copy the ; we mutate it shallow-copy(*previous-search-strings*) end function previous-search-strings; define function previous-search-strings-setter (strings :: ) => (strings :: ) // Make sure the strings are in a *previous-search-strings* := make(); for (i from 0 below $max-search-history) *previous-search-strings*[i] := strings[i]; end; // Update the UI let search-frame = current-search-options-frame(); when (search-frame) search-frame.search-frame-previous-search-strings := *previous-search-strings*; end; strings end function previous-search-strings-setter; define function previous-replace-strings () => (strings :: ) // Copy the ; we mutate it shallow-copy(*previous-replace-strings*) end function previous-replace-strings; define function previous-replace-strings-setter (strings :: ) => (strings :: ) // Make sure the strings are in a *previous-replace-strings* := make(); for (i from 0 below $max-search-history) *previous-replace-strings*[i] := strings[i]; end; // Update the UI let search-frame = current-search-options-frame(); when (search-frame) search-frame.search-frame-previous-replace-strings := *previous-replace-strings*; end; strings end function previous-replace-strings-setter; /// : Frames that can issue search commands define open abstract class () end class ; define method initialize (frame :: , #key) => () next-method(); note-frame-searching-updated(frame); end method initialize; define method frame-search-description (frame :: ) => (description :: ) *current-search-description* end method frame-search-description; define method frame-search-description-setter (description :: , frame :: ) => (description :: ) *current-search-description* := description; let search-frame = current-search-options-frame(); search-frame & note-search-frame-description-changed(search-frame); description end method frame-search-description-setter; define method note-frame-searching-updated (frame :: ) => () let has-selection? = ~frame-selection-empty?(frame); let can-find? = frame-can-find?(frame); let can-replace? = frame-can-replace?(frame); let description = frame.frame-search-description; let targets = description.search-description-targets; let target-count = size(targets); let wrap? = description.search-description-wrap?; let target-index = current-search-target-index(); let can-find-in-next-target? = can-find? & (wrap? | ~target-index | target-index < target-count - 1); let can-find-in-previous-target? = can-find? & (wrap? | ~target-index | target-index > 0); command-enabled?(, frame) := can-find?; command-enabled?(, frame) := can-find?; command-enabled?(, frame) := can-find-in-next-target?; command-enabled?(, frame) := can-find-in-previous-target?; command-enabled?(, frame) := has-selection?; command-enabled?(, frame) := has-selection?; command-enabled?(, frame) := has-selection? & can-find?; command-enabled?(, frame) := has-selection? & can-find?; command-enabled?(, frame) := can-replace?; command-enabled?(, frame) := can-replace?; command-enabled?(, frame) := can-replace?; command-enabled?(, frame) := can-replace?; end method note-frame-searching-updated; define method note-frame-selection-updated (frame :: ) => () next-method(); note-frame-searching-updated(frame); end method note-frame-selection-updated; define method frame-can-find? (frame :: ) => (can-find? :: ) let description = frame.frame-search-description; let search-string = description.search-description-search-string; // The search string isn't empty, and at least one target can find? size(search-string) > 0 & block (return) let domain = description.search-description-domain; let targets = description.search-description-targets; for (target in targets) when (search-domain-target-can-find?(domain, target)) return(#t); end when; end for; #f end block end method frame-can-find?; define method frame-can-replace? (frame :: ) => (can-replace? :: ) let description = frame.frame-search-description; let search-string = description.search-description-search-string; let batch? = description.search-description-batch?; // The search string isn't empty, and at least one target can replace? size(search-string) > 0 & ~batch? & block (return) let domain = description.search-description-domain; let targets = description.search-description-targets; for (target in targets) when (search-domain-target-can-replace?(domain, target)) return(#t); end when; end for; #f end block end method frame-can-replace?; // Search failure status messages define constant = one-of(#"not-found", #"stopped-between-targets", #"all-targets-searched", #"cannot-replace", #"cannot-replace/no-match", #"cannot-replace/read-only", #"replace-all-completed"); define table $search-result-message-table = { #"not-found" => "Not Found", #"stopped-between-targets" => "Stopped between targets", #"all-targets-searched" => "All targets searched", #"cannot-replace" => "Cannot replace selection", // #"cannot-replace" is a catch-all for when there is no further information #"cannot-replace/no-match" => "Selection not a match", #"cannot-replace/read-only" => "Selection is read-only", #"replace-all-completed" => "Replaced all matches" }; // Notify the user about the result of a search command define function frame-notify-search-message (frame :: , message :: false-or()) => () let status-message = frame-search-status-message(frame); let message-string = element($search-result-message-table, message, default: ""); let message-text = if (size(status-message) > 0) concatenate(status-message, " - ", message-string) else message-string end; //---*** cpage: 1998.04.17 Here's some code that will display "Not Found" in // the status bar of both the search target frame and the Find // window. However, the message doesn't go away. I think what // we really need is a DUIM facility for displaying temporary // messages in the status bar, as when selecting a menu item, // but for a timed period, after which it reverts to the // original message. let search-frame = current-search-options-frame(); when (search-frame ~== #f) frame-status-message(search-frame) := message-text; end; //---*** cpage: 1998.08.20 Since we can't get this message to go away, // just eliminate it for now. It's more of a distraction // than it's worth. /* when (frame ~== search-frame) frame-status-message(frame) := message-text; end; */ beep(frame); end function frame-notify-search-message; define function do-frame-find (frame :: , #key backwards? :: ) => () let description = frame.frame-search-description; let domain = description.search-description-domain; let targets = description.search-description-targets; let target-count = size(targets); let search-string = description.search-description-search-string; let wrap? = description.search-description-wrap?; let target-wrap? = wrap? & (target-count = 1); let boundaries? = description.search-description-boundaries?; let match-case? = description.search-description-match-case?; let match-word? = description.search-description-match-word?; let match-regexp? = description.search-description-match-regexp?; let target-step = if (backwards?) -1 else 1 end; let target-index :: = current-search-target-index() | if (backwards?) target-count - 1 else 0 end; record-search-string(search-string); //--- cpage: 1997.10.14 Need to add support for progress callback. let (object, message :: false-or()) = block (return) // Remember where we started let start-target-index :: = target-index; let wrapped? :: = #f; while (#t) while ((target-index >= 0) & (target-index < target-count)) current-search-target-index() := target-index; let object = search-domain-find(domain, targets[target-index], search-string, from-selection?: current-search-target-from-selection?(), backwards?: backwards?, wrap?: target-wrap?, match-case?: match-case?, match-word?: match-word?, match-regexp?: match-regexp?); // Stop searching if... when (object // ...match found | boundaries?) // ...end of target and boundaries in effect // Continue searching from the selection current-search-target-from-selection?() := #t; // Calculate the reason for stopping let message = when (boundaries?) if ((backwards? & (target-index > 0)) | (~backwards? & (target-index < target-count - 1))) #"stopped-between-targets" else #"all-targets-searched" end if end when; return(object, message); end when; when (wrapped? // ...all targets searched & (target-index = start-target-index)) //---*** cpage: 1998.08.11 Do we want to reset when we've wrapped? //reset-current-search-target-index(domain); return(#f, #"all-targets-searched"); end when; target-index := target-index + target-step; end while; unless (wrap?) reset-current-search-target-index(domain); return(#f, if (target-count > 1) #"all-targets-searched" else #"not-found" end); end unless; target-index := if (backwards?) target-count - 1 else 0 end; // Continue searching from the start current-search-target-from-selection?() := #f; wrapped? := #t; end while; end block; // Notify the user if the search failed unless (object & search-domain-reveal-search-object(domain, object)) frame-notify-search-message(frame, message); end; end function do-frame-find; /*--- cpage: 1998.02.06 TBD: This is going to be "batch searching" define function do-frame-find-all (frame :: ) => () // TBD end function do-frame-find-all; */ // Replace selection; always operates on "Current Window" define function do-frame-replace-selection (frame :: ) => () let description = frame.frame-search-description; let search-string = description.search-description-search-string; let replace-string = description.search-description-replace-string; let match-case? = description.search-description-match-case?; let match-word? = description.search-description-match-word?; let match-regexp? = description.search-description-match-regexp?; record-search-string(search-string); record-replace-string(replace-string); if (search-domain-target-can-replace?($current-frame-search-domain, #"current-frame")) //--- cpage: 1997.10.14 Need to add support for progress callback. let replacement-object = search-domain-replace-selection($current-frame-search-domain, #"current-frame", search-string, replace-string, match-case?: match-case?, match-word?: match-word?, match-regexp?: match-regexp?); if (replacement-object) search-domain-reveal-search-object($current-frame-search-domain, replacement-object); else frame-notify-search-message(frame, #"cannot-replace/no-match"); end; else frame-notify-search-message(frame, #"cannot-replace/read-only"); end if; end function do-frame-replace-selection; define function do-frame-replace-all (frame :: , #key backwards? :: ) => () let description = frame.frame-search-description; let domain = description.search-description-domain; let targets = description.search-description-targets; let target-count = size(targets); let search-string = description.search-description-search-string; let replace-string = description.search-description-replace-string; let wrap? = description.search-description-wrap?; let target-wrap? = wrap? & (target-count = 1); let boundaries? = description.search-description-boundaries?; let match-case? = description.search-description-match-case?; let match-word? = description.search-description-match-word?; let match-regexp? = description.search-description-match-regexp?; let target-step = if (backwards?) -1 else 1 end; let target-index :: = current-search-target-index() | if (backwards?) target-count - 1 else 0 end; record-search-string(search-string); record-replace-string(replace-string); //--- cpage: 1997.10.14 Need to add support for progress callback. let message :: false-or() = block (return) // Remember where we started let start-target-index :: = target-index; let wrapped? :: = #f; while (#t) while ((target-index >= 0) & (target-index < target-count)) current-search-target-index() := target-index; let from-selection? = current-search-target-from-selection?(); //---*** cpage: 1998.09.17 Prevent infinite recursion when wrap? is #t by // stopping before we return to the target where we started // (unlike searching, which will wrap into the starting target). // Also, always replace from the start if wrap? is #t. // // Ideally, we should add a protocol for getting some kind // of token representing the current selection, then we'd be able to // wrap to that point. Note that a simple fixed index or offset // wouldn't be adequate because replacements may cause the position // to move. Of course, we could just not allow wrapping with // "Replace All", which is what some other environments do, but I // think it's better to keep all search/replace operations as // uniform as possible. when (wrapped? // ...all targets searched & (target-index = start-target-index)) //---*** cpage: 1998.08.11 Do we want to reset when we've wrapped? //reset-current-search-target-index(domain); return(#f, #"all-targets-searched"); end when; when (wrap?) from-selection? := #f; target-wrap? := #f; end; let replace-count = search-domain-replace-all(domain, targets[target-index], search-string, replace-string, from-selection?: from-selection?, backwards?: backwards?, wrap?: target-wrap?, match-case?: match-case?, match-word?: match-word?, match-regexp?: match-regexp?); // Stop replacing if... when ((replace-count > 1) & boundaries?) // ...replacements made and boundaries in effect // Continue searching from the selection current-search-target-from-selection?() := #t; // Calculate the reason for stopping let message = if ((backwards? & (target-index > 0)) | (~backwards? & (target-index < target-count - 1))) #"stopped-between-targets" else #"replace-all-completed" end if; return(message); end when; when (wrapped? // ...all targets searched & (target-index = start-target-index)) //---*** cpage: 1998.08.11 Do we want to reset when we've wrapped? //reset-current-search-target-index(domain); return(#"replace-all-completed"); end when; target-index := target-index + target-step; end while; unless (wrap?) reset-current-search-target-index(domain); return(#"replace-all-completed"); end unless; target-index := if (backwards?) target-count - 1 else 0 end; // Continue searching from the start current-search-target-from-selection?() := #f; wrapped? := #t; end while; end block; // Notify the user that the operation has finished frame-notify-search-message(frame, message); end function do-frame-replace-all; define method frame-find-next (frame :: ) => () do-frame-find(frame); end method frame-find-next; define method frame-find-previous (frame :: ) => () do-frame-find(frame, backwards?: #t); end method frame-find-previous; define method frame-find-in-next-target (frame :: ) => () let description = frame.frame-search-description; let targets = description.search-description-targets; let wrap? = description.search-description-wrap?; let target-index = current-search-target-index(); if (target-index) target-index := target-index + 1; else target-index := 0; end; let in-range? = target-index < size(targets); when (in-range? | wrap?) current-search-target-index() := if (in-range?) target-index else 0 end; current-search-target-from-selection?() := #f; do-frame-find(frame); end when; end method frame-find-in-next-target; define method frame-find-in-previous-target (frame :: ) => () let description = frame.frame-search-description; let targets = description.search-description-targets; let wrap? = description.search-description-wrap?; let target-index = current-search-target-index(); if (target-index) target-index := target-index - 1; else target-index := size(targets) - 1; end; let in-range? = target-index >= 0; when (in-range? | wrap?) current-search-target-index() := if (in-range?) target-index else size(targets) - 1 end; current-search-target-from-selection?() := #f; do-frame-find(frame, backwards?: #t); end when; end method frame-find-in-previous-target; define method frame-copy-selection-to-search (frame :: ) => () let string = frame-selected-text(frame); when (string) frame.frame-search-description.search-description-search-string := string; let search-frame = current-search-options-frame(); search-frame & note-search-frame-description-changed(search-frame); end end method frame-copy-selection-to-search; define method frame-copy-selection-to-replace (frame :: ) => () let string = frame-selected-text(frame); when (string) frame.frame-search-description.search-description-replace-string := string; let search-frame = current-search-options-frame(); search-frame & note-search-frame-description-changed(search-frame); end end method frame-copy-selection-to-replace; define method frame-find-selection-next (frame :: ) => () frame-copy-selection-to-search(frame); frame-find-next(frame); end method frame-find-selection-next; define method frame-find-selection-previous (frame :: ) => () frame-copy-selection-to-search(frame); frame-find-previous(frame); end method frame-find-selection-previous; define method frame-replace-selection (frame :: ) => () do-frame-replace-selection(frame); end method frame-replace-selection; define method frame-replace-and-find-next (frame :: ) => () do-frame-replace-selection(frame); do-frame-find(frame); end method frame-replace-and-find-next; define method frame-replace-and-find-previous (frame :: ) => () do-frame-replace-selection(frame); do-frame-find(frame, backwards?: #t); end method frame-replace-and-find-previous; define method frame-replace-all (frame :: ) => () do-frame-replace-all(frame); end method frame-replace-all; /*---*** Not currently used define method frame-replace-all-previous (frame :: ) => () do-frame-replace-all(frame, backwards?: #t); end method frame-replace-all-previous; */ /// : The "Find" window define constant $find-title = "Find/Replace"; define constant $find-options-title = "Find/Replace..."; define constant $find-next-title = "Find Next"; define constant $find-previous-title = "Find Previous"; define constant $find-in-next-target-title = "Find in Next Target"; define constant $find-in-previous-target-title = "Find in Previous Target"; define constant $enter-find-string-title = "Enter 'Find' String"; define constant $enter-replace-string-title = "Enter 'Replace' String"; define constant $find-selection-next-title = "Find Selection"; define constant $find-selection-previous-title = "Find Selection Previous"; define constant $replace-title = "Replace"; define constant $replace-and-find-next-title = "Replace && Find Next"; define constant $replace-and-find-previous-title = "Replace && Find Previous"; define constant $replace-all-title = "Replace All"; //--- hughg, 1998/09/11: Will need to be called via call-in-frame if //--- it's ever used other than during the initialize method for //--- , as there might be a race-condition between threads. define generic note-search-frame-items-changed (frame :: ) => (); //--- hughg, 1998/09/11: Normally called via call-in-frame. define generic note-search-frame-current-search-target-frame-changed (frame :: ) => (); define generic note-search-frame-gadget-values-changed (frame :: ) => (); define generic note-search-frame-description-changed (frame :: ) => (); define generic note-search-frame-status-changed (frame :: ) => (); define method search-frame-gadget-value-changed-callback (gadget) => () let frame = sheet-frame(gadget); note-search-frame-gadget-values-changed(frame); end method search-frame-gadget-value-changed-callback; define open frame (, , ) keyword title: = "Find/Replace"; keyword width: = 400; keyword fixed-height?: = #t; keyword reusable?: = #t; slot search-frame-previous-search-strings :: = #[], setter: %search-frame-previous-search-strings-setter, init-keyword: previous-searches:; slot search-frame-previous-replace-strings :: = #[], setter: %search-frame-previous-replace-strings-setter, init-keyword: previous-replaces:; sealed slot %search-backwards? :: = #f; pane %find-button (frame) make(