Module: motif-duim Synopsis: Motif basic gadget implementation Author: Scott McKay, Stuart Croy Based on work by John Aspinall and Richard Billington 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 /// Motif gadgets define open abstract class (, ) slot %initial-width = #f; slot %initial-height = #f; end class ; define open abstract class () end class ; define open abstract class () end class ; define protocol <> () function make-motif-widget (_port :: , gadget :: ) => (widget, scroll-widget); function initialize-gadget-mirror (gadget :: , mirror :: ) => (); function initialize-gadget-callbacks (gadget :: , mirror :: , widget) => (); end protocol <>; define sealed method do-make-mirror (_port :: , gadget :: ) => (mirror :: ) let (widget, scroll-widget) = make-motif-widget(_port, gadget); assert(~scroll-widget, "There shouldn't be a scroll widget for non-scrolling gadgets!"); let mirror = make(, sheet: gadget, widget: widget); install-gadget-callbacks(gadget, mirror, widget); xt/add-widget-destroy-callback(widget, destroy-mirror-callback, mirror); initialize-gadget-mirror(gadget, mirror); xt/XtRealizeWidget(widget); record-initial-widget-sizes(gadget, widget); //---*** Do we really need this? Won't the DUIM core do the right thing //---*** during mirroring, creating it unmapped then mapping it if necessary? when (sheet-enabled?(gadget)) xt/XtMapWidget(widget) end end method do-make-mirror; define sealed method do-make-mirror (_port :: , gadget :: ) => (mirror :: ) let (widget, scroll-widget) = make-motif-widget(_port, gadget); let mirror = make(, sheet: gadget, widget: scroll-widget, work-widget: widget); install-gadget-callbacks(gadget, mirror, widget); xt/add-widget-destroy-callback(widget, destroy-mirror-callback, mirror); initialize-gadget-mirror(gadget, mirror); if (gadget-scroll-bars(gadget)) xt/XtSetValues(widget, mapped-when-managed: #t); xt/XtRealizeWidget(scroll-widget); xt/XtRealizeWidget(widget); xt/XtManageChild(widget); xt/XtManageChild(scroll-widget) else xt/XtRealizeWidget(widget); xt/XtManageChild(widget) end; record-initial-widget-sizes(gadget, widget); //---*** Do we really need this? Won't the DUIM core do the right thing //---*** during mirroring, creating it unmapped then mapping it if necessary? when (sheet-enabled?(gadget)) xt/XtMapWidget(widget) end end method do-make-mirror; define sealed method initialize-gadget-mirror (gadget :: , mirror :: ) => () #f end method initialize-gadget-mirror; //--- Width and height for scrolled windows are a little shy of the mark, //--- by about 3 or 4 pixels, so we have to kludge it define method record-initial-widget-sizes (gadget :: , widget :: xt/) let parent = xt/XtParent(widget); let (xwidth, xheight) = xt/XtGetValues(widget, #"width", #"height"); if (xm/XmIsScrolledWindow(parent)) gadget.%initial-width := xwidth + scroll-dimension(parent, #"vertical", #"width") + 4; gadget.%initial-height := xheight + scroll-dimension(parent, #"horizontal", #"height") + 4 else gadget.%initial-width := xwidth gadget.%initial-height := xheight end end method record-initial-widget-sizes; define method scroll-dimension (scrolled-widget, orientation, dimension) => (size :: ) let orientation = select (orientation) #"horizontal" => xm/$XmHORIZONTAL; #"vertical" => xm/$XmVERTICAL; end; block (return) for (child in xt/XtGetValues(scrolled-widget, #"children")) when (xm/XmIsScrollBar(child) & xt/XtGetValues(widget, #"orientation") = orientation) return(xt/XtGetValues(child, dimension)) end end; 0 end end method scroll-dimension; define method widget-attributes (_port :: , gadget :: ) => (foreground, background, font) let foreground :: = get-default-foreground(_port, gadget); let background :: = get-default-background(_port, gadget); let text-style = get-default-text-style(_port, gadget); let foreground = if (default-foreground(gadget)) vector(foreground:, allocate-color(foreground, port-default-palette(_port))) else #[] end; let background = if (default-background(gadget)) vector(background:, allocate-color(background, port-default-palette(_port))) else #[] end; let font = if (default-text-style(gadget)) vector(text-style:, text-style-mapping(_port, text-style).%font-name) else #[] end; value(foreground, background, font) end method widget-attributes; // Not unreasonable as a default method // We take the values suggested by Motif as the default sizes define method do-compose-space (pane :: , #key width, height) => (space-req :: ) let mirror = sheet-mirror(pane); let widget = mirror-widget(mirror); let (xwidth, xheight) = xt/XtGetValues(widget, #"width", #"height"); let min-width = pane.%initial-width | xwidth; let min-height = pane.%initial-height | xheight; make(, width: if (width) max(min-width, width) else min-width end, min-width: min-width, max-width: $fill, height: if (height) max(min-height, height) else min-height end, min-height: min-height, max-height: $fill) end method do-compose-space; define sealed method defaulted-gadget-label (gadget :: ) => (label) gadget-label(gadget) | $default-label end method defaulted-gadget-label; define sealed method note-gadget-label-changed (gadget :: ) => () next-method(); let mirror = sheet-direct-mirror(gadget); mirror & update-mirror-label(gadget, mirror) end method note-gadget-label-changed; define sealed method update-mirror-label (gadget :: , mirror :: ) => () let widget = mirror-widget(sheet-direct-mirror(gadget)); let label = defaulted-gadget-label(gadget); let label :: = if (instance?(label, )) label else "" end; xt/XtSetValues(widget, label-string: label) end method update-mirror-label; define sealed method note-gadget-enabled (client, gadget :: ) => () ignore(client); next-method(); let widget = mirror-widget(sheet-direct-mirror(gadget)); xt/XtSetSensitive(widget, #t) end method note-gadget-enabled; define sealed method note-gadget-disabled (client, gadget :: ) => () ignore(client); next-method(); let widget = mirror-widget(sheet-direct-mirror(gadget)); xt/XtSetSensitive(widget, #f) end method note-gadget-disabled; //---*** DO WE NEED THIS? define sealed method activate-motif-gadget (gadget :: ) => (activated? :: ) when (gadget-activate-callback(gadget)) distribute-activate-callback(gadget); #t end end method activate-motif-gadget; //---*** DO WE NEED THIS? define sealed method activate-motif-gadget (gadget :: ) => (activated? :: ) handle-text-gadget-changed(gadget); next-method() end method activate-motif-gadget; /// Callback suppression // Motif's gadgets are inconsistent about whether they allow the suppression // of the value-changed callback when the gadget-value is set from code. // (E.g. XmToggleButtonSetState has a third argument, XmTextFieldSetString doesn't.) // Since DUIM sometimes calls its own callbacks from Silica code, we use this // mixin to suppress the callback from within the Motif callback when the // Motif gadget doesn't do so. define open abstract class () sealed slot %suppress-callback? :: = #f; end class ; define method gadget-value-setter (value, gadget :: , #key do-callback?) => (value) ignore(do-callback?); block () gadget.%suppress-callback? := #t; next-method() cleanup gadget.%suppress-callback? := #f; end end method gadget-value-setter; define macro suppressing-value-changed-callback { suppressing-value-changed-callback (?gadget:expression) ?:body end } => { unless (?gadget.%suppress-callback?) ?body end } end macro suppressing-value-changed-callback; /// Exit, cancel, default button, etc. //---*** DO WE NEED THIS? define method handle-command-for-id (sheet :: , id :: ) => (handled? :: ) let frame = sheet-frame(sheet); select (id) $IDOK => duim-debug-message("Handling IDOK for %=", sheet); activate-default-button(frame); $IDCANCEL => duim-debug-message("Handling IDCANCEL for %=", sheet); handle-cancel(frame); otherwise => let gadget = id->gadget(sheet, id); if (gadget) handle-gadget-activation(gadget) else handle-id-activation(frame, id) end; end end method handle-command-for-id; //---*** DO WE NEED THIS? define sealed method handle-gadget-activation (gadget :: ) => (handled? :: ) duim-debug-message("Ignoring activation command for gadget %=", gadget); #f end method handle-gadget-activation; //---*** DO WE NEED THIS? // This handles IDOK commands for more than just buttons... define method activate-default-button (frame :: ) => (activated? :: ) let gadget = motif-sheet-with-focus(); duim-debug-message(" Handling IDOK: focus currently %=", gadget); let activated? = instance?(gadget, ) & gadget-enabled?(gadget) & activate-motif-gadget(gadget); // If we didn't activate the gadget, try to activate the default button unless (activated?) let button = frame-default-button(frame); // Don't activate an upmapped or disabled default button... when (button & sheet-mapped?(button) & gadget-enabled?(button)) handle-gadget-activation(button) end end end method activate-default-button; //---*** DO WE NEED THIS? define function motif-sheet-with-focus () => (sheet :: false-or()) let handle = GetFocus(); let mirror = window-mirror(handle); when (mirror) let sheet = mirror-sheet(mirror); if (instance?(sheet, )) subgadget-owner(sheet) else sheet end end end function motif-sheet-with-focus; //---*** DO WE NEED THIS? define function handle-cancel (frame :: ) => (handled? :: ) let gadget = motif-sheet-with-focus(); duim-debug-message(" Handling IDCANCEL: focus currently %=", gadget); if (instance?(gadget, ) & cancel-gadget(gadget)) #t else cancel-frame(frame) end end function handle-cancel; //---*** DO WE NEED THIS? define sealed method cancel-frame (frame :: ) => (handled? :: ) //---*** We should handle ESCAPE as canceling popups by default, //---*** for example in combo boxes. #f end method cancel-frame; //---*** DO WE NEED THIS? define sealed method cancel-gadget (gadget :: ) => (handled? :: ) #f end method cancel-gadget; //---*** What do we do about setting the color and font of a gadget? /// Labels define sealed class (,