%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2011-2012. 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, user_term/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
]).
-include_lib("wx/include/wx.hrl").
-include("observer_defs.hrl").
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_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) ->
{Box, InfoFs} = create_box(Panel, BoxInfo),
wxSizer:add(Sizer, Box, [{flag, ?wxEXPAND bor ?wxALL},
{border, 5}]),
wxSizer:addSpacer(Sizer, 5),
InfoFs
end,
InfoFs = [Add(I) || I <- Info],
wxWindow:setSizerAndFit(Panel, Sizer),
{Panel, Sizer, InfoFs}.
fill_info([{Str, Key}|Rest], Data) when is_atom(Key); is_function(Key) ->
[{Str, get_value(Key, Data)} | fill_info(Rest, Data)];
fill_info([{Str, {Format, Key}}|Rest], Data)
when is_atom(Key); is_function(Key), is_atom(Format) ->
[{Str, {Format, get_value(Key,Data)}} | fill_info(Rest, Data)];
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]) ->
wxStaticText:setLabel(Field, to_str(Value)),
update_info2(Fs, Rest);
update_info2([], []) -> ok.
to_str(Value) when is_atom(Value) ->
atom_to_list(Value);
to_str({bytes, B}) ->
KB = B div 1024,
MB = KB div 1024,
if
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, Data) ->
{Title, Align, Info} = get_box_info(Data),
Box = wxStaticBoxSizer:new(?wxHORIZONTAL, Panel, [{label, Title}]),
Left = wxBoxSizer:new(?wxVERTICAL),
Right = wxBoxSizer:new(?wxVERTICAL),
Expand = [{flag, ?wxEXPAND}],
ExpAlign = [{flag, Align}],
AddRow = fun({Desc, Value}) ->
wxSizer:add(Left, wxStaticText:new(Panel, ?wxID_ANY, Desc ++ ":"), Expand),
Field = wxStaticText:new(Panel, ?wxID_ANY, to_str(Value)),
wxSizer:add(Right, Field, ExpAlign),
Field
end,
InfoFields = [AddRow(Entry) || Entry <- Info],
wxSizer:add(Box, Left),
wxSizer:addSpacer(Box, 10),
wxSizer:add(Box, Right),
wxSizer:addSpacer(Box, 30),
{Box, InfoFields}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
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.
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.