%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1998-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(tv_nodewin).
-compile([{nowarn_deprecated_function,{gs,config,2}},
{nowarn_deprecated_function,{gs,destroy,1}},
{nowarn_deprecated_function,{gs,listbox,3}},
{nowarn_deprecated_function,{gs,menu,3}},
{nowarn_deprecated_function,{gs,menubar,3}},
{nowarn_deprecated_function,{gs,menubutton,3}},
{nowarn_deprecated_function,{gs,menuitem,2}},
{nowarn_deprecated_function,{gs,start,0}},
{nowarn_deprecated_function,{gs,window,3}}]).
-export([start/2, init/3]).
-include("tv_int_msg.hrl").
-define(WINDOW_WIDTH, 230).
-define(WINDOW_HEIGHT, 260).
-define(DEFAULT_BG_COLOR, {217,217,217}).
-define(POLL_INTERVAL, 5000).
%%%*********************************************************************
%%% EXTERNAL FUNCTIONS
%%%*********************************************************************
start(CurrNode, ErrMsgMode) ->
spawn_link(?MODULE, init, [self(), CurrNode, ErrMsgMode]).
init(Pid, CurrNode, ErrMsgMode) ->
process_flag(trap_exit, true),
net_kernel:monitor_nodes(true),
put(error_msg_mode, ErrMsgMode),
gs:start(),
NewCurrNode = update_node_listbox(CurrNode, false),
tell_master(NewCurrNode, CurrNode, Pid),
loop(Pid, NewCurrNode, node(), false).
%%%*********************************************************************
%%% INTERNAL FUNCTIONS
%%%*********************************************************************
init_window(CurrNode, Pid) ->
create_window(),
NewCurrNode = update_node_listbox(CurrNode, true),
tell_master(NewCurrNode, CurrNode, Pid),
gs:config(win, [{map,true}]),
NewCurrNode.
handle_error(nodedown) ->
gs:window(errorwin, gs:start(), []),
gs:config(errorwin, [beep]),
case get(error_msg_mode) of
normal ->
tv_utils:notify(errorwin, "TV Notification", ["The selected node is down!"]);
haiku ->
Msg = ["With searching comes loss",
"And the presence of absence:",
"Node is down."],
tv_utils:notify(errorwin, "TV Notification", Msg)
end,
gs:destroy(errorwin);
handle_error(distributed) ->
gs:window(errorwin, gs:start(), []),
gs:config(errorwin, [beep]),
case get(error_msg_mode) of
normal ->
tv_utils:notify(errorwin, "TV Notification",
["The system has become distributed!"]);
haiku ->
Msg = [],
tv_utils:notify(errorwin, "TV Notification", Msg)
end,
gs:destroy(errorwin);
handle_error(undistributed) ->
gs:window(errorwin, gs:start(), []),
gs:config(errorwin, [beep]),
case get(error_msg_mode) of
normal ->
tv_utils:notify(errorwin, "TV Notification",
["The system is no longer distributed!"]);
haiku ->
Msg = ["The system you see",
"Is not a distributed",
"system anymore."],
tv_utils:notify(errorwin, "TV Notification", Msg)
end,
gs:destroy(errorwin).
get_node_lists(CurrNode) ->
NodeDataList = lists:sort([node() | nodes()]),
NodeTextList = lists:map(fun(Item) ->
" " ++ atom_to_list(Item)
end,
NodeDataList),
%% It *may* be possible that CurrNode has disappeared!
%% If this is the case, use the node where TV resides
%% as new current node.
%% This also covers the case when our own node (or some
%% other node) suddenly goes distributed.
NewCurrNode = case lists:member(CurrNode, NodeDataList) of
true ->
CurrNode;
false ->
node()
end,
%% Now get the index that shall be marked in the node listbox.
%% Remember that the first item has number 0 (zero)!
NodeMarkIndex = get_node_mark_index(NewCurrNode, NodeDataList, 0),
{NewCurrNode, NodeDataList, NodeTextList, NodeMarkIndex}.
%% We know that CurrNode is *somewhere* in the list, since we have checked.
%% If the original CurrNode wasn't there, then we are using node() instead,
%% which definitely is in the list. (node() may have gone distributed in the
%% meantime, but it *IS* in the list!) :-)
get_node_mark_index(CurrNode, [H | T], Acc) when CurrNode =/= H ->
get_node_mark_index(CurrNode, T, Acc + 1);
get_node_mark_index(CurrNode, [CurrNode | _], Acc) ->
Acc. %% Acc tells the index of the current head. :-)
check_selected_node('nonode@nohost', _OldNode, _WinCreated) when node() =:= 'nonode@nohost' ->
%% Not distributed, OK!
'nonode@nohost';
check_selected_node(_Node, _OldNode, WinCreated) when node() =:= 'nonode@nohost' ->
%% No longer distributed, but previously was!
handle_error(undistributed),
update_node_listbox('nonode@nohost', WinCreated);
check_selected_node(Node, _OldNode, _WinCreated) when Node =:= node() ->
%% We are distributed, but on
%% our own node! Since we
% still are running, the node
%% is up.
Node;
check_selected_node(Node, 'nonode@nohost', WinCreated) ->
%% The system has been distributed!
net_kernel:monitor_nodes(true),
handle_error(distributed),
update_node_listbox(Node, WinCreated);
check_selected_node(Node, _OldNode, WinCreated) ->
%% We are distributed, and a new node has been chosen!
%% We better check this node!
case net_adm:ping(Node) of
pong ->
Node;
_Other ->
handle_error(nodedown),
update_node_listbox(Node, WinCreated)
end.
available_nodes() ->
lists:sort([node() | nodes()]).
loop(Pid, CurrNode, HomeNode, WinCreated) ->
receive
{nodedown, _Node} ->
flush_nodedown_messages(),
flush_nodeup_messages(),
case lists:member(CurrNode, available_nodes()) of
true ->
done;
false when node() =:= 'nonode@nohost', CurrNode =/= 'nonode@nohost' ->
handle_error(undistributed);
false ->
handle_error(nodedown)
end,
NewCurrNode = update_node_listbox(CurrNode, WinCreated),
tell_master(NewCurrNode, CurrNode, Pid),
loop(Pid, NewCurrNode, node(), WinCreated);
{nodeup, _Node} ->
flush_nodeup_messages(),
flush_nodedown_messages(),
case lists:member(CurrNode, available_nodes()) of
true ->
done;
false when node() =:= 'nonode@nohost', CurrNode =/= 'nonode@nohost' ->
handle_error(undistributed);
false when CurrNode =:= 'nonode@nohost' ->
net_kernel:monitor_nodes(true),
handle_error(distributed);
false ->
handle_error(nodedown)
end,
NewCurrNode = update_node_listbox(CurrNode, WinCreated),
tell_master(NewCurrNode, CurrNode, Pid),
loop(Pid, NewCurrNode, node(), WinCreated);
{gs, node_listbox, click, Data, [Idx, _Txt | _]} ->
NewCurrNode = check_selected_node(lists:nth(Idx + 1, Data), CurrNode, WinCreated),
tell_master(NewCurrNode, CurrNode, Pid),
loop(Pid, NewCurrNode, node(), WinCreated);
{gs, win, configure, _, _} ->
gs:config(win, [{width, ?WINDOW_WIDTH}, {height, ?WINDOW_HEIGHT}]),
loop(Pid, CurrNode, HomeNode, WinCreated);
show_window when WinCreated->
gs:config(win, [raise]),
loop(Pid, CurrNode, HomeNode, WinCreated);
show_window when not WinCreated ->
init_window(CurrNode, Pid),
loop(Pid, CurrNode, HomeNode, true);
{gs, _Id, click, close_menu, _Args} ->
gs:destroy(win),
loop(Pid, CurrNode, HomeNode, false);
{gs, _Id, keypress, _Data, [c, _, 0, 1 | _]} ->
gs:destroy(win),
loop(Pid, CurrNode, HomeNode, false);
{gs, _Id, keypress, _Data, ['C', _, 1, 1 | _]} ->
gs:destroy(win),
loop(Pid, CurrNode, HomeNode, false);
{gs, _Id, keypress, _Data, _Args} ->
loop(Pid, CurrNode, HomeNode, WinCreated);
{gs, _, destroy, _, _} ->
loop(Pid, CurrNode, HomeNode, false);
{error_msg_mode, Mode} ->
put(error_msg_mode, Mode),
loop(Pid, CurrNode, HomeNode, WinCreated);
{'EXIT', Pid, _Reason} ->
net_kernel:monitor_nodes(false),
exit(normal);
{'EXIT', _OtherPid, _Reason} ->
loop(Pid, CurrNode, HomeNode, WinCreated);
_Other ->
io:format("Node window received message ~p ~n", [_Other]),
loop(Pid, CurrNode, HomeNode, WinCreated)
after
1000 ->
NewHomeNode = case node() of
HomeNode ->
HomeNode;
Other ->
self() ! {nodeup, Other}
end,
loop(Pid, CurrNode, NewHomeNode, WinCreated)
end.
tell_master(NewNode, NewNode, _Pid) ->
done;
tell_master(NewNode, _OldNode, Pid) ->
Pid ! {tv_new_node, self(), NewNode}.
flush_nodedown_messages() ->
receive
{nodedown,_Node} ->
flush_nodedown_messages()
after
0 ->
done
end.
flush_nodeup_messages() ->
receive
{nodeup,_Node} ->
flush_nodeup_messages()
after
0 ->
done
end.
update_node_listbox(Node, WinCreated) ->
{NewNode, NodeDataList, NodeTextList, MarkIndex} = get_node_lists(Node),
case WinCreated of
false ->
done;
true ->
catch gs:config(node_listbox, [{data, NodeDataList},
{items, NodeTextList},
{selection, MarkIndex}
])
end,
NewNode.
create_window() ->
gs:window(win, gs:start(), [{width, ?WINDOW_WIDTH},
{height, ?WINDOW_HEIGHT},
{bg, ?DEFAULT_BG_COLOR},
{title, "[TV] Connected nodes"},
{configure, true},
{destroy, true},
{cursor, arrow},
{keypress, true}
]),
gs:menubar(menubar, win, [{bg, ?DEFAULT_BG_COLOR}
]),
gs:menubutton(mbutt, menubar, [{bg, ?DEFAULT_BG_COLOR},
{fg, {178, 34, 34}}, % firebrick
{label, {text, " File "}},
{underline, 1}
]),
% Create the actual menu!
gs:menu(menu, mbutt, [{bg, ?DEFAULT_BG_COLOR},
{fg, {178, 34, 34}}]),
gs:menuitem(menu, [{bg, ?DEFAULT_BG_COLOR},
{fg, {178, 34, 34}},
{label, {text, " Close Ctrl-C "}},
{data, close_menu},
{underline, 1}
]),
Xpos = 4,
Ypos = 40,
gs:listbox(node_listbox, win, [{x, Xpos},
{y, Ypos},
{width, ?WINDOW_WIDTH - 2 * Xpos},
{height, ?WINDOW_HEIGHT - Ypos - Xpos},
{bg, {255,255,255}},
{vscroll, right},
{hscroll, true},
{click, true}
]).