%%
%% %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.