From 84adefa331c4159d432d22840663c38f155cd4c1 Mon Sep 17 00:00:00 2001 From: Erlang/OTP Date: Fri, 20 Nov 2009 14:54:40 +0000 Subject: The R13B03 release. --- lib/tv/src/tv_rec_edit.erl | 744 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 744 insertions(+) create mode 100644 lib/tv/src/tv_rec_edit.erl (limited to 'lib/tv/src/tv_rec_edit.erl') diff --git a/lib/tv/src/tv_rec_edit.erl b/lib/tv/src/tv_rec_edit.erl new file mode 100644 index 0000000000..e8f663073e --- /dev/null +++ b/lib/tv/src/tv_rec_edit.erl @@ -0,0 +1,744 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1998-2009. 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). + + + +-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. -- cgit v1.2.3