%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2008-2013. 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(dbg_wx_win).
%% External exports
-export([init/0,
create_menus/4, %% For wx
add_break/3, update_break/2, delete_break/1,
motion/2,
confirm/2, notify/2, entry/4, open_help/2,
to_string/1, to_string/2,
find_icon/1
]).
-record(break, {mb, smi, emi, dimi, demi}).
-include_lib("wx/include/wx.hrl").
%%====================================================================
%% External exports
%%====================================================================
%%--------------------------------------------------------------------
%% init() -> GS
%% GS = term()
%%--------------------------------------------------------------------
init() ->
wx:new().
%%--------------------------------------------------------------------
%% create_menus(MenuBar, [Menu])
%% MenuBar = gsobj()
%% Menu = {Name, [Item]}
%% Name = atom()
%% Item = {Name, N} | {Name, N, Type} | {Name, N, cascade, [Item]}
%% | separator
%% N = no | integer()
%% Type = check | radio
%% Create the specified menus and menuitems.
%%
%% Normal menuitems are specified as {Name, N}. Generates the event:
%% {gs, _Id, click, {menuitem, Name}, _Arg}
%%
%% Check and radio menuitems are specified as {Name, N, check|radio}.
%% They are assumed to be children to a cascade menuitem! (And all children
%% to one cascade menuitem are assumed to be either check OR radio
%% menuitems)!
%% Selecting a check/radio menuitem generates the event:
%% {gs, _Id, click, {menu, Menu}, _Arg}
%% where Menu is the name of the parent, the cascade menuitem.
%% Use selected(Menu) to retrieve which check/radio menuitems are
%% selected.
%%--------------------------------------------------------------------
create_menus(MB, [{Title,Items}|Ms], Win, Id0) ->
Menu = wxMenu:new([]),
put(Title,Menu),
Id = create_menu_item(Menu, Items, Win, Id0, true),
wxMenuBar:append(MB,Menu,menu_name(Title,ignore)),
create_menus(MB,Ms,Win,Id);
create_menus(_MB,[], _Win,Id) ->
Id.
create_menu_item(Menu, [separator|Is], Win, Id,Connect) ->
wxMenu:appendSeparator(Menu),
create_menu_item(Menu,Is,Win,Id+1,Connect);
create_menu_item(Menu, [{Name, _N, cascade, Items}|Is], Win, Id0,Connect) ->
Sub = wxMenu:new([]),
Id = create_menu_item(Sub, Items, Win, Id0, false),
wxMenu:append(Menu, ?wxID_ANY, menu_name(Name,ignore), Sub),
%% Simulate GS sub checkBox/RadioBox behaviour
Self = self(),
Butts = [{MI,get(MI)} || {MI,_,_} <- Items],
IsChecked = fun({MiName,MI},Acc) ->
case wxMenuItem:isChecked(MI) of
true -> [MiName|Acc];
false -> Acc
end
end,
Filter = fun(_,_) ->
Enabled = lists:foldl(IsChecked, [], Butts),
Self ! #wx{userData={Name, Enabled},
event=#wxCommand{type=command_menu_selected}}
end,
wxMenu:connect(Win, command_menu_selected,
[{id,Id0},{lastId, Id-1},{callback,Filter}]),
create_menu_item(Menu, Is, Win, Id, Connect);
create_menu_item(Menu, [{Name,Pos}|Is], Win, Id, Connect) ->
MenuId = case lists:member(Name, ['Debugger']) of
true -> ?wxID_HELP;
_ -> Id
end,
Item = wxMenu:append(Menu, MenuId, menu_name(Name,Pos)),
put(Name,Item),
if Connect ->
wxMenu:connect(Win, command_menu_selected, [{id,MenuId},{userData, Name}]);
true -> ignore
end,
create_menu_item(Menu,Is,Win,Id+1, Connect);
create_menu_item(Menu, [{Name,N,check}|Is], Win, Id, Connect) ->
Item = wxMenu:appendCheckItem(Menu, Id, menu_name(Name,N)),
put(Name,Item),
if Connect ->
wxMenu:connect(Win, command_menu_selected, [{id,Id},{userData, Name}]);
true -> ignore
end,
create_menu_item(Menu,Is,Win,Id+1,Connect);
create_menu_item(Menu, [{Name, N, radio}|Is], Win, Id,Connect) ->
Item = wxMenu:appendRadioItem(Menu, Id, menu_name(Name,N)),
put(Name,Item),
if Connect ->
wxMenu:connect(Win, command_menu_selected, [{id,Id},{userData, Name}]);
true -> ignore
end,
create_menu_item(Menu,Is,Win,Id+1,Connect);
create_menu_item(_, [], _, Id,_) ->
Id.
%%--------------------------------------------------------------------
%% add_break(Name, Point) -> #break{}
%% Name = atom()
%% Point = {Mod, Line}
%% The break will generate the following events:
%% #wx{userData= {break, Point, Event}}
%% Event = delete | {trigger, Action} | {status, Status}
%% Action = enable | disable | delete
%% Status = active | inactive
%%--------------------------------------------------------------------
add_break(Win, MenuName, Point) ->
%% Create a name for the breakpoint
{Mod, Line} = Point,
Label = to_string("~w ~5w", [Mod, Line]),
Menu = get(MenuName),
%% Create a menu for the breakpoint
Add = fun(Item,Action) ->
Id = wxMenuItem:getId(Item),
wxMenu:connect(Win, command_menu_selected,
[{id,Id}, {userData, Action}])
end,
Sub = wxMenu:new([]),
Dis = wxMenu:append(Sub, ?wxID_ANY, "Disable"),
Add(Dis, {break,Point,status}),
Del = wxMenu:append(Sub, ?wxID_ANY, "Delete"),
Add(Del, {break,Point,delete}),
Trigger = wxMenu:new([]),
Enable = wxMenu:appendRadioItem(Trigger, ?wxID_ANY,"Enable"),
Add(Enable, {break,Point,{trigger,enable}}),
TDisable = wxMenu:appendRadioItem(Trigger, ?wxID_ANY,"Disable"),
Add(TDisable, {break,Point,{trigger,disable}}),
Delete = wxMenu:appendRadioItem(Trigger, ?wxID_ANY,"Delete"),
Add(Delete, {break,Point,{trigger,delete}}),
wxMenu:append(Sub, ?wxID_ANY, "Trigger Action", Trigger),
MenuBtn = wxMenu:append(Menu,?wxID_ANY, Label, Sub),
#break{mb={Menu,MenuBtn},
smi=Dis, emi=Enable, dimi=TDisable, demi=Delete}.
%%--------------------------------------------------------------------
%% update_break(Break, Options)
%% Break = #break{}
%% Options = [Status, Action, Mods, Cond]
%% Status = active | inactive
%% Action = enable | disable | delete
%% Mods = null (not used)
%% Cond = null | {Mod, Func}
%%--------------------------------------------------------------------
update_break(Break, Options) ->
[Status, Trigger|_] = Options,
Label = case Status of
active -> "Disable";
inactive -> "Enable"
end,
wxMenuItem:setText(Break#break.smi, Label),
TriggerMI = case Trigger of
enable -> Break#break.emi;
disable -> Break#break.dimi;
delete -> Break#break.demi
end,
wxMenuItem:check(TriggerMI).
%%--------------------------------------------------------------------
%% delete_break(Break)
%% Break = #break{}
%%--------------------------------------------------------------------
delete_break(Break) ->
{Menu, MenuBtn} = Break#break.mb,
wxMenu:'Destroy'(Menu,MenuBtn).
%%--------------------------------------------------------------------
%% motion(X, Y) -> {X, Y}
%% X = Y = integer()
%%--------------------------------------------------------------------
motion(X, Y) ->
receive
{gs, _Id, motion, _Data, [NX,NY]} ->
motion(NX, NY)
after 0 ->
{X, Y}
end.
%%--------------------------------------------------------------------
%% confirm(MonWin, String) -> ok | cancel
%%--------------------------------------------------------------------
confirm(Win,Message) ->
MD = wxMessageDialog:new(Win,to_string(Message),
[{style, ?wxOK bor ?wxCANCEL},
{caption, "Confirm"}]),
Res = case wxDialog:showModal(MD) of
?wxID_OK -> ok;
_ -> cancel
end,
wxDialog:destroy(MD),
Res.
%%--------------------------------------------------------------------
%% notify(MonWin, String) -> ok
%%--------------------------------------------------------------------
notify(Win,Message) ->
MD = wxMessageDialog:new(Win,to_string(Message),
[{style, ?wxOK},
{caption, "Confirm"}]),
wxDialog:showModal(MD),
wxDialog:destroy(MD),
ok.
%%--------------------------------------------------------------------
%% entry(Parent, Title, Prompt, {Type, Value}) -> {Prompt, Val} | cancel
%%--------------------------------------------------------------------
entry(Parent, Title, Prompt, {Type, Value}) ->
Ted = wxTextEntryDialog:new(Parent, to_string(Prompt),
[{caption, to_string(Title)},
{value, Value}]),
case wxDialog:showModal(Ted) of
?wxID_OK ->
Res = case verify(Type, wxTextEntryDialog:getValue(Ted)) of
{edit,NewVal} ->
{Prompt,NewVal};
ignore ->
cancel
end,
wxTextEntryDialog:destroy(Ted),
Res;
_ ->
cancel
end.
verify(Type, Str) ->
case erl_scan:string(Str) of
{ok, Tokens, _EndLine} when Type==term ->
case erl_parse:parse_term(Tokens++[{dot, 1}]) of
{ok, Value} -> {edit, Value};
_Error ->
ignore
end;
{ok, [{Type, _Line, Value}], _EndLine} when Type/=term ->
{edit, Value};
_Err ->
ignore
end.
%%--------------------------------------------------------------------
%% open_help/2
%% opens browser with help file
%%--------------------------------------------------------------------
open_help(_Parent, HelpHtmlFile) ->
wx_misc:launchDefaultBrowser("file://" ++ HelpHtmlFile).
%%--------------------------------------------------------------------
%% to_string(Term) -> [integer()]
%% to_string(Format,Args) -> [integer()]
%%--------------------------------------------------------------------
to_string(Atom) when is_atom(Atom) ->
atom_to_list(Atom);
to_string(Integer) when is_integer(Integer) ->
integer_to_list(Integer);
to_string([]) -> "";
to_string(List) when is_list(List) ->
List;
to_string(Term) ->
io_lib:format("~tp",[Term]).
to_string(Format,Args) ->
io_lib:format(Format, Args).
menu_name(Atom, N) when is_atom(Atom) ->
menu_name(atom_to_list(Atom),N);
menu_name("Help", _) -> %% Mac needs this to be exactly this
"&Help";
menu_name(Str, Pos) when is_integer(Pos) ->
{S1,S2} = lists:split(Pos,Str),
S1 ++ [$&|S2];
menu_name(Str,_) ->
Str.
%%--------------------------------------------------------------------
%% find_icon(File) -> Path or exists
%%--------------------------------------------------------------------
find_icon(File) ->
PrivDir = code:priv_dir(debugger),
PrivIcon = filename:append(PrivDir, File),
case filelib:is_regular(PrivIcon) of
true -> PrivIcon;
false ->
CurrDir = filename:dirname(code:which(?MODULE)),
CurrIcon = filename:append(CurrDir, File),
true = filelib:is_regular(CurrIcon),
CurrIcon
end.