diff options
Diffstat (limited to 'lib/dialyzer/src/dialyzer_gui_wx.erl')
-rw-r--r-- | lib/dialyzer/src/dialyzer_gui_wx.erl | 1243 |
1 files changed, 1243 insertions, 0 deletions
diff --git a/lib/dialyzer/src/dialyzer_gui_wx.erl b/lib/dialyzer/src/dialyzer_gui_wx.erl new file mode 100644 index 0000000000..2d97f88680 --- /dev/null +++ b/lib/dialyzer/src/dialyzer_gui_wx.erl @@ -0,0 +1,1243 @@ +%% -*- erlang-indent-level: 2 -*- +%%------------------------------------------------------------------------ +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 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_wx.erl +%%% Authors : Elli Fragkaki <[email protected]> +%%% Description : The wx-based graphical user interface of dialyzer. +%%% +%%% Created : 07 Oct 2009 by Elli Fragkaki <[email protected]> +%%%----------------------------------------------------------------------- + +-module(dialyzer_gui_wx). + +-export([start/1]). + +-include("dialyzer.hrl"). +-include("dialyzer_gui_wx.hrl"). + +%%------------------------------------------------------------------------ + +-define(DIALYZER_ERROR_TITLE, "Dialyzer Error"). +-define(DIALYZER_MESSAGE_TITLE, "Dialyzer Message"). + +%%------------------------------------------------------------------------ + +-type wx_object() :: any(). %% XXX: should be imported from wx + +-record(menu, {file :: wx_object(), + warnings :: wx_object(), + plt :: wx_object(), + options :: wx_object(), + help :: wx_object()}). + +-record(gui_state, {add :: wx_object(), + add_dir :: wx_object(), + add_rec :: wx_object(), + chosen_box :: wx_object(), + analysis_pid :: pid(), + del_file :: wx_object(), + doc_plt :: dialyzer_plt:plt(), + clear_chosen :: wx_object(), + clear_log :: wx_object(), + explain_warn :: wx_object(), + clear_warn :: wx_object(), + init_plt :: dialyzer_plt:plt(), + dir_entry :: wx_object(), + file_box :: wx_object(), + files_to_analyze :: ordset(string()), + gui :: wx_object(), + log :: wx_object(), + menu :: #menu{}, + mode :: wx_object(), + options :: #options{}, + run :: wx_object(), + stop :: wx_object(), + frame :: wx_object(), + warnings_box :: wx_object(), + explanation_box :: wx_object(), + wantedWarnings :: list(), + rawWarnings :: list(), + backend_pid :: pid(), + expl_pid :: pid()}). + +%%------------------------------------------------------------------------ + +-spec start(#options{}) -> ?RET_NOTHING_SUSPICIOUS. + +start(DialyzerOptions) -> + process_flag(trap_exit, true), + Wx = wx:new(), + State = wx:batch(fun() -> create_window(Wx, DialyzerOptions) end), + gui_loop(State). + +create_window(Wx, DialyzerOptions) -> + {ok, Host} = inet:gethostname(), + + %%---------- initializing frame --------- + Frame = wxFrame:new(Wx, -1, "Dialyzer " ++ ?VSN ++ " @ " ++ Host), + wxFrame:connect(Frame, close_window), + FileMenu = createFileMenu(), + WarningsMenu = createWarningsMenu(), + PltMenu = createPltMenu(), + OptionsMenu = createOptionsMenu(), + HelpMenu = createHelpMenu(), + + MenuBar = wxMenuBar:new(), + wxMenuBar:append(MenuBar, FileMenu, "File"), + wxMenuBar:append(MenuBar, WarningsMenu, "Warnings"), + wxMenuBar:append(MenuBar, PltMenu, "Plt"), + wxMenuBar:append(MenuBar, OptionsMenu, "Options"), + wxMenuBar:append(MenuBar, HelpMenu, "Help"), + wxFrame:setMenuBar(Frame, MenuBar), + ok = wxFrame:connect(Frame, command_menu_selected), + + %%----------- Set Labels ------------- + Lab1 = wxStaticText:new(Frame, ?LABEL1, "Directories or modules to analyze"), + OptionsLabel = wxStaticText:new(Frame, ?LABEL2, "Analysis Options"), + LogLabel = wxStaticText:new(Frame, ?LABEL3, "Log"), + FileLabel = wxStaticText:new(Frame, ?LABEL4, "File: "), + DirLabel = wxStaticText:new(Frame, ?LABEL5, "Dir: "), + WarningsLabel = wxStaticText:new(Frame, ?LABEL6, "Warnings"), + + %%---------- Set TextBoxes ----------- + ChosenBox = wxListBox:new(Frame, ?ChosenBox, + [{size, {250,200}}, + {style, ?wxLB_EXTENDED bor ?wxLB_HSCROLL + bor ?wxLB_NEEDED_SB}]), + LogBox = wxTextCtrl:new(Frame, ?LogBox, + [{size, {530,200}}, + {style, ?wxTE_MULTILINE + bor ?wxTE_READONLY bor ?wxHSCROLL}]), + DefaultPath = code:root_dir(), + + FilePicker = wxFilePickerCtrl:new(Frame, ?FilePicker, + [{path, DefaultPath}, + {message, "Choose File to Analyse"}, + {style,?wxFLP_FILE_MUST_EXIST bor ?wxFLP_USE_TEXTCTRL}]), + wxPickerBase:setTextCtrlProportion(FilePicker,3), + wxPickerBase:setPickerCtrlProportion(FilePicker,2), + DirPicker = wxDirPickerCtrl:new(Frame, ?DirPicker, + [{path, DefaultPath}, + {message, "Choose Directory to Analyze"}, + {style,?wxDIRP_DIR_MUST_EXIST bor ?wxDIRP_USE_TEXTCTRL}]), + WarningsBox = wxListBox:new(Frame, ?WarningsBox, + [{size, {700,200}}, + {style, ?wxLB_HSCROLL + bor ?wxLB_NEEDED_SB}]), + + %%--------- Set Buttons -------------- + DeleteButton = wxButton:new(Frame, ?Del_Button, [{label, "Delete"}]), + DeleteAllButton = wxButton:new(Frame, ?DelAll_Button, [{label, "Delete All"}]), + FileType = wxRadioBox:new(Frame, ?RADIOBOX, " File Type: " , {1,1}, {150,90}, + [["BeamFiles"],["SourceFiles"]]), + ClearLogButton = wxButton:new(Frame, ?ClearLog_Button, [{label, "Clear Log"}]), + AddButton = wxButton:new(Frame, ?Add_Button, [{label, "Add"}]), + AddDirButton = wxButton:new(Frame, ?AddDir_Button, [{label, "Add Dir"}]), + AddRecButton = wxButton:new(Frame, ?AddRec_Button, [{label, "Add Recursively"}]), + ExplainWarnButton = wxButton:new(Frame, ?ExplWarn_Button, [{label, "Explain Warning"}]), + ClearWarningsButton = wxButton:new(Frame, ?ClearWarn_Button, [{label, "Clear Warnings"}]), + RunButton = wxButton:new(Frame, ?Run_Button, [{label, "Run"}]), + StopButton = wxButton:new(Frame, ?Stop_Button, [{label, "Stop"}]), + wxWindow:disable(StopButton), + %%--------- Connect Buttons ----------- + wxButton:connect(DeleteButton, command_button_clicked), + wxButton:connect(DeleteAllButton, command_button_clicked), + wxButton:connect(ClearLogButton, command_button_clicked), + wxButton:connect(AddButton, command_button_clicked), + wxButton:connect(AddDirButton, command_button_clicked), + wxButton:connect(AddRecButton, command_button_clicked), + wxButton:connect(ExplainWarnButton, command_button_clicked), + wxButton:connect(ClearWarningsButton, command_button_clicked), + wxButton:connect(RunButton, command_button_clicked), + wxButton:connect(StopButton, command_button_clicked), + + %%------------Set Layout ------------ + All = wxBoxSizer:new(?wxVERTICAL), + Top = wxBoxSizer:new(?wxHORIZONTAL), + Left = wxBoxSizer:new(?wxVERTICAL), + Right = wxBoxSizer:new(?wxVERTICAL), + RightUp = wxBoxSizer:new(?wxHORIZONTAL), + + Opts = [{flag,?wxEXPAND bor ?wxALL}, {proportion,1}, {border, 1}], + Opts3 = [{flag,?wxEXPAND bor ?wxALL}, {proportion,3}, {border, 1}], + Center = [{flag, ?wxALIGN_CENTER_HORIZONTAL}], + + ChooseItem = wxBoxSizer:new(?wxVERTICAL), + FileTypeItem = wxBoxSizer:new(?wxVERTICAL), + LogItem = wxBoxSizer:new(?wxVERTICAL), + FileDirItem = wxBoxSizer:new(?wxVERTICAL), + FileItem = wxBoxSizer:new(?wxHORIZONTAL), + DirItem = wxBoxSizer:new(?wxHORIZONTAL), + AddDirButtons = wxBoxSizer:new(?wxHORIZONTAL), + WarningsItem = wxBoxSizer:new(?wxVERTICAL), + ChooseButtons = wxBoxSizer:new(?wxHORIZONTAL), + WarnButtons = wxBoxSizer:new(?wxHORIZONTAL), + RunButtons = wxBoxSizer:new(?wxHORIZONTAL), + Buttons = wxFlexGridSizer:new(3), + + wxSizer:add(ChooseButtons, DeleteButton, ?BorderOpt), + wxSizer:add(ChooseButtons, DeleteAllButton, ?BorderOpt), + wxSizer:add(ChooseItem, Lab1, Center), + wxSizer:add(ChooseItem, ChosenBox, Opts), + wxSizer:add(ChooseItem, ChooseButtons, ?BorderOpt), + wxSizer:add(FileTypeItem, OptionsLabel), + wxSizer:add(FileTypeItem, FileType, [{border, 5}, {flag, ?wxALL}]), + wxSizer:add(LogItem, LogLabel, Center), + wxSizer:add(LogItem, LogBox, Opts3), + wxSizer:add(LogItem, ClearLogButton, ?BorderOpt), + wxSizer:add(FileItem, FileLabel), + wxSizer:add(FileItem, FilePicker), + wxSizer:add(DirItem, DirLabel), + wxSizer:add(DirItem, DirPicker), + wxSizer:add(AddDirButtons, AddDirButton, ?BorderOpt), + wxSizer:add(AddDirButtons, AddRecButton, ?BorderOpt), + wxSizer:add(FileDirItem, FileItem), + wxSizer:add(FileDirItem, AddButton, ?BorderOpt), + wxSizer:add(FileDirItem, DirItem, ?BorderOpt), + wxSizer:add(FileDirItem, AddDirButtons, ?BorderOpt), + wxSizer:add(WarnButtons, ExplainWarnButton, ?BorderOpt), + wxSizer:add(WarnButtons, ClearWarningsButton, ?BorderOpt), + wxSizer:add(RunButtons, RunButton, ?BorderOpt), + wxSizer:add(RunButtons, StopButton, ?BorderOpt), + wxSizer:add(Buttons, WarnButtons), + wxSizer:add(Buttons, wxStaticText:new(Frame, ?LABEL7, ""), [{flag, ?wxEXPAND}]), + wxSizer:add(Buttons, RunButtons), + wxFlexGridSizer:addGrowableCol(Buttons, 1), + wxSizer:add(WarningsItem, WarningsLabel, Center), + wxSizer:add(WarningsItem, WarningsBox, Opts3), + wxSizer:add(WarningsItem, Buttons, [{flag, ?wxEXPAND bor ?wxALL},?Border]), + + wxSizer:add(Left, ChooseItem, Opts), + wxSizer:add(Left, FileDirItem, [{proportion, 1}, {border, 60}, {flag, ?wxTOP}]), + wxSizer:add(RightUp, FileTypeItem, ?BorderOpt), + wxSizer:add(RightUp, LogItem, Opts3), + wxSizer:add(Right, RightUp, Opts3), + wxSizer:add(Right, WarningsItem, Opts3), + wxSizer:add(Top, Left, Opts), + wxSizer:add(Top, Right, Opts3), + + wxSizer:add(All, Top, Opts), + wxWindow:setSizer(Frame, All), + wxWindow:setSizeHints(Frame, {1150,600}), + wxWindow:show(Frame), + + Warnings = [{?WARN_RETURN_NO_RETURN, ?menuID_WARN_NO_RETURN_FUN}, + {?WARN_RETURN_ONLY_EXIT, ?menuID_WARN_ERROR_HANDLING_FUN}, + {?WARN_NOT_CALLED, ?menuID_WARN_UNUSED_FUN}, + {?WARN_NON_PROPER_LIST, ?menuID_WARN_LIST_CONSTR}, + {?WARN_FUN_APP, ?menuID_WARN_BAD_FUN}, + {?WARN_MATCHING, ?menuID_WARN_MATCH_FAILURES}, + {?WARN_OPAQUE, ?menuID_WARN_OPAQUE}, + {?WARN_FAILING_CALL, ?menuID_WARN_FAIL_FUN_CALLS}, + {?WARN_CALLGRAPH, ?menuID_WARN_UNEXPORTED_FUN}, + {?WARN_RACE_CONDITION, ?menuID_WARN_RACE_CONDITIONS}, + %% For contracts. + {?WARN_CONTRACT_TYPES,?menuID_WARN_WRONG_CONTRACTS}, + {?WARN_CONTRACT_SYNTAX, ?menuID_WARN_CONTRACT_SYNTAX} + ], + Menu = #menu{file = FileMenu, + warnings = WarningsMenu, + plt = PltMenu, + options =OptionsMenu, + help = HelpMenu}, + + InitPlt = try dialyzer_plt:from_file(DialyzerOptions#options.init_plt) + catch throw:{dialyzer_error, _} -> dialyzer_plt:new() + end, + + #gui_state{add = AddButton, + add_dir = AddDirButton, + add_rec = AddRecButton, + chosen_box = ChosenBox, + clear_chosen = DeleteAllButton, + clear_log = ClearLogButton, + explain_warn = ExplainWarnButton, + clear_warn = ClearWarningsButton, + del_file = DeleteButton, + doc_plt = dialyzer_plt:new(), + dir_entry = DirPicker, + file_box = FilePicker, + files_to_analyze = ordsets:new(), + gui = Wx, + init_plt = InitPlt, + log = LogBox, + menu = Menu, + mode = FileType, + options = DialyzerOptions, + run = RunButton, + stop = StopButton, + frame = Frame, + warnings_box = WarningsBox, + wantedWarnings = Warnings, + rawWarnings = []}. + +createFileMenu() -> + FileMenu = wxMenu:new(), + wxMenu:append(FileMenu, wxMenuItem:new([{id, ?menuID_FILE_SAVE_WARNINGS}, + {text, "Save &Warnings"}])), + wxMenu:append(FileMenu, wxMenuItem:new([{id, ?menuID_FILE_SAVE_LOG}, + {text, "Save &Log"}])), + wxMenu:append(FileMenu, wxMenuItem:new([{id, ?menuID_FILE_QUIT}, + {text, "E&xit\tAlt-X"}])), + FileMenu. + +createWarningsMenu() -> + WarningsMenu = wxMenu:new(), + wxMenu:appendCheckItem(WarningsMenu, + ?menuID_WARN_MATCH_FAILURES, + "Match failures"), + wxMenu:check(WarningsMenu, ?menuID_WARN_MATCH_FAILURES, true), + wxMenu:appendCheckItem(WarningsMenu, + ?menuID_WARN_FAIL_FUN_CALLS, + "Failing function calls"), + wxMenu:check(WarningsMenu, ?menuID_WARN_FAIL_FUN_CALLS, true), + wxMenu:appendCheckItem(WarningsMenu, + ?menuID_WARN_BAD_FUN, + "Bad fun applications"), + wxMenu:check(WarningsMenu, ?menuID_WARN_BAD_FUN, true), + wxMenu:appendCheckItem(WarningsMenu, + ?menuID_WARN_OPAQUE, + "Opaqueness violations"), + wxMenu:check(WarningsMenu, ?menuID_WARN_OPAQUE, true), + wxMenu:appendCheckItem(WarningsMenu, + ?menuID_WARN_LIST_CONSTR, + "Improper list constructions"), + wxMenu:check(WarningsMenu, ?menuID_WARN_LIST_CONSTR, true), + wxMenu:appendCheckItem(WarningsMenu, + ?menuID_WARN_UNUSED_FUN, + "Unused functions"), + wxMenu:check(WarningsMenu, ?menuID_WARN_UNUSED_FUN, true), + wxMenu:appendCheckItem(WarningsMenu, + ?menuID_WARN_ERROR_HANDLING_FUN, + "Error handling functions"), + wxMenu:appendCheckItem(WarningsMenu, + ?menuID_WARN_NO_RETURN_FUN, + "Functions of no return"), + wxMenu:check(WarningsMenu, ?menuID_WARN_NO_RETURN_FUN, true), + wxMenu:appendCheckItem(WarningsMenu, + ?menuID_WARN_UNEXPORTED_FUN, + "Call to unexported function"), + wxMenu:check(WarningsMenu, ?menuID_WARN_UNEXPORTED_FUN, true), + wxMenu:appendCheckItem(WarningsMenu, + ?menuID_WARN_RACE_CONDITIONS, + "Possible race conditions"), + wxMenu:appendCheckItem(WarningsMenu, + ?menuID_WARN_WRONG_CONTRACTS, + "Wrong contracts"), + wxMenu:check(WarningsMenu, ?menuID_WARN_WRONG_CONTRACTS, true), + wxMenu:appendCheckItem(WarningsMenu, + ?menuID_WARN_CONTRACT_SYNTAX, + "Wrong contract syntax"), + wxMenu:check(WarningsMenu, ?menuID_WARN_CONTRACT_SYNTAX, true), + WarningsMenu. + +createPltMenu() -> + PltMenu = wxMenu:new(), + wxMenu:appendCheckItem(PltMenu, + ?menuID_PLT_INIT_EMPTY, + "Init with empty PLT"), + wxMenu:append(PltMenu, wxMenuItem:new([{id, ?menuID_PLT_SHOW_CONTENTS}, + {text, "Show contents"}])), + wxMenu:append(PltMenu, wxMenuItem:new([{id, ?menuID_PLT_SEARCH_CONTENTS}, + {text, "Search contents"}])), + PltMenu. + +createOptionsMenu() -> + OptsMenu = wxMenu:new(), + wxMenu:append(OptsMenu, wxMenuItem:new([{id, ?menuID_OPTIONS_MACRO}, + {text, "Manage Macro Definitions"}])), + wxMenu:append(OptsMenu, wxMenuItem:new([{id, ?menuID_OPTIONS_INCLUDE_DIR}, + {text, "Manage Include Directories"}])), + OptsMenu. + +createHelpMenu() -> + HelpMenu = wxMenu:new(), + wxMenu:append(HelpMenu, wxMenuItem:new([{id, ?menuID_HELP_MANUAL}, + {text, "Manual"}])), + wxMenu:append(HelpMenu, wxMenuItem:new([{id, ?menuID_HELP_WARNING_OPTIONS}, + {text, "Warning Options"}])), + wxMenu:append(HelpMenu, wxMenuItem:new([{id, ?menuID_HELP_ABOUT}, + {text, "About"}])), + HelpMenu. + +%% ---------------------------------------------------------------- +%% +%% Main GUI Loop +%% + +-spec gui_loop(#gui_state{}) -> ?RET_NOTHING_SUSPICIOUS. + +gui_loop(#gui_state{backend_pid = BackendPid, doc_plt = DocPlt, + log = Log, frame = Frame, + warnings_box = WarningsBox} = State) -> + receive + #wx{event = #wxClose{}} -> + io:format("~p Closing window ~n", [self()]), + ok = wxFrame:setStatusText(Frame, "Closing...",[]), + wxWindow:destroy(Frame), + ?RET_NOTHING_SUSPICIOUS; + %% ----- Menu ----- + #wx{id = ?menuID_FILE_SAVE_LOG, obj = Frame, + event = #wxCommand{type = command_menu_selected}} -> + save_file(State, log), + gui_loop(State); + #wx{id=?menuID_FILE_SAVE_WARNINGS, obj=Frame, + event=#wxCommand{type=command_menu_selected}} -> + save_file(State, warnings), + gui_loop(State); + #wx{id=?menuID_FILE_QUIT, obj=Frame, + event=#wxCommand{type=command_menu_selected}} -> + case maybe_quit(State) of + true -> ?RET_NOTHING_SUSPICIOUS; + false -> gui_loop(State) + end; + #wx{id=?menuID_PLT_SHOW_CONTENTS, obj=Frame, + event=#wxCommand{type=command_menu_selected}} -> + show_doc_plt(State), + gui_loop(State); + #wx{id=?menuID_PLT_SEARCH_CONTENTS, obj=Frame, + event=#wxCommand{type=command_menu_selected}} -> + case dialyzer_plt:get_specs(DocPlt) of + "" -> error_sms(State, "No analysis has been made yet!\n"); + _ -> search_doc_plt(State) + end, + gui_loop(State); + #wx{id=?menuID_OPTIONS_INCLUDE_DIR, obj=Frame, + event=#wxCommand{type=command_menu_selected}} -> + NewOptions = include_dialog(State), + NewState = State#gui_state{options = NewOptions}, + gui_loop(NewState); + #wx{id=?menuID_OPTIONS_MACRO, obj=Frame, + event=#wxCommand{type=command_menu_selected}} -> + NewOptions = macro_dialog(State), + NewState = State#gui_state{options = NewOptions}, + gui_loop(NewState); + #wx{id=?menuID_HELP_MANUAL, obj=Frame, + event=#wxCommand{type=command_menu_selected}} -> + handle_help(State, "Dialyzer Manual", "manual.txt"), + gui_loop(State); + #wx{id=?menuID_HELP_WARNING_OPTIONS, obj=Frame, + event=#wxCommand{type=command_menu_selected}} -> + handle_help(State, "Dialyzer Warnings", "warnings.txt"), + gui_loop(State); + #wx{id=?menuID_HELP_ABOUT, obj=Frame, + event=#wxCommand{type=command_menu_selected}} -> + Message = " This is DIALYZER version " ++ ?VSN ++ " \n"++ + "DIALYZER is a DIscrepany AnaLYZer for ERlang programs.\n\n"++ + " Copyright (C) Tobias Lindahl <[email protected]>\n"++ + " Kostis Sagonas <[email protected]>\n\n", + output_sms(State, "About Dialyzer", Message, info), + gui_loop(State); + %% ------ Buttons --------- + #wx{id=?Add_Button, + event=#wxCommand{type=command_button_clicked}} -> + State1 = handle_add_files(State), + gui_loop(State1); + #wx{id=?AddDir_Button, + event=#wxCommand{type=command_button_clicked}} -> + State1 = handle_add_dir(State), + gui_loop(State1); + #wx{id=?AddRec_Button, + event=#wxCommand{type=command_button_clicked}} -> + State1 = handle_add_rec(State), + gui_loop(State1); + #wx{id=?Del_Button, + event=#wxCommand{type=command_button_clicked}} -> + State1 = handle_file_delete(State), + gui_loop(State1); + #wx{id=?DelAll_Button, + event=#wxCommand{type=command_button_clicked}} -> + State1 = handle_file_delete_all(State), + gui_loop(State1); + #wx{id=?ClearLog_Button, + event=#wxCommand{type=command_button_clicked}} -> + wxTextCtrl:clear(State#gui_state.log), + gui_loop(State); + #wx{id=?ExplWarn_Button, + event=#wxCommand{type=command_button_clicked}} -> + handle_explanation(State), + gui_loop(State); + #wx{id=?ClearWarn_Button, + event=#wxCommand{type=command_button_clicked}} -> + wxListBox:clear(WarningsBox), + NewState = State#gui_state{rawWarnings = []}, + gui_loop(NewState); + #wx{id=?Run_Button, + event=#wxCommand{type=command_button_clicked}} -> + NewState = start_analysis(State), + gui_loop(NewState); + #wx{id=?Stop_Button, + event=#wxCommand{type=command_button_clicked}} -> + BackendPid ! {self(), stop}, + config_gui_stop(State), + update_editor(Log, "\n***** Analysis stopped ****\n"), + gui_loop(State); + %% ----- 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 + NewState = add_warnings(State, SortedWarns), + gui_loop(NewState); + {BackendPid, cserver, CServer, Plt} -> + Self = self(), + Fun = + fun() -> + dialyzer_explanation:expl_loop(Self, CServer, Plt) + end, + ExplanationPid = spawn_link(Fun), + gui_loop(State#gui_state{expl_pid = ExplanationPid}); + {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) + end. + +maybe_quit(#gui_state{frame = Frame} = State) -> + case dialog(State, "Do you really want to quit?", ?DIALYZER_MESSAGE_TITLE) of + true -> + wxWindow:destroy(Frame), + true; + false -> + false + end. + +%% ------------ Yes/No Question ------------ +dialog(#gui_state{frame = Frame}, Message, Title) -> + MessageWin = wxMessageDialog:new(Frame,Message,[{caption, Title},{style, ?wxYES_NO bor ?wxICON_QUESTION bor ?wxNO_DEFAULT}]), + case wxDialog:showModal(MessageWin) of + ?wxID_YES -> + true; + ?wxID_NO -> + false; + ?wxID_CANCEL -> + false + end. + +search_doc_plt(#gui_state{gui = Wx} = State) -> + Dialog = wxFrame:new(Wx, ?SearchPltDialog, "Search the PLT",[{size,{400,100}},{style, ?wxSTAY_ON_TOP}]), + Size = {size,{120,30}}, + ModLabel = wxStaticText:new(Dialog, ?ModLabel, "Module"), + ModText = wxTextCtrl:new(Dialog, ?ModText,[Size]), + FunLabel = wxStaticText:new(Dialog, ?FunLabel, "Function"), + FunText = wxTextCtrl:new(Dialog, ?FunText,[Size]), + ArLabel = wxStaticText:new(Dialog, ?ArLabel, "Arity"), + ArText = wxTextCtrl:new(Dialog, ?ArText,[Size]), + SearchButton = wxButton:new(Dialog, ?SearchButton, [{label, "Search"}]), + wxButton:connect(SearchButton, command_button_clicked), + Cancel = wxButton:new(Dialog, ?Search_Cancel, [{label, "Cancel"}]), + wxButton:connect(Cancel, command_button_clicked), + + Layout = wxBoxSizer:new(?wxVERTICAL), + Top = wxBoxSizer:new(?wxHORIZONTAL), + ModLayout = wxBoxSizer:new(?wxVERTICAL), + FunLayout = wxBoxSizer:new(?wxVERTICAL), + ArLayout = wxBoxSizer:new(?wxVERTICAL), + Buttons = wxBoxSizer:new(?wxHORIZONTAL), + + wxSizer:add(ModLayout, ModLabel, ?BorderOpt), + wxSizer:add(ModLayout,ModText, ?BorderOpt), + wxSizer:add(FunLayout, FunLabel, ?BorderOpt), + wxSizer:add(FunLayout,FunText, ?BorderOpt), + wxSizer:add(ArLayout, ArLabel, ?BorderOpt), + wxSizer:add(ArLayout,ArText, ?BorderOpt), + wxSizer:add(Buttons, SearchButton, ?BorderOpt), + wxSizer:add(Buttons,Cancel, ?BorderOpt), + + wxSizer:add(Top, ModLayout), + wxSizer:add(Top, FunLayout), + wxSizer:add(Top, ArLayout), + wxSizer:add(Layout, Top,[{flag, ?wxALIGN_CENTER}]), + wxSizer:add(Layout, Buttons,[{flag, ?wxALIGN_CENTER bor ?wxBOTTOM}]), + wxFrame:connect(Dialog, close_window), + wxWindow:setSizer(Dialog, Layout), + wxFrame:show(Dialog), + search_plt_loop(State, Dialog, ModText, FunText, ArText, SearchButton, Cancel). + +search_plt_loop(State= #gui_state{doc_plt = DocPlt, frame = Frame}, Win, ModText, FunText, ArText, Search, Cancel) -> + receive + #wx{id = ?Search_Cancel, + event = #wxCommand{type = command_button_clicked}} -> + wxWindow:destroy(Win); + #wx{id = ?SearchPltDialog, event = #wxClose{type = close_window}} -> + wxWindow:destroy(Win); + #wx{event = #wxClose{type = close_window}} -> + wxWindow:destroy(Win), + wxWindow:destroy(Frame); + #wx{id = ?SearchButton, + event = #wxCommand{type = command_button_clicked}} -> + M = format_search(wxTextCtrl:getValue(ModText)), + F = format_search(wxTextCtrl:getValue(FunText)), + A = format_search(wxTextCtrl:getValue(ArText)), + + if + (M == '_') or (F == '_') or (A == '_') -> + error_sms(State, "Please give:\n Module (atom)\n Function (atom)\n Arity (integer)\n"), + search_plt_loop(State, Win, ModText, FunText, ArText, Search, Cancel); + true -> + case dialyzer_plt:get_specs(DocPlt, M, F, A) of + none -> + error_sms(State, "No such function"), + search_plt_loop(State, Win, ModText, FunText, ArText, Search, Cancel); + NonEmptyString -> + wxWindow:destroy(Win), + free_editor(State, "Content of PLT", NonEmptyString) + end + 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. + +message(State, Message) -> + output_sms(State, ?DIALYZER_MESSAGE_TITLE, Message, info). + +error_sms(State, Message) -> + output_sms(State, ?DIALYZER_ERROR_TITLE, Message, error). + +output_sms(#gui_state{frame = Frame}, Title, Message, Type) -> + case Type of + error -> + MessageWin = wxMessageDialog:new(Frame,Message,[{caption, Title},{style, ?wxOK bor ?wxICON_ERROR}]); + info -> + MessageWin = wxMessageDialog:new(Frame,Message,[{caption, Title},{style, ?wxOK bor ?wxICON_INFORMATION}]) + end, + wxWindow:setSizeHints(MessageWin, {350,100}), + wxDialog:showModal(MessageWin). + +free_editor(#gui_state{gui = Wx, frame = Frame}, 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, + Size = {size,{Width, Height}}, + Win = wxFrame:new(Wx, ?Message, Title, [{size,{Width+4, Height+50}}]), + + Editor = wxTextCtrl:new(Win, ?Message_Info, + [Size, + {style, ?wxTE_MULTILINE + bor ?wxTE_READONLY bor ?wxVSCROLL bor ?wxEXPAND}]), + wxTextCtrl:appendText(Editor, Contents), + wxFrame:connect(Win, close_window), + Ok = wxButton:new(Win, ?Message_Ok, [{label, "OK"}]), + wxButton:connect(Ok, command_button_clicked), + Layout = wxBoxSizer:new(?wxVERTICAL), + + wxSizer:add(Layout, Editor, ?BorderOpt), + wxSizer:add(Layout, Ok, [{flag, ?wxALIGN_CENTER bor ?wxBOTTOM bor ?wxALL}, ?Border]), + wxWindow:setSizer(Win, Layout), + wxWindow:show(Win), + show_info_loop(Frame, Win). + +show_info_loop(Frame, Win) -> + receive + #wx{id = ?Message_Ok, event = #wxCommand{type = command_button_clicked}} -> + wxWindow:destroy(Win); + #wx{id = ?Message, event = #wxClose{type = close_window}} -> + wxWindow:destroy(Win); + #wx{event = #wxClose{type = close_window}} -> + wxWindow:destroy(Frame) + end. + +handle_add_files(#gui_state{chosen_box = ChosenBox, file_box = FileBox, + files_to_analyze = FileList, + mode = Mode} = State) -> + case wxFilePickerCtrl:getPath(FileBox) of + "" -> + State; + File -> + NewFile = ordsets:new(), + NewFile1 = ordsets:add_element(File,NewFile), + Ext = + case wxRadioBox:getSelection(Mode) of + 0 -> ".beam"; + 1-> ".erl" + end, + State#gui_state{files_to_analyze = add_files(filter_mods(NewFile1, Ext), FileList, ChosenBox, Ext)} + end. + +handle_add_dir(#gui_state{chosen_box = ChosenBox, dir_entry = DirBox, + files_to_analyze = FileList, + mode = Mode} = State) -> + case wxDirPickerCtrl:getPath(DirBox) of + "" -> + State; + Dir -> + NewDir = ordsets:new(), + NewDir1 = ordsets:add_element(Dir,NewDir), + Ext = case wxRadioBox:getSelection(Mode) of + 0 -> ".beam"; + 1-> ".erl" + end, + State#gui_state{files_to_analyze = add_files(filter_mods(NewDir1,Ext), FileList, ChosenBox, Ext)} + end. + +handle_add_rec(#gui_state{chosen_box = ChosenBox, dir_entry = DirBox, files_to_analyze = FileList, + mode = Mode} = State) -> + case wxDirPickerCtrl:getPath(DirBox) of + "" -> + State; + Dir -> + NewDir = ordsets:new(), + NewDir1 = ordsets:add_element(Dir,NewDir), + TargetDirs = ordsets:union(NewDir1, all_subdirs(NewDir1)), + case wxRadioBox:getSelection(Mode) of + 0 -> Ext = ".beam"; + 1-> Ext = ".erl" + end, + State#gui_state{files_to_analyze = add_files(filter_mods(TargetDirs,Ext), FileList, ChosenBox, Ext)} + end. + +handle_file_delete(#gui_state{chosen_box = ChosenBox, + files_to_analyze = FileList} = State) -> + {_, List} = wxListBox:getSelections(ChosenBox), + Set = ordsets:from_list([wxControlWithItems:getString(ChosenBox, X) || X <- List]), + FileList1 = ordsets:subtract(FileList,Set), + lists:foreach(fun (X) -> wxListBox:delete(ChosenBox, X) end, List), + State#gui_state{files_to_analyze = FileList1}. + +handle_file_delete_all(#gui_state{chosen_box = ChosenBox} = State) -> + wxListBox:clear(ChosenBox), + State#gui_state{files_to_analyze = ordsets:new()}. + +add_files(File, FileList, ChosenBox, Ext) -> + Set = filter_mods(FileList, Ext), + Files = ordsets:union(File, Set), + Files1 = ordsets:to_list(Files), + wxListBox:set(ChosenBox, Files1), + Files. + +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). + +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. + +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 wxRadioBox:getSelection(Mode) of + 0 -> byte_code; + 1 -> src_code + end, + InitPlt = + case wxMenu:isChecked(Menu#menu.plt,?menuID_PLT_INIT_EMPTY) 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{files_to_analyze = Files}, StartFrom) -> + 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 = MenuWarnings}, + wantedWarnings = Warnings }) -> + ordsets:from_list([Tag || {Tag, MenuItem} <- Warnings, + wxMenu:isChecked(MenuWarnings, MenuItem)]). + +update_editor(Editor, Msg) -> + wxTextCtrl:appendText(Editor,Msg). + +config_gui_stop(State) -> + wxWindow:disable(State#gui_state.stop), + wxWindow:enable(State#gui_state.run), + wxWindow:enable(State#gui_state.del_file), + wxWindow:enable(State#gui_state.clear_chosen), + wxWindow:enable(State#gui_state.add), + wxWindow:enable(State#gui_state.add_dir), + wxWindow:enable(State#gui_state.add_rec), + wxWindow:enable(State#gui_state.clear_warn), + wxWindow:enable(State#gui_state.clear_log), + Menu = State#gui_state.menu, + wxMenu:enable(Menu#menu.file,?menuID_FILE_SAVE_WARNINGS,true), + wxMenu:enable(Menu#menu.file,?menuID_FILE_SAVE_LOG,true), + wxMenu:enable(Menu#menu.options,?menuID_OPTIONS_MACRO,true), + wxMenu:enable(Menu#menu.options,?menuID_OPTIONS_INCLUDE_DIR,true), + wxMenu:enable(Menu#menu.plt,?menuID_PLT_INIT_EMPTY,true), + wxMenu:enable(Menu#menu.plt,?menuID_PLT_SHOW_CONTENTS,true), + wxMenu:enable(Menu#menu.plt,?menuID_PLT_SEARCH_CONTENTS,true), + wxRadioBox:enable(State#gui_state.mode). + +config_gui_start(State) -> + wxWindow:enable(State#gui_state.stop), + wxWindow:disable(State#gui_state.run), + wxWindow:disable(State#gui_state.del_file), + wxWindow:disable(State#gui_state.clear_chosen), + wxWindow:disable(State#gui_state.add), + wxWindow:disable(State#gui_state.add_dir), + wxWindow:disable(State#gui_state.add_rec), + wxWindow:disable(State#gui_state.clear_warn), + wxWindow:disable(State#gui_state.clear_log), + Menu = State#gui_state.menu, + wxMenu:enable(Menu#menu.file,?menuID_FILE_SAVE_WARNINGS, false), + wxMenu:enable(Menu#menu.file,?menuID_FILE_SAVE_LOG, false), + wxMenu:enable(Menu#menu.options,?menuID_OPTIONS_MACRO, false), + wxMenu:enable(Menu#menu.options,?menuID_OPTIONS_INCLUDE_DIR, false), + wxMenu:enable(Menu#menu.plt,?menuID_PLT_INIT_EMPTY, false), + wxMenu:enable(Menu#menu.plt,?menuID_PLT_SHOW_CONTENTS, false), + wxMenu:enable(Menu#menu.plt,?menuID_PLT_SEARCH_CONTENTS, false), + wxRadioBox:disable(State#gui_state.mode). + +save_file(#gui_state{frame = Frame, warnings_box = WBox, log = Log} = State, Type) -> + case Type of + warnings -> + Message = "Save Warnings", + Box = WBox; + log -> Message = "Save Log", + Box = Log + end, + case wxTextCtrl:getValue(Box) of + "" -> error_sms(State,"There is nothing to save...\n"); + _ -> + DefaultPath = code:root_dir(), + FileDialog = wxFileDialog:new(Frame, + [{defaultDir, DefaultPath}, + {message, Message}, + {style,?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT}]), + case wxFileDialog:showModal(FileDialog) of + ?wxID_OK -> Path = wxFileDialog:getPath(FileDialog), + case wxTextCtrl:saveFile(Box,[{file,Path}]) of + true -> ok; + false -> error_sms(State,"Could not write to file:\n" ++ Path) + end; + ?wxID_CANCEL -> wxWindow:destroy(FileDialog); + _ -> error_sms(State,"Could not write to file:\n") + end + end. + +include_dialog(#gui_state{gui = Wx, frame = Frame, options = Options}) -> + Size = {size,{300,480}}, + Dialog = wxFrame:new(Wx, ?IncludeDir, "Include Directories",[Size]), + DirLabel = wxStaticText:new(Dialog, ?InclLabel, "Directory: "), + DefaultPath = code:root_dir(), + DirPicker = wxDirPickerCtrl:new(Dialog, ?InclPicker, + [{path, DefaultPath}, + {message, "Choose Directory to Include"}, + {style,?wxDIRP_DIR_MUST_EXIST bor ?wxDIRP_USE_TEXTCTRL}]), + Box = wxListBox:new(Dialog, ?InclBox, + [{size, {200,300}}, + {style, ?wxLB_EXTENDED bor ?wxLB_HSCROLL + bor ?wxLB_NEEDED_SB}]), + AddButton = wxButton:new(Dialog, ?InclAdd, [{label, "Add"}]), + DeleteButton = wxButton:new(Dialog, ?InclDel, [{label, "Delete"}]), + DeleteAllButton = wxButton:new(Dialog, ?InclDelAll, [{label, "Delete All"}]), + Ok = wxButton:new(Dialog, ?InclOk, [{label, "OK"}]), + Cancel = wxButton:new(Dialog, ?InclCancel, [{label, "Cancel"}]), + wxButton:connect(AddButton, command_button_clicked), + wxButton:connect(DeleteButton, command_button_clicked), + wxButton:connect(DeleteAllButton, command_button_clicked), + wxButton:connect(Ok, command_button_clicked), + wxButton:connect(Cancel, command_button_clicked), + Dirs = [io_lib:format("~s", [X]) + || X <- Options#options.include_dirs], + wxListBox:set(Box, Dirs), + Layout = wxBoxSizer:new(?wxVERTICAL), + Buttons = wxBoxSizer:new(?wxHORIZONTAL), + Buttons1 = wxBoxSizer:new(?wxHORIZONTAL), + + wxSizer:add(Layout, DirLabel, [{flag, ?wxALIGN_CENTER_HORIZONTAL}]), + wxSizer:add(Layout, DirPicker, [{flag, ?wxALIGN_CENTER_HORIZONTAL}]), + wxSizer:add(Layout,AddButton, [{flag, ?wxALIGN_CENTER_HORIZONTAL bor ?wxALL}, ?Border]), + wxSizer:add(Layout,Box, [{flag, ?wxALIGN_CENTER_HORIZONTAL bor ?wxALL}, ?Border]), + wxSizer:add(Buttons, DeleteButton, ?BorderOpt), + wxSizer:add(Buttons, DeleteAllButton, ?BorderOpt), + wxSizer:add(Layout,Buttons, [{flag, ?wxALIGN_CENTER_HORIZONTAL}]), + wxSizer:add(Buttons1, Ok, ?BorderOpt), + wxSizer:add(Buttons1,Cancel, ?BorderOpt), + wxSizer:add(Layout,Buttons1,[{flag, ?wxALIGN_RIGHT bor ?wxBOTTOM}]), + + wxFrame:connect(Dialog, close_window), + wxWindow:setSizer(Dialog, Layout), + wxFrame:show(Dialog), + include_loop(Options, Dialog, Box, DirPicker, Frame). + +include_loop(Options, Win, Box, DirPicker, Frame) -> + receive + #wx{id = ?InclCancel, + event = #wxCommand{type = command_button_clicked}} -> + wxWindow:destroy(Win), + Options; + #wx{id = ?IncludeDir, event = #wxClose{type = close_window}} -> + wxWindow:destroy(Win), + Options; + #wx{event = #wxClose{type = close_window}} -> + wxWindow:destroy(Win), + wxWindow:destroy(Frame); + #wx{id = ?InclOk, + event = #wxCommand{type = command_button_clicked}} -> + wxWindow:destroy(Win), + Options; + #wx{id = ?InclAdd, + event = #wxCommand{type = command_button_clicked}} -> + Dirs = Options#options.include_dirs, + NewDirs = + case wxDirPickerCtrl:getPath(DirPicker) of + "" -> Dirs; + Add -> [Add|Dirs] + end, + NewOptions = Options#options{include_dirs = NewDirs}, + wxListBox:set(Box, NewDirs), + include_loop(NewOptions, Win, Box, DirPicker, Frame); + #wx{id = ?InclDel, + event = #wxCommand{type = command_button_clicked}} -> + NewOptions = + case wxListBox:getSelections(Box) of + {0,_} -> Options; + {_,List} -> + DelList = [wxControlWithItems:getString(Box,X) || X <- List], + NewDirs = Options#options.include_dirs -- DelList, + lists:foreach(fun (X) -> wxListBox:delete(Box, X) end, List), + Options#options{include_dirs = NewDirs} + end, + include_loop(NewOptions, Win, Box, DirPicker, Frame); + #wx{id = ?InclDelAll, + event = #wxCommand{type = command_button_clicked}} -> + wxListBox:clear(Box), + NewOptions = Options#options{include_dirs = []}, + include_loop(NewOptions, Win, Box, DirPicker, Frame) + end. + +macro_dialog(#gui_state{gui = Wx, frame = Frame, options = Options}) -> + Size = {size,{300,480}}, + Size1 = {size,{120,30}}, + Dialog = wxFrame:new(Wx, ?MacroDir, "Macro Definitions",[Size]), + MacroLabel = wxStaticText:new(Dialog, ?MacroLabel, "Macro"), + TermLabel = wxStaticText:new(Dialog, ?TermLabel, "Term"), + MacroText = wxTextCtrl:new(Dialog, ?MacroText, [Size1]), + TermText = wxTextCtrl:new(Dialog, ?TermText, [Size1]), + Box = wxListBox:new(Dialog, ?MacroBox, + [{size, {250,300}}, + {style, ?wxLB_EXTENDED bor ?wxLB_HSCROLL + bor ?wxLB_NEEDED_SB}]), + + AddButton = wxButton:new(Dialog, ?MacroAdd, [{label, "Add"}]), + DeleteButton = wxButton:new(Dialog, ?MacroDel, [{label, "Delete"}]), + DeleteAllButton = wxButton:new(Dialog, ?MacroDelAll, [{label, "Delete All"}]), + Ok = wxButton:new(Dialog, ?MacroOk, [{label, "OK"}]), + Cancel = wxButton:new(Dialog, ?MacroCancel, [{label, "Cancel"}]), + wxButton:connect(AddButton, command_button_clicked), + wxButton:connect(DeleteButton, command_button_clicked), + wxButton:connect(DeleteAllButton, command_button_clicked), + wxButton:connect(Ok, command_button_clicked), + wxButton:connect(Cancel, command_button_clicked), + + Macros = [io_lib:format("~p = ~p", [X, Y]) + || {X,Y} <- Options#options.defines], + + wxListBox:set(Box, Macros), + Layout = wxBoxSizer:new(?wxVERTICAL), + Item = wxBoxSizer:new(?wxHORIZONTAL), + MacroItem = wxBoxSizer:new(?wxVERTICAL), + TermItem = wxBoxSizer:new(?wxVERTICAL), + Buttons = wxBoxSizer:new(?wxHORIZONTAL), + Buttons1 = wxBoxSizer:new(?wxHORIZONTAL), + + wxSizer:add(MacroItem, MacroLabel, ?BorderOpt), + wxSizer:add(MacroItem, MacroText, ?BorderOpt), + wxSizer:add(TermItem, TermLabel, ?BorderOpt), + wxSizer:add(TermItem, TermText, ?BorderOpt), + wxSizer:add(Item, MacroItem), + wxSizer:add(Item, TermItem), + wxSizer:add(Layout, Item, [{flag, ?wxALIGN_CENTER_HORIZONTAL}]), + wxSizer:add(Layout, AddButton, [{flag, ?wxALIGN_CENTER_HORIZONTAL bor ?wxALL}, ?Border]), + wxSizer:add(Layout, Box, [{flag, ?wxALIGN_CENTER_HORIZONTAL bor ?wxALL}, ?Border]), + wxSizer:add(Buttons, DeleteButton, ?BorderOpt), + wxSizer:add(Buttons, DeleteAllButton, ?BorderOpt), + wxSizer:add(Layout, Buttons, [{flag, ?wxALIGN_CENTER_HORIZONTAL}]), + wxSizer:add(Buttons1, Ok, ?BorderOpt), + wxSizer:add(Buttons1, Cancel, ?BorderOpt), + wxSizer:add(Layout, Buttons1, [{flag, ?wxALIGN_RIGHT bor ?wxBOTTOM}]), + + wxFrame:connect(Dialog, close_window), + wxWindow:setSizer(Dialog, Layout), + wxFrame:show(Dialog), + macro_loop(Options, Dialog, Box, MacroText, TermText, Frame). + +macro_loop(Options, Win, Box, MacroText, TermText, Frame) -> + receive + #wx{id = ?MacroCancel, + event = #wxCommand{type = command_button_clicked}} -> + wxWindow:destroy(Win), + Options; + #wx{id = ?MacroDir, event = #wxClose{type = close_window}} -> + wxWindow:destroy(Win), + Options; + #wx{event = #wxClose{type = close_window}} -> + wxWindow:destroy(Win), + wxWindow:destroy(Frame); + #wx{id = ?MacroOk, + event = #wxCommand{type = command_button_clicked}} -> + wxWindow:destroy(Win), + Options; + #wx{id = ?MacroAdd, + event = #wxCommand{type = command_button_clicked}} -> + Defines = Options#options.defines, + NewDefines = + case wxTextCtrl:getValue(MacroText) of + "" -> Defines; + Macro -> + case wxTextCtrl:getValue(TermText) of + "" -> + orddict:store(list_to_atom(Macro), true, Defines); + String -> + orddict:store(list_to_atom(Macro), String, Defines) + end + end, + NewOptions = Options#options{defines = NewDefines}, + NewEntries = [io_lib:format("~p = ~p", [X, Y]) || {X, Y} <- NewDefines], + wxListBox:set(Box, NewEntries), + macro_loop(NewOptions, Win, Box, MacroText, TermText, Frame); + #wx{id = ?MacroDel, + event = #wxCommand{type = command_button_clicked}} -> + NewOptions = + case wxListBox:getSelections(Box) of + {0, _} -> Options; + {_, List} -> + Fun = + fun(X) -> + Val = wxControlWithItems:getString(Box,X), + [MacroName|_] = re:split(Val, " ", [{return, list}]), + list_to_atom(MacroName) + end, + Delete = [Fun(X) || X <- List], + lists:foreach(fun (X) -> wxListBox:delete(Box, X) end, 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(NewOptions, Win, Box, MacroText, TermText, Frame); + #wx{id = ?MacroDelAll, + event = #wxCommand{type = command_button_clicked}} -> + wxListBox:clear(Box), + NewOptions = Options#options{defines = []}, + macro_loop(NewOptions, Win, Box, MacroText, TermText, Frame) + end. + +handle_help(State, Title, Txt) -> + FileName = filename:join([code:lib_dir(dialyzer), "doc", Txt]), + case file:open(FileName, [read]) of + {error, Reason} -> + error_sms(State, + io_lib:format("Could not find doc/~s file!\n\n ~p", + [Txt, Reason])); + {ok, _Handle} -> + case file:read_file(FileName) of + {error, Reason} -> + error_sms(State, + io_lib:format("Could not read doc/~s file!\n\n ~p", + [Txt, Reason])); + {ok, Binary} -> + Contents = binary_to_list(Binary), + free_editor(State, Title, Contents) + end + end. + +add_warnings(#gui_state{warnings_box = WarnBox, + rawWarnings = RawWarns} = State, Warnings) -> + NewRawWarns = RawWarns ++ Warnings, + WarnList = [dialyzer:format_warning(W) -- "\n" || W <- NewRawWarns], + wxListBox:set(WarnBox, WarnList), + State#gui_state{rawWarnings = NewRawWarns}. + +handle_explanation(#gui_state{rawWarnings = RawWarns, + warnings_box = WarnBox, + expl_pid = ExplPid} = State) -> + case wxListBox:isEmpty(WarnBox) of + true -> error_sms(State, "\nThere are no warnings.\nRun the dialyzer first."); + false -> + case wxListBox:getSelections(WarnBox)of + {0, []} -> + error_sms(State,"\nYou must choose a warning to be explained\n"); + {_, [WarnNumber]} -> + Warn = lists:nth(WarnNumber+1,RawWarns), + Self = self(), + ExplPid ! {Self, warning, Warn}, + explanation_loop(State) + end + end. + +explanation_loop(#gui_state{expl_pid = ExplPid} = State) -> + receive + {ExplPid, explanation, Explanation} -> + show_explanation(State, Explanation); + _ -> io:format("Unknown message\n"), + explanation_loop(State) + end. + +show_explanation(#gui_state{gui = Wx} = State, Explanation) -> + case Explanation of + none -> + output_sms(State, ?DIALYZER_MESSAGE_TITLE, + "There is not any explanation for this error!\n", info); + Expl -> + ExplString = format_explanation(Expl), + Size = {size,{700, 300}}, + Win = wxFrame:new(Wx, ?ExplWin, "Dialyzer Explanation", [{size,{740, 350}}]), + + Editor = wxTextCtrl:new(Win, ?ExplText, + [Size, + {style, ?wxTE_MULTILINE + bor ?wxTE_READONLY bor ?wxVSCROLL bor ?wxEXPAND}]), + wxTextCtrl:appendText(Editor, ExplString), + wxFrame:connect(Win, close_window), + ExplButton = wxButton:new(Win, ?ExplButton, [{label, "Further Explain"}]), + wxButton:connect(ExplButton, command_button_clicked), + Ok = wxButton:new(Win, ?ExplOk, [{label, "OK"}]), + wxButton:connect(Ok, command_button_clicked), + Layout = wxBoxSizer:new(?wxVERTICAL), + Buttons = wxBoxSizer:new(?wxHORIZONTAL), + wxSizer:add(Buttons, ExplButton, ?BorderOpt), + wxSizer:add(Buttons, Ok, ?BorderOpt), + wxSizer:add(Layout, Editor,[{flag, ?wxALIGN_CENTER_HORIZONTAL bor ?wxALL}, ?Border]), + wxSizer:add(Layout, Buttons,[{flag, ?wxALIGN_CENTER_HORIZONTAL}]), + wxWindow:setSizer(Win, Layout), + wxWindow:show(Win), + show_explanation_loop(State#gui_state{explanation_box = Editor}, Win, Explanation) + end. + +show_explanation_loop(#gui_state{frame = Frame, expl_pid = ExplPid} = State, Win, Explanation) -> + receive + {ExplPid, none, _} -> + output_sms(State, ?DIALYZER_MESSAGE_TITLE, + "There is not any other explanation for this error!\n", info), + show_explanation_loop(State, Win, Explanation); + {ExplPid, further, NewExplanation} -> + update_explanation(State, NewExplanation), + show_explanation_loop(State, Win, NewExplanation); + #wx{id = ?ExplButton, event = #wxCommand{type = command_button_clicked}} -> + ExplPid ! {self(), further, Explanation}, + show_explanation_loop(State, Win, Explanation); + #wx{id = ?ExplOk, event = #wxCommand{type = command_button_clicked}} -> + wxWindow:destroy(Win); + #wx{id = ?ExplWin, event = #wxClose{type = close_window}} -> + wxWindow:destroy(Win); + #wx{event = #wxClose{type = close_window}} -> + wxWindow:destroy(Frame) + end. + +update_explanation(#gui_state{explanation_box = Box}, Explanation) -> + ExplString = format_explanation(Explanation), + wxTextCtrl:appendText(Box, "\n --------------------------- \n"), + wxTextCtrl:appendText(Box, ExplString). + +format_explanation({function_return, {M, F, A}, NewList}) -> + io_lib:format("The function ~p: ~p/~p returns ~p\n", + [M, F, A, erl_types:t_to_string(NewList)]); +format_explanation(Explanation) -> + io_lib:format("~p\n", [Explanation]). |