%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1997-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%
%%%*********************************************************************
%%%
%%% Description: pc part of the table tool, i.e., the process
%%% controlling all other processes, and managing
%%% the actions to take.
%%%
%%%*********************************************************************
-module(tv_pc).
-compile([{nowarn_deprecated_function,{gs,config,2}}]).
-export([pc/7,
send_data/2
]).
-include("tv_int_def.hrl").
-include("tv_int_msg.hrl").
-include("tv_pc_int_def.hrl").
%%%*********************************************************************
%%% EXTERNAL FUNCTIONS
%%%*********************************************************************
%%======================================================================
%% Function: pc.
%%
%% Return Value: None.
%%
%% Description: Process controlling the processes 'pd', 'pw', 'dbs' and 'etsread'.
%% After necessary initialisations, an eternal loop is
%% entered, where window created messages are received and
%% handled, as well as user input.
%%
%% Parameters:
%%======================================================================
pc(Master, Node, LocalNode, TableId, KindOfTable, TableName, ErrMsgMode) ->
process_flag(trap_exit, true),
put(error_msg_mode, ErrMsgMode),
ProcVars = prepare_and_open_table(Node, LocalNode, TableId, KindOfTable, TableName,
false, #process_variables{parent_pid=Master}),
loop(ProcVars).
%%%********************************************************************
%%% INTERNAL FUNCTIONS
%%%********************************************************************
prepare_and_open_table(Node, LocalNode, TabId, TabType, TabName, Raise, ProcVars) ->
IpPid = spawn(tv_ip, ip, [self()]),
show_progress(IpPid, 5, "Initializing graphics..."),
TmpProcVars = start_procs(IpPid, ProcVars),
show_progress(IpPid, 5, "Loading table..."),
NewProcVars = ?MENU_FUNC_FILE:open_table(Node, LocalNode, TabId, TabType, TabName,
Raise, TmpProcVars),
IpPid ! #ip_quit{sender = self()},
% Now make window visible!
WinP = NewProcVars#process_variables.window_params,
gs:config(WinP#window_params.window_id, [{map, true}]),
NewProcVars.
start_procs(IpPid, ProcVars) ->
ErrorMsgMode = get(error_msg_mode),
PwPid = spawn_link(tv_pw, pw, [self()]),
PdPid = spawn_link(tv_pd, pd, [self(), ErrorMsgMode]),
DbsPid = spawn_link(tv_db, dbs, [self(), ErrorMsgMode]),
EtsreadPid = spawn_link(tv_etsread, etsread, [self(), ErrorMsgMode]),
show_progress(IpPid, 5, "Initializing graphics..."),
NewWinP = init_pw(PwPid, ProcVars),
show_progress(IpPid, 5, "Initializing graphics..."),
init_pd(PdPid, NewWinP),
ProcVars#process_variables{pw_pid = PwPid,
pd_pid = PdPid,
dbs_pid = DbsPid,
etsread_pid = EtsreadPid,
current_node = node(), %% Will be replaced, when table opened.
local_node = true,
window_params = NewWinP
}.
%%======================================================================
%% Function:
%%
%% Return Value:
%%
%% Description:
%%
%% Parameters:
%%======================================================================
show_progress(IpPid, NofElements, Text) ->
IpPid ! #ip_update{sender = self(),
nof_elements_to_mark = NofElements,
text = Text
}.
%%======================================================================
%% Function: loop.
%%
%% Return Value: None.
%%
%% Description: Eternal (well, almost) loop, receiving messages and
%% handling them.
%%
%% Parameters: None.
%%======================================================================
loop(ProcVars) ->
receive
Msg ->
case Msg of
% Normal messages!
#dbs_subset{} ->
NewProcVars1 = send_data(Msg, ProcVars),
NewProcVars2 = check_time_to_poll_table(Msg, NewProcVars1),
loop(NewProcVars2);
#pc_poll_table{} ->
TmpProcVars = check_node(ProcVars),
NewProcVars = ?MENU_FUNC_FILE:poll_table(TmpProcVars),
loop(NewProcVars);
#pc_search_req{} ->
DbsPid = ProcVars#process_variables.dbs_pid,
DbsPid ! #dbs_search_req{sender=self()},
loop(ProcVars);
#pc_set_sorting_mode{} ->
set_sorting_mode(Msg, ProcVars),
loop(ProcVars);
#pc_data_req{element = Pos, nof_elements = Length} ->
DbsPid = ProcVars#process_variables.dbs_pid,
DbsPid ! #dbs_subset_req{sender = self(),
subset_pos = Pos,
subset_length = Length
},
loop(ProcVars);
#pc_marked_row{row_no=RowNo, object=Obj, color=Color} ->
DbsPid = ProcVars#process_variables.dbs_pid,
DbsPid ! #dbs_marked_row{sender = self(),
row_no = RowNo
},
NewProcVars = ProcVars#process_variables{marked_row = RowNo,
marked_object = Obj,
marked_color = Color},
loop(NewProcVars);
#pc_menu_msg{} ->
Fcn = Msg#pc_menu_msg.data,
NewProcVars = ?MENU_FUNC_FILE:Fcn(ProcVars),
loop(NewProcVars);
#pd_updated_object{object=Obj,old_object=OldObj,old_color=Color,obj_no=ObjNo} ->
DbsPid = ProcVars#process_variables.dbs_pid,
DbsPid ! #dbs_updated_object{sender = self(),
object = Obj,
old_object = OldObj,
old_color = Color,
obj_no = ObjNo},
loop(ProcVars);
#pd_new_object{object=Obj} ->
DbsPid = ProcVars#process_variables.dbs_pid,
DbsPid ! #dbs_new_object{sender = self(),
object = Obj},
loop(ProcVars);
#pc_show_table_info{} ->
NewProcVars = ?MENU_FUNC_FILE:table_info(ProcVars),
loop(NewProcVars);
#pc_win_conf{} ->
NewProcVars = ?GRAPH_FUNC_FILE:win_conf(Msg, ProcVars),
loop(NewProcVars);
#pc_help{} ->
NewProcVars = ?MENU_FUNC_FILE:help_button(ProcVars),
loop(NewProcVars);
#pc_dead_table{automatic_polling = AutoPoll} ->
WinP = ProcVars#process_variables.window_params,
WinId = WinP#window_params.window_id,
gs:config(WinId, [beep]),
case get(error_msg_mode) of
normal ->
tv_utils:notify(WinId, "TV Notification",
["The table no longer exists!"]);
haiku ->
ErrMsg1 = ["A table that big?",
"It might be very useful.",
"But now it is gone."],
tv_utils:notify(WinId, "TV Notification", ErrMsg1)
end,
NewProcVars =
case AutoPoll of
true ->
gs:config(WinId, [beep]),
case get(error_msg_mode) of
normal ->
tv_utils:notify(WinId, "TV Notification",
["The automatic polling is turned off!"]);
haiku ->
ErrMsg2 = ["Previously on",
"The polling is now idled.",
"That's the way it is."],
tv_utils:notify(WinId, "TV Notification", ErrMsg2)
end,
ProcVars#process_variables{poll_interval = infinity};
false ->
ProcVars
end,
loop(NewProcVars);
#pc_nodedown{automatic_polling = AutoPoll} ->
WinP = ProcVars#process_variables.window_params,
WinId = WinP#window_params.window_id,
gs:config(WinId, [beep]),
case get(error_msg_mode) of
normal ->
tv_utils:notify(WinId, "TV Notification",
["The node is down, and the",
"table cannot be reached."]);
haiku ->
ErrMsg1 = ["With searching comes loss",
"And the presence of absence:",
"Node is down."],
tv_utils:notify(WinId, "TV Notification", ErrMsg1)
end,
NewProcVars =
case AutoPoll of
true ->
gs:config(WinId, [beep]),
case get(error_msg_mode) of
normal ->
tv_utils:notify(WinId, "TV Notification",
["The automatic polling is turned off!"]);
haiku ->
ErrMsg = ["Previously on,",
"The polling is now idled.",
"That's the way it is."],
tv_utils:notify(WinId, "TV Notification", ErrMsg)
end,
ProcVars#process_variables{poll_interval = infinity};
false ->
ProcVars
end,
loop(NewProcVars);
{pc_edit_object, _Sender} ->
NewProcVars = ?MENU_FUNC_FILE:insert_object(ProcVars),
loop(NewProcVars);
check_node ->
NewProcVars = check_node(ProcVars),
loop(NewProcVars);
raise ->
WinP = ProcVars#process_variables.window_params,
gs:config(WinP#window_params.window_id, [raise]),
loop(ProcVars);
{error_msg_mode, Mode} ->
ProcVars#process_variables.dbs_pid ! {error_msg_mode, Mode},
ProcVars#process_variables.etsread_pid ! {error_msg_mode, Mode},
ProcVars#process_variables.pd_pid ! {error_msg_mode, Mode},
put(error_msg_mode, Mode),
loop(ProcVars);
% Exit messages!
{'EXIT', Sender, Reason} ->
exit_signals({Sender, Reason}, ProcVars);
_Other ->
loop(ProcVars)
end
end.
check_node(ProcVars) ->
#process_variables{pw_pid = PwPid,
current_node = OldCurrNode,
local_node = LocalNode,
table_id = TableId,
table_type = TableType,
table_name = TableName} = ProcVars,
HomeNode = node(),
case net_adm:ping(OldCurrNode) of
pong ->
ProcVars;
pang when not LocalNode ->
ProcVars;
pang when LocalNode ->
%% XXX [siri] Will this ever happen? I thought local_node
%% indicated if current_node was the node where tv was
%% started. If so, we are pinging ourselves here, and
%% a pang can never happen??
WinTitle = ?MENU_FUNC_FILE:get_window_title(TableType,HomeNode,TableId,TableName),
PwPid ! #pw_set_window_title{sender = self(),
win_title = WinTitle},
ProcVars#process_variables{current_node = HomeNode}
end.
send_data(Msg, ProcVars) ->
#process_variables{pd_pid = PdPid,
parent_pid = ParentPid,
table_id = Table,
table_type = Type,
current_node = Node} = ProcVars,
ParentPid ! {tv_update_infowin, Table, Node, Type},
#dbs_subset{data = DbData,
subset_pos = ScalePos,
db_length = DbLength,
list_of_keys = ListOfKeys,
max_elem_size = MaxElemSize,
requested_row = ReqRowData} = Msg,
Range = case ScalePos of
0 ->
{0, 0};
_Other ->
{1, DbLength}
end,
PdPid ! #pc_data{sender = self(),
scale_pos = ScalePos,
scale_range = Range,
elementlist = DbData,
list_of_keys = ListOfKeys,
max_elem_size = MaxElemSize,
marked_row = ReqRowData
},
{MarkedObject, MarkedColor} =
case ReqRowData of
[] ->
{undefined, undefined};
[Data] ->
Data
end,
ProcVars#process_variables{marked_object = MarkedObject,
marked_color = MarkedColor}.
%%======================================================================
%% Function:
%%
%% Return Value:
%%
%% Description:
%%
%% Parameters:
%%======================================================================
set_sorting_mode(Msg, ProcVars) ->
#pc_set_sorting_mode{sorting = Sorting,
reverse = Reverse,
sort_key_no = SortKeyNo} = Msg,
DbsPid = ProcVars#process_variables.dbs_pid,
PdPid = ProcVars#process_variables.pd_pid,
PwPid = ProcVars#process_variables.pw_pid,
TableType = ProcVars#process_variables.table_type,
NewSortKeyNo =
case SortKeyNo of
undefined ->
if
TableType =:= mnesia ->
2;
true ->
1
end;
_Other ->
SortKeyNo
end,
Menu =
case Sorting of
true ->
case Reverse of
true ->
sort_falling_order;
false ->
sort_rising_order
end;
false ->
no_sorting
end,
PwPid ! #pw_select_menu{sender = self(),
menu = Menu},
DbsPid ! #dbs_sorting_mode{sender = self(),
sorting = Sorting,
reverse = Reverse,
sort_key_no = NewSortKeyNo
},
PdPid ! #pc_set_sorting_mode_cfm{sender = self(),
sort_key_no = NewSortKeyNo
}.
%%======================================================================
%% Function: init_pw.
%%
%% Return Value: Tuple containing the Pid of the pw process, and the id of
%% the window created by the pw process.
%%
%% Description: Starts the pw process, and orders it to create a window.
%% (The size of the window may be given as option.)
%%
%% Parameters: None.
%%======================================================================
init_pw(PwPid, ProcVars) ->
#process_variables{window_params = WinP} = ProcVars,
% Now deblock pw, and order it to create a window!
PwPid ! #pw_deblock{sender = self(),
win_title = ?APPLICATION_NAME,
win_width = ?DEFAULT_WINDOW_WIDTH,
win_height = ?DEFAULT_WINDOW_HEIGHT,
min_win_width = ?DEFAULT_MIN_WINDOW_WIDTH,
min_win_height = ?DEFAULT_MIN_WINDOW_HEIGHT
},
receive
#pw_deblock_cfm{win_id = WindowId} ->
?MENU_FUNC_FILE:create_menus(PwPid),
% Store the window id as well as the size of it.
WinP#window_params{window_id = WindowId,
window_width = ?DEFAULT_WINDOW_WIDTH,
window_height = ?DEFAULT_WINDOW_HEIGHT
}
after 180000 -> % A timeout of 1000 ms is too short, at least the first
% time the system is started!
exit(error)
end.
%%======================================================================
%% Function:
%%
%% Return Value:
%%
%% Description:
%%
%% Parameters:
%%======================================================================
init_pd(PdPid, WinP) ->
#window_params{window_id = WindowId,
window_width = WindowWidth,
window_height = WindowHeight} = WinP,
% Now deblock pd, and order it to create a canvas and a scale!
PdPid ! #pd_deblock{sender = self(),
win = WindowId,
win_width = WindowWidth,
win_height = WindowHeight,
scale = true
},
receive
#pd_deblock_cfm{} ->
done
after 180000 ->
exit(error)
end.
%%======================================================================
%% Function: exit_signals.
%%
%% Return Value: None.
%%
%% Description: Decides, given an error message, action to take, i.e., whether
%% operation shall procede, any process shall be restarted, or
%% the table tool terminated.
%%
%% Parameters: Exit_info: tuple containing sender of the error message, and the
%% reason.
%%======================================================================
exit_signals(ExitInfo, ProcVars) ->
#process_variables{parent_pid = ParentPid,
pd_pid = PdPid,
pw_pid = PwPid,
dbs_pid = DbsPid,
etsread_pid = EtsreadPid,
table_id = TabId,
table_type = TabType,
table_name = TabName,
current_node = Node,
local_node = LocalNode
} = ProcVars,
case ExitInfo of
{ParentPid, Reason} ->
exit(Reason);
{PwPid, normal} ->
exit(normal);
{PwPid, error} ->
io:format("Internal error... restarting. ~n"),
kill_procs(normal, [PdPid, EtsreadPid, DbsPid]),
NewProcVars = pc(ParentPid, Node, LocalNode, TabId, TabType, TabName,
get(error_msg_mode)),
loop(NewProcVars);
{PdPid, _Reason} ->
io:format("Internal error... restarting. ~n"),
kill_procs(normal, [PwPid, EtsreadPid, DbsPid]),
NewProcVars = pc(ParentPid, Node, LocalNode, TabId, TabType, TabName,
get(error_msg_mode)),
loop(NewProcVars);
{DbsPid, _Reason} ->
io:format("Internal error... restarting. ~n"),
kill_procs(normal, [PdPid, PwPid, EtsreadPid]),
NewProcVars = pc(ParentPid, Node, LocalNode, TabId, TabType, TabName,
get(error_msg_mode)),
loop(NewProcVars);
{EtsreadPid, _Reason} ->
io:format("Internal error... restarting. ~n"),
kill_procs(normal, [PdPid, PwPid, DbsPid]),
NewProcVars = pc(ParentPid, Node, LocalNode, TabId, TabType, TabName,
get(error_msg_mode)),
loop(NewProcVars);
{_Sender, _OtherReason} ->
loop(ProcVars)
end.
%%======================================================================
%% Function:
%%
%% Return Value:
%%
%% Description:
%%
%% Parameters:
%%======================================================================
kill_procs(_Status, []) ->
done;
kill_procs(Status, [Pid | Tail]) ->
exit(Pid, Status),
kill_procs(Status, Tail).
%%======================================================================
%% Function:
%%
%% Return Value:
%%
%% Description:
%%
%% Parameters:
%%======================================================================
check_time_to_poll_table(Msg, ProcVars) ->
#dbs_subset{required_time_etsread = EtsreadTime,
required_time_dbs = DbsTime} = Msg,
UserSetPollInterval = ProcVars#process_variables.poll_interval,
WinP = ProcVars#process_variables.window_params,
WinId = WinP#window_params.window_id,
case too_short_pollinterval_chosen(UserSetPollInterval, EtsreadTime, DbsTime) of
true ->
EtsreadPid = ProcVars#process_variables.etsread_pid,
EtsreadPid ! #etsread_set_poll_interval{sender = self(),
interval = infinity},
TimeRequired = trunc(max_time_required(EtsreadTime, DbsTime) / 10 + 0.5) * 10 + 20,
gs:config(WinId, [beep]),
case get(error_msg_mode) of
normal ->
tv_utils:notify(WinId, "TV Notification",
["The current poll interval is too short!"]),
Str = "to " ++ lists:flatten(io_lib:write(TimeRequired)) ++ " seconds!",
tv_utils:notify(WinId, "TV Notification", ["Setting the poll interval", Str]);
haiku ->
ErrMsg = ["Being way too short",
"The interval of polling",
"Is simply increased."],
tv_utils:notify(WinId, "TV Notification", ErrMsg)
end,
clear_message_buffer(),
EtsreadPid ! #etsread_set_poll_interval{sender = self(),
interval = TimeRequired},
ProcVars#process_variables{poll_interval = TimeRequired};
false ->
ProcVars
end.
clear_message_buffer() ->
receive
#dbs_subset{} ->
clear_message_buffer()
after 100 ->
done
end.
max_time_required(T1, T2) when is_number(T1), is_number(T2) ->
if
T1 > T2 ->
T1;
true ->
T2
end;
max_time_required(T1, _T2) when is_number(T1) ->
T1;
max_time_required(_T1, T2) ->
T2.
%%======================================================================
%% Function:
%%
%% Return Value:
%%
%% Description:
%%
%% Parameters:
%%======================================================================
too_short_pollinterval_chosen(infinity, _EtsreadTime, _DbsTime) ->
false;
too_short_pollinterval_chosen(undefined, _EtsreadTime, _DbsTime) ->
false;
too_short_pollinterval_chosen(PollInt, EtsreadTime, _DbsTime) when EtsreadTime >= PollInt, is_number(EtsreadTime) ->
true;
too_short_pollinterval_chosen(PollInt, _EtsreadTime, DbsTime) when DbsTime >= PollInt, is_number(DbsTime) ->
true;
too_short_pollinterval_chosen(_PollInt, _EtsreadTime, _DbsTime) ->
false.