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 /// Text editing gadgets // Selection change notifications define protocol <> (<>) function execute-text-selection-changed-callback (gadget :: , client, id) => (); function do-execute-text-selection-changed-callback (gadget :: , client, id) => (); getter gadget-text-selection-changed-callback (gadget :: ) => (callback :: ); setter gadget-text-selection-changed-callback-setter (callback :: , gadget :: ) => (callback :: ); end protocol <>; define open abstract class () sealed slot gadget-text-selection-changed-callback :: = #f, init-keyword: text-selection-changed-callback:; end class ; define method execute-text-selection-changed-callback (gadget :: , client, id) => () ignore(client, id); let callback = gadget-text-selection-changed-callback(gadget); if (callback) execute-callback(gadget, callback, gadget) else do-execute-text-selection-changed-callback(gadget, client, id) end end method execute-text-selection-changed-callback; define method do-execute-text-selection-changed-callback (gadget :: , client, id) => () ignore(client, id); #f end method do-execute-text-selection-changed-callback; // Protection notifications define protocol <> (<>) function execute-protection-callback (gadget :: , client, id, range) => (); function do-execute-protection-callback (gadget :: , client, id, range) => (); getter gadget-protection-callback (gadget :: ) => (callback :: ); setter gadget-protection-callback-setter (callback :: , gadget :: ) => (callback :: ); end protocol <>; define open abstract class () sealed slot gadget-protection-callback :: = #f, init-keyword: protection-callback:; end class ; define method execute-protection-callback (gadget :: , client, id, range) => () ignore(client, id); let callback = gadget-protection-callback(gadget); if (callback) execute-callback(gadget, callback, gadget, range) else do-execute-protection-callback(gadget, client, id, range) end end method execute-protection-callback; define method do-execute-protection-callback (gadget :: , client, id, range) => () ignore(client, id, range); #f end method do-execute-protection-callback; /// Text gadget protocols and support classes define open abstract class () end; define open abstract class () end; define protocol <> (<>) getter gadget-text (gadget :: ) => (text :: ); setter gadget-text-setter (text :: , gadget :: , #key do-callback?) => (text :: ); getter gadget-text-buffer (gadget :: ) => (text :: ); setter gadget-text-buffer-setter (text :: , gadget :: ) => (text :: ); function note-gadget-text-changed (gadget :: ) => (); function gadget-text-parser (type :: , text :: ) => (value); function gadget-value-printer (type :: , value) => (text :: ); // Get and set the current selection function text-selection (gadget :: ) => (range :: type-union(, one-of(#f))); function text-selection-setter (range :: type-union(, one-of(#t, #f)), gadget :: ) => (range :: type-union(, one-of(#t, #f))); // Return or replace the current selection. Note that this works // on all sorts of sheets, not just text gadgets function selected-text (gadget :: ) => (string :: false-or()); function selected-text-setter (string :: false-or(), gadget :: ) => (string :: false-or()); function text-field-modified? (gadget :: ) => (modified? :: ); function text-field-modified?-setter (modified? :: , gadget :: ) => (modified? :: ); // These can be more efficient than using 'gadget-value' function text-field-size (gadget :: ) => (size :: ); function text-field-text (gadget :: , range :: ) => (string :: false-or()); // Get and set the current caret position function text-caret-position (gadget :: ) => (index :: false-or()); function text-caret-position-setter (index :: false-or(), gadget :: ) => (index :: false-or()); // Mapping indices to caret positions, and vice-versa function character-position (gadget :: , x, y) => (index :: ); function position-character (gadget :: , index :: ) => (x, y); end protocol <>; // Default methods for these are no-ops define method selected-text (sheet :: ) => (string :: false-or()) #f end method selected-text; define method selected-text-setter (string :: false-or(), sheet :: ) => (string :: false-or()) string end method selected-text-setter; define sealed class () sealed slot text-range-start :: , required-init-keyword: start:; sealed slot text-range-end :: , required-init-keyword: end:; end class ; define sealed domain make (singleton()); define sealed domain initialize (); define sealed inline method make (class == , #rest initargs, #key) => (range :: ) dynamic-extent(initargs); apply(make, , initargs) end method make; define sealed class () sealed slot text-range-object = #f, init-keyword: object:; end class ; define sealed domain make (singleton()); define sealed domain initialize (); /// Text gadget state define sealed class () sealed constant slot %state-text, required-init-keyword: text:; end class ; define sealed domain make (singleton()); define sealed domain initialize (); define method gadget-state (gadget :: ) => (state :: ) make(, text: gadget-text(gadget)) end method gadget-state; define method gadget-state-setter (state :: , gadget :: ) => (state :: ) gadget-text(gadget) := state.%state-text; state end method gadget-state-setter; /// Text gadget classes // A single-line text editing field // The callbacks are as follows: // - value-changing callback during "casual" typing // - value-changed callback when the change is "committed" // - activate callback when some sort of activation gesture is seen // The "commit" and "activation" gestures are defined by the back-end define open abstract class (, , , , , ) // We maintain the text buffer separately from 'gadget-text' in case // the internal representation is different from the external represenation, // as is the case with Windows multi-line text editors 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:; end class ; define method initialize (gadget :: , #key x-alignment = #"left", case: text-case = #f, auto-scroll? = #f) next-method(); let xa = select (x-alignment) #"left" => %x_alignment_left; #"right" => %x_alignment_right; #"center", #"centre" => %x_alignment_center; end; let c = select (text-case) #f => %text_case_false; #"lower" => %text_case_lower; #"upper" => %text_case_upper; end; let scroll = if (auto-scroll?) %auto_scroll else 0 end; gadget-flags(gadget) := logior(logand(gadget-flags(gadget), lognot(%x_alignment_mask)), xa + c + scroll) end method initialize; define constant $text-field-cases :: = #[#f, #"lower", #"upper"]; define sealed inline method text-field-case (gadget :: ) => (text-case) let index = ash(logand(gadget-flags(gadget), %text_case_mask), -%text_case_shift); $text-field-cases[index] end method text-field-case; define sealed inline method text-field-auto-scroll? (gadget :: ) => (auto-scroll? :: ) logand(gadget-flags(gadget), %auto_scroll) = %auto_scroll end method text-field-auto-scroll?; define method viewport-fencepost? (sheet :: ) => (true? :: ) #t end method viewport-fencepost?; // Back-ends where 'gadget-text' and 'gadget-text-buffer' use different // representations should specialize 'gadget-text' and 'gadget-text-setter' define method gadget-text (gadget :: ) => (text :: ) gadget-text-buffer(gadget) end method gadget-text; define method gadget-text-setter (text :: , gadget :: , #key do-callback? = #f) => (text :: ) gadget-text-buffer(gadget) := text; when (do-callback?) execute-value-changed-callback(gadget, gadget-client(gadget), gadget-id(gadget)) end; note-gadget-text-changed(gadget); note-gadget-value-changed(gadget); text end method gadget-text-setter; define method note-gadget-text-changed (gadget :: ) => () #f end method note-gadget-text-changed; define method gadget-value (gadget :: ) => (value) gadget-text-parser(gadget-value-type(gadget), gadget-text(gadget)) end method gadget-value; 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(gadget) := text end end method do-gadget-value-setter; /// 'accept' and 'present', ha ha ha define method gadget-text-parser (type :: subclass(), text :: ) => (value :: ) text end method gadget-text-parser; define method gadget-value-printer (type :: subclass(), value :: ) => (text :: ) value end method gadget-value-printer; define method gadget-text-parser (type :: subclass(), text :: ) => (value :: ) as(, text) end method gadget-text-parser; define method gadget-value-printer (type :: subclass(), value :: ) => (text :: ) as(, value) end method gadget-value-printer; define method gadget-text-parser (type :: subclass(), text :: ) => (value :: false-or()) block () let (value, next) = string-to-integer(text); next = size(text) & value exception () #f end end method gadget-text-parser; define method gadget-value-printer (type :: subclass(), value :: false-or()) => (text :: ) if (value) integer-to-string(value) else "" end end method gadget-value-printer; /* define method gadget-text-parser (type :: subclass(), text :: ) => (value :: false-or()) block () let (value, next) = string-to-float(text); next = size(text) & value exception () #f end end method gadget-text-parser; define method gadget-value-printer (type :: subclass(), value :: false-or()) => (text :: ) if (value) float-to-string(value) else "" end end method gadget-value-printer; */ /// Password fields define open abstract class () end class ; /// Multi-line text editors define protocol <> (<>) getter text-field-word-wrap? (gadget :: ) => (word-wrap? :: ); // Hacking lines function current-line (gadget :: ) => (line :: false-or()); function line-length (gadget :: , line :: ) => (length :: false-or()); function get-line (gadget :: , line :: ) => (line :: false-or()); // Mapping indices to lines, and vice-versa function index-line (gadget :: , index :: ) => (line :: false-or()); function line-index (gadget :: , line :: ) => (index :: false-or()); // Protection function text-range-protected? (gadget :: , range :: ) => (protected? :: ); function text-range-protected?-setter (protected? :: , gadget :: , range :: ) => (protected? :: ); // Searching function find-text (gadget :: , string :: ) => (index :: false-or()); end protocol <>; // A multi-line text editing field define open abstract class (, ) sealed constant slot gadget-lines :: false-or() = #f, // i.e., bottomless init-keyword: lines:; sealed constant slot gadget-columns :: false-or() = #f, init-keyword: columns:; sealed slot text-field-word-wrap? :: = #t, init-keyword: word-wrap?:; end class ; /// "Rich" text editors define protocol <> (<>) getter character-format (gadget :: ) => (sd :: false-or()); // Set the default character format, or set the format for the indicated range setter character-format-setter (sd :: false-or(), gadget :: , #key range :: false-or()) => (sd :: false-or()); getter paragraph-format (gadget :: , #key range) => (paragraph-format); setter paragraph-format-setter (paragraph-format, gadget :: , #key range) => (paragraph-format); end protocol <>; define open abstract class () sealed slot %paragraph-format = #f, init-keyword: paragraph-format:; sealed slot %word-break-policy = #f, init-keyword: word-break-policy:; sealed slot %line-break-policy = #f, init-keyword: line-break-policy: end class ; define method paragraph-format (gadget :: , #key range) => (paragraph-format) //---*** What to do about the range? gadget.%paragraph-format end method paragraph-format; define method paragraph-format-setter (paragraph-format, gadget :: , #key range) => (paragraph-format) //---*** What to do about the range? gadget.%paragraph-format := paragraph-format end method paragraph-format-setter; /// Text changed events define sealed class () sealed constant slot event-text :: , required-init-keyword: text:; end class ; define sealed domain make (singleton()); define sealed domain initialize (); define method handle-event (gadget :: , event :: ) => () gadget-text-buffer(gadget) := event-text(event); execute-value-changing-callback(gadget, gadget-client(gadget), gadget-id(gadget)) end method handle-event; define function distribute-text-changing-callback (gadget :: , text :: ) => () distribute-event(port(gadget), make(, gadget: gadget, text: text)) end function distribute-text-changing-callback; define sealed class () end class ; define sealed domain make (singleton()); define sealed domain initialize (); define method handle-event (gadget :: , event :: ) => () gadget-text-buffer(gadget) := event-text(event); execute-value-changed-callback(gadget, gadget-client(gadget), gadget-id(gadget)) end method handle-event; define function distribute-text-changed-callback (gadget :: , text :: ) => () distribute-event(port(gadget), make(, gadget: gadget, text: text)) end function distribute-text-changed-callback;