%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 1997-2009. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. %% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. %% %% %CopyrightEnd% %% %% -module(tool_utils). -include_lib("kernel/include/file.hrl"). %%%--------------------------------------------------------------------- %%% Auxiliary functions to be used by the tools (internal module) %%%--------------------------------------------------------------------- %% External exports -export([open_help/2]). -export([file_dialog/1]). -export([notify/2, confirm/2, confirm_yesno/2, request/2]). -record(state, {type, % notify | confirm[_yesno] | request win, % gsobj(), window entry, % gsobj(), entry in_focus, % 0 | 1 | undefined Entry is in focus is_cursor, % bool() | undefined Cursor is over Entry buttons, % [gsobj()], buttons highlighted % int() highlighted buttone }). %%---------------------------------------------------------------------- %% open_help(Parent, File) %% Parent = gsobj() (GS root object or parent window) %% File = string() | nofile %% View the help file File, which can be an URL, an HTML file or a text %% file. %% This function is OS dependant. %% Unix: Assumes Netscape is up & running, and use Netscape remote %% commands to display the file. %% NT: If File is a file, use the NT command 'start' which will open the %% default tool for viewing the file. %% If File is an URL, try to view it using Netscape.exe which %% requires that the path Netscape.exe must be in TBD. %% (TEMPORARY solution..., can be done better) %%---------------------------------------------------------------------- open_help(Parent, nofile) -> notify(Parent, "Sorry, no help information exists"); open_help(Parent, File) -> case application:get_env(kernel, browser_cmd) of undefined -> open_help_default(Parent, File); {ok, Cmd} when is_list(Cmd) -> spawn(os, cmd, [Cmd ++ " " ++ File]); {ok, {M, F, A}} -> apply(M, F, [File|A]); _Other -> Str = ["Bad Kernel configuration parameter browser_cmd", "Do not know how to display help file"], notify(Parent, Str) end. open_help_default(Parent, File) -> Cmd = case file_type(File) of %% Local file local -> case os:type() of {unix,Type} -> case Type of darwin -> "open " ++ File; _Else -> "netscape -remote \"openURL(file:" ++ File ++ ")\"" end; {win32,_AnyType} -> "start " ++ filename:nativename(File); _Other -> unknown end; %% URL remote -> case os:type() of {unix,Type} -> case Type of darwin -> "open " ++ File; _Else -> "netscape -remote \"openURL(file:" ++ File ++ ")\"" end; {win32,_AnyType} -> "netscape.exe -h " ++ regexp:gsub(File,"\\\\","/"); _Other -> unknown end; Error -> % {error,Reason} Error end, if is_list(Cmd) -> spawn(os, cmd, [Cmd]); Cmd==unknown -> Str = ["Sorry, do not know how to", "display HTML files at this platform"], notify(Parent, Str); true -> {error, Reason} = Cmd, Str = file:format_error(Reason), notify(Parent, [File,Str]) end. %% file_type(File) -> local | remote | {error,Reason} %% File = string() %% Reason - see file(3) %% Returns local if File is an existing, readable file %% Returns remote if File is a remote URL (ie begins with 'http:') file_type(File) -> case File of "http://"++_URL -> remote; _ -> %% HTML files can have a tag (.html#tag), this must be %% removed when checking if the file exists File2 = case filename:extension(File) of ".html#"++_Index -> filename:rootname(File)++".html"; _ -> File end, case file:read_file_info(File2) of {ok, FileInfo} when FileInfo#file_info.type==regular, FileInfo#file_info.access/=none -> local; {ok, FileInfo} when FileInfo#file_info.type/=regular -> {error,einval}; {ok, FileInfo} when FileInfo#file_info.access==none -> {error,eacces}; Error -> Error end end. %%---------------------------------------------------------------------- %% file_dialog(Options) -> tbd %%---------------------------------------------------------------------- file_dialog(Options) -> tool_file_dialog:start(Options). %%---------------------------------------------------------------------- %% notify(Parent, Strings) -> ok %% confirm(Parent, Strings) -> ok | cancel %% confirm_yesno(Parent, Strings) -> yes | no | cancel %% request(Parent, Strings) -> {ok,string()} | cancel %% Parent = gsobj() (GS root object or parent window) %% Strings = string() | [string()] %% Opens a window with the specified message (Strings) and locks the GUI %% until the user confirms the message. %% If the Parent argument is the parent window, the help window will be %% centered above it, otherwise it can end up anywhere on the screen. %% A 'notify' window contains an 'Ok' button. %% A 'confirm' window contains an 'Ok' and a 'Cancel' button. %% A 'confirm_yesno' window contains a 'Yes', a 'No', and a 'Cancel' %% button. %% A 'request' window contains an entry, an 'Ok' and a 'Cancel' button. %%---------------------------------------------------------------------- -define(Wlbl, 130). -define(Hlbl, 30). -define(Hent, 30). -define(Wbtn, 50). -define(Hbtn, 30). -define(PAD, 10). notify(Parent, Strings) -> help_win(notify, Parent, Strings). confirm(Parent, Strings) -> help_win(confirm, Parent, Strings). confirm_yesno(Parent, Strings) -> help_win(confirm_yesno, Parent, Strings). request(Parent, Strings) -> help_win(request, Parent, Strings). help_win(Type, Parent, Strings) -> GenOpts = [{keypress,true}], GenOpts2 = [{font,{screen,12}} | GenOpts], Buttons = buttons(Type), Nbtn = length(Buttons), %% Create the window and its contents Win = gs:create(window, Parent, [{title,title(Type)} | GenOpts]), Top = gs:create(frame, Win, GenOpts), Lbl = gs:create(label, Top, [{align,c}, {justify,center}|GenOpts2]), Mid = if Type==request -> gs:create(frame, Win, GenOpts); true -> ignore end, Ent = if Type==request -> Events = [{setfocus,true}, {focus,true},{enter,true},{leave,true}], gs:create(entry, Mid, GenOpts2++Events); true -> ignore end, Bot = gs:create(frame, Win, GenOpts), %% Find out minimum size required for label, entry and buttons Font = gs:read(Parent, {choose_font, {screen,12}}), Text = insert_newlines(Strings), {Wlbl0,Hlbl0} = gs:read(Lbl, {font_wh,{Font,Text}}), {_Went0,Hent0} = gs:read(Lbl, {font_wh,{Font,"Entry"}}), {Wbtn0,Hbtn0} = gs:read(Lbl, {font_wh,{Font,"Cancel"}}), %% Compute size of the objects and adjust the graphics accordingly Wbtn = erlang:max(Wbtn0+10, ?Wbtn), Hbtn = erlang:max(Hbtn0+10, ?Hbtn), Hent = erlang:max(Hent0+10, ?Hent), Wlbl = erlang:max(Wlbl0, erlang:max(Nbtn*Wbtn+(Nbtn-1)*?PAD, ?Wlbl)), Hlbl = erlang:max(Hlbl0, ?Hlbl), Wwin = ?PAD+Wlbl+?PAD, Htop = ?PAD+Hlbl, Hmid = if Type==request -> ?PAD+Hent; true -> 0 end, Hbot = ?PAD+Hbtn+?PAD, Hwin = Htop+Hmid+Hbot, case catch get_coords(Parent, Wwin, Hwin) of {Xw, Yw} when is_integer(Xw), is_integer(Yw) -> gs:config(Win, [{x,Xw}, {y,Yw}]); _ -> ignore end, gs:config(Win, [ {width,Wwin},{height,Hwin}]), gs:config(Top, [{x,0}, {y,0}, {width,Wwin},{height,Htop}]), gs:config(Lbl, [{x,?PAD},{y,?PAD}, {width,Wlbl},{height,Hlbl}]), gs:config(Mid, [{x,0}, {y,Htop}, {width,Wwin},{height,Hmid}]), gs:config(Ent, [{x,?PAD},{y,?PAD}, {width,Wlbl},{height,Hent}]), gs:config(Bot, [{x,0}, {y,Htop+Hmid},{width,Wwin},{height,Hbot}]), %% Insert the label text gs:config(Lbl, {label,{text,Text}}), %% Add the buttons Xbtns = xbuttons(Buttons, Wbtn, Wwin, Wlbl), BtnObjs = lists:map(fun({Btext,BX}) -> gs:create(button, Bot, [{x,BX-1}, {y,?PAD-1}, {width,Wbtn+2}, {height,Hbtn+2}, {label,{text,Btext}}, {data,data(Btext)} | GenOpts2]) end, Xbtns), Highlighted = highlight(undef, 1, BtnObjs), gs:config(Win, [{map,true}]), State = if Type==request -> #state{in_focus=1, is_cursor=false}; true -> #state{} end, event_loop(State#state{type=Type, win=Win, entry=Ent, buttons=BtnObjs, highlighted=Highlighted}). title(notify) -> "Notification"; title(confirm) -> "Confirmation"; title(confirm_yesno) -> "Confirmation"; title(request) -> "Request". buttons(notify) -> ["Ok"]; buttons(confirm) -> ["Ok", "Cancel"]; buttons(confirm_yesno) -> ["Yes", "No", "Cancel"]; buttons(request) -> ["Ok", "Cancel"]. data("Ok") -> {helpwin,ok}; data("Yes") -> {helpwin,yes}; data("No") -> {helpwin,no}; data("Cancel") -> {helpwin,cancel}. get_coords(Parent, W, H) -> case gs:read(Parent, x) of X when is_integer(X) -> case gs:read(Parent, y) of Y when is_integer(Y) -> case gs:read(Parent, width) of W0 when is_integer(W0) -> case gs:read(Parent, height) of H0 when is_integer(H0) -> {round((X+W0/2)-W/2), round((Y+H0/2)-H/2)}; _ -> error end; _ -> error end; _ -> error end; _ -> error end. xbuttons([B], Wbtn, Wwin, _Wlbl) -> [{B, round(Wwin/2-Wbtn/2)}]; xbuttons([B1,B2], Wbtn, Wwin, Wlbl) -> Margin = (Wwin-Wlbl)/2, [{B1,round(Margin)}, {B2,round(Wwin-Margin-Wbtn)}]; xbuttons([B1,B2,B3], Wbtn, Wwin, Wlbl) -> Margin = (Wwin-Wlbl)/2, [{B1,round(Margin)}, {B2,round(Wwin/2-Wbtn/2)}, {B3,round(Wwin-Margin-Wbtn)}]. highlight(Prev, New, BtnObjs) when New>0, New= if Prev==undef -> ignore; true -> gs:config(lists:nth(Prev, BtnObjs), [{highlightbw,0}]) end, gs:config(lists:nth(New, BtnObjs), [{highlightbw,1}, {highlightbg,black}]), New; highlight(Prev, _New, _BtnObjs) -> % New is outside allowed range Prev. event_loop(State) -> receive GsEvent when element(1, GsEvent)==gs -> case handle_event(GsEvent, State) of {continue, NewState} -> event_loop(NewState); {return, Result} -> gs:destroy(State#state.win), Result end end. handle_event({gs,_,click,{helpwin,Result},_}, State) -> if State#state.type/=request; Result==cancel -> {return, Result}; State#state.type==request, Result==ok -> case gs:read(State#state.entry, text) of "" -> {continue, State}; Info -> {return, {ok, Info}} end end; %% When the entry (Type==request) is in focus and the mouse pointer is %% over it, don't let 'Left'|'Right' keypresses affect which button is %% selected handle_event({gs,Ent,enter,_,_}, #state{entry=Ent}=State) -> {continue, State#state{is_cursor=true}}; handle_event({gs,Ent,leave,_,_}, #state{entry=Ent}=State) -> {continue, State#state{is_cursor=false}}; handle_event({gs,Ent,focus,_,[Int|_]}, #state{entry=Ent}=State) -> {continue, State#state{in_focus=Int}}; handle_event({gs,Win,keypress,_,['Right'|_]}, #state{win=Win}=State) -> if State#state.type==request, State#state.in_focus==1, State#state.is_cursor==true -> {continue, State}; true -> Prev = State#state.highlighted, New = highlight(Prev, Prev+1, State#state.buttons), {continue, State#state{highlighted=New}} end; handle_event({gs,Win,keypress,_,['Left'|_]}, #state{win=Win}=State) -> if State#state.type==request, State#state.in_focus==1, State#state.is_cursor==true -> {continue, State}; true -> Prev = State#state.highlighted, New = highlight(Prev, Prev-1, State#state.buttons), {continue, State#state{highlighted=New}} end; handle_event({gs,Ent,keypress,_,['Tab'|_]}, #state{entry=Ent}=State) -> gs:config(hd(State#state.buttons), {setfocus,true}), gs:config(Ent, {select,clear}), {continue, State#state{in_focus=0}}; handle_event({gs,Win,keypress,_,['Return'|_]}, #state{win=Win}=State) -> Selected = lists:nth(State#state.highlighted, State#state.buttons), Data = gs:read(Selected, data), handle_event({gs,Win,click,Data,undef}, State); handle_event({gs,Win,destroy,_,_}, #state{win=Win}=State) -> if State#state.type==notify -> {return, ok}; true -> {return, cancel} end; %% Flush any other GS events handle_event({gs,_Obj,_Event,_Data,_Arg}, State) -> {continue, State}. %% insert_newlines(Strings) => string() %% Strings - string() | [string()] %% If Strings is a list of strings, return a string where all these %% strings are concatenated with newlines in between,otherwise return %% Strings. insert_newlines([String|Rest]) when is_list(String),Rest/=[]-> String ++ "\n" ++ insert_newlines(Rest); insert_newlines([Last]) -> [Last]; insert_newlines(Other) -> Other.