Module: environment-tools Synopsis: Environment tools Author: Andy Armstrong, Chris Page, Jason Trenouth 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 /// General protocols for environment frames define open generic environment-frame-class-name (frame :: ) => (name :: false-or()); define method environment-frame-class-name (frame :: ) => (name :: false-or()) #f end method environment-frame-class-name; /// Environment frame define open abstract class () sealed constant each-subclass slot environment-frame-class-name :: false-or() = #f, init-keyword: frame-class-name:; sealed slot frame-qualify-names? :: = environment-qualify-names?(), setter: %qualify-names?-setter, init-keyword: qualify-names?:; sealed slot frame-popup-menu-cache :: = make(); slot frame-command-target :: false-or() = #f, setter: %command-target-setter; end class ; define open abstract class (, ) end class ; define open abstract class (, ) end class ; define method initialize (frame :: , #key) => () next-method(); frame.frame-title := generate-frame-title(frame); // Update command enabling command-enabled?(undo-minimize-all-frames, frame) := ~empty?(*minimized-frames*); end method initialize; define method frame-qualify-names?-setter (qualify? :: , frame :: ) => (qualify? :: ) frame.%qualify-names? := qualify?; refresh-frame(frame); qualify? end method frame-qualify-names?-setter; define method frame-primary-object-name (frame :: , object :: ) => (name :: ) frame-default-object-name(frame, object) end method frame-primary-object-name; /// Environment frame protocols define open generic generate-frame-title (frame :: ) => (title :: ); define open generic frame-current-project (frame :: ) => (project :: false-or()); define method generate-frame-title (frame :: ) => (title :: ) release-product-name() end method generate-frame-title; define method frame-current-project (frame :: ) => (project :: singleton(#f)) #f; end method frame-current-project; define function ensure-frame-project (frame :: ) => (project :: ) let project = frame.frame-current-project; assert(project, "Frame '%s' has no project", frame-title(frame)); project end function ensure-frame-project; define method frame-command-target-setter (target :: false-or(), frame :: ) => (target :: false-or()) frame.%command-target := target; note-frame-command-target-updated(frame); target end method frame-command-target-setter; define method note-frame-command-target-updated (frame :: ) => () #f end method note-frame-command-target-updated; /// Notification protocols define open generic frame-note-application-state-changed (frame :: , state :: false-or()) => (); define open generic frame-note-application-starting (frame :: ) => (); // Note: This function is called when we've finished trying to start the // application; that is, either the application is about to "really" start // (all initialization, breakpoint setup etc. is done) or we failed to // start it at all. It started if // frame.frame-project.project-application.application-closed? // is false. define open generic frame-note-application-starting-done (frame :: ) => (); define open generic frame-note-application-threads-changed (frame :: ) => (); define method frame-note-application-state-changed (frame :: , state :: false-or()) => () enable-application-command-table(frame, state); end method frame-note-application-state-changed; define method frame-note-application-starting (frame :: ) => () //---*** This doesn't deal well with multiple background operations frame-cursor-override(frame) := #"starting" end method frame-note-application-starting; define method frame-note-application-starting-done (frame :: ) => () //---*** This doesn't deal well with multiple background operations frame-cursor-override(frame) := #f end method frame-note-application-starting-done; define method frame-note-application-threads-changed (frame :: ) => () // I don't think we need to do anything in the general case. end method frame-note-application-threads-changed; define open generic frame-note-breakpoint-state-changed (frame :: , breakpoint :: , state :: ) => (); define method frame-note-breakpoint-state-changed (frame :: , breakpoint :: , state :: ) => () #f end method frame-note-breakpoint-state-changed; define open generic frame-note-interaction-returned (frame :: , thread :: , id :: ) => (); define open generic frame-note-interactive-compilation-warnings (frame :: , thread :: , id :: , warnings :: ) => (); define method frame-note-interaction-returned (frame :: , thread :: , id :: ) => () #f end method frame-note-interaction-returned; define method frame-note-interactive-compilation-warnings (frame :: , thread :: , id :: , warnings :: ) => () #f end method frame-note-interactive-compilation-warnings; define open generic frame-note-project-changed (frame :: , project :: false-or()) => (); define method frame-note-project-changed (frame :: , project :: false-or()) => () // Do nothing. end method frame-note-project-changed; /// Application message handlers define function environment-tools-application-message-receiver (message :: ) => () let project = message.message-project; let _default-port = default-port(); select (message by instance?) => do-frames (method (frame :: ) when (instance?(frame, )) call-in-frame(frame, frame-note-application-starting, frame); end; end method, port: _default-port); => do-frames (method (frame :: ) when (instance?(frame, )) call-in-frame(frame, frame-note-application-starting-done, frame); end; end method, port: _default-port); => do-frames (method (frame :: ) when (instance?(frame, )) call-in-frame(frame, frame-note-application-starting-done, frame); end; end method, port: _default-port); let application = project.project-application; with-environment-frame (frame = _default-port, , project: project) let project-name = environment-object-display-name(project, project, #f); let message = format-to-string("Could not execute the file '%s' to start project '%s'.", application.application-filename, project-name); environment-error-message(message, owner: frame); end; => let application = project.project-application; if (application) let state = application.application-state; do-project-frames (method (frame :: ) call-in-frame(frame, frame-note-application-state-changed, frame, state) end method, project, include: ) end; => do-project-frames (method (frame :: ) call-in-frame(frame, frame-note-application-threads-changed, frame) end method, project, include: ); => let thread = message.message-thread; let id = message.message-transaction-id; let warnings = message.message-warnings; do-project-frames (method (frame) call-in-frame(frame, frame-note-interactive-compilation-warnings, frame, thread, id, warnings) end method, project); otherwise => #f; end end function environment-tools-application-message-receiver; //---*** Should we do this at top-level, or in some initialisation function? tune-in($project-channel, environment-tools-application-message-receiver, message-type: ); define function environment-tools-breakpoint-message-receiver (message :: ) => () let project = message.message-project; let state = message.message-breakpoint-state; select (message by instance?) => let breakpoint = message.message-breakpoint; do-project-frames (method (frame :: ) call-in-frame(frame, frame-note-breakpoint-state-changed, frame, breakpoint, state) end, project, include: ); => do-project-frames (method (frame :: ) call-in-frame(frame, frame-note-all-breakpoints-changed, frame, state) end, project); => let breakpoints = message.message-breakpoints; notify-user-breakpoint-state-changes-failed (project, state, breakpoints); end; end function environment-tools-breakpoint-message-receiver; //---*** Should we do this at top-level, or in some initialisation function? tune-in($project-channel, environment-tools-breakpoint-message-receiver, message-type: ); define open generic frame-note-all-breakpoints-changed (frame :: , state :: ) => (); define method frame-note-all-breakpoints-changed (frame :: , state :: ) => () #f end method frame-note-all-breakpoints-changed; /// Frame operation handling ///--- Should this be in DUIM? define macro with-frame-background-operation { with-frame-background-operation (?frame:name, ?name:expression) ?body:body end } => { call-in-frame(?frame, method() frame-status-message(?frame) := ?name end); make(, name: ?name, function: method () with-background-cursor (?frame) ?body end end) } end macro with-frame-background-operation; /// Open define open generic frame-open-object (frame :: , object :: ) => (); define method frame-open-object (frame :: , object :: ) => () environment-action-unavailable(frame, "No open action available") end method frame-open-object; define method frame-open-object (frame :: , project :: ) => () frame-open-project(frame, project) end method frame-open-object; /// Locator type handling ///--- Probably should use MIME types, somehow define method frame-open-by-default? (frame :: , locator :: ) => (default? :: ) select (locator.environment-locator-type) #"dylan", #"html" => #t; otherwise => #f; end end method frame-open-by-default?; define method locator-has-source? (locator :: ) => (source? :: ) select (locator.environment-locator-type) #f, #"lid", #"dylan", #"dyl", #"spec" => #t; #"c", #"cpp", #"cxx", #"h", #"hpp", #"hxx" => #t; #"inl", #"rc", #"txt", #"text", #"html", #"idl" => #t; otherwise => #f; end end method locator-has-source?; /// Browsing define open generic frame-browse-object (frame :: , object :: ) => (); define open generic frame-browse-object? (frame :: , object :: ) => (browse? :: ); define open generic frame-browse-object-type (frame :: , object :: ) => (); define open generic frame-browse-object-generic-function (frame :: , object :: ) => (); define open generic frame-describe-object (frame :: , object :: ) => (); define open generic frame-describe-object? (frame :: , object :: ) => (describe? :: ); define open generic frame-document-object (frame :: , object :: ) => (); define open generic frame-document-object? (frame :: , object :: ) => (document? :: ); // Default method define method frame-browse-object (frame :: , object :: ) => () environment-action-unavailable (frame, format-to-string("No browser information for '%s'.", frame-default-object-name(frame, object))) end method frame-browse-object; define method frame-browse-object (frame :: , object :: false-or()) => () // This command should only be enabled when there is a project. let project :: = frame.frame-current-project; if (frame-browse-object?(frame, object)) with-current-environment-frame(frame) browse-object(project, object) end else next-method() end end method frame-browse-object; // Default method define method frame-browse-object-type (frame :: , object :: ) => () environment-action-unavailable (frame, format-to-string("No type to browse for '%s'.", frame-default-object-name(frame, object))) end method frame-browse-object-type; define method frame-browse-object-type (frame :: , object :: ) => () // This command should only be enabled when there is a project. let project :: = frame.frame-current-project; with-current-environment-frame (frame) browse-object-type(project, object) end | next-method() end method frame-browse-object-type; // Default method define method frame-browse-object-generic-function (frame :: , object :: ) => () environment-action-unavailable (frame, format-to-string("No generic function to browse for '%s'.", frame-default-object-name(frame, object))) end method frame-browse-object-generic-function; define method frame-browse-object-generic-function (frame :: , object :: type-union(, )) => () // This command should only be enabled when there is a project. let project :: = frame.frame-current-project; with-current-environment-frame (frame) browse-object-generic-function(project, object) end | next-method() end method frame-browse-object-generic-function; /// Describe define method frame-describe-object? (frame :: , object :: ) => (describe? :: ) #f end method frame-describe-object?; define method frame-describe-object? (frame :: , object :: ) => (describe? :: ) #t end method frame-describe-object?; define method frame-describe-object? (frame :: , object :: ) => (describe? :: ) let project = frame.frame-current-project; project & application-object-class(project, object) ~== #f end method frame-describe-object?; define method frame-describe-object? (frame :: , object :: ) => (describe? :: ) #t end method frame-describe-object?; define method frame-describe-object (frame :: , object :: ) => () environment-action-unavailable (frame, format-to-string("Cannot describe '%s'.", frame-default-object-name(frame, object))) end method frame-describe-object; define method frame-describe-object (frame :: , object :: ) => () let project = frame.frame-current-project; let module = frame.frame-current-module; let name = frame-default-object-name(frame, object); describe-object(name, project, module, object) end method frame-describe-object; /// Documentation define method frame-document-object? (frame :: , object :: ) => (document? :: ) #f end method frame-document-object?; define method frame-document-object? (frame :: , object :: ) => (document? :: ) #t end method frame-document-object?; define method frame-document-object (frame :: , object :: ) => () environment-action-unavailable (frame, format-to-string("Cannot show documentation for '%s'.", frame-default-object-name(frame, object))) end method frame-document-object; define method frame-document-object (frame :: , object :: ) => () let project = frame.frame-current-project; let module = frame.frame-current-module; let name = frame-default-object-name(frame, object); show-documentation(name, project, module, object) end method frame-document-object; /// frame-target-object define method frame-target-object (frame :: , name :: ) => (object :: false-or()) let project = frame-current-project(frame); project & name-value(project, name) end method frame-target-object; define method frame-target-object (frame :: , slot :: ) => (object :: false-or()) let project = frame-current-project(frame); project & slot-getter(project, slot) end method frame-target-object; define method frame-target-object (frame :: , variable :: ) => (result :: false-or()) let project = frame-current-project(frame); project & variable-value(project, variable) end method frame-target-object; define method frame-target-object (frame :: , breakpoint :: ) => (result :: false-or()) breakpoint-object(breakpoint) end method frame-target-object; /// Wrapper objects define open abstract class () constant slot wrapper-object :: , required-init-keyword: object:; end class ; define open abstract class () constant slot wrapper-project :: , required-init-keyword: project:; end class ; define method command-table-for-target (frame :: , wrapper :: ) => (comtab :: ) command-table-for-target(frame, wrapper.wrapper-object) end method command-table-for-target; define method default-command-for-target (frame :: , wrapper :: ) => (command :: false-or()) default-command-for-target(frame, wrapper.wrapper-object) end method default-command-for-target; define method frame-target-object (frame :: , wrapper :: ) => (object :: ) frame-target-object(frame, wrapper.wrapper-object) end method frame-target-object; define method environment-object-small-icon (project :: , wrapper :: ) environment-object-small-icon(project, wrapper.wrapper-object) end method environment-object-small-icon; define method environment-object-large-icon (project :: , wrapper :: ) environment-object-large-icon(project, wrapper.wrapper-object) end method environment-object-large-icon; // If this is a performance bottleneck, define \= methods on each subclass define method \= (wrapper1 :: , wrapper2 :: ) => (equal? :: ) wrapper1.wrapper-object = wrapper2.wrapper-object end method \=; /// Object and location wrappers define open abstract class () constant slot wrapper-source-location :: false-or(), required-init-keyword: source-location:; end class ; define method frame-target-edit-object (frame :: , wrapper :: ) => (object :: ) wrapper.wrapper-source-location end method frame-target-edit-object; /// Source wrappers define abstract class () end class ; // A wrapper define class () end class ; // A wrapper define class () end class ; // A wrapper define class () end class ; define generic wrapper-filename (wrapper :: ) => (location :: false-or()); define method wrapper-filename (wrapper :: ) => (location :: false-or()) let project = wrapper.wrapper-project; let record = wrapper.wrapper-object; project-file-location(project, record.source-record-location) end method wrapper-filename; define method wrapper-filename (wrapper :: ) => (location :: ) project-file-location(wrapper.wrapper-project, wrapper.wrapper-object) end method wrapper-filename; define method wrapper-filename (wrapper :: ) => (location :: false-or()) let project = wrapper.wrapper-object; project.project-filename end method wrapper-filename; define function project-file-location (project :: , file :: type-union(, )) => (location :: ) merge-locators(as(, file), project.project-directory) end function project-file-location; // Instantiation define sealed method make (class == , #rest keys, #key object) => (wrapper :: ) let concrete-class = select (object by instance?) => ; => ; => ; end select; apply(make, concrete-class, keys) end method make; define sealed domain make (singleton()); define sealed domain make (singleton()); define sealed domain make (singleton()); define sealed domain initialize (); define sealed domain initialize (); define sealed domain initialize (); /// frame-target-browse-object define method frame-target-browse-object (frame :: , stack-frame :: ) => (object :: false-or()) let project = frame-current-project(frame); project & stack-frame-environment-object(project, stack-frame) end method frame-target-browse-object; define method frame-target-browse-object (frame :: , warning :: ) => (object :: false-or()) let project = frame-current-project(frame); project & warning-owner(project, warning) end method frame-target-browse-object; /// String coercion define method target-as-string (sheet :: , object :: ) => (string :: ) select (object by instance?) => object; otherwise => target-sheet-item-label(sheet, object); end end method target-as-string; define method target-as-string (sheet :: , locator :: ) => (string :: ) as(, locator) end method target-as-string; define method target-as-string (sheet :: , target :: ) => (string :: ) let object = target.target-object; select (object by instance?) => let items = object.target-sequence; target-sheet-items-to-string(sheet, items); => as(, object.wrapper-filename); otherwise => let frame = sheet-frame(sheet); target-as-string(sheet, frame-target-object(frame, target)); end end method target-as-string; define method target-sheet-item-label (sheet :: , item :: ) => (label :: ) frame-default-object-name(sheet-frame(sheet), item) end method target-sheet-item-label; define method target-sheet-item-label (gadget :: , object) => (label :: ) block () gadget-item-label(gadget, object) exception () //--- This is disgusting, but sometimes there is //--- no applicable gadget-item-label function. next-method() end end method target-sheet-item-label; define method target-sheet-items-to-string (sheet :: , items :: ) => (string :: ) with-output-to-string (stream) for (item in items) let label = target-as-string(sheet, item); format(stream, "%s\n", label); end end end method target-sheet-items-to-string; define method target-sheet-items-to-string (gadget :: , items :: ) => (string :: ) with-output-to-string (stream) let columns = table-control-columns(gadget); for (item in items) for (column :: in columns, prefix = "" then "\t") let generator = table-column-generator(column); let subitem = generator(item); let label = gadget-item-label(gadget, subitem); format(stream, "%s%s", prefix, label) end; format(stream, "\n") end end end method target-sheet-items-to-string; /// Standard popup menu // Displays and "executes" a target-specific menu define sealed method display-environment-popup-menu (frame :: , target, #key x, y, #all-keys) => () ignore(x, y); // let DUIM place the menu appropriately do-display-environment-popup-menu(frame, top-level-sheet(frame), target) end method display-environment-popup-menu; define sealed method display-environment-popup-menu (sheet :: , target, #key x, y, #all-keys) => () ignore(x, y); // let DUIM place the menu appropriately do-display-environment-popup-menu(sheet-frame(sheet), sheet, target) end method display-environment-popup-menu; define sealed method display-environment-popup-menu (sheet :: , target, #key x, y, #all-keys) => () ignore(x, y); // let DUIM place the menu appropriately do-display-environment-popup-menu(sheet-frame(sheet), sheet, gadget-value-key(sheet)(target)) end method display-environment-popup-menu; define open generic make-environment-popup-menu (frame :: , sheet :: , target :: ) => (menu :: false-or()); define open generic update-environment-popup-menu (frame :: , menu :: , target :: ) => (); define open generic update-frame-commands-for-target (frame :: , object :: ) => (); define open generic update-frame-commands-for-browse-target (frame :: , object :: ) => (); // Implementations of this should pop up the appropriate menu, allow the user // to choose something, and then process the choice. The simplest way to do // this is to display the results from a 'make-menu-from-command-table-menu'. define open generic do-display-environment-popup-menu (frame :: , sheet :: , target, #key x, y) => (); define method do-display-environment-popup-menu (frame :: , sheet :: , object, #key x, y) => () let target = make-command-target(sheet, object); update-frame-commands-for-target(frame, target); let menu = make-environment-popup-menu(frame, sheet, target); if (menu) frame-command-target(frame) := target; update-environment-popup-menu(frame, menu, target); // Display the menu, which also happens to call any commands by means // of activate callbacks for the menu items. Cool, huh? display-menu(menu, x: x, y: y) end end method do-display-environment-popup-menu; define method frame-edit-object (frame :: , object :: ) => () let project = frame-current-project(frame); if (project & object-has-source?(project, object)) do-frame-edit-object(frame, object) else environment-action-unavailable (frame, format-to-string("No known source for '%s'.", frame-default-object-name(frame, object))) end end method frame-edit-object; define open generic frame-edit-objects (frame :: , objects :: , #key title :: false-or()) => (); define sideways method frame-edit-objects (frame :: , objects :: , #key title :: false-or()) => () let project = frame-current-project(frame); when (project) editor-edit-definitions(project, objects, title: title) end end method frame-edit-objects; define method object-exists? (server :: , object) => (exists? == #t) #t end method object-exists?; define method object-exists? (server :: , object :: ) => (exists? :: ) environment-object-exists?(server, object) end method object-exists?; define method object-has-source? (server :: , object) => (source? :: ) #f end method object-has-source?; define method object-has-source? (server :: , locator :: ) => (source? :: ) ignore(server); locator-has-source?(locator) end method object-has-source?; define method object-has-source? (server :: , object :: ) => (source? :: ) #t end method object-has-source?; define method object-has-source? (server :: , location :: ) => (source? :: ) let record = location.source-location-source-record; object-has-source?(server, record) end method object-has-source?; define method object-has-source? (server :: , object :: ) => (source? :: ) let location = environment-object-source-location(server, object); location & object-has-source?(server, location) end method object-has-source?; define method object-has-source? (server :: , object :: ) => (source? :: ) #f end method object-has-source?; define method object-has-source? (server :: , object :: ) => (found-definition? :: ) let location = object.breakpoint-object; object-has-source?(server, location) end method object-has-source?; define method object-has-source? (server :: , name :: ) => (found-definition? :: ) let value = name-value(server, name); value & object-has-source?(server, value) end method object-has-source?; define method do-frame-edit-object (frame :: , object) => (found-definition? :: ) // The 'object-has-source?' protocol should prevent us from ever getting // here, unless it's inconsistent with the implementation of do-frame- // edit-object, which would be a bug. But just in case ... environment-action-unavailable (frame, format-to-string("Don't know how to edit source for '%s'.", frame-default-object-name(frame, object))); #f end method do-frame-edit-object; define method do-frame-edit-object (frame :: , locator :: ) => (found-definition? :: ) if (file-exists?(locator)) editor-open-file(locator); #t end end method do-frame-edit-object; define method do-frame-edit-object (frame :: , object :: ) => (found-definition? :: ) let project = frame-current-project(frame); when (project) edit-source-record(project, object); #t end; end method do-frame-edit-object; define method do-frame-edit-object (frame :: , object :: ) => (found-definition? :: ) let project = frame-current-project(frame); block () when (project) edit-source-location(project, object); #t end; exception (type-union(, )) #f; end end method do-frame-edit-object; define method do-frame-edit-object (frame :: , object :: ) => (found-definition? :: ) let project = frame-current-project(frame); let location = project & environment-object-source-location(project, object); location & do-frame-edit-object(frame, location) end method do-frame-edit-object; define variable $edit-object-dialog-width :: false-or() = #f; define variable $edit-object-dialog-height :: false-or() = #f; define method do-frame-edit-object (frame :: , function :: ) => (found-definition? :: ) let project = frame-current-project(frame); when (project) let methods :: = make(); // Collect the generic function and all its methods add!(methods, function); do-generic-function-methods (method (object) add!(methods, object) end method, project, function); // Remove the generic function if it seems to be implicit when (any?(method (m) let l1 = environment-object-source-location(project, function); let l2 = environment-object-source-location(project, m); m ~== function & l1 & l2 & source-location-source-record(l1) = source-location-source-record(l2) & source-location-start-offset(l1) = source-location-start-offset(l2) end method, methods)) remove!(methods, function) end; // We need this helper method in order to avoid recursion when the // object is just the generic function again... local method edit-object (object :: ) => (found-definition? :: ) let location = project & environment-object-source-location(project, object); location & do-frame-edit-object(frame, location) end method; select (size(methods)) 0 => #f; 1 => edit-object(methods[0]); otherwise => let (methods, success?, width, height) = choose-from-dialog (methods, value: vector(methods[0]), label-key: curry(frame-default-object-name, frame), selection-mode: #"multiple", frame: frame, title: "Choose methods to edit", width: $edit-object-dialog-width, height: $edit-object-dialog-height); when (success? & methods & size(methods) > 0) $edit-object-dialog-width := width; $edit-object-dialog-height := height; if (size(methods) = 1) edit-object(methods[0]) else let title = print-environment-object-name-to-string(project, function); frame-edit-objects(frame, methods, title: format-to-string("Methods of %s", title)) end; #t; end; end end end method do-frame-edit-object; define method do-frame-edit-object (frame :: , name :: ) => (found-definition? :: ) let project = frame-current-project(frame); let value = project & name-value(project, name); value & do-frame-edit-object(frame, value) end method do-frame-edit-object; /// Clipboard handling define method dylan-clipboard-object (frame :: ) => (object) with-clipboard (clipboard = top-level-sheet(frame)) (clipboard-data-available?(, clipboard) & get-clipboard-data-as(, clipboard)) | (clipboard-data-available?(, clipboard) & get-clipboard-data-as(, clipboard)) end end method dylan-clipboard-object; define method dylan-clipboard-object-available? (frame :: , class :: ) => (available? :: ) with-clipboard (clipboard = top-level-sheet(frame)) (clipboard-data-available?(, clipboard) & instance?(get-clipboard-data-as(, clipboard), class)) end end method dylan-clipboard-object-available?; define method dylan-clipboard-object-available? (frame :: , class :: subclass()) => (available? :: ) with-clipboard (clipboard = top-level-sheet(frame)) (clipboard-data-available?(, clipboard) & instance?(get-clipboard-data-as(, clipboard), class)) | clipboard-data-available?(, clipboard) end end method dylan-clipboard-object-available?; /// Popup menu handling define method make-environment-popup-menu (frame :: , sheet :: , target :: ) => (menu :: false-or()) let framem = frame-manager(frame); let object = frame-target-object(frame, target); let class = object-class(object); let cache = frame-popup-menu-cache(frame); // Build a menu from the command table for this object element(cache, class, default: #f) | begin let comtab = frame-command-table-for-target(frame, target); let menu = make-menu-from-command-table-menu (command-table-menu(comtab), frame, framem, command-table: comtab, owner: frame); element(cache, class) := menu end end method make-environment-popup-menu; define method update-frame-commands-for-target (frame :: , target :: ) => () let browse-target = frame-target-browse-object(frame, target); update-frame-commands-for-browse-target(frame, browse-target) end method update-frame-commands-for-target; define method update-frame-commands-for-target (frame :: , target :: ) => () next-method(); let pane = target.target-pane; command-enabled?(frame-cut-target, frame) := cut-object?(pane, target); command-enabled?(frame-copy-target, frame) := copy-object?(pane, target); command-enabled?(frame-paste-target, frame) := paste-object?(pane, target); command-enabled?(frame-delete-target, frame) := delete-object?(pane, target); end method update-frame-commands-for-target; define method update-frame-commands-for-target (frame :: , target :: ) => () next-method(); let project = frame-current-project(frame); let edit-target = frame-target-edit-object(frame, target); let editable? = project & object-exists?(project, edit-target) & object-has-source?(project, edit-target); command-enabled?(frame-edit-target, frame) := editable? end method update-frame-commands-for-target; define method update-frame-commands-for-browse-target (frame :: , object :: ) => () #f end method update-frame-commands-for-browse-target; define method update-frame-commands-for-browse-target (frame :: , object :: ) => () let project = frame.frame-current-project; let environment-object? = instance?(object, ); let (describable?, documentable?, browsable?, type-browsable?, generic?) = if (project & object-exists?(project, object)) let type = environment-object? & environment-object-type(project, object); let describable? = frame-describe-object?(frame, object); let documentable? = frame-document-object?(frame, object); let browsable? = frame-browse-object?(frame, object); let type-browsable? = type & (~ instance?(type, )); let generic? = instance?(object, ) & method-generic-function(project, object) ~= #f; values(describable?, documentable?, browsable?, type-browsable?, generic?) else values(#f, #f, #f, #f, #f) end; command-enabled?(frame-describe-target, frame) := describable?; command-enabled?(frame-document-target, frame) := documentable?; command-enabled?(frame-browse-target, frame) := browsable?; command-enabled?(frame-browse-target-type, frame) := type-browsable?; command-enabled?(frame-browse-target-generic-function, frame) := generic?; end method update-frame-commands-for-browse-target; //---*** Maybe this should be in DUIM? define method menu-default-gadget-setter (default-gadget :: false-or(), menu :: ) => () local method update-gadget-default-state (button :: ) => () gadget-default?(button) := (button == default-gadget) end method update-gadget-default-state; local method update-children-default-state (gadget :: ) => () for (child in sheet-children(gadget)) select (child by instance?) => //---*** Can we make a menu be the default? #f; => update-children-default-state(child); => update-gadget-default-state(child); otherwise => #f; end end end method update-children-default-state; update-children-default-state(menu) end method menu-default-gadget-setter; //---*** Maybe this should be in DUIM? define method find-menu-button (menu :: , function :: ) => (button :: false-or()) block (return) do-sheet-tree (method (sheet :: ) if (instance?(sheet, ) & gadget-command(sheet) == function) return(sheet) end end, menu) end end method find-menu-button; //---*** andrewa: this is a lot of work to update the default command. //---*** Ideally we'd be able to change a command table's default command //---*** without having to know about all of this UI! define method update-environment-popup-menu (frame :: , menu :: , target :: ) => () let browse-target = frame-target-browse-object(frame, target); let default-command = default-command-for-target(frame, target); let default-button = default-command & find-menu-button(menu, default-command); menu-default-gadget(menu) := default-button end method update-environment-popup-menu; /// Activation define method environment-activate-callback (sheet :: , object :: ) => () let frame = sheet-frame(sheet); let target = make-command-target(sheet, object); let command = default-command-for-target(frame, target); if (command) frame-command-target(frame) := target; command(frame) else let object = frame-target-browse-object(frame, target); let name = frame-default-object-name(frame, object); environment-action-unavailable (frame, format-to-string("No action available", if (name) format-to-string(" for %s", name) else "" end)) end end method environment-activate-callback; define method environment-activate-callback (frame :: , object :: ) => () environment-activate-callback(top-level-sheet(frame), object) end method environment-activate-callback; /// Environment status bar define method make-environment-status-bar (frame :: ) => (bar :: ) with-frame-manager (frame-manager(frame)) make() end end method make-environment-status-bar; /// Searching define constant $search-string-count = 10; define settings () key-name "Search"; slot search-batch? :: = #f; slot search-wrap? :: = #f; slot search-boundaries? :: = #f; slot match-word? :: = #f; slot match-case? :: = #f; slot match-regexp? :: = #f; slot search1 :: = ""; slot search2 :: = ""; slot search3 :: = ""; slot search4 :: = ""; slot search5 :: = ""; slot search6 :: = ""; slot search7 :: = ""; slot search8 :: = ""; slot search9 :: = ""; slot search10 :: = ""; slot replace1 :: = ""; slot replace2 :: = ""; slot replace3 :: = ""; slot replace4 :: = ""; slot replace5 :: = ""; slot replace6 :: = ""; slot replace7 :: = ""; slot replace8 :: = ""; slot replace9 :: = ""; slot replace10 :: = ""; end settings ; define constant $search-settings = make(); // Store search settings define function store-search-settings () => () let description = current-search-description(); $search-settings.search-batch? := description.search-description-batch?; $search-settings.search-wrap? := description.search-description-wrap?; $search-settings.search-boundaries? := description.search-description-boundaries?; $search-settings.match-word? := description.search-description-match-word?; $search-settings.match-case? := description.search-description-match-case?; $search-settings.match-regexp? := description.search-description-match-regexp?; local method pad (c :: , min :: ) => (c :: ) let blanks = min - size(c); if (blanks > 0) concatenate(c, make(, fill: "", size: blanks)) else c end; end method pad; let search-strings = pad(previous-search-strings(), $search-string-count); $search-settings.search1 := search-strings[0]; $search-settings.search2 := search-strings[1]; $search-settings.search3 := search-strings[2]; $search-settings.search4 := search-strings[3]; $search-settings.search5 := search-strings[4]; $search-settings.search6 := search-strings[5]; $search-settings.search7 := search-strings[6]; $search-settings.search8 := search-strings[7]; $search-settings.search9 := search-strings[8]; $search-settings.search10 := search-strings[9]; let replace-strings = pad(previous-replace-strings(), $search-string-count); $search-settings.replace1 := replace-strings[0]; $search-settings.replace2 := replace-strings[1]; $search-settings.replace3 := replace-strings[2]; $search-settings.replace4 := replace-strings[3]; $search-settings.replace5 := replace-strings[4]; $search-settings.replace6 := replace-strings[5]; $search-settings.replace7 := replace-strings[6]; $search-settings.replace8 := replace-strings[7]; $search-settings.replace9 := replace-strings[8]; $search-settings.replace10 := replace-strings[9]; end function store-search-settings; // Restore search settings define function restore-search-settings () => () let description = current-search-description(); description.search-description-batch? := $search-settings.search-batch?; description.search-description-wrap? := $search-settings.search-wrap?; description.search-description-boundaries? := $search-settings.search-boundaries?; description.search-description-match-word? := $search-settings.match-word?; description.search-description-match-case? := $search-settings.match-case?; description.search-description-match-regexp? := $search-settings.match-regexp?; current-search-description() := description; previous-search-strings() := vector($search-settings.search1, $search-settings.search2, $search-settings.search3, $search-settings.search4, $search-settings.search5, $search-settings.search6, $search-settings.search7, $search-settings.search8, $search-settings.search9, $search-settings.search10); previous-replace-strings() := vector($search-settings.replace1, $search-settings.replace2, $search-settings.replace3, $search-settings.replace4, $search-settings.replace5, $search-settings.replace6, $search-settings.replace7, $search-settings.replace8, $search-settings.replace9, $search-settings.replace10); end function restore-search-settings; restore-search-settings(); //---*** cpage: 1998.08.27 Is it too late to do this here? Does settings-definer // use this message for storing settings? define function store-search-settings-receiver (message :: ) => () ignore(message); store-search-settings(); end function store-search-settings-receiver; tune-in($environment-channel, store-search-settings-receiver, message-type: ); // Subclass in order to add persistent settings //--- cpage: 1998.06.29 Frankly, I consider this a bit of a kludge. // We probably need to move most of the searching functionality // out of the framework and into this library. Alternatively, // we probably need a more general approach to customizing // framework-defined frames. define frame (, ) keyword icon: = $find-window-small-icon; end frame ; define window-settings find-window :: = "Find Window"; define method frame-search-frame-class (frame :: ) => (class :: ) end method frame-search-frame-class; /// Window Layering // Perform some actions on all frames when certain frames are manipulated // in order to treat all the environment frames as a "layer" as on Mac OS, // for example. // When to perform a given action on all frames: // #"none" => never // #"primary-frame" => when action is performed on primary frame // #"all-frames" => when action is performed on any frame define constant = one-of(#"none", #"primary-frame", #"all-frames"); // This is used to prevent recursion when raising all frames // This is a global variable rather than a thread variable because raising // could conceivably be requested from multiple frames simultaneously define variable *raising-all-frames?* :: = #f; define method raise-all-frames (frame :: ) => () // TESTING: Check whether this case ever arises when (*raising-all-frames?*) debug-message("raise-all-frames: Prevented Recursion"); end; unless (*raising-all-frames?*) block () *raising-all-frames?* := #t; next-method(); cleanup *raising-all-frames?* := #f; end; end unless; end method raise-all-frames; // Raise all frames when trigger frame is raised define method handle-event (frame :: , event :: ) => () next-method(); when (environment-auto-raise-all-frames() == #"all-frames" | (environment-auto-raise-all-frames() == #"primary-frame" & instance?(frame, ))) raise-all-frames(frame); end when; end method handle-event; // This is used to prevent recursion when minimizing or restoring all frames // This is a global variable rather than a thread variable because raising // could conceivably be requested from multiple frames simultaneously define variable *minimizing-or-restoring-all-frames?* :: = #f; define method minimize-all-frames (frame :: ) => () when (*minimizing-or-restoring-all-frames?*) // TESTING: Check whether this case ever arises debug-message("minimize-all-frames: Prevented Recursion"); end; unless (*minimizing-or-restoring-all-frames?*) block () *minimizing-or-restoring-all-frames?* := #t; next-method(); cleanup *minimizing-or-restoring-all-frames?* := #f; end; end unless; end method minimize-all-frames; define method undo-minimize-all-frames (frame :: ) => () when (*minimizing-or-restoring-all-frames?*) // TESTING: Check whether this case ever arises debug-message("undo-minimize-all-frames: Prevented Recursion"); end; unless (*minimizing-or-restoring-all-frames?*) block () *minimizing-or-restoring-all-frames?* := #t; next-method(); cleanup *minimizing-or-restoring-all-frames?* := #f; end; end unless; end method undo-minimize-all-frames; // Minimize/restore all frames when trigger frame is minimized/restored define method note-frame-state-changed (frame :: , old-state, new-state) => () next-method(); // If the state hasn't actually changed, don't trigger anything unless (new-state = old-state) when (environment-auto-lower-all-frames() == #"all-frames" | (environment-auto-lower-all-frames() == #"primary-frame" & instance?(frame, ))) select (new-state) #"iconified" => minimize-all-frames(frame); // Make sure the trigger frame gets restored by // undo-minimize-all-frames, too. *minimized-frames* := add-new!(*minimized-frames*, frame); #"deiconified" => undo-minimize-all-frames(frame); otherwise => #f; end select; end when; end unless; end method note-frame-state-changed; /// Environment project frame /// Frames "permanently" associated with one project define open abstract class () // This slot is "almost constant" -- the only places that set // the project are 'reinitialize-frame' methods //---*** This accessor is an attractive nuisance //---*** You don't want to use it, use 'frame-current-project' instead slot frame-project :: , setter: %frame-project-setter, required-init-keyword: project:; slot frame-exiting? :: = #f; end class ; define sealed inline method frame-current-project (frame :: ) => (project :: ) frame.frame-project end method frame-current-project; define sealed method frame-note-project-changed (frame :: , project :: false-or()) => () error("Changed project in a fixed-project frame.") end method frame-note-project-changed; define method make-clone (frame :: , #rest initargs) => (new-frame :: ) apply(next-method, frame, project: frame.frame-project, initargs) end method make-clone; define method reuse-matching-frame? (portd :: , frame :: , class :: subclass(), #rest initargs, #key project :: false-or() = #f) => (reuse? :: ) frame.frame-project = project & next-method() end method reuse-matching-frame?; define method handle-event (frame :: , event :: ) => () next-method(); let project = frame.frame-project; let application = project & project.project-application; frame-note-application-state-changed (frame, application & application.application-state); end method handle-event; /// Cloning define open generic clone-tool (frame :: ) => (); define open generic clone-and-link-tool (frame :: ) => (); define method clone-tool (frame :: ) => () let tool = make-clone(frame); fork-environment-function(frame, object-class(frame), always(tool)) end method clone-tool; define method clone-and-link-tool (frame :: ) => () let tool = make-clone(frame); link-frames(frame, tool); fork-environment-function(frame, object-class(frame), always(tool)) end method clone-and-link-tool; define command-table *clone-command-table* (*global-command-table*) menu-item "New Window" = clone-tool, accelerator: make-keyboard-gesture(#"n", #"control"), documentation: "Opens another window for the document."; //---*** andrewa: removed for 1.0 // menu-item "New Linked Window" = clone-and-link-tool, // documentation: "Opens another window for the document, linked to its selection."; end command-table *clone-command-table*; define command-table *windows-command-table* (*global-command-table*) include *clone-command-table*; include *window-actions-command-table*; include *window-settings-command-table*; include *open-windows-command-table*; end command-table *windows-command-table*; define variable $clone-bitmap :: = "New Window"; define method make-clone-tool-bar-buttons (frame :: ) => (buttons :: ) vector(make(