aboutsummaryrefslogtreecommitdiffstats
path: root/lib/dialyzer/src/dialyzer_gui.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/dialyzer/src/dialyzer_gui.erl')
-rw-r--r--lib/dialyzer/src/dialyzer_gui.erl1349
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}]).