%% %% %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_rec_edit). -compile([{nowarn_deprecated_function,{gs,button,2}}, {nowarn_deprecated_function,{gs,button,3}}, {nowarn_deprecated_function,{gs,config,2}}, {nowarn_deprecated_function,{gs,entry,3}}, {nowarn_deprecated_function,{gs,frame,2}}, {nowarn_deprecated_function,{gs,frame,3}}, {nowarn_deprecated_function,{gs,label,2}}, {nowarn_deprecated_function,{gs,read,2}}, {nowarn_deprecated_function,{gs,start,0}}, {nowarn_deprecated_function,{gs,window,3}}]). -export([start/5, start/6, init/8 ]). -include("tv_int_def.hrl"). -define(DEFAULT_BG_COLOR, {217,217,217}). -define(WIN_WIDTH, 375). -define(WIN_HEIGHT, 341). -define(ETS_WIN_HEIGHT, 154). -define(FRAME_WIDTH, 375). -define(FRAME_HEIGHT, 265). -define(ETS_FRAME_HEIGHT, 74). -define(MAX_LABEL_WIDTH, 165). -define(X0, 15). -define(Y0, 20). -define(LABEL_HEIGHT, 30). -define(ENTRY_HEIGHT, 30). -define(FONT, {screen,12}). -define(NEXT_BTN_WIDTH, 57). -define(NEXT_BTN_HEIGHT, 22). -define(NEXT_BTN_FG, {178,34,34}). -define(INSERT_BTN_WIDTH, 80). -define(INSERT_BTN_HEIGHT, 30). -define(INSERT_BTN_DIST_BETWEEN, 23). -define(INSERT_BTN_DIST_FROM_BOTTOM, 23). start(TableType, TableName, AttributeList, ListsAsStr, ErrMsgMode) -> AttributeValues = lists:duplicate(length(AttributeList), undefined), spawn_link(?MODULE, init, [TableType, TableName, AttributeList, AttributeValues, ListsAsStr, ErrMsgMode, self(), true]). start(TableType, TableName, AttributeList, AttributeValues, ListsAsStr, ErrMsgMode) -> spawn_link(?MODULE, init, [TableType, TableName, AttributeList, AttributeValues, ListsAsStr, ErrMsgMode, self(), false]). init(TableType,TableName,AttributeList,AttributeValues,ListsAsStr,ErrMsgMode,MasterPid,Insert) -> process_flag(trap_exit, true), put(error_msg_mode, ErrMsgMode), Frames = create_window(TableType, TableName, AttributeList, AttributeValues, ListsAsStr, Insert), loop(TableType, TableName, Frames, AttributeList, AttributeValues, MasterPid, ListsAsStr). loop(TabType, TabName, Frames, AttrList, AttrVals, MPid, ListsAsStr) -> receive {gs, insert, click, Insert, _Args} -> gs:config(win, [{cursor, busy}]), case get_record(TabType, TabName, AttrList, AttrList, Frames) of {ok, NewRec} -> case Insert of insert -> MPid ! {new_object, NewRec}; change -> MPid ! {updated_object, NewRec} end; error -> done end, gs:config(win, [{cursor, arrow}]), loop(TabType, TabName, Frames, AttrList, AttrVals, MPid, ListsAsStr); {gs, cancel, click, _Data, _Args} -> exit(normal); {gs, reset, click, _Data, _Args} -> gs:config(win, [{cursor, busy}]), set_entry_values(TabType, AttrList, AttrVals, ListsAsStr), gs:config(win, [{cursor, arrow}]), loop(TabType, TabName, Frames, AttrList, AttrVals, MPid, ListsAsStr); {gs, EntryId, keypress, _Data, ['Tab', _No, 0 | _T]} -> {_Term, {NextEntry, NextFrame}} = check_entry_content(EntryId, AttrList, Frames, forward), case NextEntry of EntryId -> gs:config(NextEntry, [{setfocus, true}]); _OtherId -> gs:config(NextFrame, [raise]), gs:config(NextEntry, [{setfocus, true}, {select, {0,100000000}}]) end, loop(TabType, TabName, Frames, AttrList, AttrVals, MPid, ListsAsStr); {gs, EntryId, keypress, _Data, ['Down' | _T]} -> {_Term, {NextEntry, NextFrame}} = check_entry_content(EntryId, AttrList, Frames, forward), case NextEntry of EntryId -> gs:config(NextEntry, [{setfocus, true}]); _OtherId -> gs:config(NextFrame, [raise]), gs:config(NextEntry, [{setfocus, true}, {select, {0,100000000}}]) end, loop(TabType, TabName, Frames, AttrList, AttrVals, MPid, ListsAsStr); {gs, EntryId, keypress, _Data, ['Tab', _No, 1 | _T]} -> {_Term, {NextEntry, NextFrame}} = check_entry_content(EntryId, AttrList, Frames, backward), gs:config(NextFrame, [raise]), case NextEntry of EntryId -> gs:config(NextEntry, [{setfocus, true}]); _OtherId -> gs:config(NextFrame, [raise]), gs:config(NextEntry, [{setfocus, true}, {select, {0,100000000}}]) end, loop(TabType, TabName, Frames, AttrList, AttrVals, MPid, ListsAsStr); {gs, EntryId, keypress, _Data, ['Up' | _T]} -> {_Term, {NextEntry, NextFrame}} = check_entry_content(EntryId, AttrList, Frames, backward), gs:config(NextFrame, [raise]), case NextEntry of EntryId -> gs:config(NextEntry, [{setfocus, true}]); _OtherId -> gs:config(NextFrame, [raise]), gs:config(NextEntry, [{setfocus, true}, {select, {0,100000000}}]) end, loop(TabType, TabName, Frames, AttrList, AttrVals, MPid, ListsAsStr); {gs, Id, keypress, _Data, ['Return' | _T]} -> OldCursor = gs:read(Id, cursor), gs:config(Id, [{cursor, busy}]), gs:config(win, [{cursor, busy}]), Insert = gs:read(insert, data), case get_record(TabType, TabName, AttrList, AttrList, Frames) of {ok, NewRec} -> case Insert of insert -> MPid ! {new_object, NewRec}; change -> MPid ! {updated_object, NewRec} end; error -> done end, gs:config(win, [{cursor, arrow}]), gs:config(Id, [{cursor, OldCursor}]), loop(TabType, TabName, Frames, AttrList, AttrVals, MPid, ListsAsStr); {gs, _Id, click, FrameNo, _Args} -> gs:config(lists:nth(FrameNo, Frames), [raise]), loop(TabType, TabName, Frames, AttrList, AttrVals, MPid, ListsAsStr); {gs, win, configure, _Data, [Width | _T]} -> resize_window(TabType, lists:max([Width, ?WIN_WIDTH]), Frames, AttrList), loop(TabType, TabName, Frames, AttrList, AttrVals, MPid, ListsAsStr); {gs, win, destroy, _Data, _Args} -> exit(normal); insert_mode -> NewAttrVals = lists:duplicate(length(AttrList), undefined), set_entry_values(TabType, AttrList, NewAttrVals, ListsAsStr), loop(TabType, TabName, Frames, AttrList, NewAttrVals, MPid, ListsAsStr); {update_mode, Obj} -> NewAttrVals = case TabType of mnesia -> case Obj of undefined -> lists:duplicate(length(AttrList), undefined); _AnyRec -> tl(tuple_to_list(Obj)) end; ets -> [Obj] end, set_entry_values(TabType, AttrList, NewAttrVals, ListsAsStr), loop(TabType, TabName, Frames, AttrList, NewAttrVals, MPid, ListsAsStr); {reset_info, Obj} -> %% Info to use, instead of old info, when reset button is pressed. NewAttrVals = case TabType of mnesia -> case Obj of undefined -> lists:duplicate(length(AttrList), undefined); _AnyRec -> tl(tuple_to_list(Obj)) end; ets -> [Obj] end, loop(TabType, TabName, Frames, AttrList, NewAttrVals, MPid, ListsAsStr); raise -> gs:config(win, [raise]), loop(TabType, TabName, Frames, AttrList,AttrVals, MPid, ListsAsStr); {'EXIT', _Pid, _Reason} -> exit(normal); _Other -> loop(TabType, TabName, Frames, AttrList,AttrVals, MPid, ListsAsStr) end. resize_window(TabType, WinWidth, Frames, AttrList) -> WinHeight = case TabType of mnesia -> get_window_height(length(AttrList)); ets -> ?ETS_WIN_HEIGHT end, gs:config(win, [{width, WinWidth}, {height, WinHeight} ]), FrameWidth = WinWidth, LblL = lists:map(fun(H) -> gs:config(H, [{width, FrameWidth}]), {LblW, BId, NId} = gs:read(H, data), XNext = get_next_btn_xpos(FrameWidth), XBack = XNext - ?NEXT_BTN_WIDTH, gs:config(BId, [{x, XBack}]), gs:config(NId, [{x, XNext}]), LblW end, Frames), LblW = hd(LblL), EntryW = get_entry_width(TabType, FrameWidth, LblW), lists:foreach(fun(H) -> gs:config(H, [{width, EntryW}]) end, AttrList), gs:config(btnframe, [{width, FrameWidth}]), {XInsert, XCancel, XReset} = get_insert_btn_coords(WinWidth), gs:config(insert, [{x, XInsert}]), gs:config(cancel, [{x, XCancel}]), gs:config(reset, [{x, XReset}]). check_entry_content(EntryId, AttributeList, Frames, Direction) -> EditedStr = gs:read(EntryId, text), case tv_db_search:string_to_term(EditedStr) of {error, {_Reason, Msg}} -> gs:config(EntryId, [beep]), tv_utils:notify(gs:start(), "TV Notification", Msg), {error, {EntryId, no_matter}}; {ok, NewTerm} -> {{ok,NewTerm}, get_next_entry_id(EntryId, AttributeList, Frames, Direction)} end. get_next_entry_id(EntryId, AttributeList, Frames, Direction) -> OldPos = get_pos(EntryId, AttributeList), MaxPos = length(AttributeList), NewPos = case Direction of forward when OldPos < MaxPos -> OldPos + 1; forward -> 1; backward when OldPos > 1 -> OldPos - 1; backward -> MaxPos; stationary -> OldPos end, FramePos = get_next_frame_id(NewPos), {lists:nth(NewPos, AttributeList), lists:nth(FramePos, Frames)}. get_next_frame_id(Pos) -> case Pos rem 5 of 0 -> Pos div 5; _Other -> (Pos div 5) + 1 end. get_record(TabType, TabName, AttrList, AttrList, Frames) -> case get_record(AttrList, AttrList, Frames, []) of {ok, RecList} -> case TabType of mnesia -> NewRecList = [TabName | RecList], {ok, list_to_tuple(NewRecList)}; ets -> {ok, hd(RecList)} %% Only one element, a tuple! end; error -> error end. get_record([H | T], AttrList, Frames, Acc) -> case check_entry_content(H, AttrList, Frames, forward) of {{ok, NewTerm}, _PosTuple} -> get_record(T, AttrList, Frames, [NewTerm | Acc]); {error, _PosTuple} -> {EntryId, FrameId} = get_next_entry_id(H, AttrList, Frames, stationary), gs:config(FrameId, [raise]), gs:config(EntryId, [{setfocus, true}]), error end; get_record([], _AttrList, _Frames, Acc) -> {ok, lists:reverse(Acc)}. get_pos(Elem, L) -> get_pos(Elem, L, 1). get_pos(Elem, [Elem | _T], N) -> N; get_pos(Elem, [_H | T], N) -> get_pos(Elem, T, N + 1). create_window(mnesia, TableName, AttrList, AttrValues, ListsAsStr, Insert) -> NofAttr = length(AttrList), NofFrames = case NofAttr rem 5 of 0 -> NofAttr div 5; _Rem -> (NofAttr div 5) + 1 end, WinHeight = get_window_height(NofAttr), FrameHeight = get_frame_height(NofAttr), Attr = get_longest_attribute_name(AttrList), LabelWidth = lists:min([?MAX_LABEL_WIDTH, element(1, gs:read(gs:start(), {font_wh, {?FONT, atom_to_list(Attr)}}))]), gs:window(win, gs:start(), [{width, ?WIN_WIDTH}, {height, WinHeight}, {title, "[TV] Record Editor: '" ++ atom_to_list(TableName) ++ "'"}, {bg, ?DEFAULT_BG_COLOR}, {configure, true}, {destroy, true}, {cursor, arrow} ]), create_insert_and_cancel_btns(Insert, WinHeight, FrameHeight), FrameList = create_frames(NofFrames, LabelWidth, AttrList, AttrValues, NofFrames, ListsAsStr, FrameHeight), gs:config(hd(FrameList), [raise]), gs:config(hd(AttrList), [{setfocus, true}, {select, {0,100000000}}]), gs:config(win, [{map,true}]), FrameList; create_window(ets, TableName, [Attr], [AttrVal], ListsAsStr, Insert) -> gs:window(win, gs:start(), [{width, ?WIN_WIDTH}, {height, ?ETS_WIN_HEIGHT}, {title, "[TV] Tuple Editor, table '" ++ atom_to_list(TableName) ++ "'"}, {bg, ?DEFAULT_BG_COLOR}, {configure, true}, {destroy, true}, {cursor, arrow} ]), F = gs:frame(win, [{width, ?FRAME_WIDTH}, {height, ?ETS_FRAME_HEIGHT}, {x, 0}, {y, 0}, {bg, ?DEFAULT_BG_COLOR}, {bw,2}, {data, {0, undefined, undefined}} ]), create_insert_and_cancel_btns(Insert, ?ETS_WIN_HEIGHT, ?ETS_FRAME_HEIGHT), EntryW = get_entry_width(ets, ?FRAME_WIDTH, 0), EntryX = ?X0 - 2, EntryText = case AttrVal of undefined -> ""; _OtherVal -> case ListsAsStr of true -> tv_io_lib:format("~p", [AttrVal]); false -> lists:flatten(io_lib:write(AttrVal)) end end, gs:entry(Attr, F, [{width, EntryW}, {height, ?LABEL_HEIGHT}, {x, EntryX}, {y, ?Y0}, {bg, {255,255,255}}, {fg, {0,0,0}}, {bw, 1}, {font, ?FONT}, {justify, left}, {text, EntryText}, {cursor, text}, {setfocus, true}, {enable, true}, {keypress,true}, {select, {0,100000000}} ]), gs:config(win, [{map,true}]), [F]. get_insert_btn_coords(WinWidth) -> Middle = round(WinWidth / 2), XInsert = Middle - round(1.5 * ?INSERT_BTN_WIDTH) - ?INSERT_BTN_DIST_BETWEEN, XCancel = Middle - round(0.5 * ?INSERT_BTN_WIDTH), XReset = Middle + round(0.5 * ?INSERT_BTN_WIDTH) + ?INSERT_BTN_DIST_BETWEEN, {XInsert, XCancel, XReset}. create_insert_and_cancel_btns(Insert, WinHeight, FrameHeight) -> LowerFrameHeight = WinHeight - FrameHeight, Y = ?INSERT_BTN_DIST_FROM_BOTTOM, {XInsert, XCancel, XReset} = get_insert_btn_coords(?WIN_WIDTH), {InsertBtnText, InsertBtnData} = case Insert of true -> {"Insert", insert}; false -> {"Change", change} end, gs:frame(btnframe, win, [{width, ?FRAME_WIDTH}, {height, LowerFrameHeight}, {x, 0}, {y, FrameHeight}, {bg, ?DEFAULT_BG_COLOR}, {bw,2} ]), gs:button(insert, btnframe, [{width, ?INSERT_BTN_WIDTH}, {height, ?INSERT_BTN_HEIGHT}, {x, XInsert}, {y, Y}, {bg, ?DEFAULT_BG_COLOR}, {fg, {0,0,0}}, {font, ?FONT}, {label, {text, InsertBtnText}}, {align, center}, {data, InsertBtnData} ]), gs:button(cancel, btnframe, [{width, ?INSERT_BTN_WIDTH}, {height, ?INSERT_BTN_HEIGHT}, {x, XCancel}, {y, Y}, {bg, ?DEFAULT_BG_COLOR}, {fg, {0,0,0}}, {font, ?FONT}, {label, {text, "Cancel"}}, {align, center} ]), gs:button(reset, btnframe, [{width, ?INSERT_BTN_WIDTH}, {height, ?INSERT_BTN_HEIGHT}, {x, XReset}, {y, Y}, {bg, ?DEFAULT_BG_COLOR}, {fg, {0,0,0}}, {font, ?FONT}, {label, {text, "Reset"}}, {align, center} ]). create_frames(0, _LblW, _AttrList, _AttrValues, _NofFrames, _ListsAsStr, _FrameHeight) -> []; create_frames(N, LblW, AttrList, AttrValues, NofFrames, ListsAsStr, FrameHeight) -> F = gs:frame(win, [{width, ?FRAME_WIDTH}, {height, FrameHeight}, {x, 0}, {y, 0}, {bg, ?DEFAULT_BG_COLOR}, {bw,2} ]), {BId, NId} = create_back_and_next_btns(F, 5, N, NofFrames), gs:config(F, [{data, {LblW, BId, NId}}]), {RemAttrList, RemAttrValues} = create_labels_and_entries(5, AttrList, AttrValues, LblW, F, ListsAsStr), [F | create_frames(N - 1,LblW,RemAttrList,RemAttrValues,NofFrames,ListsAsStr,FrameHeight)]. create_back_and_next_btns(FrameId, NofEntries, FrameNo, NofFrames) -> Y = ?Y0 + NofEntries * (?LABEL_HEIGHT + 10) + 8, XNext = get_next_btn_xpos(?FRAME_WIDTH), XBack = XNext - ?NEXT_BTN_WIDTH, DataNext = (NofFrames - FrameNo + 1) + 1, DataBack = (NofFrames - FrameNo + 1) - 1, BId = if DataBack =< 0 -> undefined; true -> gs:button(FrameId, [{width, ?NEXT_BTN_WIDTH}, {height, ?NEXT_BTN_HEIGHT}, {x, XBack}, {y, Y}, {bg, ?DEFAULT_BG_COLOR}, {fg, ?NEXT_BTN_FG}, {font, ?FONT}, {align, center}, {label, {text, "< Back"}}, %% {underline, 2}, {data, DataBack} ]) end, NId = if DataNext > NofFrames -> undefined; true -> gs:button(FrameId, [{width, ?NEXT_BTN_WIDTH}, {height, ?NEXT_BTN_HEIGHT}, {x, XNext}, {y, Y}, {bg, ?DEFAULT_BG_COLOR}, {fg, ?NEXT_BTN_FG}, {font, ?FONT}, {align, center}, {label, {text, " Next >"}}, %% {underline, 1}, {data, DataNext} ]) end, {BId, NId}. get_next_btn_xpos(FrameWidth) -> FrameWidth - ?X0 - ?NEXT_BTN_WIDTH. get_entry_width(TableType, FrameWidth, LblWidth) -> HorizontalSpacing = case TableType of mnesia -> 10; ets -> 0 end, FrameWidth - LblWidth - 2 * ?X0 - HorizontalSpacing. create_labels_and_entries(N, [H | T], [VH | VT], LblW, F, ListsAsStr) when N > 0 -> Y = ?Y0 + (5 - N) * (?LABEL_HEIGHT + 10), EntryW = get_entry_width(mnesia, ?FRAME_WIDTH, LblW), EntryX = ?FRAME_WIDTH - EntryW - ?X0 - 2, EntryText = case ListsAsStr of true -> tv_io_lib:format("~p", [VH]); false -> lists:flatten(io_lib:write(VH)) end, gs:label(F, [{width, LblW}, {height, ?LABEL_HEIGHT}, {x, ?X0}, {y, Y}, {bg, ?DEFAULT_BG_COLOR}, {fg, {0,0,0}}, {align,w}, {font, ?FONT}, {label, {text, atom_to_list(H)}} ]), gs:entry(H, F, [{width, EntryW}, {height, ?LABEL_HEIGHT}, {x, EntryX}, {y, Y}, {bg, {255,255,255}}, {fg, {0,0,0}}, {bw, 1}, {font, ?FONT}, {justify, left}, {text, EntryText}, {cursor, text}, {setfocus, false}, {enable, true}, {keypress,true} ]), create_labels_and_entries(N - 1, T, VT, LblW, F, ListsAsStr); create_labels_and_entries(0, RemAttrList, RemAttrValues, _LblW, _F, _ListsAsStr) -> {RemAttrList, RemAttrValues}; create_labels_and_entries(_N, [], [], _LblW, _F, _ListsAsStr) -> {[], []}. get_longest_attribute_name(AttrList) -> get_longest_attribute_name(AttrList, 0, undefined). get_longest_attribute_name([H | T], Max, Attr) -> CurrLength = length(atom_to_list(H)), if CurrLength >= Max -> get_longest_attribute_name(T, CurrLength, H); true -> get_longest_attribute_name(T, Max, Attr) end; get_longest_attribute_name([], _Max, Attr) -> Attr. get_window_height(N) -> if N >= 5 -> ?WIN_HEIGHT; true -> ?WIN_HEIGHT - ((5 - N) * (?LABEL_HEIGHT + 10) + ?NEXT_BTN_HEIGHT + 8) end. get_frame_height(N) -> if N >= 5 -> ?FRAME_HEIGHT; true -> ?FRAME_HEIGHT - ((5 - N) * (?LABEL_HEIGHT + 10) + ?NEXT_BTN_HEIGHT + 8) end. set_entry_values(TabType, [H | T], [VH | VT], ListsAsStr) -> EntryText = case VH of undefined when TabType =:= ets -> ""; _AnyValue -> case ListsAsStr of true -> tv_io_lib:format("~p", [VH]); false -> lists:flatten(io_lib:write(VH)) end end, gs:config(H, [{text, EntryText}]), set_entry_values(TabType, T, VT, ListsAsStr); set_entry_values(_TabType, [], [], _ListsAsStr) -> done.