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/dialyzer/src/dialyzer_gui.erl | |
download | otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.gz otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.bz2 otp-84adefa331c4159d432d22840663c38f155cd4c1.zip |
The R13B03 release.OTP_R13B03
Diffstat (limited to 'lib/dialyzer/src/dialyzer_gui.erl')
-rw-r--r-- | lib/dialyzer/src/dialyzer_gui.erl | 1349 |
1 files changed, 1349 insertions, 0 deletions
diff --git a/lib/dialyzer/src/dialyzer_gui.erl b/lib/dialyzer/src/dialyzer_gui.erl new file mode 100644 index 0000000000..f353638cdf --- /dev/null +++ b/lib/dialyzer/src/dialyzer_gui.erl @@ -0,0 +1,1349 @@ +%% -*- erlang-indent-level: 2 -*- +%%------------------------------------------------------------------------ +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2006-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% +%% + +%%%----------------------------------------------------------------------- +%%% File : dialyzer_gui.erl +%%% Authors : Tobias Lindahl <[email protected]> +%%% Kostis Sagonas <[email protected]> +%%% Description : The graphical user interface for the Dialyzer tool. +%%% +%%% Created : 27 Apr 2004 by Tobias Lindahl <[email protected]> +%%%----------------------------------------------------------------------- + +-module(dialyzer_gui). + +-export([start/1]). + +-include("dialyzer.hrl"). + +%%------------------------------------------------------------------------ + +-define(DIALYZER_ERROR_TITLE, "Dialyzer Error"). +-define(DIALYZER_MESSAGE_TITLE, "Dialyzer Message"). + +%%------------------------------------------------------------------------ + +-type gs_object() :: any(). %% XXX: should be imported from gs + +-record(mode, {start_byte_code :: gs_object(), + start_src_code :: gs_object()}). + +-record(menu, {file_save_log :: gs_object(), + file_save_warn :: gs_object(), + file_quit :: gs_object(), + help_about :: gs_object(), + help_manual :: gs_object(), + help_warnings :: gs_object(), + opts_macros :: gs_object(), + opts_includes :: gs_object(), + plt_empty :: gs_object(), + plt_search_doc :: gs_object(), + plt_show_doc :: gs_object(), + warnings :: gs_object()}). + +-record(gui_state, {add_all :: gs_object(), + add_file :: gs_object(), + add_rec :: gs_object(), + chosen_box :: gs_object(), + analysis_pid :: pid(), + del_file :: gs_object(), + doc_plt :: dialyzer_plt:plt(), + clear_chosen :: gs_object(), + clear_log :: gs_object(), + clear_warn :: gs_object(), + init_plt :: dialyzer_plt:plt(), + dir_entry :: gs_object(), + file_box :: gs_object(), + file_wd :: gs_object(), + gs :: gs_object(), + log :: gs_object(), + menu :: #menu{}, + mode :: #mode{}, + options :: #options{}, + packer :: gs_object(), + run :: gs_object(), + stop :: gs_object(), + top :: gs_object(), + warnings_box :: gs_object(), + backend_pid :: pid()}). + +%%------------------------------------------------------------------------ + +-spec start(#options{}) -> ?RET_NOTHING_SUSPICIOUS. + +start(DialyzerOptions = #options{from = From, init_plt = InitPltFile, + legal_warnings = LegalWarnings}) -> + process_flag(trap_exit, true), + + GS = gs:start(), + code:add_pathsa(["."]), + WH = [{width, 1000}, {height, 550}], + EmptySpace = {stretch, 1}, + + {ok, Host} = inet:gethostname(), + %% --------- Top Window -------------- + TopWin = gs:window(GS, [{title, "Dialyzer " ++ ?VSN ++ " @ " ++ Host}, + {configure, true}, + {default, listbox, {bg, white}}, + {default, editor, {bg, white}}, + {default, entry, {bg, white}}, + {default, button, {font, {helvetica, bold, 12}}}, + {default, label, {font, {helvetica, bold, 12}}} + |WH]), + Packer = gs:frame(TopWin, [{packer_x, [{stretch, 3},{fixed, 200}, + {stretch, 7}]}, + {packer_y, [{fixed, 25}, {fixed, 20}, + {stretch, 1, 50}, + {fixed, 25}, {fixed, 20}, + {stretch, 1, 50}, + {fixed, 25}]}]), + + %% --------- Chosen box -------------- + gs:label(Packer, [{label, {text, "Directories or modules to analyze"}}, + {height, 20}, {pack_xy, {1, 2}}]), + ChosenBox = gs:listbox(Packer, [{pack_xy, {1, 3}}, {vscroll, right}, + {selectmode, multiple}]), + + %% --------- File box -------------- + gs:label(Packer, [{label, {text, "File"}}, {height, 20}, {pack_xy, {1,5}}]), + FilePacker = gs:frame(Packer, [{packer_x, [{fixed, 30}, {stretch, 1, 100}]}, + {packer_y, [{fixed, 25}, {stretch, 1, 25}]}, + {pack_xy, {1, 6}}]), + gs:label(FilePacker, [{label, {text, "Dir:"}}, {pack_xy, {1, 1}}]), + DirEntry = gs:entry(FilePacker, [{height, 30}, {pack_xy, {2, 1}}, + {keypress, true}]), + File = gs:listbox(FilePacker, [{pack_x, {1,2}}, {pack_y, 2}, + {selectmode, multiple}, {doubleclick, true}, + {vscroll, right}]), + + %% --------- Options -------------- + gs:label(Packer, [{label, {text, "Analysis Options"}}, + {height, 20}, {pack_xy, {2, 2}}]), + ModePacker = gs:frame(Packer, [{packer_x, [{fixed, 75}, {fixed, 120}]}, + {packer_y, [{fixed, 20}, {fixed, 20}, + {fixed, 20}, + %% EmptySpace, + {fixed, 20}, {fixed, 20}, + {fixed, 20}, EmptySpace]}, + {bw, 10}, {relief, flat}, + {default, {radiobutton, {align, w}}}, + {default, {label, {align, w}}}, + {pack_xy, {2, 3}}]), + + %% Bytecode vs. Source code + gs:label(ModePacker, [{label, {text, "File Type:"}}, + {height, 20}, {pack_xy, {1,1}}]), + {ByteSel, SrcSel} = case From of + byte_code -> {[{select, true}], []}; + src_code -> {[], [{select, true}]} + end, + ModeByteCode = gs:radiobutton(ModePacker, + ByteSel ++ [{group, start_from}, + {label, {text,"BeamFiles"}}, + {pack_xy, {2,1}}]), + ModeSrcCode = gs:radiobutton(ModePacker, + SrcSel ++ [{group, start_from}, + {label, {text,"SourceFiles"}}, + {pack_xy, {2,2}}]), + Mode = #mode{start_byte_code = ModeByteCode, + start_src_code = ModeSrcCode}, + + %% --------- Log box -------------- + gs:label(Packer, [{label, {text, "Log"}}, {height, 20}, {pack_xy, {3,2}}]), + Log = gs:editor(Packer, [{pack_x, 3}, {pack_y, 3}, {enable, false}, + {font, {courier, 12}}, {vscroll, right}, + {wrap, word}]), + + %% --------- Warnings box -------------- + gs:label(Packer, [{label, {text, "Warnings"}},{height, 20},{pack_xy, {3,5}}]), + WarningsBox = gs:editor(Packer, [{pack_x, {2,3}}, {pack_y, 6}, + {enable, false}, + {font, {courier, 12}}, {vscroll, right}, + {wrap, word}]), + + %% --------- Buttons -------------- + ButtonPackerHighLeft = + gs:frame(Packer, [{packer_x, [{fixed, 50}, {fixed, 65}, EmptySpace]}, + {pack_xy, {1,4}}]), + ButtonPackerHighRight = + gs:frame(Packer, [{packer_x, [{fixed, 70}, {fixed, 70}, EmptySpace]}, + {pack_xy, {3,4}}]), + ButtonPackerLowLeft = + gs:frame(Packer, [{packer_x, [{fixed, 50}, + {fixed, 60}, + {fixed, 110}, + EmptySpace]}, + {pack_xy, {1,7}}]), + ButtonPackerLowRight = + gs:frame(Packer, [{packer_x, [{fixed, 100}, + {fixed, 70}, + EmptySpace, + {fixed, 70}, + {fixed, 70}]}, + {pack_x, {2,3}}, {pack_y, 7}]), + + WHButton = [{width, 60}, {height, 20}], + AddFile = gs:button(ButtonPackerLowLeft, [{pack_xy, {1, 1}}, + {label, {text,"Add"}}|WHButton]), + AddAll = gs:button(ButtonPackerLowLeft, [{pack_xy, {2, 1}}, + {label, {text,"Add All"}}|WHButton]), + AddRec = gs:button(ButtonPackerLowLeft, [{pack_xy, {3, 1}}, + {label, {text,"Add Recursively"}} + |WHButton]), + DelFile = gs:button(ButtonPackerHighLeft, [{pack_xy, {1, 1}}, + {label, {text,"Delete"}}|WHButton]), + ClearChosen = gs:button(ButtonPackerHighLeft, [{pack_xy, {2, 1}}, + {label, {text,"Delete All"}} + |WHButton]), + ClearLog = gs:button(ButtonPackerHighRight, [{pack_xy, {1, 1}}, + {label, {text,"Clear Log"}} + |WHButton]), + ClearWarn = gs:button(ButtonPackerLowRight, [{pack_xy, {1, 1}}, + {label, {text,"Clear Warnings"}} + |WHButton]), + + Run = gs:button(ButtonPackerLowRight, [{pack_xy, {4, 1}}, + {label, {text,"Run"}}|WHButton]), + Stop = gs:button(ButtonPackerLowRight, [{pack_xy, {5, 1}}, {enable, false}, + {label, {text,"Stop"}}|WHButton]), + + %% --------- Menu -------------- + MenuBar = gs:menubar(TopWin, []), + + %% File Menu + MenuBarFile = gs:menubutton(MenuBar, [{label, {text, "File"}}]), + MenuFile = gs:menu(MenuBarFile, []), + MenuFileSaveWarn = gs:menuitem(MenuFile, [{label, {text, "Save Warnings"}}]), + MenuFileSaveLog = gs:menuitem(MenuFile, [{label, {text, "Save Log"}}]), + MenuFileQuit = gs:menuitem(MenuFile, [{label, {text, "Quit"}}]), + + %% Warnings Menu + MenuBarWarn = gs:menubutton(MenuBar, [{label, {text, "Warnings"}}]), + MenuWarn = gs:menu(MenuBarWarn, []), + MenuWarnMatch = gs:menuitem(MenuWarn, [{label, {text, "Match failures"}}, + {itemtype, check}, {select, true}]), + MenuWarnFailingCall = gs:menuitem(MenuWarn, + [{label, {text, "Failing function calls"}}, + {itemtype, check}, {select, true}]), + MenuWarnFunApp = gs:menuitem(MenuWarn, [{label, + {text, "Bad fun applications"}}, + {itemtype, check}, {select, true}]), + MenuWarnOpaque = gs:menuitem(MenuWarn, [{label, + {text, "Opaqueness violations"}}, + {itemtype, check}, {select, true}]), + MenuWarnLists = gs:menuitem(MenuWarn, + [{label, {text, "Improper list constructions"}}, + {itemtype, check}, {select, true}]), + MenuWarnNotCalled = gs:menuitem(MenuWarn, + [{label, {text, "Unused functions"}}, + {itemtype, check}, {select, true}]), + MenuWarnReturnOnlyExit = gs:menuitem(MenuWarn, + [{label, + {text, "Error handling functions"}}, + {itemtype, check}, {select, false}]), + MenuWarnReturnNoReturn = gs:menuitem(MenuWarn, + [{label, + {text, "Functions of no return"}}, + {itemtype, check}, {select, true}]), + MenuWarnCallNonExported = gs:menuitem(MenuWarn, + [{label, + {text, "Call to unexported function"}}, + {itemtype, check}, {select, true}]), + MenuWarnRaceCondition = gs:menuitem(MenuWarn, + [{label, + {text,"Possible race conditions"}}, + {itemtype, check}, {select, false}]), + MenuWarnContractTypes = gs:menuitem(MenuWarn, + [{label, {text, "Wrong contracts"}}, + {itemtype, check}, {select, true}]), + MenuWarnContractSyntax = gs:menuitem(MenuWarn, + [{label, + {text, "Wrong contract syntax"}}, + {itemtype, check}, {select, true}]), + + %% PLT Menu + MenuBarPLT = gs:menubutton(MenuBar, [{label, {text,"PLT"}}]), + MenuPLT = gs:menu(MenuBarPLT, []), + MenuPLTEmpty = gs:menuitem(MenuPLT, [{label, {text, "Init with empty PLT"}}, + {itemtype, check}, {select, false}]), + MenuPLTShow = gs:menuitem(MenuPLT, [{label, {text, "Show contents"}}]), + MenuPLTSearch = gs:menuitem(MenuPLT, [{label, {text, "Search contents"}}]), + + %% Options Menu + MenuBarOpts = gs:menubutton(MenuBar, [{label,{text,"Options"}}]), + MenuOpts = gs:menu(MenuBarOpts, []), + MenuOptsMacros = gs:menuitem(MenuOpts, + [{label, {text, "Manage Macro Definitions"}}]), + MenuOptsIncludes = gs:menuitem(MenuOpts, + [{label, {text, "Manage Include Directories"}}]), + + %% Help + MenuBarHelp = gs:menubutton(MenuBar, [{label, {text, "Help"}}, {side, right}]), + MenuHelp = gs:menu(MenuBarHelp, []), + MenuHelpManual = gs:menuitem(MenuHelp, [{label, {text, "Manual"}}]), + MenuHelpWarnings = gs:menuitem(MenuHelp, [{label, {text, "Warning Options"}}]), + MenuHelpAbout = gs:menuitem(MenuHelp, [{label, {text, "About"}}]), + + Warnings = [{?WARN_RETURN_NO_RETURN, MenuWarnReturnNoReturn}, + {?WARN_RETURN_ONLY_EXIT, MenuWarnReturnOnlyExit}, + {?WARN_NOT_CALLED, MenuWarnNotCalled}, + {?WARN_NON_PROPER_LIST, MenuWarnLists}, + {?WARN_FUN_APP, MenuWarnFunApp}, + {?WARN_MATCHING, MenuWarnMatch}, + {?WARN_OPAQUE, MenuWarnOpaque}, + {?WARN_FAILING_CALL, MenuWarnFailingCall}, + {?WARN_CALLGRAPH, MenuWarnCallNonExported}, + {?WARN_RACE_CONDITION, MenuWarnRaceCondition}, + %% For contracts. + {?WARN_CONTRACT_TYPES, MenuWarnContractTypes}, + {?WARN_CONTRACT_SYNTAX, MenuWarnContractSyntax} + ], + + init_warnings(Warnings, LegalWarnings), + + Menu = #menu{file_quit = MenuFileQuit, + plt_empty = MenuPLTEmpty, + help_manual = MenuHelpManual, + help_about = MenuHelpAbout, + help_warnings = MenuHelpWarnings, + opts_macros = MenuOptsMacros, + opts_includes = MenuOptsIncludes, + plt_search_doc = MenuPLTSearch, + plt_show_doc = MenuPLTShow, + file_save_log = MenuFileSaveLog, + file_save_warn = MenuFileSaveWarn, + warnings = Warnings}, + + %% --------- Init -------------- + gs:config(TopWin, [{map, true}]), + gs:config(Packer, WH), + {ok, CWD} = file:get_cwd(), + + InitPlt = try dialyzer_plt:from_file(InitPltFile) + catch throw:{dialyzer_error, _} -> dialyzer_plt:new() + end, + + State = #gui_state{add_all = AddAll, + add_file = AddFile, + add_rec = AddRec, + chosen_box = ChosenBox, + clear_chosen = ClearChosen, + clear_log = ClearLog, + clear_warn = ClearWarn, + del_file = DelFile, + doc_plt = dialyzer_plt:new(), + dir_entry = DirEntry, + file_box = File, + file_wd = CWD, + gs = GS, + init_plt = InitPlt, + log = Log, + menu = Menu, + mode = Mode, + options = DialyzerOptions, + packer = Packer, + run = Run, + stop = Stop, + top = TopWin, + warnings_box = WarningsBox}, + NewState = change_dir_or_add_file(State, "."), + gui_loop(NewState). + +%% ---------------------------------------------------------------- +%% +%% Main GUI Loop +%% + +-spec gui_loop(#gui_state{}) -> ?RET_NOTHING_SUSPICIOUS. + +gui_loop(#gui_state{add_all = AddAll, add_file = AddFile, add_rec = AddRec, + backend_pid = BackendPid, chosen_box = ChosenBox, + clear_chosen = ClearChosen, clear_log = ClearLog, + clear_warn = ClearWarn, del_file = DelFile, + dir_entry = DirEntry, file_box = File, log = Log, + menu = Menu, packer = Packer, run = Run, stop = Stop, + top = TopWin, warnings_box = Warn} = State) -> + %% --- Menu --- + Quit = Menu#menu.file_quit, + Manual = Menu#menu.help_manual, + Warnings = Menu#menu.help_warnings, + About = Menu#menu.help_about, + SaveLog = Menu#menu.file_save_log, + SaveWarn = Menu#menu.file_save_warn, + SearchPlt = Menu#menu.plt_search_doc, + ShowPlt = Menu#menu.plt_show_doc, + Macros = Menu#menu.opts_macros, + Includes = Menu#menu.opts_includes, + + receive + {gs, TopWin, configure, _Data, [W, H|_]} -> + gs:config(Packer, [{width, W}, {height, H}]), + gui_loop(State); + {gs, TopWin, destroy, _Data, _Args} -> + ?RET_NOTHING_SUSPICIOUS; + {gs, File, doubleclick, _, [_Id, Text|_]} -> + NewState = change_dir_or_add_file(State, Text), + gui_loop(NewState); + {gs, DirEntry, keypress, _, ['Return'|_]} -> + gs:config(TopWin, [{setfocus, true}]), + NewState = change_dir_absolute(State, gs:read(DirEntry, text)), + gui_loop(NewState); + {gs, DirEntry, keypress, _, _} -> + gui_loop(State); + %% ----- Buttons ----- + {gs, AddFile, click, _, _} -> + handle_add_files(State), + gui_loop(State); + {gs, AddAll, click, _, _} -> + handle_add_all_click(State), + gui_loop(State); + {gs, AddRec, click, _, _} -> + handle_add_rec_click(State), + gui_loop(State); + {gs, DelFile, click, _, _} -> + handle_file_delete(State), + gui_loop(State); + {gs, ClearChosen, click, _, _} -> + gs:config(ChosenBox, [clear]), + gui_loop(State); + {gs, ClearLog, click, _, _} -> + Log = State#gui_state.log, + gs:config(Log, [{enable, true}]), + gs:config(Log, [clear]), + gs:config(Log, [{enable, false}]), + gui_loop(State); + {gs, ClearWarn, click, _, _} -> + Warn = State#gui_state.warnings_box, + gs:config(Warn, [{enable, true}]), + gs:config(Warn, [clear]), + gs:config(Warn, [{enable, false}]), + gui_loop(State); + {gs, Run, click, _, _} -> + NewState = start_analysis(State), + gui_loop(NewState); + {gs, Stop, click, _, _} -> + config_gui_stop(State), + BackendPid ! {self(), stop}, + update_editor(Log, "\n***** Analysis stopped ****\n"), + gui_loop(State); + %% ----- Menu ----- + {gs, Quit, click, _, _} -> + case maybe_quit(State) of + true -> ?RET_NOTHING_SUSPICIOUS; + false -> gui_loop(State) + end; + {gs, Manual, click, _, _} -> + spawn_link(fun() -> manual(State) end), + gui_loop(State); + {gs, Warnings, click, _, _} -> + spawn_link(fun() -> warnings(State) end), + gui_loop(State); + {gs, About, click, _, _} -> + spawn_link(fun() -> about(State) end), + gui_loop(State); + {gs, SaveLog, click, _, _} -> + save_log(State), + gui_loop(State); + {gs, SaveWarn, click, _, _} -> + save_warn(State), + gui_loop(State); + {gs, SearchPlt, click, _, _} -> + spawn_link(fun() -> search_doc_plt(State) end), + gui_loop(State); + {gs, ShowPlt, click, _, _} -> + spawn_link(fun() -> show_doc_plt(State) end), + gui_loop(State); + {gs, Macros, click, _, _} -> + Self = self(), + spawn_link(fun() -> macro_dialog(State, Self) end), + gui_loop(State); + {gs, Includes, click, _, _} -> + Self = self(), + spawn_link(fun() -> include_dialog(State, Self) end), + gui_loop(State); + {new_options, NewOptions} -> + NewState = State#gui_state{options = NewOptions}, + gui_loop(NewState); + %% ----- Analysis ----- + {BackendPid, ext_calls, ExtCalls} -> + Msg = io_lib:format("The following functions are called " + "but type information about them is not available.\n" + "The analysis might get more precise by including " + "the modules containing these functions:\n\n\t~p\n", + [ExtCalls]), + free_editor(State, "Analysis done", Msg), + gui_loop(State); + {BackendPid, log, LogMsg} -> + update_editor(Log, LogMsg), + gui_loop(State); + {BackendPid, warnings, Warns} -> + SortedWarns = lists:keysort(2, Warns), %% Sort on file/line + WarnList = [dialyzer:format_warning(W) || W <- SortedWarns], + update_editor(Warn, lists:flatten(WarnList)), + gui_loop(State); + {BackendPid, done, _NewPlt, NewDocPlt} -> + message(State, "Analysis done"), + config_gui_stop(State), + gui_loop(State#gui_state{doc_plt = NewDocPlt}); + {'EXIT', BackendPid, {error, Reason}} -> + free_editor(State, ?DIALYZER_ERROR_TITLE, Reason), + config_gui_stop(State), + gui_loop(State); + {'EXIT', BackendPid, Reason} when Reason =/= 'normal' -> + free_editor(State, ?DIALYZER_ERROR_TITLE, io_lib:format("~p", [Reason])), + config_gui_stop(State), + gui_loop(State); + _Other -> + %% io:format("Received ~p\n", [Other]), + gui_loop(State) + end. + +%% ---------------------------------------------------------------- +%% +%% Main window actions +%% + +%% ---- Adding and deleting files ---- + +handle_add_all_click(#gui_state{chosen_box = ChosenBox, file_box = File, + file_wd = FWD, mode = Mode}) -> + case gs:read(File, items) of + [] -> + ok; + Add0 -> + gs:config(File, [{selection, clear}]), + Add1 = ordsets:subtract(Add0, [".."]), + Add = ordsets:from_list([filename:join(FWD, X) || X <- Add1]), + case gs:read(Mode#mode.start_byte_code, select) of + true -> + add_files(filter_mods(Add, ".beam"), ChosenBox, byte_code); + false -> + add_files(filter_mods(Add, ".erl"), ChosenBox, src_code) + end + end. + +all_subdirs(Dirs) -> + all_subdirs(Dirs, []). + +all_subdirs([Dir|T], Acc) -> + {ok, Files} = file:list_dir(Dir), + SubDirs = lists:zf(fun(F) -> + SubDir = filename:join(Dir, F), + case filelib:is_dir(SubDir) of + true -> {true, SubDir}; + false -> false + end + end, Files), + NewAcc = ordsets:union(ordsets:from_list(SubDirs), Acc), + all_subdirs(T ++ SubDirs, NewAcc); +all_subdirs([], Acc) -> + Acc. + +handle_add_rec_click(#gui_state{chosen_box = ChosenBox, file_box = File, + file_wd = FWD, mode = Mode}) -> + case gs:read(File, selection) of + [] -> + ok; + List -> + gs:config(File, [{selection, clear}]), + Dirs1 = [gs:read(File, {get, X}) || X <- List], + Dirs2 = ordsets:from_list([filename:join(FWD, X) || X <- Dirs1]), + Dirs3 = ordsets:filter(fun(X) -> filelib:is_dir(X) end, Dirs2), + TargetDirs = ordsets:union(Dirs3, all_subdirs(Dirs3)), + {Code, Ext} = case gs:read(Mode#mode.start_byte_code, select) of + true -> {byte_code, ".beam"}; + false -> {src_code, ".erl"} + end, + add_files(filter_mods(TargetDirs, Ext), ChosenBox, Code) + end. + +handle_add_files(#gui_state{chosen_box = ChosenBox, file_box = File, + file_wd = FWD, mode = Mode}) -> + case gs:read(File, selection) of + [] -> + ok; + List -> + gs:config(File, [{selection, clear}]), + Add0 = [gs:read(File, {get, X}) || X <- List], + Add = ordsets:from_list([filename:join(FWD, X) || X <- Add0]), + case gs:read(Mode#mode.start_byte_code, select) of + true -> + add_files(filter_mods(Add, ".beam"), ChosenBox, byte_code); + false -> + add_files(filter_mods(Add, ".erl"), ChosenBox, src_code) + end + end. + +filter_mods(Mods, Extension) -> + Fun = fun(X) -> + filename:extension(X) =:= Extension + orelse + (filelib:is_dir(X) andalso + contains_files(X, Extension)) + end, + ordsets:filter(Fun, Mods). + +contains_files(Dir, Extension) -> + {ok, Files} = file:list_dir(Dir), + lists:any(fun(X) -> filename:extension(X) =:= Extension end, Files). + +add_files(Add, ChosenBox, Type) -> + Set = gs:read(ChosenBox, items), + Set1 = + case Type of + byte_code -> filter_mods(Set, ".beam"); + src_code -> filter_mods(Set, ".erl") + end, + Files = ordsets:union(Add, Set1), + gs:config(ChosenBox, [{items, Files}]), + ok. + +handle_file_delete(#gui_state{chosen_box = ChosenBox}) -> + List = gs:read(ChosenBox, selection), + lists:foreach(fun(X) -> gs:config(ChosenBox, [{del, X}]) end, + lists:reverse(lists:sort(List))). + +%% ---- Other ---- + +change_dir_or_add_file(#gui_state{file_wd = FWD, mode = Mode, dir_entry = Dir, + chosen_box = CBox, file_box = File} = State, + Text) -> + NewWDorFile = + case Text of + ".." -> filename:join(butlast(filename:split(FWD))); + "." -> FWD; + _ -> filename:join(FWD, Text) + end, + case filelib:is_dir(NewWDorFile) of + true -> + gs:config(Dir, [{text, NewWDorFile}]), + {ok, List} = file:list_dir(NewWDorFile), + gs:config(File, [{items, [".."|lists:sort(List)]}]), + State#gui_state{file_wd = NewWDorFile}; + false -> + case gs:read(Mode#mode.start_byte_code, select) of + true -> + case filter_mods([NewWDorFile], ".beam") of + [] -> ok; + RealFiles -> add_files(RealFiles, CBox, byte_code) + end; + false -> + case filter_mods([NewWDorFile], ".erl") of + [] -> ok; + RealFiles -> add_files(RealFiles, CBox, src_code) + end + end, + State + end. + +butlast([H1, H2 | T]) -> + [H1 | butlast([H2|T])]; +butlast([_]) -> + []; +butlast([]) -> + ["/"]. + +change_dir_absolute(#gui_state{file_wd = FWD, dir_entry = Dir, + file_box = File} = State, + Text) -> + case filelib:is_dir(Text) of + true -> + WD = filename:join(FWD, Text), + gs:config(Dir, [{text, WD}]), + {ok, List} = file:list_dir(WD), + gs:config(File, [{items, [".."|lists:sort(List)]}]), + State#gui_state{file_wd = WD}; + false -> + State + end. + +init_warnings([{Tag, GSItem}|Left], LegalWarnings) -> + Select = ordsets:is_element(Tag, LegalWarnings), + gs:config(GSItem, [{select, Select}]), + init_warnings(Left, LegalWarnings); +init_warnings([], _LegalWarnings) -> + ok. + +config_gui_start(State) -> + Enabled = [{enable, true}], + Disabled = [{enable, false}], + gs:config(State#gui_state.stop, Enabled), + gs:config(State#gui_state.run, Disabled), + gs:config(State#gui_state.del_file, Disabled), + gs:config(State#gui_state.clear_chosen, Disabled), + gs:config(State#gui_state.add_file, Disabled), + gs:config(State#gui_state.add_all, Disabled), + gs:config(State#gui_state.add_rec, Disabled), + gs:config(State#gui_state.clear_warn, Disabled), + gs:config(State#gui_state.clear_log, Disabled), + Menu = State#gui_state.menu, + gs:config(Menu#menu.file_save_warn, Disabled), + gs:config(Menu#menu.file_save_log, Disabled), + gs:config(Menu#menu.opts_macros, Disabled), + gs:config(Menu#menu.opts_includes, Disabled), + gs:config(Menu#menu.plt_empty, Disabled), + gs:config(Menu#menu.plt_search_doc, Disabled), + gs:config(Menu#menu.plt_show_doc, Disabled), + Mode = State#gui_state.mode, + gs:config(Mode#mode.start_byte_code, Disabled), + gs:config(Mode#mode.start_src_code, Disabled). + +config_gui_stop(State) -> + Enabled = [{enable, true}], + Disabled = [{enable, false}], + gs:config(State#gui_state.stop, Disabled), + gs:config(State#gui_state.run, Enabled), + gs:config(State#gui_state.del_file, Enabled), + gs:config(State#gui_state.clear_chosen, Enabled), + gs:config(State#gui_state.add_file, Enabled), + gs:config(State#gui_state.add_all, Enabled), + gs:config(State#gui_state.add_rec, Enabled), + gs:config(State#gui_state.clear_warn, Enabled), + gs:config(State#gui_state.clear_log, Enabled), + Menu = State#gui_state.menu, + gs:config(Menu#menu.file_save_warn, Enabled), + gs:config(Menu#menu.file_save_log, Enabled), + gs:config(Menu#menu.opts_macros, Enabled), + gs:config(Menu#menu.opts_includes, Enabled), + gs:config(Menu#menu.plt_empty, Enabled), + gs:config(Menu#menu.plt_search_doc, Enabled), + gs:config(Menu#menu.plt_show_doc, Enabled), + Mode = State#gui_state.mode, + gs:config(Mode#mode.start_byte_code, Enabled), + gs:config(Mode#mode.start_src_code, Enabled). + +%% ---------------------------------------------------------------- +%% +%% Messages +%% + +message(State, Message) -> + output_sms(State, ?DIALYZER_MESSAGE_TITLE, Message). + +error_sms(State, Message) -> + output_sms(State, ?DIALYZER_ERROR_TITLE, Message). + +%% +%% This function is to be used *only* for small messages because lines +%% are not wrapped and the created window has a limited area for text. +%% For bigger messages, the function free_editor/3 is to be used. +%% +output_sms(#gui_state{gs = GS, top = TopWin}, Title, Message) -> + %% Lines = string:words(Message, $\n), + %% io:format("The message has ~w lines\n", [Lines]), + WH = [{width, 400}, {height, 100}], + MessageWin = gs:window(GS, [{title, Title}, + {default, button, {font, {helvetica, bold, 12}}} + |WH]), + MessagePacker = gs:frame(MessageWin, [{packer_y, [{fixed, 75}, {fixed, 25}]}, + {packer_x, [{fixed, 175},{fixed, 50}, + {fixed, 175}]}]), + gs:label(MessagePacker, [{pack_x, {1, 3}}, {pack_y, 1}, + {label, {text, Message}}]), + OK = gs:button(MessagePacker, [{label, {text, "OK"}}, {pack_xy, {2, 2}}]), + gs:config(MessageWin, [{map, true}]), + gs:config(MessagePacker, WH), + message_loop(OK, MessageWin, TopWin). + +message_loop(Ok, Win, TopWin) -> + receive + {gs, Ok, click, _, _} -> + gs:destroy(Win); + {gs, Win, destroy, _, _} -> + ok; + {gs, TopWin, destroy, _, _} -> + exit(normal); + {gs, _, _, _, _} -> + message_loop(Ok, Win, TopWin) + end. + +dialog(#gui_state{gs = GS, top = TopWin}, Message, OkLabel, CancelLabel) -> + WH = [{width, 400}, {height, 100}], + WHButton = [{width, 70}, {height, 20}], + DialogWin = gs:window(GS, [{title, "Dialyzer Message"}, + {default, button, {font, {helvetica, bold, 12}}} + |WH]), + DialogPacker = gs:frame(DialogWin, [{packer_y, [{fixed, 75}, {fixed, 25}]}, + {packer_x, [{fixed, 150}, {fixed, 50}, + {fixed, 50}, {fixed, 150}]}]), + gs:label(DialogPacker, [{pack_x, {1,4}}, {pack_y, 1}, + {label, {text, Message}}]), + Ok = gs:button(DialogPacker, [{label, {text, OkLabel}}, + {pack_xy, {2,2}}|WHButton]), + Cancel = gs:button(DialogPacker, [{label, {text, CancelLabel}}, + {pack_xy, {3,2}}|WHButton]), + gs:config(DialogWin, [{map, true}]), + gs:config(DialogPacker, WH), + dialog_loop(Ok, Cancel, DialogWin, TopWin). + +dialog_loop(Ok, Cancel, Win, TopWin) -> + receive + {gs, Ok, click, _, _} -> + gs:destroy(Win), + true; + {gs, Cancel, click, _, _} -> + gs:destroy(Win), + false; + {gs, Win, destroy, _, _} -> + false; + {gs, TopWin, destroy, _, _} -> + exit(normal); + {gs, _, _, _, _} -> + dialog_loop(Ok, Cancel, Win, TopWin) + end. + +maybe_quit(#gui_state{top = TopWin} = State) -> + case dialog(State, "Do you really want to quit?", "Yes", "No") of + true -> + flush(), + gs:destroy(TopWin), + gs:stop(), + true; + false -> + false + end. + + +%% ---------------------------------------------------------------- +%% +%% Menu actions +%% + +%% ---- Help Menu ---- + +manual(State) -> + help_menu_common(State, "Dialyzer Manual", 500, "manual.txt", white). + +warnings(State) -> + help_menu_common(State, "Dialyzer Warnings", 500, "warnings.txt", white). + +about(State) -> + help_menu_common(State, "About Dialyzer", 160, "about.txt", yellow). + +help_menu_common(#gui_state{gs = GS, top = TopWin} = State, + Title, Height, TxtFileName, BackGroundColor) -> + WH = [{width, 600}, {height, Height}], + Win = gs:window(GS, [{title, Title}, {configure, true}, + {default, editor, {bg, BackGroundColor}} | WH]), + EmptySpace = {stretch, 1}, + Frame = gs:frame(Win, [{packer_x, [EmptySpace, {fixed, 60}, EmptySpace]}, + {packer_y, [EmptySpace, {fixed, 30}]} | WH]), + Editor = gs:editor(Frame, [{pack_x, {1, 3}}, {pack_y, 1}, + {font, {courier, 12}}, {vscroll, right}, + {wrap, word}]), + Button = gs:button(Frame, [{label, {text, "Ok"}}, {pack_xy, {2, 2}}]), + gs:config(Win, [{map, true}]), + gs:config(Frame, WH), + AboutFile = filename:join([code:lib_dir(dialyzer), "doc", TxtFileName]), + case gs:config(Editor, {load, AboutFile}) of + {error, Reason} -> + gs:destroy(Win), + error_sms(State, + io_lib:format("Could not find doc/~s file!\n\n ~p", + [TxtFileName, Reason])); + ok -> + gs:config(Editor, [{enable, false}]), + show_info_loop(TopWin, Win, Frame, Button) + end. + +%% ---- File Menu ---- + +save_log(#gui_state{file_wd = CWD, log = Log} = State) -> + {Win, Entry, OkButton, CancelButton} = file_box(State, "Save Log", CWD), + save_loop(State, OkButton, CancelButton, Entry, Win, Log). + +save_warn(#gui_state{file_wd = CWD, warnings_box = WBox} = State) -> + {Win, Entry, OkButton, CancelButton} = file_box(State, "Save Warnings", CWD), + save_loop(State, OkButton, CancelButton, Entry, Win, WBox). + +file_box(#gui_state{gs = GS}, Title, Default) -> + WH = [{width, 400}, {height, 75}], + Win = gs:window(GS, [{title, Title}|WH]), + Fix25 = {fixed, 27}, Fix75 = {fixed, 75}, + WinPacker = gs:frame(Win, [{packer_y, [Fix25, Fix25, Fix25]}, + {packer_x, [Fix75, Fix75, Fix75, {fixed, 175}]}]), + gs:label(WinPacker, [{pack_xy, {1,2}}, {label, {text, "Enter file:"}}]), + Entry = gs:entry(WinPacker, [{pack_x, {2,4}}, {pack_y, 2}, {keypress, true}]), + OkButton = gs:button(WinPacker, [{label, {text, "Ok"}}, {pack_xy, {2,3}}]), + CancelButton = gs:button(WinPacker, [{label, {text, "Cancel"}}, + {pack_xy, {3,3}}]), + gs:config(Entry, [{text, Default}]), + gs:config(Win, [{map, true}]), + gs:config(WinPacker, WH), + {Win, Entry, OkButton, CancelButton}. + +save_loop(#gui_state{top = TopWin} = State, + OkButton, CancelButton, Entry, Save, Editor) -> + receive + {gs, OkButton, click, _, _} -> + File = gs:read(Entry, text), + case gs:config(Editor, [{save, File}]) of + {error, _} -> + error_sms(State, "Could not write to file:\n" ++ File), + save_loop(State, OkButton, CancelButton, Entry, Save, Editor); + _ -> + gs:destroy(Save) + end; + {gs, Entry, keypress, _, ['Return'|_]} -> + File = gs:read(Entry, text), + case gs:config(Editor, [{save, File}]) of + {error, _} -> + error_sms(State, "Could not write to file:\n" ++ File), + save_loop(State, OkButton, CancelButton, Entry, Save, Editor); + _ -> + gs:destroy(Save) + end; + {gs, Entry, keypress, _, _} -> + save_loop(State, OkButton, CancelButton, Entry, Save, Editor); + {gs, CancelButton, click, _, _} -> + gs:destroy(Save); + {gs, TopWin, destroy, _, _} -> + exit(normal); + {gs, Save, destroy, _, _} -> + ok; + {gs, _, _, _, _} -> + save_loop(State, OkButton, CancelButton, Entry, Save, Editor) + end. + +%% ---- Plt Menu ---- + +search_doc_plt(#gui_state{gs = GS, top = TopWin} = State) -> + WH = [{width, 400}, {height, 100}], + WHB = [{width, 120}, {height, 30}], + Title = io_lib:format("Search the PLT", []), + Win = gs:window(GS, [{title, Title}, {configure, true}, + {default, editor, {bg, white}} | WH]), + EmptySpace = {stretch, 1}, + Frame = gs:frame(Win, [{packer_x, [EmptySpace, EmptySpace, EmptySpace]}, + {packer_y, [{fixed, 30}, {fixed, 30}, + EmptySpace, {fixed, 30}]} | WH]), + gs:label(Frame, [{pack_xy, {1,1}}, {label, {text, "Module"}}]), + ModEntry = gs:entry(Frame, [{pack_xy, {1,2}}]), + gs:label(Frame, [{pack_xy, {2,1}}, {label, {text, "Function"}}]), + FunEntry = gs:entry(Frame, [{pack_xy, {2,2}}]), + gs:label(Frame, [{pack_xy, {3,1}}, {label, {text, "Arity"}}]), + ArityEntry = gs:entry(Frame, [{pack_xy, {3,2}}]), + ButtonPacker = gs:frame(Frame, [{pack_xy, {2,4}}, + {packer_x, [{fixed, 60}, {fixed, 60}]}, + {packer_y, {fixed, 30}}]), + SearchButton = gs:button(ButtonPacker, [{label, {text, "Search"}}, + {pack_xy, {1,1}}]), + CancelButton = gs:button(ButtonPacker, [{label, {text, "Cancel"}}, + {pack_xy, {2,1}}]), + gs:config(Win, [{map, true}]), + gs:config(Frame, WH), + gs:config(ButtonPacker, WHB), + search_doc_plt_loop(State, CancelButton, SearchButton, ModEntry, + FunEntry, ArityEntry, Win, TopWin). + +search_doc_plt_loop(State, CancelButton, SearchButton, ModEntry, + FunEntry, ArityEntry, Win, TopWin) -> + receive + {gs, CancelButton, click, _, _} -> + gs:destroy(Win), + ok; + {gs, TopWin, destroy, _, _} -> + exit(normal); + {gs, SearchButton, click, _, _} -> + M = format_search(gs:read(ModEntry, text)), + F = format_search(gs:read(FunEntry, text)), + A = format_search(gs:read(ArityEntry, text)), + case dialyzer_plt:get_specs(State#gui_state.doc_plt, M, F, A) of + "" -> + error_sms(State, "No such function"), + search_doc_plt_loop(State, CancelButton, SearchButton, ModEntry, + FunEntry, ArityEntry, Win, TopWin); + NonEmptyString -> + gs:destroy(Win), + free_editor(State, "Content of PLT", NonEmptyString) + end + end. + +format_search([]) -> + '_'; +format_search(String) -> + try list_to_integer(String) + catch error:_ -> list_to_atom(String) + end. + +show_doc_plt(#gui_state{doc_plt = DocPLT} = State) -> + case dialyzer_plt:get_specs(DocPLT) of + "" -> error_sms(State, "No analysis has been made yet!\n"); + NonEmptyString -> free_editor(State, "Content of PLT", NonEmptyString) + end. + +free_editor(#gui_state{gs = GS, top = TopWin}, Title, Contents0) -> + Contents = lists:flatten(Contents0), + Tokens = string:tokens(Contents, "\n"), + NofLines = length(Tokens), + LongestLine = lists:max([length(X) || X <- Tokens]), + Height0 = NofLines * 25 + 80, + Height = if Height0 > 500 -> 500; true -> Height0 end, + Width0 = LongestLine * 7 + 60, + Width = if Width0 > 800 -> 800; true -> Width0 end, + WH = [{width, Width}, {height, Height}], + Win = gs:window(GS, [{title, Title}, {configure, true}, + {default, editor, {bg, white}} | WH]), + EmptySpace = {stretch, 1}, + Frame = gs:frame(Win, [{packer_x, [EmptySpace, {fixed, 60}, EmptySpace]}, + {packer_y, [EmptySpace, {fixed, 30}]} + | WH]), + Editor = gs:editor(Frame, [{pack_x, {1,3}}, {pack_y, 1}, + {font, {courier, 12}}, {vscroll, right}, + {wrap, word}, {enable, true}]), + Button = gs:button(Frame, [{label, {text, "Ok"}}, {pack_xy, {2,2}}]), + gs:config(Editor, [{insert, {insert, Contents}}]), + gs:config(Editor, [{enable, false}]), + gs:config(Win, [{map, true}]), + gs:config(Frame, WH), + show_info_loop(TopWin, Win, Frame, Button). + +%% ---- Common ---- + +show_info_loop(TopWin, Win, Frame, Button) -> + receive + {gs, Button, click, _, _} -> + gs:destroy(Win); + {gs, TopWin, destroy, _, _} -> + exit(normal); + {gs, Win, destroy, _, _} -> + ok; + {gs, Win, configure, _Data, [W, H|_]} -> + gs:config(Frame, [{width, W}, {height, H}]), + show_info_loop(TopWin, Win, Frame, Button) + end. + +include_dialog(#gui_state{gs = GS, options = Options}, Parent) -> + WH = [{width, 300}, {height, 400}], + Title = io_lib:format("Include Directories", []), + Win = gs:window(GS, [{title, Title}, {configure, true}, + {default, entry, {bg, white}}| WH]), + EmptySpace = {stretch, 1}, + Frame = gs:frame(Win, [{packer_x, [EmptySpace]}, + {packer_y, [{fixed, 30}, {fixed, 30}, {fixed, 30}, + EmptySpace, {fixed, 30}, {fixed, 30}]} + | WH]), + gs:label(Frame, [{pack_xy, {1,1}}, {label, {text, "Directory"}}]), + DirEntry = gs:entry(Frame, [{pack_xy, {1,2}}]), + ButtonPacker1 = gs:frame(Frame, [{pack_xy, {1,3}}, + {packer_x, [{fixed, 70}, {fixed, 70}, + EmptySpace]}, + {packer_y, {fixed, 30}}]), + AddButton = gs:button(ButtonPacker1, [{label, {text, "Add"}}, + {pack_xy, {1,1}}]), + Dirs = [io_lib:format("~s", [X]) || X <- Options#options.include_dirs], + DirBox = gs:listbox(Frame, [{pack_xy, {1,4}}, {vscroll, right}, + {bg, white}, {configure, true}, + {selectmode, multiple}, {items, Dirs}]), + ButtonPacker2 = gs:frame(Frame, [{pack_xy, {1,5}}, + {packer_x, [{fixed, 60}, {fixed, 70}, + EmptySpace]}, + {packer_y, {fixed, 30}}]), + DeleteButton = gs:button(ButtonPacker2, [{label, {text, "Delete"}}, + {pack_xy, {1,1}}]), + DeleteAllButton = gs:button(ButtonPacker2, [{label, {text, "Delete All"}}, + {pack_xy, {2,1}}]), + ButtonPacker3 = gs:frame(Frame, [{pack_xy, {1,6}}, + {packer_x, [EmptySpace, + {fixed, 60}, {fixed, 60}]}, + {packer_y, {fixed, 30}}]), + OkButton = gs:button(ButtonPacker3, [{label, {text, "Ok"}}, + {pack_xy, {2,1}}]), + CancelButton = gs:button(ButtonPacker3, [{label, {text, "Cancel"}}, + {pack_xy, {3,1}}]), + gs:config(Win, [{map, true}]), + gs:config(Frame, WH), + include_loop(Parent, Options, Frame, AddButton, DeleteAllButton, DeleteButton, + DirBox, DirEntry, OkButton, CancelButton, Win). + +include_loop(Parent, Options, Frame, AddButton, DeleteAllButton, DeleteButton, + DirBox, DirEntry, OkButton, CancelButton, Win) -> + receive + {gs, CancelButton, click, _, _} -> + gs:destroy(Win), + ok; + {gs, OkButton, click, _, _} -> + gs:destroy(Win), + Parent ! {new_options, Options}, + ok; + {gs, Win, configure, _Data, [W, H|_]} -> + gs:config(Frame, [{width, W}, {height, H}]), + include_loop(Parent, Options, Frame, AddButton, DeleteAllButton, + DeleteButton, DirBox, DirEntry, OkButton, CancelButton, Win); + {gs, AddButton, click, _, _} -> + Dirs = Options#options.include_dirs, + NewDirs = + case gs:read(DirEntry, text) of + [] -> Dirs; + Add -> [Add|Dirs] + end, + NewOptions = Options#options{include_dirs = NewDirs}, + gs:config(DirBox, [{items, NewDirs}]), + include_loop(Parent, NewOptions, Frame, AddButton, DeleteAllButton, + DeleteButton, DirBox, DirEntry, OkButton, CancelButton, Win); + {gs, DeleteAllButton, click, _, _} -> + gs:config(DirBox, [clear]), + NewOptions = Options#options{include_dirs = []}, + include_loop(Parent, NewOptions, Frame, AddButton, DeleteAllButton, + DeleteButton, DirBox, DirEntry, OkButton, CancelButton, Win); + {gs, DeleteButton, click, _, _} -> + NewOptions = + case gs:read(DirBox, selection) of + [] -> + Options; + List -> + lists:foreach(fun(X) -> gs:config(DirBox, [{del, X}]) end, + lists:sort(List)), + NewDirs = gs:read(DirBox, items), + Options#options{include_dirs = NewDirs} + end, + include_loop(Parent, NewOptions, Frame, AddButton, DeleteAllButton, + DeleteButton, DirBox, DirEntry, OkButton, CancelButton, Win); + {gs, Win, destroy, _, _} -> + ok + end. + +macro_dialog(#gui_state{gs = GS, options = Options}, Parent) -> + WH = [{width, 300}, {height, 400}], + Title = io_lib:format("Macro Definitions", []), + Win = gs:window(GS, [{title, Title}, {configure, true}, + {default, entry, {bg, white}}| WH]), + EmptySpace = {stretch, 1}, + Frame = gs:frame(Win, [{packer_x, [EmptySpace, EmptySpace]}, + {packer_y, [{fixed, 30}, {fixed, 30}, {fixed, 30}, + EmptySpace, {fixed, 30}, {fixed, 30}]} + | WH]), + gs:label(Frame, [{pack_xy, {1,1}}, {label, {text, "Macro"}}]), + MacroEntry = gs:entry(Frame, [{pack_xy, {1,2}}]), + gs:label(Frame, [{pack_xy, {2,1}}, {label, {text, "Term"}}]), + TermEntry = gs:entry(Frame, [{pack_xy, {2,2}}]), + ButtonPacker1 = gs:frame(Frame, [{pack_x, {1,2}}, {pack_y, 3}, + {packer_x, [{fixed, 70},{fixed, 70}, + EmptySpace]}, + {packer_y, {fixed, 30}}]), + AddButton = gs:button(ButtonPacker1, [{label, {text, "Add"}}, + {pack_xy, {1,1}}]), + Macros = [io_lib:format("~p = ~p",[X,Y]) || {X,Y} <- Options#options.defines], + MacroBox = gs:listbox(Frame, [{pack_x, {1,2}}, {pack_y, 4}, {vscroll, right}, + {bg, white}, {configure, true}, + {selectmode, multiple}, + {items, Macros}]), + ButtonPacker2 = gs:frame(Frame, [{pack_x, {1,2}}, {pack_y, 5}, + {packer_x, [{fixed, 60}, {fixed, 70}, + EmptySpace]}, + {packer_y, {fixed, 30}}]), + DeleteButton = gs:button(ButtonPacker2, [{label, {text, "Delete"}}, + {pack_xy, {1,1}}]), + DeleteAllButton = gs:button(ButtonPacker2, [{label, {text, "Delete All"}}, + {pack_xy, {2,1}}]), + ButtonPacker3 = gs:frame(Frame, [{pack_x, {1,2}}, {pack_y, 6}, + {packer_x, [EmptySpace, + {fixed, 60}, {fixed, 60}]}, + {packer_y, {fixed, 30}}]), + OkButton = gs:button(ButtonPacker3, [{label, {text, "Ok"}}, + {pack_xy, {2,1}}]), + CancelButton = gs:button(ButtonPacker3, [{label, {text, "Cancel"}}, + {pack_xy, {3,1}}]), + gs:config(Win, [{map, true}]), + gs:config(Frame, WH), + macro_loop(Parent, Options, Frame, AddButton, DeleteAllButton, DeleteButton, + MacroBox, MacroEntry, TermEntry, OkButton, CancelButton, Win). + +macro_loop(Parent, Options, Frame, AddButton, DeleteAllButton, DeleteButton, + MacroBox, MacroEntry, TermEntry, OkButton, CancelButton, Win) -> + receive + {gs, CancelButton, click, _, _} -> + gs:destroy(Win), + ok; + {gs, OkButton, click, _, _} -> + gs:destroy(Win), + Parent ! {new_options, Options}, + ok; + {gs, Win, configure, _Data, [W, H|_]} -> + gs:config(Frame, [{width, W}, {height, H}]), + macro_loop(Parent, Options, Frame, AddButton, DeleteAllButton, + DeleteButton, MacroBox, MacroEntry, TermEntry, OkButton, + CancelButton, Win); + {gs, AddButton, click, _, _} -> + Defines = Options#options.defines, + NewDefines = + case gs:read(MacroEntry, text) of + "" -> Defines; + Macro -> + Empty = [{text, ""}], + case gs:read(TermEntry, text) of + "" -> + gs:config(MacroEntry, Empty), + orddict:store(list_to_atom(Macro), true, Defines); + String -> + case parse(String) of + {ok, Term} -> + gs:config(MacroEntry, Empty), + gs:config(TermEntry, Empty), + orddict:store(list_to_atom(Macro), Term, Defines); + {error, _Reason} -> + Defines + end + end + end, + NewOptions = Options#options{defines = NewDefines}, + NewEntries = [io_lib:format("~p = ~p", [X, Y]) || {X, Y} <- NewDefines], + gs:config(MacroBox, [{items, NewEntries}]), + macro_loop(Parent, NewOptions, Frame, AddButton, DeleteAllButton, + DeleteButton, MacroBox, MacroEntry, TermEntry, OkButton, + CancelButton, Win); + {gs, DeleteAllButton, click, _, _} -> + gs:config(MacroBox, [clear]), + NewOptions = Options#options{defines = []}, + macro_loop(Parent, NewOptions, Frame, AddButton, DeleteAllButton, + DeleteButton, MacroBox, MacroEntry, TermEntry, OkButton, + CancelButton, Win); + {gs, DeleteButton, click, _, _} -> + NewOptions = + case gs:read(MacroBox, selection) of + [] -> + Options; + List -> + gs:config(MacroBox, [{selection, clear}]), + Fun = + fun(X) -> + Val = gs:read(MacroBox, {get, X}), + [MacroName|_] = re:split(Val, " ", [{return, list}]), + list_to_atom(MacroName) + end, + Delete = [Fun(X) || X <- List], + lists:foreach(fun(X) -> gs:config(MacroBox, [{del, X}]) end, + lists:reverse(lists:sort(List))), + Defines = Options#options.defines, + NewDefines = lists:foldl(fun(X, Acc) -> + orddict:erase(X, Acc) + end, + Defines, Delete), + Options#options{defines = NewDefines} + end, + macro_loop(Parent, NewOptions, Frame, AddButton, DeleteAllButton, + DeleteButton, MacroBox, MacroEntry, TermEntry, OkButton, + CancelButton, Win); + {gs, Win, destroy, _, _} -> + ok + end. + +parse(String) -> + case erl_scan:string(String ++ ".", 1) of + {ok, Ts, _} -> + case erl_parse:parse_exprs(Ts) of + {ok, [Expr]} -> + try erl_parse:normalise(Expr) + catch error:Reason -> {error, Reason} + end; + {error, E} -> + parse_error(E) + end; + {error, E, _} -> + parse_error(E) + end. + +parse_error(E) -> + S = io_lib:fwrite("Error parsing expression: ~P.", [E,15]), + {error, S}. + +%% ---------------------------------------------------------------- +%% +%% Run the analysis +%% + +start_analysis(State) -> + Analysis = build_analysis_record(State), + case get_anal_files(State, Analysis#analysis.start_from) of + error -> + Msg = "You must choose one or more files or dirs\n" + "before starting the analysis!", + error_sms(State, Msg), + config_gui_stop(State), + State; + {ok, Files} -> + Msg = "\n========== Starting Analysis ==========\n\n", + update_editor(State#gui_state.log, Msg), + NewAnalysis = Analysis#analysis{files = Files}, + run_analysis(State, NewAnalysis) + end. + +build_analysis_record(#gui_state{mode = Mode, menu = Menu, options = Options, + init_plt = InitPlt0}) -> + StartFrom = + case gs:read(Mode#mode.start_byte_code, select) of + true -> byte_code; + false -> src_code + end, + InitPlt = + case gs:read(Menu#menu.plt_empty, select) of + true -> dialyzer_plt:new(); + false -> InitPlt0 + end, + #analysis{defines = Options#options.defines, + include_dirs = Options#options.include_dirs, + plt = InitPlt, + start_from = StartFrom}. + +get_anal_files(#gui_state{chosen_box = ChosenBox}, StartFrom) -> + Files = gs:read(ChosenBox, items), + FilteredMods = + case StartFrom of + src_code -> filter_mods(Files, ".erl"); + byte_code -> filter_mods(Files, ".beam") + end, + FilteredDirs = [X || X <- Files, filelib:is_dir(X)], + case ordsets:union(FilteredMods, FilteredDirs) of + [] -> error; + Set -> {ok, Set} + end. + +run_analysis(State, Analysis) -> + config_gui_start(State), + Self = self(), + NewAnalysis = Analysis#analysis{doc_plt = dialyzer_plt:new()}, + LegalWarnings = find_legal_warnings(State), + Fun = + fun() -> + dialyzer_analysis_callgraph:start(Self, LegalWarnings, NewAnalysis) + end, + BackendPid = spawn_link(Fun), + State#gui_state{backend_pid = BackendPid}. + +find_legal_warnings(#gui_state{menu = #menu{warnings = Warnings}}) -> + ordsets:from_list([Tag || {Tag, GSItem} <- Warnings, + gs:read(GSItem, select) =:= true]). + +flush() -> + receive + _ -> flush() + after + 0 -> ok + end. + +update_editor(Editor, Msg) -> + gs:config(Editor, [{enable, true}]), + NofRows = gs:read(Editor, size), + gs:config(Editor, [{insertpos, 'end'}]), + gs:config(Editor, [{insert, {insert, Msg}}]), + NewNofRows = gs:read(Editor, size), + ScrollPos = gs:read(Editor, vscrollpos), + gs:config(Editor, [{vscrollpos, ScrollPos + NewNofRows - NofRows}]), + gs:config(Editor, [{enable, false}]). |