Module: duim-gadgets-internals Synopsis: DUIM gadgets Author: Scott McKay, Andy Armstrong Copyright: Original Code is Copyright (c) 1995-2004 Functional Objects, Inc. All rights reserved. License: Functional Objects Library Public License Version 1.0 Dual-license: GNU Lesser General Public License Warranty: Distributed WITHOUT WARRANTY OF ANY KIND /// Useful constants //---*** These should really be computed by the backend define constant $progress-bar-best-width :: = 120; define constant $status-bar-label-min-width :: = 150; /// Collection gadgets define constant = type-union(one-of(#"all"), ); define open abstract class () end class ; define protocol <> () getter gadget-items (gadget :: ) => (items :: ); getter gadget-items-setter (items :: , gadget :: ) => (items :: ); function note-gadget-items-changed (gadget :: ) => (); getter gadget-test (gadget :: ) => (test :: ); getter gadget-selection (gadget :: ) => (selection :: ); getter gadget-selection-setter (selection-specifier :: , gadget :: , #key do-callback?) => (selection :: ); function note-gadget-selection-changed (gadget :: ) => (); end protocol <>; // Mix-in for gadgets that operate upon collections (e.g. button-boxes) define open abstract class (, ) slot gadget-items :: = #[], setter: %items-setter, init-keyword: items:; sealed slot gadget-label-key :: = collection-gadget-default-label-key, init-keyword: label-key:; sealed slot gadget-value-key :: = collection-gadget-default-value-key, init-keyword: value-key:; sealed slot gadget-test :: = \==, init-keyword: test:; end class ; define method initialize (gadget :: , #key name-key) => () //---*** Hack to remove when name-key has really gone when (name-key) gadget-label-key(gadget) := name-key end; next-method() end method initialize; define method gadget-items-setter (items :: , gadget :: ) => (items :: ) unless (items = gadget-items(gadget)) gadget.%items := items; note-gadget-items-changed(gadget) end; items end method gadget-items-setter; define method note-gadget-items-changed (gadget :: ) => () #f end method note-gadget-items-changed; define method gadget-item-value (gadget :: , item) => (value) let value-key = gadget-value-key(gadget); value-key(item) end method gadget-item-value; define method gadget-item-label (gadget :: , item) => (label) let label-key = gadget-label-key(gadget); label-key(item) end method gadget-item-label; define method collection-gadget-item-label (gadget :: , item) => (label :: ) let label = gadget-label-key(gadget)(item); assert(instance?(label, ), "'gadget-label-key' returned non-string %= for gadget %=", label, gadget); let ampersand = position(label, '&'); if (ampersand & (ampersand < size(label) - 1)) remove(label, '&', count: 1) else label end end method collection-gadget-item-label; define macro with-preserved-selection { with-preserved-selection (?gadget:expression) ?body:body end } => { let gadget = ?gadget; let value = gadget-value(gadget); // Clear the selection, since it may no longer be valid gadget-selection(gadget) := #[]; ?body; when (value) gadget-value(gadget) := value end } end macro with-preserved-selection; define method update-gadget (gadget :: ) => () with-preserved-selection (gadget) note-gadget-items-changed(gadget) end end method update-gadget; //--- Can we do anything better than this? define method gadget-value-type (gadget :: ) => (type :: ) select (gadget-selection-mode(gadget)) #"multiple" => ; otherwise => ; end end method gadget-value-type; // Mix-in to allow a selection of zero or more items // Note: This must come before in any CPL, // as it overrides some of the behavior. define open abstract class () sealed slot gadget-selection :: = #[], init-keyword: selection:, setter: %selection-setter; end class ; define method initialize (gadget :: , #key keep-selection-visible? = #t, value = $unsupplied) => () next-method(); gadget-flags(gadget) := logior(logand(gadget-flags(gadget), lognot(%keep_selection_visible)), if (keep-selection-visible?) %keep_selection_visible else 0 end); if (unsupplied?(value)) let selection = gadget-selection(gadget); let items = gadget-items(gadget); when (empty?(selection) & items & size(items) > 0 & gadget-selection-mode(gadget) = #"single") gadget-selection(gadget) := #[0] end else gadget-selection(gadget) := gadget-selection-for-value(gadget, value) end end method initialize; define sealed method gadget-selection-setter (selection :: , gadget :: , #key do-callback? = #f) => (selection :: ) unless (selection = gadget-selection(gadget)) let items-size = size(gadget-items(gadget)); for (key in selection) assert(instance?(key, ) & key >= 0 & key < items-size, "Invalid key %= in selection for %=", key, gadget) end; gadget.%selection := selection; when (do-callback?) execute-value-changed-callback (gadget, gadget-client(gadget), gadget-id(gadget)) end; note-gadget-value-changed(gadget); note-gadget-selection-changed(gadget) end; selection end method gadget-selection-setter; define sealed method gadget-selection-setter (selection == #"all", gadget :: , #key do-callback? = #f) => (new-selection :: ) assert(gadget-selection-mode(gadget) == #"multiple", "Cannot set selection of non-multiple selection gadget to %=: %=", selection, gadget); let new-selection = range(from: 0, below: size(gadget-items(gadget))); gadget-selection-setter(new-selection, gadget, do-callback?: do-callback?) end method gadget-selection-setter; define method gadget-items-setter (items :: , gadget :: ) => (items :: ) with-preserved-selection (gadget) next-method() end; items end method gadget-items-setter; define method note-gadget-selection-changed (gadget :: ) => () #f end method note-gadget-selection-changed; define method gadget-value (gadget :: ) => (value) let items = gadget-items(gadget); let selection = gadget-selection(gadget); when (selection) select (gadget-selection-mode(gadget)) #"single" => unless (empty?(selection)) gadget-item-value(gadget, items[selection[0]]) end; #"multiple" => map-as(, method (index :: ) gadget-item-value(gadget, items[index]) end, selection); #"none" => #f; end end end method gadget-value; define sealed method gadget-value-index (gadget :: , value) => (index :: false-or()) let test = gadget-test(gadget); find-key(gadget-items(gadget), method (item) test(gadget-item-value(gadget, item), value) end) end method gadget-value-index; define sealed method gadget-selection-for-value (gadget :: , value) => (selection :: ) select (gadget-selection-mode(gadget)) #"single" => let index = gadget-value-index(gadget, value); if (index) vector(index) else #[] end; #"multiple" => error("Non-sequence %= supplied as value for multiple selection gadget %=", value, gadget); end end method gadget-selection-for-value; define sealed method gadget-selection-for-value (gadget :: , value :: ) => (selection :: ) select (gadget-selection-mode(gadget)) #"single" => next-method(); #"multiple" => let indices = map-as(, method (subvalue) gadget-value-index(gadget, subvalue) end, value); remove!(indices, #f) end end method gadget-selection-for-value; define sealed method do-gadget-value-setter (gadget :: , value) => () unless (value = gadget-value(gadget)) let selection = gadget-selection-for-value(gadget, value); gadget.%selection := selection; note-gadget-selection-changed(gadget) end end method do-gadget-value-setter; // Returns #t if the item is selected in the collection gadget define sealed method gadget-item-selected? (gadget :: , item) => (true? :: ) let value = gadget-value(gadget); let test = gadget-test(gadget); let mode = gadget-selection-mode(gadget); select (mode) #"single" => test(gadget-item-value(gadget, item), value); #"multiple" => member?(gadget-item-value(gadget, item), value, test: test); #"none" => #f; end end method gadget-item-selected?; define sealed inline method gadget-keep-selection-visible? (gadget :: ) => (true? :: ) logand(gadget-flags(gadget), %keep_selection_visible) = %keep_selection_visible end method gadget-keep-selection-visible?; /// Collection gadget state define sealed class () sealed constant slot %state-items :: , required-init-keyword: items:; end class ; define sealed domain make (singleton()); define sealed domain initialize (); define method gadget-state (gadget :: ) => (state :: ) make(, value: gadget-value(gadget), items: gadget-items(gadget)) end method gadget-state; define method gadget-state-setter (state :: , gadget :: ) => (state :: ) gadget-items(gadget) := state.%state-items; next-method() end method gadget-state-setter; // The base class for hairy controls like list and tree controls, etc define open abstract class (, , ) sealed slot gadget-selection-mode :: = #"single", init-keyword: selection-mode:; end class ; /// List boxes // This is both an action gadget and a value gadget. Changing the // selection invokes the value-changed-callback, and double clicking // invokes the activate-callback. // When 'read-only?: #t', this is like a Windows list box. // When 'read-only?: #f', this is like a Windows combo box, if one existed. define open abstract class (, , , ) sealed constant slot gadget-lines :: false-or() = #f, init-keyword: lines:; keyword read-only?: = #t; end class ; /// Option boxes // An option box is like a list box, except that it only displays a single // selection, and you pull it down to make a different selection. // When 'read-only?: #t', this is like a Windows drop-down list box. // When 'read-only?: #f', this is like a Windows drop-down combo box. define open abstract class (, , // No because you can't double click! ) keyword read-only?: = #t; end class ; define sealed method gadget-selection-mode (pane :: ) => (selection-mode :: ) #"single" end method gadget-selection-mode; /// Combo boxes // As it turns out, in real life we don't actually model 'read-only?: #f' // list and option boxes as combo boxes, because we need all the text gadget // protocols as well. So we have , too... define open abstract class (, , , , , ) // Same deal as ... sealed slot gadget-text-buffer :: = "", init-keyword: text:; constant slot gadget-value-type :: = , init-keyword: value-type:; sealed slot text-field-maximum-size :: false-or() = #f, init-keyword: maximum-size:; keyword read-only?: = #f; end class ; define sealed method gadget-selection-mode (pane :: ) => (selection-mode :: ) #"single" end method gadget-selection-mode; define method gadget-value (gadget :: ) => (value) gadget-text-parser(gadget-value-type(gadget), gadget-text(gadget)) end method gadget-value; // Back-ends where 'gadget-text' and 'gadget-text-buffer' use different // representations should specialize this method define method do-gadget-value-setter (gadget :: , value) => () let text = gadget-value-printer(gadget-value-type(gadget), value); unless (text = gadget-text(gadget)) gadget-text-buffer(gadget) := text; note-gadget-text-changed(gadget) end end method do-gadget-value-setter; /// Spin boxes // You can get "integer spin boxes" by specifying 'items: range(...)', // or -like spin boxes by specifying a set of items define open abstract class (, , , ) end class ; define sealed method gadget-selection-mode (pane :: ) => (selection-mode :: ) #"single" end method gadget-selection-mode; define method gadget-label (pane :: ) => (label :: ) let selection = gadget-selection(pane); case empty?(selection) => ""; otherwise => let item = gadget-items(pane)[selection[0]]; gadget-label-key(pane)(item) end end method gadget-label; /// Gadget Boxes // A gadget box is a value gadget whose value is given by a selection // of zero of more buttons contained with the box. For example, radio // and check boxes. define open abstract class (, , , ) end class ; /// Tool bars define abstract class () end class ; define open abstract class (, , ) end class ; define sealed class (, ) end class ; define method class-for-make-pane (framem :: , class == , #key) => (class :: , options :: false-or()) values(, #f) end method class-for-make-pane; define sealed domain make (singleton()); define sealed domain initialize (); /// Status bars define open abstract class (, , , , , ) sealed slot status-bar-label-pane :: false-or() = #f, init-keyword: label-pane:; sealed slot status-bar-progress-bar :: false-or() = #f, init-keyword: progress-bar:; end class ; define method initialize (pane :: , #key frame-manager: framem, label, value, value-range, progress-bar?) => () next-method(); when (empty?(sheet-children(pane))) with-frame-manager (framem) let label-pane = make(