-module(gs_dialog). % behaviour -define(VERSION, '2001.1101'). -vsn(?VERSION). -author('cpressey@catseye.mb.ca'). -copyright('Copyright (c)2001 Cat`s Eye Technologies. All rights reserved.'). %%% Redistribution and use in source and binary forms, with or without %%% modification, are permitted provided that the following conditions %%% are met: %%% %%% Redistributions of source code must retain the above copyright %%% notice, this list of conditions and the following disclaimer. %%% %%% Redistributions in binary form must reproduce the above copyright %%% notice, this list of conditions and the following disclaimer in %%% the documentation and/or other materials provided with the %%% distribution. %%% %%% Neither the name of Cat's Eye Technologies nor the names of its %%% contributors may be used to endorse or promote products derived %%% from this software without specific prior written permission. %%% %%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND %%% CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, %%% INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF %%% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE %%% DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE %%% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, %%% OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, %%% PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, %%% OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON %%% ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, %%% OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY %%% OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE %%% POSSIBILITY OF SUCH DAMAGE. -export([behaviour_info/1, show/4, test/0]). %%% BEGIN gs_dialog.erl %%% %%% This module specifies a common behaviour for "modal" dialog boxes %%% using GS. %%-------------------------------------------------------------------- %% behaviour_info(callbacks) -> ListOfFunctionArityTuples %% Used by R8 to check the implementation modules for consistency %% with the behaviour specification, what callbacks this module needs. behaviour_info(callbacks) -> [ %%---------------------------------------------------------------- %% Module:buttons() -> ListOfAtoms %% Should return the labels used on the main (non-extra) buttons %% of the dialog box. %% e.g. ['OK', 'Cancel']. {buttons, 0}, %%---------------------------------------------------------------- %% Module:icon() -> FileNameString | {Text, FgColour, BgColour} %% Should return the icon displayed in the dialog box. %% This should either be the fully qualified filename of a 32x32 GIF %% file (e.g. in the application's priv dir,) or a 3-tuple %% describing a simple "circle" icon to be rendered by GS itself. %% The latter option was added because some versions of Erlang for %% Windows use a Tk emulation package which is not always on the %% ball when it comes to correct image transparency and colour. %% e.g. filename:join(code:priv_dir(?MODULE), "notify.gif") {icon, 0}, %%---------------------------------------------------------------- %% Module:controls(Parent, ArgList) -> {GSControl | nil, NewArgList} %% Used by the implementation to provide extra controls in the dialog %% box, if any. If not, nil should be returned instead of the control. %% If many controls are added, it is recommended they are placed in a %% frame, with the frame returned as the control. %% The control need not have positioning information, as it will be %% assigned a pack_xy option when it is placed into the Parent frame. %% The list of arguments may be modified by this callback. %% e.g. {nil, Args} {controls, 2}, %%---------------------------------------------------------------- %% Module:on_key(ExtraControl, KeyAtom, ArgList) -> %% {button, ButtonNameAtom} | nil %% Called when a key is pressed in the dialog box. The return value %% specified whether it is linked to pressing a button, or whether it %% it is ignored and passed on to a further handler (if any.) {on_key, 3}, %%---------------------------------------------------------------- %% Module:on_button(ExtraControl, ButtonNameAtom, ArgList) -> Result %% Called when one of the main (non-extra) buttons are pressed in %% the dialog box. Since this closes the dialog box, the implementation %% module is expected to provide a final result term with this function. {on_button, 3}, %%---------------------------------------------------------------- %% Module:on_event(ExtraControl, Event, ArgList) -> Result %% Allows the implementation module to handle other GS events, %% e.g. those generated by the extra controls specified. {on_event, 3} ]. %%% Public Interface %%-------------------------------------------------------------------- %% show(ModuleNameAtom, TitleString, MessageString, ArgList) -> Result %% Display a generic modal dialog box, customized by the %% callback functions in Module. This should be called by %% the 'show' function in the Module in question. %% The argument list is passed back to the callback functions in the %% module, for retaining information pertinent to the callback module; %% the behaviour itself does not inspect or care about this list. show(Module, Title, Message, Args) -> Screen = gs:start(), Buttons = Module:buttons(), NumButtons = length(Buttons), application:load(?MODULE), {ok, Font} = application:get_env(?MODULE, font), {ok, {ScreenWidth, ScreenHeight}} = application:get_env(?MODULE, screen_size), {ok, {DialogWidth, DialogHeight}} = application:get_env(?MODULE, dialog_size), Window = gs:create(window, Screen, [{width, DialogWidth}, {height, DialogHeight}, {x, (ScreenWidth - DialogWidth) div 2}, {y, (ScreenHeight - DialogHeight) div 2}, {title, Title}, {configure, true}, {keypress, true}]), Frame = gs:create(frame, Window, [{bw, 0}, {packer_x, lists:duplicate(NumButtons, {stretch, 1})}, {packer_y, [{stretch, 1},{stretch, 2},{stretch, 1}]}]), case Module:icon() of nil -> Label = gs:create(label, Frame, [{label, {text, Message}}, {font, Font}, {justify, center}, {pack_xy, {{1, NumButtons}, 1}}]); {Text, Fg, Bg} -> InnerFrame = gs:create(frame, Frame, [{pack_xy, {{1, NumButtons}, 1}}, {bw, 0}, {packer_x, [{stretch, 1}, {fixed, 32}, {stretch, 8}]}, {packer_y, [{stretch, 1}, {fixed, 32}, {stretch, 1}]}]), IconCanvas = gs:create(canvas, InnerFrame, [{pack_xy, {2, 2}}]), IconCircle = gs:create(oval, IconCanvas, [{coords, [{0, 0}, {31, 31}]}, {fg, black}, {fill, Bg}]), IconFont = {screen, bold, 24}, {ITW,ITH} = gs:read(IconCanvas, {font_wh, {IconFont, Text}}), ITX = 16 - ITW div 2, ITY = 16 - ITH div 2, IconText = gs:create(text, IconCanvas, [{coords, [{ITX, ITY}]}, {fg, Fg}, {text, Text}, {font, IconFont}]), Label = gs:create(label, InnerFrame, [{label, {text, Message}}, {font, Font}, {justify, center}, {pack_xy, {3, {1,3}}}]); FileName when list(FileName) -> InnerFrame = gs:create(frame, Frame, [{pack_xy, {{1, NumButtons}, 1}}, {bw, 0}, {packer_x, [{stretch, 1}, {fixed, 32}, {stretch, 8}]}, {packer_y, [{stretch, 1}, {fixed, 32}, {stretch, 1}]}]), IconCanvas = gs:create(canvas, InnerFrame, [{pack_xy, {2, 2}}]), Icon = gs:create(image, IconCanvas, [{coords, [{0, 0}]}, {load_gif, FileName}]), Label = gs:create(label, InnerFrame, [{label, {text, Message}}, {font, Font}, {justify, center}, {pack_xy, {3, {1,3}}}]) end, {Extra, NewArgs} = Module:controls(Frame, Args), case Extra of nil -> gs:config(Frame, {packer_y, [{stretch, 2},{fixed, 0},{stretch, 1}]}); _ -> gs:config(Extra, {pack_xy, {{1, NumButtons}, 2}}) end, lists:foldl(fun(X, A) -> I = gs:create(frame, Frame, [{packer_x, [{stretch, 1}, {fixed, 80}, {stretch, 1}]}, {packer_y, [{stretch, 1}, {fixed, 24}, {stretch, 1}]}, {pack_xy, {A, 3}}]), gs:create(button, I, [{label, {text, atom_to_list(X)}}, {font, Font}, {data, {button, X}}, {pack_xy, {2, 2}}]), A + 1 end, 1, Buttons), gs:config(Frame, [{width, DialogWidth}, {height, DialogHeight}]), {MessageWidth, MessageHeight} = gs:read(Frame, {font_wh, {Font, Message}}), case MessageWidth of N1 when N1 > trunc(DialogWidth * 0.8) -> NewDialogWidth = trunc(MessageWidth * 1.2), gs:config(Window, [{width, NewDialogWidth}, {x, (ScreenWidth - NewDialogWidth) div 2}]); _ -> ok end, case MessageHeight of N2 when N2 > trunc(DialogHeight * 0.666) -> NewDialogHeight = trunc(MessageHeight * 1.666), gs:config(Window, [{height, NewDialogHeight}, {y, (ScreenHeight - NewDialogHeight) div 2}]); _ -> ok end, gs:config(Window, {map, true}), dialog_loop(Module, Window, Frame, Extra, NewArgs). %%-------------------------------------------------------------------- %% dialog_loop(Module, Window, Frame, Extra, Args) -> Result %% Called by show/4, handles generic events in a dialog box. dialog_loop(Module, Window, Frame, Extra, Args) -> receive {gs, Window, destroy, Data, EventArgs} -> Module:on_button(Extra, 'Cancel', Args); {gs, Window, configure, Data, [W,H | Rest]} -> gs:config(Frame, [{width, W}, {height, H}]), dialog_loop(Module, Window, Frame, Extra, Args); {gs, Window, keypress, Data, [KeyCode | Rest]} -> case Module:on_key(Extra, KeyCode, Args) of {button, ButtonType} -> Return = Module:on_button(Extra, ButtonType, Args), gs:destroy(Window), Return; _ -> dialog_loop(Module, Window, Frame, Extra, Args) end; {gs, Button, click, {button, ButtonType}, EventArgs} -> Return = Module:on_button(Extra, ButtonType, Args), gs:destroy(Window), Return; Other -> % io:fwrite("~w~n", [Other]), case Module:on_event(Extra, Other, Args) of {button, ButtonType} -> Return = Module:on_button(Extra, ButtonType, Args), gs:destroy(Window), Return; _ -> dialog_loop(Module, Window, Frame, Extra, Args) end end. %%-------------------------------------------------------------------- %% test() -> ResultTuple. %% Tests some of the common dialog boxes implemented with this behaviour. test() -> A = gs_dialog_notify:show("Notification", "This is a notification dialog."), B = gs_dialog_confirm:show("Confirmation", "Are you sure you want to\ntake some sort of drastic action?"), C = gs_dialog_question:show("Question", "Save your barcodes first?"), D = gs_dialog_entry:show("Text Entry", "Enter the address of this order:", "555 Twenty-third St."), E = gs_dialog_list:show("Select One", "Select a game to play.", ["Chess", "Checkers", "Othello", "Go", "Backgammon", "Kali", "Sink"]), F = gs_dialog_color:show("Choose Colour", "Pick your favourite colour.", {255, 0, 128}), G = gs_dialog_notify:show("Lengthy Notification", "This is an extremely long message with no line breaks. " "The dialog box should expand to display the entire message."), H = gs_dialog_notify:show("Lengthy Notification", "This is an extremely\nlong message with\nmany lines.\n\n" "The dialog box\nshould\nexpand\nto\ndisplay\nthe\nentire\nmessage."), {A,B,C,D,E,F,G,H}. %%% END of gs_dialog.erl %%%