diff options
author | Erlang/OTP <[email protected]> | 2009-11-20 14:54:40 +0000 |
---|---|---|
committer | Erlang/OTP <[email protected]> | 2009-11-20 14:54:40 +0000 |
commit | 84adefa331c4159d432d22840663c38f155cd4c1 (patch) | |
tree | bff9a9c66adda4df2106dfd0e5c053ab182a12bd /lib/debugger/src/dbg_ui_trace_win.erl | |
download | otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.gz otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.bz2 otp-84adefa331c4159d432d22840663c38f155cd4c1.zip |
The R13B03 release.OTP_R13B03
Diffstat (limited to 'lib/debugger/src/dbg_ui_trace_win.erl')
-rw-r--r-- | lib/debugger/src/dbg_ui_trace_win.erl | 1597 |
1 files changed, 1597 insertions, 0 deletions
diff --git a/lib/debugger/src/dbg_ui_trace_win.erl b/lib/debugger/src/dbg_ui_trace_win.erl new file mode 100644 index 0000000000..dbf93c7f45 --- /dev/null +++ b/lib/debugger/src/dbg_ui_trace_win.erl @@ -0,0 +1,1597 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-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(dbg_ui_trace_win). + +%% External exports +-export([init/0]). +-export([create_win/4, get_window/1, + configure/2, + enable/2, is_enabled/1, select/2, + add_break/3, update_break/2, delete_break/2, + clear_breaks/1, clear_breaks/2, + display/1, % Help messages + is_shown/2, % Code area + show_code/3, show_no_code/1, remove_code/2, + mark_line/3, unmark_line/1, + select_line/2, selected_line/1, + eval_output/2, % Evaluator area + update_bindings/1, % Bindings area + trace_output/1, % Trace area + handle_event/2 + ]). +-export([helpwin/4, % Help windows + helpwin/5]). + +-record(breakInfo, {point, status, break}). +-record(winInfo, {window, % gsobj() + size, % {W, H} + flags, % {F,F,F,F} F = open|close + + marked_line=0, % integer() Current line + selected_line=0, % integer() Selected line + + breaks=[], % [#breakInfo{}] Known breakpoints + + editor, % {Mod, Editor} Visible code editor + editors=[] % [{Mod,Editor}] Code editors + }). + + +%%==================================================================== +%% External exports +%%==================================================================== + +%%-------------------------------------------------------------------- +%% init() -> GS +%% GS = term() +%%-------------------------------------------------------------------- +init() -> + dbg_ui_win:init(). + +%%-------------------------------------------------------------------- +%% create_win(GS, Title, TraceWin, Menus) -> #winInfo{} +%% GS = gsobj() +%% Title = string() +%% TraceWin = [WinArea] +%% WinArea = 'Button|Evaluator|Bindings|Trace Area' +%% Menus = [menu()] See dbg_ui_win.erl +%%-------------------------------------------------------------------- +create_win(GS, Title, TraceWin, Menus) -> + Bu = flip(lists:member('Button Area', TraceWin)), + Ev = flip(lists:member('Evaluator Area', TraceWin)), + Bi = flip(lists:member('Bindings Area', TraceWin)), + Tr = flip(lists:member('Trace Area', TraceWin)), + + Win = gs:window(trace_window, GS, [{title, Title}, + {width, 550}, + {configure,true}, {destroy,true}, + {keypress,true}, {motion,true}]), + + MenuBar = gs:menubar(Win, []), + dbg_ui_win:create_menus(MenuBar, Menus), + dbg_ui_winman:windows_menu(MenuBar), + + FrameOpts = [{anchor,nw}, {relief,raised}, {bw,2}, {keypress,true}], + Editor = code_area(2, 25, FrameOpts, Win), + button_area(Bu, 2, 235, FrameOpts, Win), + eval_area({Ev,Bi}, 2, 265, FrameOpts, Win), + bind_area({Ev,Bi}, 300, 265, FrameOpts, Win), + trace_area(Tr, 2, 475, FrameOpts, Win), + + Flags = {Bu, Ev, Bi, Tr}, + resizebar(rb1(Flags), 'RB1', 2, 225, 710, 10, Win), + resizebar(rb2(Flags), 'RB2', 2, 465, 710, 10, Win), + resizebar(rb3(Flags), 'RB3', 290, 265, 10, 200, Win), + config_v(), + config_h(), + + gs:config(Win,{height, + 25 + + gs:read('CodeArea', height) + + gs:read('RB1', height) + + gs:read('ButtonArea', height) + + max(gs:read('EvalArea', height), + gs:read('BindArea', height)) + + gs:read('RB2', height) + + gs:read('TraceArea', height)}), + + gs:config(Win, {map, true}), + #winInfo{window=Win, size={gs:read(Win,width), gs:read(Win,height)}, + flags=Flags, + editor={'$top', Editor}, editors=[{'$top', Editor}]}. + +flip(true) -> open; +flip(false) -> close. + +%%-------------------------------------------------------------------- +%% get_window(WinInfo) -> Window +%% WinInfo = #winInfo{} +%% Window = gsobj() +%%-------------------------------------------------------------------- +get_window(WinInfo) -> + WinInfo#winInfo.window. + +%%-------------------------------------------------------------------- +%% configure(WinInfo, TraceWin) -> WinInfo +%% WinInfo = #winInfo{} +%% TraceWin = [WinArea] +%% WinArea = 'Button|Evaluator|Bindings|Trace Area' +%% Window areas should be opened or closed. +%%-------------------------------------------------------------------- +configure(WinInfo, TraceWin) -> + {Bu1, Ev1, Bi1, Tr1} = OldFlags = WinInfo#winInfo.flags, + Bu2 = flip(lists:member('Button Area', TraceWin)), + Ev2 = flip(lists:member('Evaluator Area', TraceWin)), + Bi2 = flip(lists:member('Bindings Area', TraceWin)), + Tr2 = flip(lists:member('Trace Area', TraceWin)), + NewFlags = {Bu2, Ev2, Bi2, Tr2}, + + Win = WinInfo#winInfo.window, + W = gs:read(Win, width), + H = gs:read(Win, height), + + H2 = if + Bu1==close, Bu2==open -> + resize_button_area(open, width, W-4), + gs:config('ButtonArea', {height, 30}), + H+30; + Bu1==open, Bu2==close -> + gs:config('ButtonArea', [{width, 0}, {height, 0}]), + H-30; + true -> H + end, + H3 = if + Ev1==close, Ev2==open, Bi1==open -> + Wnew1 = round((W-10-4)/2), % W = window/2 - rb - pads + Hbi1 = gs:read('BindArea', height), % H = bind area h + resize_eval_area(open, width, Wnew1), + resize_eval_area(open, height, Hbi1), + gs:config('RB3', {width, 10}), + gs:config('RB3', {height, Hbi1}), + resize_bind_area(open, width, + Wnew1-gs:read('BindArea', width)), + H2; + Ev1==close, Ev2==open, Bi1==close -> + resize_eval_area(open, width, W-4), + resize_eval_area(open, height, 200), + H2+200; + Ev1==open, Ev2==close, Bi1==open -> + gs:config('EvalArea', [{width,0}, {height,0}]), + gs:config('RB3', [{width, 0}, {height, 0}]), + Wnew2 = W-4, + resize_bind_area(open, width, + Wnew2-gs:read('BindArea', width)), + H2; + Ev1==open, Ev2==close, Bi1==close -> + Hs1 = gs:read('EvalArea', height), + gs:config('EvalArea', [{width, 0}, {height, 0}]), + H2-Hs1; + true -> H2 + end, + H4 = if + Bi1==close, Bi2==open, Ev2==open -> + Wnew3 = round((W-10-4)/2), % W = window/2 - rb - pads + Hs2 = gs:read('EvalArea', height), % H = eval area h + resize_bind_area(open, width, Wnew3), + resize_bind_area(open, height, Hs2), + gs:config('RB3', [{width,10},{height,Hs2}]), + resize_eval_area(open, width, + Wnew3-gs:read('EvalArea', width)), + H3; + Bi1==close, Bi2==open, Ev2==close -> + resize_bind_area(open, width, W-4), + resize_bind_area(open, height, 200), + H3+200; + Bi1==open, Bi2==close, Ev2==open -> + gs:config('BindArea', [{width, 0}, {height, 0}]), + gs:config('RB3', [{width, 0}, {height, 0}]), + Wnew4 = W-4, + resize_eval_area(open, width, + Wnew4-gs:read('EvalArea', width)), + H3; + Bi1==open, Bi2==close, Ev2==close -> + Hbi2 = gs:read('BindArea', height), + gs:config('BindArea', [{width, 0}, {height, 0}]), + H3-Hbi2; + true -> H3 + end, + H5 = if + Tr1==close, Tr2==open -> + resize_trace_area(open, width, W-4), + resize_trace_area(open, height, 200), + H4+200; + Tr1==open, Tr2==close -> + Hf = gs:read('TraceArea', height), + gs:config('TraceArea', [{width, 0}, {height, 0}]), + H4-Hf; + true -> H4 + end, + gs:config(Win, {height, H5}), + + RB1old = rb1(OldFlags), RB1new = rb1(NewFlags), + if + RB1old==close, RB1new==open -> + gs:config('RB1', [{width, W-4}, {height, 10}]), + gs:config(Win, {height, gs:read(Win, height)+10}); + RB1old==open, RB1new==close -> + gs:config('RB1', [{width, 0}, {height, 0}, lower]), + gs:config(Win, {height, gs:read(Win, height)-10}); + true -> ignore + end, + + RB2old = rb2(OldFlags), RB2new = rb2(NewFlags), + if + RB2old==close, RB2new==open -> + gs:config('RB2', [{width, W-4}, {height, 10}]), + gs:config(Win, {height,gs:read(Win, height)+10}); + RB2old==open, RB2new==close -> + gs:config('RB2', [{width, 0}, {height, 0}, lower]), + gs:config(Win, {height, gs:read(Win, height)-10}); + true -> ignore + end, + config_v(), + config_h(), + + flush_configure(), + + WinInfo#winInfo{size={gs:read(Win, width), gs:read(Win, height)}, + flags=NewFlags}. + +flush_configure() -> + receive + {gs, _Id, configure, _Data, _Arg} -> + flush_configure() + after 100 -> + true + end. + +%%-------------------------------------------------------------------- +%% enable([MenuItem], Bool) +%% is_enabled(MenuItem) -> Bool +%% MenuItem = atom() +%% Bool = boolean() +%%-------------------------------------------------------------------- +enable(MenuItems, Bool) -> + lists:foreach(fun(MenuItem) -> + gs:config(MenuItem, {enable, Bool}), + case is_button(MenuItem) of + {true, Button} -> + gs:config(Button, {enable, Bool}); + false -> ignore + end + end, + MenuItems). + +is_enabled(MenuItem) -> + gs:read(MenuItem, enable). + +%%-------------------------------------------------------------------- +%% select(MenuItem, Bool) +%% MenuItem = atom() +%% Bool = boolean() +%%-------------------------------------------------------------------- +select(MenuItem, Bool) -> + dbg_ui_win:select(MenuItem, Bool). + +%%-------------------------------------------------------------------- +%% add_break(WinInfo, Name, {Point, Options}) -> WinInfo +%% WinInfo = #winInfo{} +%% Name = atom() Menu name +%% Point = {Mod, Line} +%% Options = [Status, Action, Mods, Cond] +%% Status = active | inactive +%% Action = enable | disable | delete +%% Mods = null (not used) +%% Cond = null | {Mod, Func} +%%-------------------------------------------------------------------- +add_break(WinInfo, Menu, {{Mod,Line},[Status|_Options]}=Break) -> + case lists:keysearch(Mod, 1, WinInfo#winInfo.editors) of + {value, {Mod, Editor}} -> + add_break_to_code(Editor, Line, Status); + false -> ignore + end, + add_break_to_menu(WinInfo, Menu, Break). + +add_break_to_code(Editor, Line, Status) -> + Color = if Status==active -> red; Status==inactive -> blue end, + config_editor(Editor, [{overwrite,{{Line,0},"-@- "}}, + {fg,{{{Line,0},{Line,lineend}}, Color}}]). + +add_break_to_menu(WinInfo, Menu, {Point, [Status|_Options]=Options}) -> + Break = dbg_ui_win:add_break(Menu, Point), + dbg_ui_win:update_break(Break, Options), + BreakInfo = #breakInfo{point=Point, status=Status, break=Break}, + WinInfo#winInfo{breaks=[BreakInfo|WinInfo#winInfo.breaks]}. + +%%-------------------------------------------------------------------- +%% update_break(WinInfo, {Point, Options}) -> WinInfo +%% WinInfo = #winInfo{} +%% Point = {Mod, Line} +%% Options = [Status, Action, Mods, Cond] +%% Status = active | inactive +%% Action = enable | disable | delete +%% Mods = null (not used) +%% Cond = null | {Mod, Func} +%%-------------------------------------------------------------------- +update_break(WinInfo, {{Mod,Line},[Status|_Options]}=Break) -> + case lists:keysearch(Mod, 1, WinInfo#winInfo.editors) of + {value, {Mod, Editor}} -> + add_break_to_code(Editor, Line, Status); + false -> ignore + end, + update_break_in_menu(WinInfo, Break). + +update_break_in_menu(WinInfo, {Point, [Status|_Options]=Options}) -> + {value, BreakInfo} = lists:keysearch(Point, #breakInfo.point, + WinInfo#winInfo.breaks), + dbg_ui_win:update_break(BreakInfo#breakInfo.break, Options), + BreakInfo2 = BreakInfo#breakInfo{status=Status}, + WinInfo#winInfo{breaks=lists:keyreplace(Point, #breakInfo.point, + WinInfo#winInfo.breaks, + BreakInfo2)}. + +%%-------------------------------------------------------------------- +%% delete_break(WinInfo, Point) -> WinInfo +%% WinInfo = #winInfo{} +%% Point = {Mod, Line} +%%-------------------------------------------------------------------- +delete_break(WinInfo, {Mod,Line}=Point) -> + case lists:keysearch(Mod, 1, WinInfo#winInfo.editors) of + {value, {Mod, Editor}} -> delete_break_from_code(Editor, Line); + false -> ignore + end, + delete_break_from_menu(WinInfo, Point). + +delete_break_from_code(Editor, Line) -> + Prefix = string:substr(integer_to_list(Line)++": ", 1, 5), + config_editor(Editor, [{overwrite,{{Line,0},Prefix}}, + {fg,{{{Line,0},{Line,lineend}}, black}}]). + +delete_break_from_menu(WinInfo, Point) -> + {value, BreakInfo} = lists:keysearch(Point, #breakInfo.point, + WinInfo#winInfo.breaks), + dbg_ui_win:delete_break(BreakInfo#breakInfo.break), + WinInfo#winInfo{breaks=lists:keydelete(Point, #breakInfo.point, + WinInfo#winInfo.breaks)}. + +%%-------------------------------------------------------------------- +%% clear_breaks(WinInfo) -> WinInfo +%% clear_breaks(WinInfo, Mod) -> WinInfo +%% WinInfo = #winInfo{} +%%-------------------------------------------------------------------- +clear_breaks(WinInfo) -> + clear_breaks(WinInfo, all). +clear_breaks(WinInfo, Mod) -> + Remove = if + Mod==all -> WinInfo#winInfo.breaks; + true -> + lists:filter(fun(#breakInfo{point={Mod2,_L}}) -> + if + Mod2==Mod -> true; + true -> false + end + end, + WinInfo#winInfo.breaks) + end, + lists:foreach(fun(#breakInfo{point=Point}) -> + delete_break(WinInfo, Point) + end, + Remove), + Remain = WinInfo#winInfo.breaks -- Remove, + WinInfo#winInfo{breaks=Remain}. + +%%-------------------------------------------------------------------- +%% display(Arg) +%% Arg = idle | {Status,Mod,Line} | {running,Mod} +%% � {exit,Where,Reason} | {text,Text} +%% Status = break | wait � Level +%% Level = int() +%% Mod = atom() +%% Line = integer() +%% Where = {Mod,Line} | null +%% Reason = term() +%% Text = string() +%%-------------------------------------------------------------------- +display(Arg) -> + Str = case Arg of + idle -> "State: uninterpreted"; + {exit, {Mod,Line}, Reason} -> + gs:config(trace_window, raise), + io_lib:format("State: EXITED [~w.erl/~w], Reason:~w", + [Mod, Line, Reason]); + {exit, null, Reason} -> + gs:config(trace_window, raise), + io_lib:format("State: EXITED [uninterpreted], " + "Reason:~w", [Reason]); + {Level, null, _Line} when is_integer(Level) -> + io_lib:format("*** Call level #~w " + "(in non-interpreted code)", + [Level]); + {Level, Mod, Line} when is_integer(Level) -> + io_lib:format("*** Call level #~w [~w.erl/~w]", + [Level, Mod, Line]); + {Status, Mod, Line} -> + What = case Status of + wait -> 'receive'; + _ -> Status + end, + io_lib:format("State: ~w [~w.erl/~w]", + [What, Mod, Line]); + {running, Mod} -> + io_lib:format("State: running [~w.erl]", [Mod]); + {text, Text} -> Text + end, + gs:config(info_window, {label,{text,lists:flatten(Str)}}). + +%%-------------------------------------------------------------------- +%% is_shown(WinInfo, Mod) -> {true, WinInfo} | false +%% show_code(WinInfo, Mod, Contents) -> WinInfo +%% show_no_code(WinInfo) -> WinInfo +%% remove_code(WinInfo, Mod) -> WinInfo +%% WinInfo = #winInfo{} +%% Mod = atom() +%% Contents = string() +%% Note: remove_code/2 should not be used for currently shown module. +%%-------------------------------------------------------------------- +is_shown(WinInfo, Mod) -> + case lists:keysearch(Mod, 1, WinInfo#winInfo.editors) of + {value, {Mod, Editor}} -> + gs:config(Editor, raise), + {true, WinInfo#winInfo{editor={Mod, Editor}}}; + false -> false + end. + +show_code(WinInfo, Mod, Contents) -> + Editors = WinInfo#winInfo.editors, + {Flag, Editor} = case lists:keysearch(Mod, 1, Editors) of + {value, {Mod, Ed}} -> {existing, Ed}; + false -> {new, code_editor()} + end, + + %% Insert code and update breakpoints, if any + config_editor(Editor, [raise, clear]), + show_code(Editor, Contents), + lists:foreach(fun(BreakInfo) -> + case BreakInfo#breakInfo.point of + {Mod2, Line} when Mod2==Mod -> + Status = BreakInfo#breakInfo.status, + add_break_to_code(Editor, Line,Status); + _Point -> ignore + end + end, + WinInfo#winInfo.breaks), + + case Flag of + existing -> + WinInfo#winInfo{editor={Mod, Editor}}; + new -> + WinInfo#winInfo{editor={Mod, Editor}, + editors=[{Mod, Editor} | Editors]} + end. + +show_code(Editor, Text) when length(Text)>1500 -> + %% Add some text at a time so that other processes may get scheduled + Str = string:sub_string(Text, 1, 1500), + config_editor(Editor, {insert,{'end', Str}}), + show_code(Editor, string:sub_string(Text, 1501)); +show_code(Editor, Text) -> + config_editor(Editor, {insert,{'end',Text}}). + +show_no_code(WinInfo) -> + {value, {'$top', Editor}} = + lists:keysearch('$top', 1, WinInfo#winInfo.editors), + gs:config(Editor, raise), + WinInfo#winInfo{editor={'$top', Editor}}. + +remove_code(WinInfo, Mod) -> + Editors = WinInfo#winInfo.editors, + case lists:keysearch(Mod, 1, Editors) of + {value, {Mod, Editor}} -> + gs:destroy(Editor), + WinInfo#winInfo{editors=lists:keydelete(Mod, 1, Editors)}; + false -> + WinInfo + end. + + +%%-------------------------------------------------------------------- +%% mark_line(WinInfo, Line, How) -> WinInfo +%% WinInfo = #winInfo{} +%% Line = integer() +%% How = break | where +%% Mark the code line where the process is executing. +%%-------------------------------------------------------------------- +mark_line(WinInfo, Line, How) -> + {_Mod, Editor} = WinInfo#winInfo.editor, + mark_line2(Editor, WinInfo#winInfo.marked_line, false), + mark_line2(Editor, Line, How), + if + Line/=0 -> config_editor(Editor, {vscrollpos, Line-5}); + true -> ignore + end, + WinInfo#winInfo{marked_line=Line}. + +unmark_line(WinInfo) -> + mark_line(WinInfo, 0, false). + +mark_line2(Editor, Line, How) -> + Prefix = case How of + break -> "-->"; + where -> ">>>"; + false -> " " + end, + Font = if + How==false -> dbg_ui_win:font(normal); + true -> dbg_ui_win:font(bold) + end, + config_editor(Editor, [{overwrite, {{Line,5}, Prefix}}, + {font_style, + {{{Line,0},{Line,lineend}}, Font}}]). + +%%-------------------------------------------------------------------- +%% select_line(WinInfo, Line) -> WinInfo +%% selected_line(WinInfo) -> undefined | Line +%% WinInfo = #winInfo{} +%% Line = integer() +%% Select/unselect a line (unselect if Line=0). +%%-------------------------------------------------------------------- +select_line(WinInfo, Line) -> + {_Mod, Editor} = WinInfo#winInfo.editor, + + %% Since 'Line' may be specified by the user in the 'Go To Line' + %% help window, it must be checked that it is correct + Size = gs:read(Editor, size), + if + Line==0 -> + select_line(Editor, WinInfo#winInfo.selected_line, false), + WinInfo#winInfo{selected_line=0}; + Line<Size -> + select_line(Editor, Line, true), + config_editor(Editor, {vscrollpos, Line-5}), + WinInfo#winInfo{selected_line=Line}; + true -> + WinInfo + end. + +select_line(Editor, Line, true) -> + config_editor(Editor, {selection, {{Line,0}, {Line,lineend}}}); +select_line(Editor, _Line, false) -> + config_editor(Editor, {selection, {{1,0}, {1,0}}}). + +selected_line(WinInfo) -> + case WinInfo#winInfo.selected_line of + 0 -> undefined; + Line -> Line + end. + +%%-------------------------------------------------------------------- +%% eval_output(Str, Face) +%% Str = string() +%% Face = normal | bold +%%-------------------------------------------------------------------- +eval_output(Text, Face) -> + Y1 = gs:read('EvalEditor', size), + config_editor('EvalEditor', {insert,{'end',Text}}), + Y2 = gs:read('EvalEditor', size), + + Font = dbg_ui_win:font(Face), + config_editor('EvalEditor', + [{font_style, {{{Y1,0},{Y2,lineend}}, Font}}, + {vscrollpos,Y2}]). + +%%-------------------------------------------------------------------- +%% update_bindings(Bs) +%% Bs = [{Var,Val}] +%%-------------------------------------------------------------------- +update_bindings(Bs) -> + gs:config('BindGrid', {rows, {1,length(Bs)+1}}), + Font = dbg_ui_win:font(normal), + Last = + lists:foldl(fun({Var, Val}, Row) -> + Opts = [{text, {1,atom_to_list(Var)}}, + {text, {2,io_lib:format("~P", + [Val, 4])}}, + {doubleclick, true}, + {data, {binding,{Var,Val}}}], + case gs:read('BindGrid',{obj_at_row,Row}) of + undefined -> + gs:gridline('BindGrid', + [{row, Row}, + {height, 14}, + {font, Font} | Opts]); + GridLine -> + gs:config(GridLine, Opts) + end, + Row+1 + end, + 2, + Bs), + delete_gridlines(Last). + +delete_gridlines(Row) -> + case gs:read('BindGrid', {obj_at_row, Row}) of + undefined -> true; + GridLine -> + gs:destroy(GridLine), + delete_gridlines(Row+1) + end. + +%%-------------------------------------------------------------------- +%% trace_output(Str) +%% Str = string() +%%-------------------------------------------------------------------- +trace_output(Str) -> + Font = dbg_ui_win:font(normal), + config_editor('TraceEditor', + [{insert, {'end',Str}}, + {fg, {{{1,0},'end'},black}}, + {font_style, {{{1,0},'end'},Font}}]), + Max = gs:read('TraceEditor', size), + config_editor('TraceEditor', {vscrollpos, Max}). + +%%-------------------------------------------------------------------- +%% handle_event(GSEvent, WinInfo) -> Command +%% GSEvent = {gs, Id, Event, Data, Arg} +%% WinInfo = #winInfo{} +%% Command = ignore +%% | {win, WinInfo} +%% | stopped +%% | {coords, {X,Y}} +%% +%% | {shortcut, Key} +%% | MenuItem | {Menu, [MenuItem]} +%% MenuItem = Menu = atom() +%% | {break, Point, What} +%% What = add | delete | {status,Status} |{trigger,Trigger} +%% | {module, Mod, view} +%% +%% | {user_command, Cmd} +%% +%% | {edit, {Var, Val}} +%%-------------------------------------------------------------------- +%% Window events +handle_event({gs, _Id, configure, _Data, [W, H|_]}, WinInfo) -> + case WinInfo#winInfo.size of + {W, H} -> ignore; + _Size -> + configure(WinInfo, W, H), + {win, WinInfo#winInfo{size={W, H}}} + end; +handle_event({gs, _Id, destroy, _Data, _Arg}, _WinInfo) -> + stopped; +handle_event({gs, _Id, motion, _Data, [X,Y]}, WinInfo) -> + {LastX, LastY} = dbg_ui_win:motion(X, Y), + Win = WinInfo#winInfo.window, + {coords, {gs:read(Win, x)+LastX-5, gs:read(Win, y)+LastY-5}}; +handle_event({gs, RB, buttonpress, resizebar, _Arg}, WinInfo) -> + resize(WinInfo, RB), % Resize window contents + ignore; + +%% Menus, buttons and keyboard shortcuts +handle_event({gs, _Id, keypress, _Data, [Key,_,_,1]}, _WinInfo) -> + {shortcut, Key}; +handle_event({gs, _Id, click, {dbg_ui_winman, Win}, _Arg}, _WinInfo) -> + dbg_ui_winman:raise(Win), + ignore; +handle_event({gs, _Id, click, {menuitem, Name}, _Arg}, _WinInfo) -> + Name; +handle_event({gs, _Id, click, {menu, Menu}, _Arg}, _WinInfo) -> + Names = dbg_ui_win:selected(Menu), + {Menu, Names}; +handle_event({gs, _Id, click, {break, Point, What}, _Arg}, _WinInfo) -> + {break, Point, What}; +handle_event({gs, _Id, click, {module, Mod, view}, _Arg}, _WinInfo) -> + {module, Mod, view}; + +%% Code area +handle_event({gs, Editor, buttonpress, code_editor, _Arg}, WinInfo) -> + {Row, _Col} = gs:read(Editor, insertpos), + Again = receive + {gs, Editor, buttonpress, code_editor, _} -> + gs:read(Editor, insertpos) + after 500 -> + false + end, + case Again of + {Row, _} -> + {Mod, _Editor} = WinInfo#winInfo.editor, + Point = {Mod, Row}, + case lists:keysearch(Point, #breakInfo.point, + WinInfo#winInfo.breaks) of + {value, _BreakInfo} -> {break, Point, delete}; + false -> {break, Point, add} + end; + {Row2, _} -> + select_line(Editor, Row2, true), + {win, WinInfo#winInfo{selected_line=Row2}}; + false -> + select_line(Editor, Row, true), + {win, WinInfo#winInfo{selected_line=Row}} + end; + +%% Button area +handle_event({gs, _Id, click, {button, Name}, _Arg}, _WinInfo) -> + Name; + +%% Evaluator area +handle_event({gs, 'EvalEntry', keypress, _Data, ['Return'|_]}, _WI) -> + Command = case gs:read('EvalEntry', text) of + [10] -> + eval_output("\n", normal), + ignore; + Cmd -> + eval_output([$>, Cmd, 10], normal), + {user_command, Cmd} + end, + gs:config('EvalEntry', [{text,""}, {focus,false}]), + Command; + +%% Bindings area +handle_event({gs, _Id, click, {binding, {Var, Val}}, _Arg}, _WinInfo) -> + Str = io_lib:format("< ~p = ~p~n", [Var, Val]), + eval_output(Str, bold), + ignore; +handle_event({gs, _Id, doubleclick, {binding, B}, _Arg}, _WinInfo) -> + {edit, B}; + +handle_event(_GSEvent, _WinInfo) -> + ignore. + + +%%==================================================================== +%% Internal functions +%%==================================================================== + +%%--Code Area--------------------------------------------------------- + +code_area(X, Y, FrameOpts, Win) -> + gs:frame('CodeArea', Win, + [{x,X}, {y,Y}, {width,546}, {height,400} | FrameOpts]), + gs:label(info_window, 'CodeArea', + [{label,{text,""}}, {font,dbg_ui_win:font(normal)}, + {x,5}, {y,10}, {width,406}, {height,15}, + {anchor,nw}, {align,w}]), + code_editor('CodeEditor', 536, 365). + +code_editor() -> + W = gs:read('CodeEditor', width), + H = gs:read('CodeEditor', height), + code_editor(null, W, H). + +code_editor(Name, W, H) -> + Editor = if + Name==null -> gs:editor('CodeArea', []); + true -> gs:editor(Name, 'CodeArea', []) + end, + gs:config(Editor, [{x,5}, {y,30}, {width,W}, {height,H}, + {keypress,false}, {buttonpress,true}, + {data,code_editor}]), + config_editor(Editor, [{vscroll,right}, {hscroll,bottom}]), + Font = dbg_ui_win:font(normal), + config_editor(Editor, [{wrap,none}, {fg,{{{1,0},'end'},black}}, + {font, Font}, + {font_style, {{{1,0},'end'},Font}}]), + Editor. + +resize_code_area(WinInfo, Key, Diff) -> + gs:config('CodeArea', {Key,gs:read('CodeArea', Key)+Diff}), + case Key of + width -> + gs:config(info_window, {Key,gs:read(info_window,Key)+Diff}); + height -> ignore + end, + + %% Resize all code editors + Value = gs:read('CodeEditor', Key)+Diff, + gs:config('CodeEditor', {Key,Value}), + Editors = WinInfo#winInfo.editors, + lists:foreach(fun({_Mod, Editor}) -> + gs:config(Editor, {Key,Value}) + end, + Editors). + +%%--Button Area------------------------------------------------------- + +buttons() -> + [{'Step','StepButton'}, {'Next','NextButton'}, + {'Continue','ContinueButton'}, {'Finish','FinishButton'}, + {'Where','WhereButton'}, {'Up','UpButton'}, {'Down','DownButton'}]. + +is_button(Name) -> + case lists:keysearch(Name, 1, buttons()) of + {value, {Name, Button}} -> {true, Button}; + false -> false + end. + +button_area(Bu, X, Y, FrameOpts, Win) -> + {W,H} = case Bu of + open -> {546,30}; + close -> {0,0} + end, + gs:frame('ButtonArea', Win, + [{x,X}, {y,Y}, {width,W}, {height,H} | FrameOpts]), + Font = dbg_ui_win:font(normal), + lists:foldl(fun({Name, Button}, Xb) -> + gs:button(Button, 'ButtonArea', + [{label, {text,Name}}, {font,Font}, + {x, Xb}, {y, 1}, + {width, 77}, {height, 24}, + {data, {button,Name}}]), + Xb+78 + end, + 1, + buttons()). + +resize_button_area(close, width, _Diff) -> + ignore; +resize_button_area(open, width, Diff) -> + gs:config('ButtonArea', {width, gs:read('ButtonArea', width)+Diff}). + +%%--Evaluator Area---------------------------------------------------- + +eval_area({Ev,Bi}, X, Y, FrameOpts, Win) -> + {W,H} = if + Ev==open -> {289,200}; + true -> {0,0} + end, + Font = dbg_ui_win:font(normal), + gs:frame('EvalArea', Win, + [{x,X}, {y,Y}, {width,W}, {height,H} | FrameOpts]), + gs:label('EvalArea', + [{label,{text,"Evaluator:"}}, {font, Font}, + {x,5}, {y,35}, {width,80}, {height,25}, + {anchor,sw}, {align,center}]), + gs:entry('EvalEntry', 'EvalArea', + [{font, Font}, + {x,80}, {y,35}, {width,185}, {height,25}, + {anchor,sw}, {keypress,true}]), + gs:editor('EvalEditor', 'EvalArea', + [{x,5}, {y,35}, {width, 280}, {height, 160}, + {keypress,false}, + {vscroll,right}, {hscroll,bottom}, + {wrap,none}, {fg,{{{1,0},'end'},black}}, + {font, Font}, + {font_style,{{{1,0},'end'},Font}}]), + gs:config('EvalEditor', {enable, false}), + if + Ev==open, Bi==close -> resize_eval_area(Ev, width, 257); + true -> ignore + end. + +resize_eval_area(close, _Key, _Diff) -> + ignore; +resize_eval_area(open, Key, Diff) -> + New = gs:read('EvalArea', Key)+Diff, + gs:config('EvalArea', {Key,New}), + case Key of + width -> + gs:config('EvalEntry', {width,New-104}), + gs:config('EvalEditor', {width,New-9}); + height -> + gs:config('EvalEditor', {height,New-40}) + end. + +%%--Bindings Area----------------------------------------------------- + +bind_area({Ev,Bi}, X, Y, FrameOpts, Win) -> + {W,H} = if + Bi==open -> {249,200}; + true -> {0,0} + end, + gs:frame('BindArea', Win, + [{x,X}, {y,Y}, {width,W}, {height,H} | FrameOpts]), + + Font = dbg_ui_win:font(bold), + gs:grid('BindGrid', 'BindArea', + [{x,2}, {y,2}, {height,193}, {width,241}, + {fg,black}, {vscroll,right}, {hscroll,bottom}, + {font,Font}, + calc_columnwidths(241), {rows, {1,50}}]), + gs:gridline('BindGrid', + [{row,1}, {height,14}, {fg,blue}, + {text,{1,"Name"}}, {text,{2,"Value"}}, {font,Font}]), + gs:config('BindGrid', {rows,{1,1}}), + if + Bi==open, Ev==close -> resize_bind_area(Bi, width, 297); + true -> ignore + end. + +resize_bind_area(close, _Key, _Diff) -> + ignore; +resize_bind_area(open, Key, Diff) -> + New = gs:read('BindArea', Key)+Diff, + gs:config('BindArea', {Key,New}), + case Key of + width -> + gs:config('BindGrid', {width,New-8}), + Cols = calc_columnwidths(New-8), + gs:config('BindGrid', Cols); + height -> + gs:config('BindGrid', {height,New-7}) + end. + +calc_columnwidths(Width) -> + if Width =< 291 -> + {columnwidths,[90,198]}; + true -> + S = (Width)/(90+198), + {columnwidths,[round(90*S),round(198*S)]} + end. + +%%--Trace Area-------------------------------------------------------- + +trace_area(Tr, X, Y, FrameOpts, Win) -> + {W,H} = case Tr of + open -> {546,200}; + close -> {0,0} + end, + gs:frame('TraceArea', Win, + [{x,X}, {y,Y}, {width,W}, {height,H} | FrameOpts]), + Editor = gs:editor('TraceEditor', 'TraceArea', + [{x,5}, {y,5}, {width,536}, {height,190}, + {keypress,false}]), + Font = dbg_ui_win:font(normal), + config_editor(Editor, + [{vscroll,right}, {hscroll,bottom}, + {wrap,none},{fg,{{{1,0},'end'},black}}, + {font, Font}, + {font_style,{{{1,0},'end'},Font}}]). + +resize_trace_area(close, _Key, _Diff) -> + ignore; +resize_trace_area(open, Key, Diff) -> + New = gs:read('TraceArea', Key)+Diff, + gs:config('TraceArea', {Key,New}), + gs:config('TraceEditor', {Key,New-10}). + +%%--Editors----------------------------------------------------------- + +config_editor(Editor, Opts) -> + gs:config(Editor, {enable,true}), + gs:config(Editor, Opts), + gs:config(Editor, {enable,false}). + +%%--Resize Bars------------------------------------------------------- +%% The resize bars are used to resize the areas within the window. + +%%-------------------------------------------------------------------- +%% resizebar(Flag, Name, X, Y, W, H, Obj) -> resizebar() +%% Flag = open | close +%% Name = atom() +%% X = Y = integer() Coordinates relative to Obj +%% W = H = integer() Width and height +%% Obj = gsobj() +%% Creates a 'resize bar', a frame object over which the cursor will +%% be of the 'resize' type. +%%-------------------------------------------------------------------- +resizebar(Flag, Name, X, Y, W, H, Obj) -> + {W2,H2} = case Flag of + open -> {W,H}; + close -> {0,0} + end, + gs:create(frame, Name, Obj, [{x,X}, {y,Y}, {width,W2}, {height,H2}, + {bw,2}, + {cursor,resize}, + {buttonpress,true}, {buttonrelease,true}, + {data,resizebar}]). + +rb1({_Bu,Ev,Bi,Tr}) -> + if + Ev==close, Bi==close, Tr==close -> close; + true -> open + end. + +rb2({_Bu,Ev,Bi,Tr}) -> + if + Tr==open -> + if + Ev==close, Bi==close -> close; + true -> open + end; + true -> close + end. + +rb3({_Bu,Ev,Bi,_Tr}) -> + if + Ev==open, Bi==open -> open; + true -> close + end. + +%%--Configuration----------------------------------------------------- +%% Resize the window as well as its contents + +%%-------------------------------------------------------------------- +%% config_v() +%% Reconfigure the window vertically. +%%-------------------------------------------------------------------- +config_v() -> + Y1 = 25+gs:read('CodeArea', height), + gs:config('RB1', {y,Y1}), + + Y2 = Y1+gs:read('RB1', height), + gs:config('ButtonArea', {y,Y2}), + + Y3 = Y2+gs:read('ButtonArea', height), + gs:config('EvalArea', {y,Y3}), + gs:config('RB3', {y,Y3}), + gs:config('BindArea', {y,Y3}), + + Y4 = Y3 + max(gs:read('EvalArea', height), + gs:read('BindArea', height)), + gs:config('RB2', {y,Y4}), + + Y5 = Y4 + gs:read('RB2', height), + gs:config('TraceArea', {y,Y5}). + +%%-------------------------------------------------------------------- +%% config_h() +%% Reconfigure the window horizontally. +%%-------------------------------------------------------------------- +config_h() -> + X1 = 2+gs:read('EvalArea', width), + gs:config('RB3', {x,X1}), + + X2 = X1+gs:read('RB3', width), + gs:config('BindArea', {x,X2}). + +%%-------------------------------------------------------------------- +%% configure(WinInfo, W, H) +%% The window has been resized, now its contents must be resized too. +%%-------------------------------------------------------------------- +configure(WinInfo, NewW, NewH) -> + {Bu,Ev,Bi,Tr} = Flags = WinInfo#winInfo.flags, + + OldW = gs:read('CodeArea', width)+4, + OldH = 25+gs:read('CodeArea', height)+ + gs:read('RB1', height)+ + gs:read('ButtonArea', height)+ + max(gs:read('EvalArea', height), gs:read('BindArea', height))+ + gs:read('RB2', height)+ + gs:read('TraceArea', height), + + %% Adjust width unless it is unchanged or less than minimum width + if + OldW/=NewW -> + {Dcode,Deval,Dbind} = configure_widths(OldW,NewW,Flags), + resize_code_area(WinInfo, width, Dcode), + case rb1(Flags) of + open -> + gs:config('RB1', {width,gs:read('RB1',width)+Dcode}); + close -> ignore + end, + resize_button_area(Bu, width, Dcode), + resize_eval_area(Ev, width, Deval), + resize_bind_area(Bi, width, Dbind), + case rb2(Flags) of + open -> + gs:config('RB2', {width,gs:read('RB2',width)+Dcode}); + close -> ignore + end, + resize_trace_area(Tr, width, Dcode), + config_h(); + true -> ignore + end, + + %% Adjust height unless it is unchanged or less than minimum height + if + OldH/=NewH -> + {Dcode2,Deval2,Dtrace} = configure_heights(OldH,NewH,Flags), + resize_code_area(WinInfo, height, Dcode2), + resize_eval_area(Ev, height, Deval2), + case rb3(Flags) of + open -> + gs:config('RB3', + {height,gs:read('RB3',height)+Deval2}); + close -> ignore + end, + resize_bind_area(Bi, height, Deval2), + resize_trace_area(Tr, height, Dtrace), + config_v(); + true -> ignore + end. + +%% Compute how much the width of each frame must be increased or +%% decreased in order to adjust to the new window width. +configure_widths(OldW, NewW, Flags) -> + {_Bu,Ev,Bi,_Tr} = Flags, + + %% Difference between old and new width, considering min window width + Diff = abs(max(OldW,330)-max(NewW,330)), + + %% Check how much the frames can be resized in reality + Limits = if + %% Window larger + NewW>OldW -> + if + Ev==open,Bi==open -> {0,Diff,Diff}; + Ev==open -> {0,Diff,0}; + Bi==open -> {0,0,Diff}; + true -> {Diff,0,0} + end; + + %% Window smaller; get difference between min size + %% and current size + OldW>NewW -> + if + Ev==open,Bi==open -> + {0, + gs:read('EvalArea',width)-204, + gs:read('BindArea',width)-112}; + Ev==open -> {0,Diff,0}; + Bi==open -> {0,0,Diff}; + true -> {Diff,0,0} + end + end, + + case Limits of + + %% No Shell or Bind frame, larger window + {T,0,0} when NewW>OldW -> {T,0,0}; + + %% No Shell or Bind frame, smaller window + {T,0,0} when OldW>NewW -> {-T,0,0}; + + %% Window larger; divide Diff among the frames and return result + {_,Sf,B} when NewW>OldW -> + {_,Sf2,B2} = divide([{0,0},{0,Sf},{0,B}],Diff), + {Sf2+B2,Sf2,B2}; + + %% Window smaller; divide Diff among the frames and return + %% the inverted result (the frames should shrink) + {_,Sf,B} when OldW>NewW -> + {_,Sf2,B2} = divide([{0,0},{0,Sf},{0,B}],Diff), + {-(Sf2+B2),-Sf2,-B2} + end. + +%% Compute how much the height of each frame must be increased or +%% decreased in order to adjust to the new window height. +configure_heights(OldH, NewH, Flags) -> + {_Bu,Ev,Bi,Tr} = Flags, + + %% Difference between old and new height, considering min win height + MinH = min_height(Flags), + Diff = abs(max(OldH,MinH)-max(NewH,MinH)), + + %% Check how much the frames can be resized in reality + {T,Sf,Ff} = if + %% Window larger + NewH>OldH -> + {Diff, + if + Ev==close, Bi==close -> 0; + true -> Diff + end, + if + Tr==open -> Diff; + true -> 0 + end}; + + %% Window smaller; get difference between min size + %% and current size + OldH>NewH -> + {gs:read('CodeArea',height)-100, + if + Ev==close, Bi==close -> 0; + true -> + if + Ev==open -> + gs:read('EvalArea',height)-100; + Bi==open -> + gs:read('BindArea',height)-100 + end + end, + if + Tr==open -> gs:read('TraceArea',height)-100; + true -> 0 + end} + end, + + if + %% Window larger; divide Diff among the frames and return result + NewH>OldH -> divide([{0,T},{0,Sf},{0,Ff}],Diff); + + %% Window smaller; divide Diff among the frames and return + %% the inverted result (the frames should shrink) + OldH>NewH -> + {T2,Sf2,Ff2} = divide([{0,T},{0,Sf},{0,Ff}],Diff), + {-T2,-Sf2,-Ff2} + end. + +%% Compute minimum window height +min_height(Flags) -> + {Bu,S,Bi,F} = Flags, + H1 = 25 + 100 + 2, % Upper pad + Trace frame + lower pad + H2 = H1 + bu(Bu) + s_bi(S,Bi) + f(F), + H3 = case rb1(Flags) of + open -> H2+10; + close -> H2 + end, + H4 = case rb2(Flags) of + open -> H3+10; + close -> H3 + end, + H4. + +bu(close) -> 0; +bu(open) -> 30. + +s_bi(close,close) -> 0; +s_bi(_,_) -> 100. + +f(close) -> 0; +f(open) -> 100. + +%% Try to distribute Diff as evenly as possible between E1, E2 and E3. +divide([{T,T},{S,S},{F,F}], _Diff) -> + {T,S,F}; +divide(L, Diff) -> + [{T,Tmax},{S,Smax},{F,Fmax}] = L, + + %% Count how many elements in L can still be filled + Rem = remaining(L), + + %% Divide Diff by Rem + D = Diff div Rem, + + if + %% All of Diff has been distributed + D==0 -> {T,S,F}; + + true -> + + %% For each element, try to add as much as possible of D + {NewT,Dt} = divide2(D,T,Tmax), + {NewS,Ds} = divide2(D,S,Smax), + {NewF,Df} = divide2(D,F,Fmax), + + %% Recur with a list of elements with new current values + %% and decreased Diff + divide([{NewT,Tmax},{NewS,Smax},{NewF,Fmax}], + (Diff rem Rem)+Dt+Ds+Df) + end. + +%% Count the number of 'non-filled' elements in L, ie where Curr<Max. +remaining([]) -> 0; +remaining([{Max,Max}|T]) -> remaining(T); +remaining([_H|T]) -> 1 + remaining(T). + +divide2(_Diff, Max, Max) -> {Max,0}; +divide2(Diff, Curr, Max) -> + New = Curr+Diff, + if + New>Max -> {Max,New-Max}; + true -> {New,0} + end. + +%%--Resizing using resize bars---------------------------------------- +%% Motions event will move the ResizeBar accordingly in Win, when +%% the mouse button is released, the window is reconfigured. + +resize(WinInfo, ResizeBar) -> + + %% Get window dimensions + W = gs:read(WinInfo#winInfo.window, width), + H = gs:read(WinInfo#winInfo.window, height), + + %% Call resize loop with min and max for the resize bars derived + %% from the window dimensions + resizeloop(WinInfo, ResizeBar, null, + rblimits('RB1',W,H), + rblimits('RB2',W,H), + rblimits('RB3',W,H)). + +resizeloop(WI, RB, Prev, {Min1,Max1},{Min2,Max2},{Min3,Max3}) -> + receive + {gs,_,motion,_,[_,Y]} when RB=='RB1', Y>Min1,Y<Max1 -> + gs:config('RB1', {y,Y}), + resizeloop(WI, RB, Y, {Min1,Max1},{Min2,Max2},{Min3,Max3}); + {gs,_,motion,_,_} when RB=='RB1' -> + resizeloop(WI, RB, Prev,{Min1,Max1},{Min2,Max2},{Min3,Max3}); + + {gs,_,motion,_,[_,Y]} when RB=='RB2', Y>Min2,Y<Max2 -> + gs:config('RB2', {y,Y}), + resizeloop(WI, RB, Y, {Min1,Max1},{Min2,Max2},{Min3,Max3}); + {gs,_,motion,_,_} when RB=='RB2' -> + resizeloop(WI, RB, Prev,{Min1,Max1},{Min2,Max2},{Min3,Max3}); + + {gs,_,motion,_,[X,_]} when RB=='RB3', X>Min3,X<Max3 -> + gs:config('RB3', {x,X}), + resizeloop(WI, RB, X, {Min1,Max1},{Min2,Max2},{Min3,Max3}); + {gs,_,motion,_,_} when RB=='RB3' -> + resizeloop(WI, RB, Prev,{Min1,Max1},{Min2,Max2},{Min3,Max3}); + + {gs,_,buttonrelease,_,_} -> + resize_win(WI, RB, Prev) + end. + +resize_win(_WinInfo, _RB, null) -> ignore; +resize_win(WinInfo, 'RB1', Y) -> + {_Bu,S,Bi,F} = Flags = WinInfo#winInfo.flags, + H = gs:read('CodeArea', height), + Diff = H-(Y-25), + + %% Resize Code, Evaluator and Binding areas + resize_code_area(WinInfo, height, -Diff), + if + S==close, Bi==close, F==open -> + resize_trace_area(open, height, Diff); + true -> + resize_eval_area(S, height, Diff), + resize_bind_area(Bi, height, Diff) + end, + + %% Resize vertical resize bar + case rb3(Flags) of + open -> gs:config('RB3', {height,gs:read('RB3',height)+Diff}); + close -> ignore + end, + + %% Adjust the frames y coordinates + config_v(); +resize_win(WinInfo, 'RB2', Y) -> + {_Bu,S,Bi,F} = Flags = WinInfo#winInfo.flags, + Prev = gs:read('TraceArea',y), + Diff = Prev-(Y+10), + + %% Resize Trace, Evaluator and Binding areas + resize_trace_area(F, height, Diff), + resize_eval_area(S, height, -Diff), + resize_bind_area(Bi, height, -Diff), + + %% Resize vertical resize bar + case rb3(Flags) of + open -> gs:config('RB3', {height,gs:read('RB3',height)-Diff}); + close -> ignore + end, + + %% Adjust the frames y coordinates + config_v(); + +resize_win(WinInfo, 'RB3', X) -> + {_Bu,S,Bi,_F} = WinInfo#winInfo.flags, + Prev = gs:read('BindArea', x), + Diff = Prev-(X+10), + + %% Resize Binding and Trace areas + resize_bind_area(Bi, width, Diff), + resize_eval_area(S, width, -Diff), + + %% Adjust the frames x coordinates + config_h(). + +%% Given the window dimensions, return the limits for a resize bar. +rblimits('RB1',_W,H) -> + + %% Code frame should not have height <100 + Min = 126, + + %% Max is decided by a minimum distance to 'RB2' + %% unless 'RB2' is invisible and 'CodeArea' is visible + %% (=> EvalFrame and BindFrame invisible) in which case + %% TraceFrame should not have height <100 + RB2 = gs:read('RB2',height), + FF = gs:read('TraceArea',height), + Max = case RB2 of + 0 when FF/=0 -> + H-112; + _ -> + Y = gs:read('RB2',y), + max(Min,Y-140) + end, + + {Min,Max}; +rblimits('RB2',_W,H) -> + + %% TraceFrame should not have height <100 + Max = H-112, + + %% Min is decided by a minimum distance to 'RB1' + Y = gs:read('RB1',y), + Min = min(Max,Y+140), + + {Min,Max}; + +rblimits('RB3',W,_H) -> + + %% Neither CodeArea nor BindArea should occupy + %% less than 1/3 of the total window width and EvalFrame should + %% be at least 289 pixels wide + {max(round(W/3),289),round(2*W/3)}. + +max(A, B) when A>B -> A; +max(_A, B) -> B. + +min(A, B) when A<B -> A; +min(_A, B) -> B. + + +%%==================================================================== +%% 'Go To Line' and 'Search' help windows +%%==================================================================== + +helpwin(gotoline, WinInfo, GS, Coords) -> + spawn_link(?MODULE, helpwin, [gotoline, WinInfo, GS, Coords,self()]); +helpwin(search, WinInfo, GS, Coords) -> + spawn_link(?MODULE, helpwin, [search, WinInfo, GS, Coords, self()]). + +helpwin(Type, WinInfo, GS, Coords, AttPid) -> + {_Mod, Editor} = WinInfo#winInfo.editor, + Data = case Type of + gotoline -> null; + search -> + {{1, 0}, false} + end, + Win = helpwin(Type, GS, Coords), + helpwin_loop(Type, AttPid, Editor, Data, Win). + +helpwin_loop(Type, AttPid, Editor, Data, Win) -> + receive + {gs, _Id, destroy, _Data, _Arg} -> + helpwin_stop(Type, AttPid, Editor, Data), + true; + + {gs, _Id, keypress, _Data, ['Return'|_]} -> + gs:config(btn(Win), flash), + Data2 = helpwin_action(Type, default, + AttPid, Editor, Data, Win), + helpwin_loop(Type, AttPid, Editor, Data2, Win); + {gs, _Id, keypress, _Data, _Arg} -> + helpwin_loop(Type, AttPid, Editor, Data, Win); + + {gs, _Id, click, _Data, ["Clear"]} -> + gs:config(ent(Win), {delete, {0,last}}), + Data2 = helpwin_clear(Type, AttPid, Editor, Data, Win), + helpwin_loop(Type, AttPid, Editor, Data2, Win); + {gs, _Id, click, _Data, ["Close"]} -> + helpwin_stop(Type, AttPid, Editor, Data), + true; + {gs, _Id, click, Action, _Arg} -> + Data2 = + helpwin_action(Type, Action, AttPid, Editor, Data, Win), + helpwin_loop(Type, AttPid, Editor, Data2, Win) + end. + +helpwin_stop(gotoline, _AttPid, _Editor, _Data) -> + ignore; +helpwin_stop(search, _AttPid, Editor, {Pos, _CS}) -> + unmark_string(Editor, Pos). + +helpwin_clear(gotoline, _AttPid, _Editor, Data, _Win) -> + Data; +helpwin_clear(search, _AttPid, Editor, {Pos, CS}, Win) -> + unmark_string(Editor, Pos), + gs:config(lbl(Win), {label, {text,""}}), + {{1, 0}, CS}. + +helpwin_action(gotoline, default, AttPid, _Editor, Data, Win) -> + case string:strip(gs:read(ent(Win), text)) of + "" -> ignore; + Str -> + case catch list_to_integer(Str) of + {'EXIT', _Reason} -> ignore; + Line -> AttPid ! {gui, {gotoline, Line}} + end + end, + Data; +helpwin_action(search, case_sensitive, _AttPid, _Ed, {Pos, CS}, _Win) -> + Bool = if CS==true -> false; CS==false -> true end, + {Pos, Bool}; +helpwin_action(search, default, _AttPid, Editor, {Pos, CS}, Win) -> + gs:config(lbl(Win), {label, {text, ""}}), + unmark_string(Editor, Pos), + case gs:read(ent(Win), text) of + "" -> {Pos, CS}; + Str -> + gs:config(lbl(Win), {label, {text,"Searching..."}}), + Str2 = lowercase(CS, Str), + case search(Str2, Editor, gs:read(Editor, size), Pos, CS) of + {Row, Col} -> + gs:config(lbl(Win), {label, {text,""}}), + mark_string(Editor, {Row, Col}, Str), + {{Row, Col}, CS}; + not_found -> + gs:config(lbl(Win), {label, {text,"Not found"}}), + {Pos, CS} + end + end. + +search(_Str, _Editor, Max, {Row, _Col}, _CS) when Row>Max -> + not_found; +search(Str, Editor, Max, {Row, Col}, CS) -> + SearchIn = lowercase(CS, gs:read(Editor, + {get,{{Row,Col+1},{Row,lineend}}})), + case string:str(SearchIn, Str) of + 0 -> search(Str, Editor, Max, {Row+1, 0}, CS); + N -> {Row, Col+N} + end. + +lowercase(true, Str) -> Str; +lowercase(false, Str) -> + lists:map(fun(Char) -> + if + Char>=$A, Char=<$Z -> Char+32; + true -> Char + end + end, + Str). + +mark_string(Editor, {Row, Col}, Str) -> + Between = {{Row,Col}, {Row,Col+length(Str)}}, + Font = dbg_ui_win:font(bold), + gs:config(Editor, [{vscrollpos, Row-5}, + {font_style, {Between, Font}}, + {fg, {Between, red}}]). + +unmark_string(Editor, {Row, Col}) -> + Between = {{Row,Col}, {Row,lineend}}, + Font = dbg_ui_win:font(normal), + gs:config(Editor, [{vscrollpos, Row-5}, + {font_style, {Between, Font}}, + {fg, {Between, black}}]). + +helpwin(Type, GS, {X, Y}) -> + W = 200, Pad=10, Wbtn = 50, + + Title = + case Type of search -> "Search"; gotoline -> "Go To Line" end, + Win = gs:window(GS, [{title, Title}, {x, X}, {y, Y}, {width, W}, + {destroy, true}]), + + Ent = gs:entry(Win, [{x, Pad}, {y, Pad}, {width, W-2*Pad}, + {keypress, true}]), + Hent = gs:read(Ent, height), + + Font = dbg_ui_win:font(normal), + + {Ybtn, Lbl} = + case Type of + search -> + Ycb = Pad+Hent, + gs:checkbutton(Win, [{label, {text, "Case Sensitive"}}, + {font, Font}, + {align, w}, + {x, Pad}, {y, Ycb}, + {width, W-2*Pad}, {height, 15}, + {data, case_sensitive}]), + Ylbl = Ycb+15, + {Ylbl+Hent+Pad, + gs:label(Win, [{x, Pad}, {y, Ylbl}, + {width, W-2*Pad}, {height, Hent}])}; + gotoline -> {Pad+Hent+Pad, null} + end, + + BtnLbl = case Type of search -> "Search"; gotoline -> "Go" end, + Btn = gs:button(Win, [{label, {text, BtnLbl}}, {font, Font}, + {x, W/2-3/2*Wbtn-Pad}, {y, Ybtn}, + {width, Wbtn}, {height, Hent}, + {data, default}]), + gs:button(Win, [{label, {text, "Clear"}}, {font, Font}, + {x, W/2-1/2*Wbtn}, {y, Ybtn}, + {width, Wbtn}, {height, Hent}]), + gs:button(Win, [{label, {text, "Close"}}, {font, Font}, + {x, W/2+1/2*Wbtn+Pad}, {y, Ybtn}, + {width, Wbtn}, {height, Hent}]), + + H = Ybtn+Hent+Pad, + gs:config(Win, [{height, H}, {map, true}]), + {Ent, Lbl, Btn}. + +ent(Win) -> element(1, Win). +lbl(Win) -> element(2, Win). +btn(Win) -> element(3, Win). |