aboutsummaryrefslogblamecommitdiffstats
path: root/lib/observer/src/observer_lib.erl
blob: ce85fb4af641c0888cabfb1c0e41e590f4460eca (plain) (tree)
1
2
3
4


                   
                                                        
















                                                                         

                                                        
                                                        
                                                              
                                            
                        


                                 
           

                                  
                              
 



                                                                              






































































                                                                                      





                                                                   






                                                                                         








                                                                                




                                          







                                                                   
                                                                         








                                                                               

                                                         
                                
                                           

                                                                 





                                                                         










                                                                        









                                                                     
                                              





                                    




                                                                               


                     
                     
      
                                                















                                                        



                                                       
                                             
                              
                                                             














                                                            

                                
 
                                        











































                                                                  





                                                                                   

                 

                                               
                
                                                                

                                          
                                                                










                                                             








                                                                                 
            






                                                                                         






















































                                                                                  

                                              

































                                                                               


                                                  
                      
 







































                                                                      


























                                                                               







                                                                      
                                                  
                       

                                              

        



















































                                                                             

                    













                                                                                

                                               









                                     


















                                                                     
%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2011-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(observer_lib).

-export([get_wx_parent/1,
	 display_info_dialog/1, display_yes_no_dialog/1,
	 user_term/3, user_term_multiline/3,
	 interval_dialog/4, start_timer/1, stop_timer/1,
	 display_info/2, fill_info/2, update_info/2, to_str/1,
	 create_menus/3, create_menu_item/3,
	 create_attrs/0,
	 set_listctrl_col_size/2,
	 create_status_bar/1,
	 html_window/2
	]).

-include_lib("wx/include/wx.hrl").
-include("observer_defs.hrl").

-define(SINGLE_LINE_STYLE, ?wxBORDER_NONE bor ?wxTE_READONLY bor ?wxTE_RICH2).
-define(MULTI_LINE_STYLE, ?SINGLE_LINE_STYLE bor ?wxTE_MULTILINE).


get_wx_parent(Window) ->
    Parent = wxWindow:getParent(Window),
    case wx:is_null(Parent) of
	true -> Window;
	false -> get_wx_parent(Parent)
    end.

interval_dialog(Parent0, {Timer, Value}, Min, Max) ->
    Parent = get_wx_parent(Parent0),
    Dialog = wxDialog:new(Parent, ?wxID_ANY, "Update Interval",
			  [{style, ?wxDEFAULT_DIALOG_STYLE bor
				?wxRESIZE_BORDER}]),
    Panel = wxPanel:new(Dialog),
    Check = wxCheckBox:new(Panel, ?wxID_ANY, "Periodical refresh"),
    wxCheckBox:setValue(Check, Timer /= false),
    Style = ?wxSL_HORIZONTAL bor ?wxSL_AUTOTICKS bor ?wxSL_LABELS,
    Slider = wxSlider:new(Panel, ?wxID_ANY, Value, Min, Max,
			  [{style, Style}, {size, {200, -1}}]),
    wxWindow:enable(Slider, [{enable, Timer /= false}]),
    InnerSizer = wxBoxSizer:new(?wxVERTICAL),
    Buttons = wxDialog:createButtonSizer(Dialog, ?wxOK bor ?wxCANCEL),
    Flags = [{flag, ?wxEXPAND bor ?wxALL}, {border, 2}],
    wxSizer:add(InnerSizer, Check,  Flags),
    wxSizer:add(InnerSizer, Slider, Flags),
    wxPanel:setSizer(Panel, InnerSizer),
    TopSizer = wxBoxSizer:new(?wxVERTICAL),
    wxSizer:add(TopSizer, Panel, [{flag, ?wxEXPAND bor ?wxALL}, {border, 5}]),
    wxSizer:add(TopSizer, Buttons, [{flag, ?wxEXPAND}]),
    wxWindow:setSizerAndFit(Dialog, TopSizer),
    wxSizer:setSizeHints(TopSizer, Dialog),
    wxCheckBox:connect(Check, command_checkbox_clicked,
		       [{callback, fun(#wx{event=#wxCommand{commandInt=Enable0}},_) ->
					   Enable = Enable0 > 0,
					   wxWindow:enable(Slider, [{enable, Enable}])
				   end}]),
    Res = case wxDialog:showModal(Dialog) of
	      ?wxID_OK ->
		  Enabled = wxCheckBox:isChecked(Check),
		  setup_timer(Enabled, {Timer, wxSlider:getValue(Slider)});
	      ?wxID_CANCEL ->
		  {Timer, Value}
	  end,
    wxDialog:destroy(Dialog),
    Res.

stop_timer(Timer = {false, _}) -> Timer;
stop_timer(Timer = {true, _}) -> Timer;
stop_timer(Timer = {_, Intv}) ->
    setup_timer(false, Timer),
    {true, Intv}.
start_timer(Intv) when is_integer(Intv) ->
    setup_timer(true, {true, Intv});
start_timer(Timer) ->
    setup_timer(true, Timer).

setup_timer(false, {Timer, Value})
  when is_boolean(Timer) ->
    {false, Value};
setup_timer(true,  {false, Value}) ->
    {ok, Timer} = timer:send_interval(Value * 1000, refresh_interval),
    {Timer, Value};
setup_timer(Bool, {Timer, Old}) ->
    timer:cancel(Timer),
    setup_timer(Bool, {false, Old}).

display_info_dialog(Str) ->
    Dlg = wxMessageDialog:new(wx:null(), Str),
    wxMessageDialog:showModal(Dlg),
    wxMessageDialog:destroy(Dlg),
    ok.

display_yes_no_dialog(Str) ->
    Dlg = wxMessageDialog:new(wx:null(), Str, [{style,?wxYES_NO}]),
    R = wxMessageDialog:showModal(Dlg),
    wxMessageDialog:destroy(Dlg),
    R.

%% display_info(Parent, [{Title, [{Label, Info}]}]) -> {Panel, Sizer, InfoFieldsToUpdate}
display_info(Frame, Info) ->
    Panel = wxPanel:new(Frame),
    wxWindow:setBackgroundColour(Panel, {255,255,255}),
    Sizer = wxBoxSizer:new(?wxVERTICAL),
    wxSizer:addSpacer(Sizer, 5),
    Add = fun(BoxInfo) ->
		  case create_box(Panel, BoxInfo) of
		      {Box, InfoFs} ->
			  wxSizer:add(Sizer, Box, [{flag, ?wxEXPAND bor ?wxALL},
						   {border, 5}]),
			  wxSizer:addSpacer(Sizer, 5),
			  InfoFs;
		      undefined ->
			  []
		  end
	  end,
    InfoFs = [Add(I) || I <- Info],
    wxWindow:setSizerAndFit(Panel, Sizer),
    {Panel, Sizer, InfoFs}.

fill_info([{dynamic, Key}|Rest], Data)
  when is_atom(Key); is_function(Key) ->
    %% Special case used by crashdump_viewer when the value decides
    %% which header to use
    case get_value(Key, Data) of
	undefined -> fill_info(Rest, Data);
	{Str,Value} -> [{Str, Value} | fill_info(Rest, Data)]
    end;
fill_info([{Str, Key}|Rest], Data) when is_atom(Key); is_function(Key) ->
    case get_value(Key, Data) of
	undefined -> fill_info(Rest, Data);
	Value -> [{Str, Value} | fill_info(Rest, Data)]
    end;
fill_info([{Str,Attrib,Key}|Rest], Data) when is_atom(Key); is_function(Key) ->
    case get_value(Key, Data) of
	undefined -> fill_info(Rest, Data);
	Value -> [{Str,Attrib,Value} | fill_info(Rest, Data)]
    end;
fill_info([{Str, {Format, Key}}|Rest], Data)
  when is_atom(Key); is_function(Key), is_atom(Format) ->
    case get_value(Key, Data) of
	undefined -> fill_info(Rest, Data);
	Value -> [{Str, {Format, Value}} | fill_info(Rest, Data)]
    end;
fill_info([{Str, Attrib, {Format, Key}}|Rest], Data)
  when is_atom(Key); is_function(Key), is_atom(Format) ->
    case get_value(Key, Data) of
	undefined -> fill_info(Rest, Data);
	Value -> [{Str, Attrib, {Format, Value}} | fill_info(Rest, Data)]
    end;
fill_info([{Str,SubStructure}|Rest], Data) when is_list(SubStructure) ->
    [{Str, fill_info(SubStructure, Data)}|fill_info(Rest,Data)];
fill_info([{Str,Attrib,SubStructure}|Rest], Data) ->
    [{Str, Attrib, fill_info(SubStructure, Data)}|fill_info(Rest,Data)];
fill_info([], _) -> [].

get_value(Key, Data) when is_atom(Key) ->
    proplists:get_value(Key,Data);
get_value(Fun, Data) when is_function(Fun) ->
    Fun(Data).

update_info([Fields|Fs], [{_Header, SubStructure}| Rest]) ->
    update_info2(Fields, SubStructure),
    update_info(Fs, Rest);
update_info([Fields|Fs], [{_Header, _Attrib, SubStructure}| Rest]) ->
    update_info2(Fields, SubStructure),
    update_info(Fs, Rest);
update_info([], []) ->
    ok.

update_info2([Field|Fs], [{_Str, Value}|Rest]) ->
    wxTextCtrl:setValue(Field, to_str(Value)),
    update_info2(Fs, Rest);
update_info2([], []) -> ok.


to_str(Value) when is_atom(Value) ->
    atom_to_list(Value);
to_str({Unit, X}) when (Unit==bytes orelse Unit==time_ms) andalso is_list(X) ->
    try list_to_integer(X) of
	B -> to_str({Unit,B})
    catch error:badarg -> X
    end;
to_str({bytes, B}) ->
    KB = B div 1024,
    MB = KB div 1024,
    GB = MB div 1024,
    if
	GB > 10 -> integer_to_list(GB) ++ " gB";
	MB > 10 -> integer_to_list(MB) ++ " mB";
	KB >  0 -> integer_to_list(KB) ++ " kB";
	true -> integer_to_list(B) ++ " B "
    end;
to_str({time_ms, MS}) ->
    S = MS div 1000,
    Min = S div 60,
    Hours = Min div 60,
    Days = Hours div 24,
    if
	Days > 0 -> integer_to_list(Days) ++ " Days";
	Hours > 0 -> integer_to_list(Hours) ++ " Hours";
	Min > 0 -> integer_to_list(Min) ++ " Mins";
	true -> integer_to_list(S) ++ " Secs"
    end;

to_str({func, {F,A}}) when is_atom(F), is_integer(A) ->
    lists:concat([F, "/", A]);
to_str({func, {F,'_'}}) when is_atom(F) ->
    atom_to_list(F);
to_str({A, B}) when is_atom(A), is_atom(B) ->
    lists:concat([A, ":", B]);
to_str({M,F,A}) when is_atom(M), is_atom(F), is_integer(A) ->
    lists:concat([M, ":", F, "/", A]);
to_str(Value) when is_list(Value) ->
    case lists:all(fun(X) -> is_integer(X) end, Value) of
	true -> Value;
	false ->
	    lists:foldl(fun(X, Acc) ->
				to_str(X) ++ " " ++ Acc end,
			"", Value)
    end;
to_str(Port) when is_port(Port) ->
    erlang:port_to_list(Port);
to_str(Pid) when is_pid(Pid) ->
    pid_to_list(Pid);
to_str(No) when is_integer(No) ->
    integer_to_list(No);
to_str(Term) ->
    io_lib:format("~w", [Term]).

create_menus([], _MenuBar, _Type) -> ok;
create_menus(Menus, MenuBar, Type) ->
    Add = fun({Tag, Ms}, Index) ->
		  create_menu(Tag, Ms, Index, MenuBar, Type)
	  end,
    [{First, _}|_] = Menus,
    OnMac = os:type() =:= {unix, darwin},
    Index = if Type =:= default -> 0;
	       First =:= "File" -> 0;
	       OnMac -> 0;
	       true -> 1
	    end,
    wx:foldl(Add, Index, Menus),
    ok.

create_menu("File", MenuItems, Index, MenuBar, Type) ->
    OnMac = os:type() =:= {unix, darwin},
    if OnMac, Type =:= default ->
	    Index;
       not OnMac, Type =:= plugin ->
	    MenuId = wxMenuBar:findMenu(MenuBar, "File"),
	    Menu = wxMenuBar:getMenu(MenuBar, MenuId),
	    lists:foldl(fun(Record, N) ->
				create_menu_item(Record, Menu, N)
			end, 0, MenuItems),
	    Index + 1;
       true ->
	    Menu = wxMenu:new(),
	    lists:foldl(fun(Record, N) ->
				create_menu_item(Record, Menu, N)
			end, 0, MenuItems),
	    wxMenuBar:insert(MenuBar, Index, Menu, "File"),
	    Index+1
    end;
create_menu(Name, MenuItems, Index, MenuBar, _Type) ->
    Menu = wxMenu:new(),
    lists:foldl(fun(Record, N) ->
			create_menu_item(Record, Menu, N)
		end, 0, MenuItems),
    wxMenuBar:insert(MenuBar, Index, Menu, Name),
    Index+1.

create_menu_item(#create_menu{id = ?wxID_HELP=Id}, Menu, Index) ->
    wxMenu:insert(Menu, Index, Id),
    Index+1;
create_menu_item(#create_menu{id=Id, text=Text, help=Help, type=Type, check=Check},
		 Menu, Index) ->
    Opts = case Help of
	       [] -> [];
	       _ -> [{help, Help}]
	   end,
    case Type of
	append ->
	    wxMenu:insert(Menu, Index, Id,
			  [{text, Text}|Opts]);
	check ->
	    wxMenu:insertCheckItem(Menu, Index, Id, Text, Opts),
	    wxMenu:check(Menu, Id, Check);
	radio ->
	    wxMenu:insertRadioItem(Menu, Index, Id, Text, Opts),
	    wxMenu:check(Menu, Id, Check);
	separator ->
	    wxMenu:insertSeparator(Menu, Index)
    end,
    Index+1;
create_menu_item(separator, Menu, Index) ->
    wxMenu:insertSeparator(Menu, Index),
    Index+1.

create_attrs() ->
    Font = wxSystemSettings:getFont(?wxSYS_DEFAULT_GUI_FONT),
    Text = case wxSystemSettings:getColour(?wxSYS_COLOUR_LISTBOXTEXT) of
	       {255,255,255,_} -> {10,10,10};  %% Is white on Mac for some reason
	       Color -> Color
	   end,
    #attrs{even = wxListItemAttr:new(Text, ?BG_EVEN, Font),
	   odd  = wxListItemAttr:new(Text, ?BG_ODD, Font),
	   deleted = wxListItemAttr:new(?FG_DELETED, ?BG_DELETED, Font),
	   changed = wxListItemAttr:new(Text, ?BG_CHANGED, Font),
	   searched = wxListItemAttr:new(Text, ?BG_SEARCHED, Font)
	  }.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

get_box_info({Title, List}) when is_list(List) -> {Title, ?wxALIGN_LEFT, List};
get_box_info({Title, left, List}) -> {Title, ?wxALIGN_LEFT, List};
get_box_info({Title, right, List}) -> {Title, ?wxALIGN_RIGHT, List}.

create_box(_Panel, {scroll_boxes,[]}) ->
    undefined;
create_box(Panel, {scroll_boxes,Data}) ->
    OuterBox = wxBoxSizer:new(?wxHORIZONTAL),
    Cursor = wxCursor:new(?wxCURSOR_HAND),
    AddBox = fun({Title,Proportion,{Format,List}}) ->
		     Box = wxStaticBoxSizer:new(?wxVERTICAL, Panel,
						[{label, Title}]),
		     Scroll = wxScrolledWindow:new(Panel),
		     wxScrolledWindow:enableScrolling(Scroll,true,true),
		     wxScrolledWindow:setScrollbars(Scroll,1,1,0,0),
		     ScrollSizer  = wxBoxSizer:new(?wxVERTICAL),
		     wxScrolledWindow:setSizer(Scroll, ScrollSizer),
		     BC = wxWindow:getBackgroundColour(Panel),
		     wxWindow:setBackgroundColour(Scroll,BC),
		     case Format of
			 click ->
			     [begin
				  TC = link_entry(Scroll, Link, Cursor),
				  wxWindow:setBackgroundColour(TC,BC),
				  wxSizer:add(ScrollSizer,TC,[{flag,?wxEXPAND}])
			      end || Link <- List];
			 plain ->
			     [begin
				  TC = wxTextCtrl:new(Scroll, ?wxID_ANY,
						      [{style,?SINGLE_LINE_STYLE},
						       {value,String}]),
				  wxSizer:add(ScrollSizer,TC,[{flag,?wxEXPAND}])
			      end || String <- List]
		     end,
		     wxSizer:add(Box,Scroll,[{flag,?wxEXPAND}]),
		     wxSizer:add(OuterBox,Box,
				 [{proportion,Proportion},{flag,?wxEXPAND}]),
		     {Scroll,length(List)}
	     end,
    Boxes = [AddBox(Entry) || Entry <- Data],
    wxCursor:destroy(Cursor),

    MaxL = lists:foldl(fun({_,L},Max) when L>Max -> L;
			  (_,Max) -> Max
		       end,
		       0,
		       Boxes),

    Dummy = wxTextCtrl:new(Panel, ?wxID_ANY, [{style, ?SINGLE_LINE_STYLE}]),
    {_,H} = wxWindow:getSize(Dummy),
    wxTextCtrl:destroy(Dummy),

    MaxH = if MaxL > 8 -> 8*H;
	      true -> MaxL*H
	   end,
    [wxWindow:setMinSize(B,{0,MaxH}) || {B,_} <- Boxes],
    wxSizer:layout(OuterBox),
    {OuterBox, []};

create_box(Panel, Data) ->
    {Title, Align, Info} = get_box_info(Data),
    Box = wxStaticBoxSizer:new(?wxVERTICAL, Panel, [{label, Title}]),
    LeftSize = get_max_size(Panel,Info),
    LeftProportion = [{proportion,0}],
    RightProportion = [{proportion,1}, {flag, Align bor ?wxEXPAND}],
    AddRow = fun({Desc0, Value0}) ->
		     Desc = Desc0++":",
		     Line = wxBoxSizer:new(?wxHORIZONTAL),
		     wxSizer:add(Line,
				 wxTextCtrl:new(Panel, ?wxID_ANY,
						[{style,?SINGLE_LINE_STYLE},
						 {size,LeftSize},
						 {value,Desc}]),
				 LeftProportion),
		     Field =
			 case Value0 of
			     {click,"unknown"} ->
				 wxTextCtrl:new(Panel, ?wxID_ANY,
						[{style,?MULTI_LINE_STYLE},
						 {value,"unknown"}]);
			     {click,Value} ->
				 link_entry(Panel,{Value,Value});
			     _ ->
				 Value = to_str(Value0),
				 wxTextCtrl:new(Panel, ?wxID_ANY,
						[{style,?MULTI_LINE_STYLE},
						 {value,Value}])
			 end,
		     wxSizer:add(Line, 10, 0), % space of size 10 horisontally
		     wxSizer:add(Line, Field, RightProportion),

		     {_,H,_,_} = wxTextCtrl:getTextExtent(Field,"W"),
		     wxTextCtrl:setMinSize(Field,{0,H}),

		     wxSizer:add(Box, Line, [{proportion,0},{flag,?wxEXPAND}]),
		     Field
	     end,
    InfoFields = [AddRow(Entry) || Entry <- Info],
    {Box, InfoFields}.

link_entry(Panel, Link) ->
    Cursor = wxCursor:new(?wxCURSOR_HAND),
    TC = link_entry(Panel, Link, Cursor),
    wxCursor:destroy(Cursor),
    TC.
link_entry(Panel,{Target,Str},Cursor) ->
    TC = wxTextCtrl:new(Panel, ?wxID_ANY,
			[{style, ?SINGLE_LINE_STYLE}]),
    wxTextCtrl:setForegroundColour(TC,?wxBLUE),
    wxTextCtrl:appendText(TC, Str),
    wxWindow:setCursor(TC, Cursor),
    wxTextCtrl:connect(TC, left_down, [{userData,Target}]),
    wxTextCtrl:connect(TC, enter_window),
    wxTextCtrl:connect(TC, leave_window),
    ToolTip = wxToolTip:new("Click to see properties for " ++ Target),
    wxWindow:setToolTip(TC, ToolTip),
    TC.

html_window(Panel,Html) ->
    Win = wxHtmlWindow:new(Panel, [{style, ?wxHW_SCROLLBAR_AUTO}]),
    wxHtmlWindow:setPage(Win,Html),
    wxHtmlWindow:connect(Win,command_html_link_clicked),
    Win.

get_max_size(Panel,Info) ->
    Txt = wxTextCtrl:new(Panel, ?wxID_ANY, []),
    Size = get_max_size(Txt,Info,0,0),
    wxTextCtrl:destroy(Txt),
    Size.

get_max_size(Txt,[{Desc,_}|Info],MaxX,MaxY) ->
    {X,Y,_,_} = wxTextCtrl:getTextExtent(Txt,Desc++":"),
    if X>MaxX ->
	    get_max_size(Txt,Info,X,Y);
       true ->
	    get_max_size(Txt,Info,MaxX,MaxY)
    end;
get_max_size(_,[],X,Y) ->
    {X+2,Y}.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
set_listctrl_col_size(LCtrl, Total) ->
    wx:batch(fun() -> calc_last(LCtrl, Total) end).

calc_last(LCtrl, _Total) ->
    Cols = wxListCtrl:getColumnCount(LCtrl),
    {Total, _} = wxWindow:getClientSize(LCtrl),
    SBSize = scroll_size(LCtrl),
    Last = lists:foldl(fun(I, Last) ->
			       Last - wxListCtrl:getColumnWidth(LCtrl, I)
		       end, Total-SBSize, lists:seq(0, Cols - 2)),
    Size = max(150, Last),
    wxListCtrl:setColumnWidth(LCtrl, Cols-1, Size).

scroll_size(LCtrl) ->
    case os:type() of
	{win32, nt} -> 0;
	{unix, darwin} ->
	    %% I can't figure out is there is a visible scrollbar
	    %% Always make room for it
	    wxSystemSettings:getMetric(?wxSYS_VSCROLL_X);
	_ ->
	    case wxWindow:hasScrollbar(LCtrl, ?wxVERTICAL) of
		true -> wxSystemSettings:getMetric(?wxSYS_VSCROLL_X);
		false -> 0
	    end
    end.


user_term(Parent, Title, Default) ->
    Dialog = wxTextEntryDialog:new(Parent, Title, [{value, Default}]),
    case wxTextEntryDialog:showModal(Dialog) of
	?wxID_OK ->
	    Str = wxTextEntryDialog:getValue(Dialog),
	    wxTextEntryDialog:destroy(Dialog),
	    parse_string(ensure_last_is_dot(Str));
	?wxID_CANCEL ->
	    wxTextEntryDialog:destroy(Dialog),
	    cancel
    end.

user_term_multiline(Parent, Title, Default) ->
    Dialog = wxDialog:new(Parent, ?wxID_ANY, Title,
			  [{style, ?wxDEFAULT_DIALOG_STYLE bor
			    ?wxRESIZE_BORDER}]),
    Panel = wxPanel:new(Dialog),

    TextCtrl = wxTextCtrl:new(Panel, ?wxID_ANY,
			      [{value, Default},
			       {style, ?wxDEFAULT bor ?wxTE_MULTILINE}]),
    Line = wxStaticLine:new(Panel, [{style, ?wxLI_HORIZONTAL}]),

    Buttons = wxDialog:createButtonSizer(Dialog, ?wxOK bor ?wxCANCEL),

    InnerSizer = wxBoxSizer:new(?wxVERTICAL),
    wxSizer:add(InnerSizer, TextCtrl,
		[{flag, ?wxEXPAND bor ?wxALL},{proportion, 1},{border, 5}]),
    wxSizer:add(InnerSizer, Line,
		[{flag, ?wxEXPAND},{proportion, 0},{border, 5}]),
    wxPanel:setSizer(Panel, InnerSizer),

    TopSizer = wxBoxSizer:new(?wxVERTICAL),
    wxSizer:add(TopSizer, Panel,
		[{flag, ?wxEXPAND bor ?wxALL},{proportion, 1},{border, 5}]),
    wxSizer:add(TopSizer, Buttons,
		[{flag, ?wxEXPAND bor ?wxBOTTOM bor ?wxRIGHT},{border, 10}]),

    % calculate the size of TopSizer when the whole user_term
    % fits in the TextCtrl
    DC = wxClientDC:new(Panel),
    W = wxDC:getCharWidth(DC),
    H = wxDC:getCharHeight(DC),
    {EW, EH} = wxDC:getMultiLineTextExtent(DC, Default),
    wxSizer:setItemMinSize(InnerSizer, 0, EW+2*W, EH+H),
    TopSize = wxSizer:getMinSize(TopSizer),
    % reset min size of TextCtrl to 40 chararacters * 4 lines
    wxSizer:setItemMinSize(InnerSizer, 0, 40*W, 4*H),

    wxWindow:setSizerAndFit(Dialog, TopSizer),
    wxSizer:setSizeHints(TopSizer, Dialog),

    wxWindow:setClientSize(Dialog, TopSize),

    case wxDialog:showModal(Dialog) of
	?wxID_OK ->
	    Str = wxTextCtrl:getValue(TextCtrl),
	    wxDialog:destroy(Dialog),
	    parse_string(ensure_last_is_dot(Str));
	?wxID_CANCEL ->
	    wxDialog:destroy(Dialog),
	    cancel
    end.

parse_string(Str) ->
    try
	Tokens = case erl_scan:string(Str) of
		     {ok, Ts, _} -> Ts;
		     {error, {_SLine, SMod, SError}, _} ->
			 throw(io_lib:format("~s", [SMod:format_error(SError)]))
		 end,
	case erl_parse:parse_term(Tokens) of
	    {error, {_PLine, PMod, PError}} ->
		throw(io_lib:format("~s", [PMod:format_error(PError)]));
	    Res -> Res
	end
    catch
	throw:ErrStr ->
	    {error, ErrStr};
	_:_Err ->
	    {error, ["Syntax error in: ", Str]}
    end.

ensure_last_is_dot([]) ->
    ".";
ensure_last_is_dot(String) ->
    case lists:last(String) =:= $. of
	true ->
	    String;
	false ->
	    String ++ "."
    end.

%%%-----------------------------------------------------------------
%%% Status bar for warnings
create_status_bar(Panel) ->
    StatusStyle = ?wxTE_MULTILINE bor ?wxTE_READONLY bor ?wxTE_RICH2,
    Red = wxTextAttr:new(?wxRED),

    %% wxTextCtrl:setSize/3 does not work, so we must create a dummy
    %% text ctrl first to get the size of the text, then set it when
    %% creating the real text ctrl.
    Dummy = wxTextCtrl:new(Panel, ?wxID_ANY,[{style,StatusStyle}]),
    {X,Y,_,_} = wxTextCtrl:getTextExtent(Dummy,"WARNING"),
    wxTextCtrl:destroy(Dummy),
    StatusBar = wxTextCtrl:new(Panel, ?wxID_ANY,
			 [{style,StatusStyle},
			  {size,{X,Y+2}}]), % Y+2 to avoid scrollbar
    wxTextCtrl:setDefaultStyle(StatusBar,Red),
    wxTextAttr:destroy(Red),
    StatusBar.