Module: gtk-duim Synopsis: GTK dialog implementation Author: Andy Armstrong, Scott McKay 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 define constant $dialog-border = 10; define constant $dialog-spacing = 10; define constant $exit-button-min-width = 100; /// DUIM dialogs define sealed class () sealed constant slot %owner :: false-or(), required-init-keyword: owner:; end class ; define sealed domain make (singleton()); define sealed domain initialize (); ignore(mirror-registered-dialogs); define sealed method make-top-level-mirror (sheet :: , frame :: ) => (mirror :: ) // let widget = GTK-WINDOW(gtk-window-new($GTK-WINDOW-DIALOG)); let widget = GTK-WINDOW(gtk-dialog-new()); let owner = frame-owner(frame); make(, widget: widget, sheet: sheet, owner: owner) end method make-top-level-mirror; define sealed method cancel-frame (dialog :: ) => (handled? :: ) let button = dialog-cancel-button(dialog); when (button & gadget-enabled?(button)) handle-gadget-activation(button) end end method cancel-frame; define sealed method map-mirror (_port :: , sheet :: , mirror :: ) => () ensure-dialog-position(sheet-frame(sheet), mirror); next-method(); let dialog = sheet-frame(sheet); let owner = frame-owner(dialog); owner & register-dialog-mirror(owner, mirror) end method map-mirror; define sealed method unmap-mirror (_port :: , sheet :: , mirror :: ) => () next-method(); let dialog = sheet-frame(sheet); let owner = frame-owner(dialog); owner & unregister-dialog-mirror(owner, mirror) end method unmap-mirror; define sealed method ensure-dialog-position (frame :: , mirror :: ) => () ignoring("ensure-dialog-position") end method ensure-dialog-position; define sealed method compute-dialog-position (frame :: ) => (x :: , y :: ) //--- Is there a better way to get this? 'frame-position' always //--- gets the info from the top level sheet which isn't what //--- we want here. let sheet = top-level-sheet(frame); let (width, height) = sheet-size(sheet); let geometry = frame-geometry(frame); let frame-x = geometry[0]; let frame-y = geometry[1]; let frame-width = geometry[2]; let frame-height = geometry[3]; let width = frame-width | width; let height = frame-height | height; if (frame-x & frame-y) values(frame-x, frame-y) else let _display = display(sheet); let owner = frame-owner(frame); let owner-top-sheet = owner & top-level-sheet(owner); let owner-mirror = owner-top-sheet & sheet-direct-mirror(owner-top-sheet); let (screen-width, screen-height) = sheet-size(_display); if (owner-mirror) // Center the dialog over the client area let (owner-x, owner-y) = client-to-screen-position(owner-mirror, 0, 0); // let owner-handle = window-handle(owner-mirror); // let (owner-width, owner-height) = get-client-size(owner-handle); let (owner-width, owner-height) = sheet-size(owner); duim-debug-message(" Owner currently %d x %d, at %d, %d", owner-width, owner-height, owner-x, owner-y); duim-debug-message(" Dialog currently %d x %d", width, height); let x = max(min(screen-width - width, owner-x + floor/(owner-width - width, 2)), 0); let y = max(min(screen-height - height, owner-y + floor/(owner-height - height, 2)), 0); values(x, y) else // Center the dialog on the screen values(max(floor/(screen-width - width, 2), 0), max(floor/(screen-height - height, 2), 0)) end end end method compute-dialog-position; /// Piggy-back on the default dialogs from gadget-panes for now define method top-level-layout-child (framem :: , dialog :: , layout :: false-or()) => (layout :: false-or()) default-dialog-frame-wrapper(framem, dialog, layout); end method top-level-layout-child; define sealed method update-frame-layout (framem :: , frame :: ) => () update-default-dialog-layout(framem, frame) end method update-frame-layout; define method make-exit-button (framem :: , dialog :: , callback :: false-or(), label :: , #rest initargs, #key enabled? = (callback ~= #f), #all-keys) => (button :: false-or()) when (callback) with-frame-manager (framem) apply(make, , activate-callback: method (button) let dialog = sheet-frame(button); execute-callback(dialog, callback, dialog) end, label: label, enabled?: enabled?, min-width: $exit-button-min-width, initargs) end end end method make-exit-button; define sealed method default-dialog-border (framem :: , dialog :: ) => (border :: ) $dialog-border end method default-dialog-border; define sealed method default-dialog-spacing (framem :: , dialog :: ) => (border :: ) $dialog-spacing end method default-dialog-spacing; define sealed method default-dialog-button-spacing (framem :: , dialog :: ) => (border :: ) select (dialog-exit-buttons-position(dialog)) #"left", #"right" => 8; #"top", #"bottom" => 8; otherwise => 8; end end method default-dialog-button-spacing; define sealed method default-dialog-extra-size (framem :: , dialog :: ) => (width :: , height :: ) ignore(framem); ignoring("default-dialog-extra-size"); values(0, 0) end method default-dialog-extra-size; /// Dialog flow control // Generate an ordinary exit event define sealed method do-exit-dialog (framem :: , dialog :: , #key destroy? = #t) => () // Under GTK, we need to re-enable the owner before we dismiss // the dialog so that the focus gets returned to the right place let owner = frame-owner(dialog); let modal? = (frame-mode(dialog) == #"modal"); when (owner & modal?) frame-enabled?(owner) := #t end; frame-mapped?(dialog) := #f; distribute-event(port(dialog), make(, frame: dialog, destroy-frame?: destroy?)) end method do-exit-dialog; // Generate an "error" exit event define sealed method do-cancel-dialog (framem :: , dialog :: , #key destroy? = #t) => () let owner = frame-owner(dialog); let modal? = (frame-mode(dialog) == #"modal"); when (owner & modal?) frame-enabled?(owner) := #t end; frame-mapped?(dialog) := #f; distribute-event(port(dialog), make(, frame: dialog, destroy-frame?: destroy?)) end method do-cancel-dialog; /// Notify user define class () slot notification-dialog-result :: one-of(#"yes", #"no", #"ok", #"cancel"), init-value: #"cancel"; constant slot notification-dialog-exit-style :: , required-init-keyword: exit-style:; constant slot notification-dialog-yes-callback :: false-or() = #f, init-keyword: yes-callback:; constant slot notification-dialog-no-callback :: false-or() = #f, init-keyword: no-callback:; end class; define sealed method do-notify-user (framem :: , owner :: , message :: , style :: , #key title, documentation, exit-boxes, name, exit-style :: false-or() = #f, foreground, background, text-style) => (ok? :: , exit-type) ignore(exit-boxes); let (x, y) = begin let (x, y) = sheet-size(owner); let (x, y) = values(floor/(x, 2), floor/(y, 2)); with-device-coordinates (sheet-device-transform(owner), x, y) values(x, y) end end; let title = title | select (style) #"information" => "Note"; #"question" => "Note"; #"warning" => "Warning"; #"error" => "Error"; #"serious-error" => "Error"; #"fatal-error" => "Error"; end; let exit-style = exit-style | if (style == #"question") #"yes-no" else #"ok" end; local method notify-callback(dialog :: , result :: one-of(#"yes", #"no", #"ok")) => (); dialog.notification-dialog-result := result; exit-dialog(dialog); end method; let exit-options = select(exit-style) #"ok" => vector(exit-callback: rcurry(notify-callback, #"ok"), cancel-callback: #f); #"ok-cancel" => vector(exit-callback: rcurry(notify-callback, #"ok")); #"yes-no" => vector(yes-callback: rcurry(notify-callback, #"yes"), no-callback: rcurry(notify-callback, #"no"), exit-callback: #f, cancel-callback: #f); #"yes-no-cancel" => vector(yes-callback: rcurry(notify-callback, #"yes"), no-callback: rcurry(notify-callback, #"no"), exit-callback: #f); end; let dialog = apply(make, , x: x, y: y, title: title, foreground: foreground, background: background, exit-style: exit-style, layout: make(