1%% -*- erlang-indent-level: 2 -*-
2%%
3%% Licensed under the Apache License, Version 2.0 (the "License");
4%% you may not use this file except in compliance with the License.
5%% You may obtain a copy of the License at
6%%
7%%     http://www.apache.org/licenses/LICENSE-2.0
8%%
9%% Unless required by applicable law or agreed to in writing, software
10%% distributed under the License is distributed on an "AS IS" BASIS,
11%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12%% See the License for the specific language governing permissions and
13%% limitations under the License.
14
15%%%-----------------------------------------------------------------------
16%%% File    : dialyzer_gui_wx.erl
17%%% Authors : Elli Fragkaki <ellifrag@gmail.com>
18%%% Description : The wx-based graphical user interface of dialyzer.
19%%%
20%%% Created : 07 Oct 2009 by Elli Fragkaki <ellifrag@gmail.com>
21%%%-----------------------------------------------------------------------
22
23-module(dialyzer_gui_wx).
24
25-export([start/1]).
26
27-include("dialyzer.hrl").
28-include("dialyzer_gui_wx.hrl").
29
30%%------------------------------------------------------------------------
31
32-define(DIALYZER_ERROR_TITLE,   "Dialyzer Error").
33-define(DIALYZER_MESSAGE_TITLE, "Dialyzer Message").
34
35%%------------------------------------------------------------------------
36
37-record(menu, {file                   :: wx:wx_object(),
38	       warnings               :: wx:wx_object(),
39	       plt                    :: wx:wx_object(),
40	       options                :: wx:wx_object(),
41	       help                   :: wx:wx_object()}).
42-type menu() :: #menu{}.
43
44-record(gui_state, {add               :: wx:wx_object(),
45		    add_dir           :: wx:wx_object(),
46		    add_rec           :: wx:wx_object(),
47		    chosen_box        :: wx:wx_object(),
48		    del_file          :: wx:wx_object(),
49		    doc_plt           :: dialyzer_plt:plt(),
50		    clear_chosen      :: wx:wx_object(),
51		    clear_log         :: wx:wx_object(),
52		    explain_warn      :: wx:wx_object(),
53		    clear_warn        :: wx:wx_object(),
54		    init_plt          :: dialyzer_plt:plt(),
55		    dir_entry         :: wx:wx_object(),
56		    file_box          :: wx:wx_object(),
57		    files_to_analyze  :: ordsets:ordset(string()),
58		    gui               :: wx:wx_object(),
59		    log               :: wx:wx_object(),
60		    menu              :: menu(),
61		    mode              :: wx:wx_object(),
62		    options           :: #options{},
63		    run               :: wx:wx_object(),
64		    stop              :: wx:wx_object(),
65		    frame             :: wx:wx_object(),
66		    warnings_box      :: wx:wx_object(),
67		    explanation_box   :: wx:wx_object() | 'undefined',
68		    wantedWarnings    :: list(),
69		    rawWarnings       :: list(),
70		    backend_pid       :: pid() | 'undefined',
71		    expl_pid          :: pid() | 'undefined'}).
72
73%%------------------------------------------------------------------------
74
75-spec start(#options{}) -> ?RET_NOTHING_SUSPICIOUS.
76
77start(DialyzerOptions) ->
78  process_flag(trap_exit, true),
79  Wx = wx:new(),
80  State = wx:batch(fun() -> create_window(Wx, DialyzerOptions) end),
81  gui_loop(State).
82
83create_window(Wx, #options{init_plts = InitPltFiles} = DialyzerOptions) ->
84  {ok, Host} = inet:gethostname(),
85
86  %%---------- initializing frame ---------
87  Frame = wxFrame:new(Wx, -1,  "Dialyzer " ++ ?VSN ++ " @ " ++ Host),
88  wxFrame:connect(Frame, close_window),
89  FileMenu = createFileMenu(),
90  WarningsMenu = createWarningsMenu(),
91  PltMenu = createPltMenu(),
92  OptionsMenu = createOptionsMenu(),
93  HelpMenu = createHelpMenu(),
94
95  MenuBar = wxMenuBar:new(),
96  wxMenuBar:append(MenuBar, FileMenu,     "File"),
97  wxMenuBar:append(MenuBar, WarningsMenu, "Warnings"),
98  wxMenuBar:append(MenuBar, PltMenu,      "Plt"),
99  wxMenuBar:append(MenuBar, OptionsMenu,  "Options"),
100  wxMenuBar:append(MenuBar, HelpMenu,     "Help"),
101  wxFrame:setMenuBar(Frame, MenuBar),
102  ok = wxFrame:connect(Frame, command_menu_selected),
103
104  %%----------- Set Labels -------------
105  Lab1 = wxStaticText:new(Frame, ?LABEL1, "Directories or modules to analyze"),
106  OptionsLabel = wxStaticText:new(Frame, ?LABEL2, "Analysis Options"),
107  LogLabel = wxStaticText:new(Frame, ?LABEL3, "Log"),
108  FileLabel = wxStaticText:new(Frame, ?LABEL4, "File: "),
109  DirLabel = wxStaticText:new(Frame, ?LABEL5, "Dir: "),
110  WarningsLabel = wxStaticText:new(Frame, ?LABEL6, "Warnings"),
111
112  %%---------- Set TextBoxes -----------
113  ChosenBox = wxListBox:new(Frame, ?ChosenBox,
114			[{size, {250,200}},
115			 {style, ?wxLB_EXTENDED bor ?wxLB_HSCROLL
116			  bor ?wxLB_NEEDED_SB}]),
117  LogBox = wxTextCtrl:new(Frame, ?LogBox,
118			[{size, {530,200}},
119			 {style, ?wxTE_MULTILINE
120			  bor ?wxTE_READONLY bor ?wxHSCROLL}]),
121  DefaultPath = code:root_dir(),
122
123  FilePicker = wxFilePickerCtrl:new(Frame, ?FilePicker,
124				   [{path, DefaultPath},
125				    {message, "Choose File to Analyse"},
126				    {style,?wxFLP_FILE_MUST_EXIST bor ?wxFLP_USE_TEXTCTRL}]),
127  wxPickerBase:setTextCtrlProportion(FilePicker,3),
128  wxPickerBase:setPickerCtrlProportion(FilePicker,2),
129  DirPicker = wxDirPickerCtrl:new(Frame, ?DirPicker,
130				   [{path, DefaultPath},
131				    {message, "Choose Directory to Analyze"},
132				    {style,?wxDIRP_DIR_MUST_EXIST bor ?wxDIRP_USE_TEXTCTRL}]),
133  WarningsBox = wxListBox:new(Frame, ?WarningsBox,
134			[{size, {700,200}},
135			 {style,  ?wxLB_HSCROLL
136			  bor ?wxLB_NEEDED_SB}]),
137
138  %%--------- Set Buttons --------------
139  DeleteButton = wxButton:new(Frame, ?Del_Button, [{label, "Delete"}]),
140  DeleteAllButton = wxButton:new(Frame, ?DelAll_Button, [{label, "Delete All"}]),
141  FileType = wxRadioBox:new(Frame, ?RADIOBOX, " File Type: " , {1,1}, {150,90},
142			    [["BeamFiles"],["SourceFiles"]]),
143  ClearLogButton = wxButton:new(Frame, ?ClearLog_Button, [{label, "Clear Log"}]),
144  AddButton = wxButton:new(Frame, ?Add_Button, [{label, "Add"}]),
145  AddDirButton = wxButton:new(Frame, ?AddDir_Button, [{label, "Add Dir"}]),
146  AddRecButton = wxButton:new(Frame, ?AddRec_Button, [{label, "Add Recursively"}]),
147  ExplainWarnButton = wxButton:new(Frame, ?ExplWarn_Button, [{label, "Explain Warning"}]),
148  ClearWarningsButton = wxButton:new(Frame, ?ClearWarn_Button, [{label, "Clear Warnings"}]),
149  RunButton = wxButton:new(Frame, ?Run_Button, [{label, "Run"}]),
150  StopButton = wxButton:new(Frame, ?Stop_Button, [{label, "Stop"}]),
151  wxWindow:disable(StopButton),
152  %%--------- Connect Buttons -----------
153  wxButton:connect(DeleteButton, command_button_clicked),
154  wxButton:connect(DeleteAllButton, command_button_clicked),
155  wxButton:connect(ClearLogButton, command_button_clicked),
156  wxButton:connect(AddButton, command_button_clicked),
157  wxButton:connect(AddDirButton, command_button_clicked),
158  wxButton:connect(AddRecButton, command_button_clicked),
159  wxButton:connect(ExplainWarnButton, command_button_clicked),
160  wxButton:connect(ClearWarningsButton, command_button_clicked),
161  wxButton:connect(RunButton, command_button_clicked),
162  wxButton:connect(StopButton, command_button_clicked),
163
164  %%------------Set Layout ------------
165  All = wxBoxSizer:new(?wxVERTICAL),
166  Top = wxBoxSizer:new(?wxHORIZONTAL),
167  Left = wxBoxSizer:new(?wxVERTICAL),
168  Right = wxBoxSizer:new(?wxVERTICAL),
169  RightUp = wxBoxSizer:new(?wxHORIZONTAL),
170
171  Opts = [{flag,?wxEXPAND bor ?wxALL}, {proportion,1}, {border, 1}],
172  Opts3 = [{flag,?wxEXPAND bor ?wxALL}, {proportion,3}, {border, 1}],
173  Center = [{flag, ?wxALIGN_CENTER_HORIZONTAL}],
174
175  ChooseItem = wxBoxSizer:new(?wxVERTICAL),
176  FileTypeItem = wxBoxSizer:new(?wxVERTICAL),
177  LogItem = wxBoxSizer:new(?wxVERTICAL),
178  FileDirItem = wxBoxSizer:new(?wxVERTICAL),
179  FileItem = wxBoxSizer:new(?wxHORIZONTAL),
180  DirItem = wxBoxSizer:new(?wxHORIZONTAL),
181  AddDirButtons = wxBoxSizer:new(?wxHORIZONTAL),
182  WarningsItem = wxBoxSizer:new(?wxVERTICAL),
183  ChooseButtons = wxBoxSizer:new(?wxHORIZONTAL),
184  WarnButtons = wxBoxSizer:new(?wxHORIZONTAL),
185  RunButtons = wxBoxSizer:new(?wxHORIZONTAL),
186  Buttons = wxFlexGridSizer:new(3),
187
188  _ = wxSizer:add(ChooseButtons, DeleteButton, ?BorderOpt),
189  _ = wxSizer:add(ChooseButtons, DeleteAllButton, ?BorderOpt),
190  _ = wxSizer:add(ChooseItem, Lab1, Center),
191  _ = wxSizer:add(ChooseItem, ChosenBox, Opts),
192  _ = wxSizer:add(ChooseItem, ChooseButtons, ?BorderOpt),
193  _ = wxSizer:add(FileTypeItem, OptionsLabel),
194  _ = wxSizer:add(FileTypeItem, FileType, [{border, 5}, {flag, ?wxALL}]),
195  _ = wxSizer:add(LogItem, LogLabel, Center),
196  _ = wxSizer:add(LogItem, LogBox, Opts3),
197  _ = wxSizer:add(LogItem, ClearLogButton, ?BorderOpt),
198  _ = wxSizer:add(FileItem, FileLabel),
199  _ = wxSizer:add(FileItem, FilePicker),
200  _ = wxSizer:add(DirItem, DirLabel),
201  _ = wxSizer:add(DirItem, DirPicker),
202  _ = wxSizer:add(AddDirButtons, AddDirButton, ?BorderOpt),
203  _ = wxSizer:add(AddDirButtons, AddRecButton, ?BorderOpt),
204  _ = wxSizer:add(FileDirItem, FileItem),
205  _ = wxSizer:add(FileDirItem, AddButton, ?BorderOpt),
206  _ = wxSizer:add(FileDirItem, DirItem, ?BorderOpt),
207  _ = wxSizer:add(FileDirItem, AddDirButtons, ?BorderOpt),
208  _ = wxSizer:add(WarnButtons, ExplainWarnButton, ?BorderOpt),
209  _ = wxSizer:add(WarnButtons, ClearWarningsButton, ?BorderOpt),
210  _ = wxSizer:add(RunButtons, RunButton, ?BorderOpt),
211  _ = wxSizer:add(RunButtons, StopButton, ?BorderOpt),
212  _ = wxSizer:add(Buttons, WarnButtons),
213  _ = wxSizer:add(Buttons, wxStaticText:new(Frame, ?LABEL7, ""),
214		  [{flag, ?wxEXPAND}]),
215  _ = wxSizer:add(Buttons, RunButtons),
216  _ = wxFlexGridSizer:addGrowableCol(Buttons, 1),
217  _ = wxSizer:add(WarningsItem, WarningsLabel, Center),
218  _ = wxSizer:add(WarningsItem, WarningsBox, Opts3),
219  _ = wxSizer:add(WarningsItem, Buttons,
220		  [{flag, ?wxEXPAND bor ?wxALL}, ?Border]),
221  _ = wxSizer:add(Left, ChooseItem, Opts),
222  _ = wxSizer:add(Left, FileDirItem,
223		  [{proportion, 1}, {border, 60}, {flag, ?wxTOP}]),
224  _ = wxSizer:add(RightUp, FileTypeItem, ?BorderOpt),
225  _ = wxSizer:add(RightUp, LogItem, Opts3),
226  _ = wxSizer:add(Right, RightUp, Opts3),
227  _ = wxSizer:add(Right, WarningsItem, Opts3),
228  _ = wxSizer:add(Top, Left, Opts),
229  _ = wxSizer:add(Top, Right, Opts3),
230
231  _ = wxSizer:add(All, Top, Opts),
232  wxWindow:setSizer(Frame, All),
233  wxWindow:setSizeHints(Frame, {1150,600}),
234  wxWindow:show(Frame),
235
236  Warnings = [{?WARN_RETURN_NO_RETURN, ?menuID_WARN_NO_RETURN_FUN},
237	      {?WARN_RETURN_ONLY_EXIT, ?menuID_WARN_ERROR_HANDLING_FUN},
238	      {?WARN_NOT_CALLED, ?menuID_WARN_UNUSED_FUN},
239	      {?WARN_NON_PROPER_LIST, ?menuID_WARN_LIST_CONSTR},
240	      {?WARN_FUN_APP, ?menuID_WARN_BAD_FUN},
241	      {?WARN_MATCHING, ?menuID_WARN_MATCH_FAILURES},
242	      {?WARN_OPAQUE, ?menuID_WARN_OPAQUE},
243	      {?WARN_FAILING_CALL, ?menuID_WARN_FAIL_FUN_CALLS},
244	      {?WARN_CALLGRAPH, ?menuID_WARN_UNEXPORTED_FUN},
245              {?WARN_RACE_CONDITION, ?menuID_WARN_RACE_CONDITIONS},
246	      %% For contracts.
247	      {?WARN_CONTRACT_TYPES,?menuID_WARN_WRONG_CONTRACTS},
248	      {?WARN_CONTRACT_SYNTAX, ?menuID_WARN_CONTRACT_SYNTAX}
249	     ],
250  Menu = #menu{file = FileMenu,
251	       warnings = WarningsMenu,
252	       plt = PltMenu,
253	       options =OptionsMenu,
254	       help = HelpMenu},
255
256  InitPlt =
257    case InitPltFiles of
258      [] -> dialyzer_plt:new();
259      _ ->
260        Plts = [dialyzer_plt:from_file(F) || F <- InitPltFiles],
261        dialyzer_plt:merge_plts_or_report_conflicts(InitPltFiles, Plts)
262    end,
263
264  #gui_state{add = AddButton,
265	     add_dir = AddDirButton,
266	     add_rec = AddRecButton,
267	     chosen_box = ChosenBox,
268	     clear_chosen = DeleteAllButton,
269	     clear_log = ClearLogButton,
270	     explain_warn = ExplainWarnButton,
271	     clear_warn = ClearWarningsButton,
272	     del_file = DeleteButton,
273	     doc_plt = dialyzer_plt:new(),
274	     dir_entry = DirPicker,
275	     file_box = FilePicker,
276	     files_to_analyze = ordsets:new(),
277	     gui = Wx,
278	     init_plt = InitPlt,
279	     log = LogBox,
280	     menu = Menu,
281	     mode = FileType,
282	     options = DialyzerOptions,
283	     run = RunButton,
284	     stop = StopButton,
285	     frame = Frame,
286	     warnings_box = WarningsBox,
287	     wantedWarnings = Warnings,
288	     rawWarnings = []}.
289
290createFileMenu() ->
291  FileMenu = wxMenu:new(),
292  _ = wxMenu:append(FileMenu, wxMenuItem:new([{id, ?menuID_FILE_SAVE_WARNINGS},
293					      {text, "Save &Warnings"}])),
294  _ = wxMenu:append(FileMenu, wxMenuItem:new([{id,   ?menuID_FILE_SAVE_LOG},
295					      {text, "Save &Log"}])),
296  _ = wxMenu:append(FileMenu, wxMenuItem:new([{id,   ?menuID_FILE_QUIT},
297					      {text, "E&xit\tAlt-X"}])),
298  FileMenu.
299
300createWarningsMenu() ->
301  WarningsMenu = wxMenu:new(),
302  addCheckedItem(WarningsMenu, ?menuID_WARN_MATCH_FAILURES, "Match failures"),
303  addCheckedItem(WarningsMenu, ?menuID_WARN_FAIL_FUN_CALLS,
304		 "Failing function calls"),
305  addCheckedItem(WarningsMenu, ?menuID_WARN_BAD_FUN, "Bad fun applications"),
306  addCheckedItem(WarningsMenu, ?menuID_WARN_OPAQUE, "Opacity violations"),
307  addCheckedItem(WarningsMenu, ?menuID_WARN_LIST_CONSTR,
308		 "Improper list constructions"),
309  addCheckedItem(WarningsMenu, ?menuID_WARN_UNUSED_FUN, "Unused functions"),
310  _ = wxMenu:appendCheckItem(WarningsMenu, ?menuID_WARN_ERROR_HANDLING_FUN,
311			     "Error handling functions"),
312  addCheckedItem(WarningsMenu, ?menuID_WARN_NO_RETURN_FUN,
313		 "Functions of no return"),
314  addCheckedItem(WarningsMenu, ?menuID_WARN_UNEXPORTED_FUN,
315		 "Call to unexported function"),
316  _ = wxMenu:appendCheckItem(WarningsMenu, ?menuID_WARN_RACE_CONDITIONS,
317			     "Possible race conditions"),
318  addCheckedItem(WarningsMenu, ?menuID_WARN_WRONG_CONTRACTS, "Wrong contracts"),
319  addCheckedItem(WarningsMenu, ?menuID_WARN_CONTRACT_SYNTAX,
320		 "Wrong contract syntax"),
321  WarningsMenu.
322
323addCheckedItem(Menu, ItemId, Str) ->
324  _ = wxMenu:appendCheckItem(Menu, ItemId, Str),
325  wxMenu:check(Menu, ItemId, true).
326
327createPltMenu() ->
328  PltMenu = wxMenu:new(),
329  _ = wxMenu:appendCheckItem(PltMenu, ?menuID_PLT_INIT_EMPTY,
330			     "Init with empty PLT"),
331  _ = wxMenu:append(PltMenu, wxMenuItem:new([{id, ?menuID_PLT_SHOW_CONTENTS},
332					     {text, "Show contents"}])),
333  _ = wxMenu:append(PltMenu, wxMenuItem:new([{id, ?menuID_PLT_SEARCH_CONTENTS},
334					     {text, "Search contents"}])),
335  PltMenu.
336
337createOptionsMenu() ->
338  OptsMenu  = wxMenu:new(),
339  _ = wxMenu:append(OptsMenu, wxMenuItem:new([{id, ?menuID_OPTIONS_MACRO},
340					      {text, "Manage Macro Definitions"}])),
341  _ = wxMenu:append(OptsMenu, wxMenuItem:new([{id, ?menuID_OPTIONS_INCLUDE_DIR},
342					      {text, "Manage Include Directories"}])),
343  OptsMenu.
344
345createHelpMenu() ->
346  HelpMenu = wxMenu:new(),
347  _ = wxMenu:append(HelpMenu, wxMenuItem:new([{id, ?menuID_HELP_MANUAL},
348					      {text, "Manual"}])),
349  _ = wxMenu:append(HelpMenu, wxMenuItem:new([{id, ?menuID_HELP_WARNING_OPTIONS},
350					      {text, "Warning Options"}])),
351  _ = wxMenu:append(HelpMenu, wxMenuItem:new([{id, ?menuID_HELP_ABOUT},
352					      {text, "About"}])),
353  HelpMenu.
354
355%% ----------------------------------------------------------------
356%%
357%%  Main GUI Loop
358%%
359
360-spec gui_loop(#gui_state{}) -> ?RET_NOTHING_SUSPICIOUS.
361
362gui_loop(#gui_state{backend_pid = BackendPid, doc_plt = DocPlt,
363		    log = Log, frame = Frame,
364		    warnings_box = WarningsBox} = State) ->
365  receive
366    #wx{event = #wxClose{}} ->
367      %% io:format("~p Closing window ~n", [self()]),
368      ok = wxFrame:setStatusText(Frame, "Closing...",[]),
369      wxWindow:destroy(Frame),
370      ?RET_NOTHING_SUSPICIOUS;
371    %% ----- Menu -----
372    #wx{id = ?menuID_FILE_SAVE_LOG, obj = Frame,
373	event = #wxCommand{type = command_menu_selected}} ->
374      save_file(State, log),
375      gui_loop(State);
376    #wx{id=?menuID_FILE_SAVE_WARNINGS, obj=Frame,
377	event=#wxCommand{type=command_menu_selected}} ->
378      save_file(State, warnings),
379      gui_loop(State);
380    #wx{id=?menuID_FILE_QUIT, obj=Frame,
381	event=#wxCommand{type=command_menu_selected}} ->
382      case maybe_quit(State) of
383	true -> ?RET_NOTHING_SUSPICIOUS;
384	false -> gui_loop(State)
385      end;
386    #wx{id=?menuID_PLT_SHOW_CONTENTS, obj=Frame,
387	event=#wxCommand{type=command_menu_selected}} ->
388      show_doc_plt(State),
389      gui_loop(State);
390    #wx{id=?menuID_PLT_SEARCH_CONTENTS, obj=Frame,
391	event=#wxCommand{type=command_menu_selected}} ->
392      case dialyzer_plt:get_specs(DocPlt) of
393	"" -> error_sms(State, "No analysis has been made yet!\n");
394	_ -> search_doc_plt(State)
395      end,
396      gui_loop(State);
397    #wx{id=?menuID_OPTIONS_INCLUDE_DIR, obj=Frame,
398	event=#wxCommand{type=command_menu_selected}} ->
399      NewOptions = include_dialog(State),
400      NewState = State#gui_state{options = NewOptions},
401      gui_loop(NewState);
402    #wx{id=?menuID_OPTIONS_MACRO, obj=Frame,
403	event=#wxCommand{type=command_menu_selected}} ->
404      NewOptions = macro_dialog(State),
405      NewState = State#gui_state{options = NewOptions},
406      gui_loop(NewState);
407    #wx{id=?menuID_HELP_MANUAL, obj=Frame,
408	event=#wxCommand{type=command_menu_selected}} ->
409      handle_help(State, "Dialyzer Manual", "manual.txt"),
410      gui_loop(State);
411    #wx{id=?menuID_HELP_WARNING_OPTIONS, obj=Frame,
412	event=#wxCommand{type=command_menu_selected}} ->
413      handle_help(State, "Dialyzer Warnings", "warnings.txt"),
414      gui_loop(State);
415    #wx{id=?menuID_HELP_ABOUT, obj=Frame,
416	event=#wxCommand{type=command_menu_selected}} ->
417      Message = "	       This is DIALYZER version "  ++ ?VSN ++  " \n"++
418	"DIALYZER is a DIscrepancy AnaLYZer for ERlang programs.\n\n"++
419	"     Copyright (C) Tobias Lindahl <tobiasl@it.uu.se>\n"++
420	"                   Kostis Sagonas <kostis@it.uu.se>\n\n",
421      output_sms(State, "About Dialyzer", Message, info),
422      gui_loop(State);
423    %% ------ Buttons ---------
424    #wx{id=?Add_Button,
425	event=#wxCommand{type=command_button_clicked}} ->
426      State1 = handle_add_files(State),
427      gui_loop(State1);
428    #wx{id=?AddDir_Button,
429	event=#wxCommand{type=command_button_clicked}} ->
430      State1 = handle_add_dir(State),
431      gui_loop(State1);
432    #wx{id=?AddRec_Button,
433	event=#wxCommand{type=command_button_clicked}} ->
434      State1 = handle_add_rec(State),
435      gui_loop(State1);
436    #wx{id=?Del_Button,
437	event=#wxCommand{type=command_button_clicked}} ->
438      State1 = handle_file_delete(State),
439      gui_loop(State1);
440    #wx{id=?DelAll_Button,
441	event=#wxCommand{type=command_button_clicked}} ->
442      State1 = handle_file_delete_all(State),
443      gui_loop(State1);
444    #wx{id=?ClearLog_Button,
445	event=#wxCommand{type=command_button_clicked}} ->
446      wxTextCtrl:clear(State#gui_state.log),
447      gui_loop(State);
448    #wx{id=?ExplWarn_Button,
449	event=#wxCommand{type=command_button_clicked}} ->
450      handle_explanation(State),
451      gui_loop(State);
452    #wx{id=?ClearWarn_Button,
453	event=#wxCommand{type=command_button_clicked}} ->
454      wxListBox:clear(WarningsBox),
455      NewState = State#gui_state{rawWarnings = []},
456      gui_loop(NewState);
457    #wx{id=?Run_Button,
458	event=#wxCommand{type=command_button_clicked}} ->
459      NewState = start_analysis(State),
460      gui_loop(NewState);
461    #wx{id=?Stop_Button,
462	event=#wxCommand{type=command_button_clicked}} ->
463      BackendPid ! {self(), stop},
464      config_gui_stop(State),
465      update_editor(Log, "\n***** Analysis stopped ****\n"),
466      gui_loop(State);
467    %% ----- Analysis -----
468    {BackendPid, ext_calls, ExtCalls} ->
469      ExtCalls1 = lists:usort([MFA || {MFA, _FileLocation} <- ExtCalls]),
470      Map = fun({M,F,A}) -> io_lib:format("\t~tp:~tp/~p",[M,F,A]) end,
471      ExtCallString = lists:join("\n", lists:map(Map, ExtCalls1)),
472      Msg = io_lib:format("The following functions are called "
473			  "but type information about them is not available.\n"
474			  "The analysis might get more precise by including "
475			  "the modules containing these functions:\n\n~ts\n",
476			  [ExtCallString]),
477      free_editor(State,"Analysis Done",  Msg),
478      gui_loop(State);
479    {BackendPid, ext_types, ExtTypes} ->
480      ExtTypes1 = lists:usort([MFA || {MFA, _FileLocation} <- ExtTypes]),
481      Map = fun({M,F,A}) -> io_lib:format("\t~tp:~tp/~p",[M,F,A]) end,
482      ExtTypeString = lists:join("\n", lists:map(Map, ExtTypes1)),
483      Msg = io_lib:format("The following remote types are being used "
484			  "but information about them is not available.\n"
485			  "The analysis might get more precise by including "
486			  "the modules containing these types and making sure "
487			  "that they are exported:\n\n~ts\n",
488                          [ExtTypeString]),
489      free_editor(State, "Analysis done", Msg),
490      gui_loop(State);
491    {BackendPid, log, LogMsg} ->
492      update_editor(Log, LogMsg),
493      gui_loop(State);
494    {BackendPid, warnings, Warns} ->
495      SortedWarns = lists:keysort(2, Warns),  %% Sort on file/location
496      NewState = add_warnings(State, SortedWarns),
497      gui_loop(NewState);
498    {BackendPid, cserver, CServer, Plt} ->
499      Self = self(),
500      Fun =
501	fun() ->
502	    dialyzer_explanation:expl_loop(Self, CServer, Plt)
503	end,
504      ExplanationPid = spawn_link(Fun),
505      gui_loop(State#gui_state{expl_pid = ExplanationPid});
506    {BackendPid, done, _NewPlt, NewDocPlt} ->
507      message(State, "Analysis done"),
508      config_gui_stop(State),
509      dialyzer_plt:delete(State#gui_state.doc_plt),
510      gui_loop(State#gui_state{doc_plt = NewDocPlt});
511    {'EXIT', BackendPid, {error, Reason}} ->
512      free_editor(State, ?DIALYZER_ERROR_TITLE, Reason),
513      config_gui_stop(State),
514      gui_loop(State);
515    {'EXIT', BackendPid, Reason} when Reason =/= 'normal' ->
516      free_editor(State, ?DIALYZER_ERROR_TITLE, io_lib:format("~tp", [Reason])),
517      config_gui_stop(State),
518      gui_loop(State)
519  end.
520
521maybe_quit(#gui_state{frame = Frame} = State) ->
522  case dialog(State, "Do you really want to quit?", ?DIALYZER_MESSAGE_TITLE) of
523    true ->
524      wxWindow:destroy(Frame),
525      true;
526    false ->
527      false
528  end.
529
530%% ------------ Yes/No Question ------------
531dialog(#gui_state{frame = Frame}, Message, Title) ->
532  MessageWin = wxMessageDialog:new(Frame, Message, [{caption, Title},{style, ?wxYES_NO bor ?wxICON_QUESTION bor ?wxNO_DEFAULT}]),
533  case wxDialog:showModal(MessageWin) of
534    ?wxID_YES ->
535      true;
536    ?wxID_NO ->
537      false;
538    ?wxID_CANCEL ->
539      false
540  end.
541
542search_doc_plt(#gui_state{gui = Wx} = State) ->
543  Dialog = wxFrame:new(Wx, ?SearchPltDialog, "Search the PLT",[{size,{400,100}},{style, ?wxSTAY_ON_TOP}]),
544  Size = {size,{120,30}},
545  ModLabel = wxStaticText:new(Dialog, ?ModLabel, "Module"),
546  ModText = wxTextCtrl:new(Dialog, ?ModText,[Size]),
547  FunLabel = wxStaticText:new(Dialog, ?FunLabel, "Function"),
548  FunText = wxTextCtrl:new(Dialog, ?FunText,[Size]),
549  ArLabel = wxStaticText:new(Dialog, ?ArLabel, "Arity"),
550  ArText = wxTextCtrl:new(Dialog, ?ArText,[Size]),
551  SearchButton = wxButton:new(Dialog, ?SearchButton, [{label, "Search"}]),
552  wxButton:connect(SearchButton, command_button_clicked),
553  Cancel = wxButton:new(Dialog, ?Search_Cancel, [{label, "Cancel"}]),
554  wxButton:connect(Cancel, command_button_clicked),
555
556  Layout = wxBoxSizer:new(?wxVERTICAL),
557  Top = wxBoxSizer:new(?wxHORIZONTAL),
558  ModLayout = wxBoxSizer:new(?wxVERTICAL),
559  FunLayout = wxBoxSizer:new(?wxVERTICAL),
560  ArLayout = wxBoxSizer:new(?wxVERTICAL),
561  Buttons = wxBoxSizer:new(?wxHORIZONTAL),
562
563  _ = wxSizer:add(ModLayout, ModLabel, ?BorderOpt),
564  _ = wxSizer:add(ModLayout, ModText, ?BorderOpt),
565  _ = wxSizer:add(FunLayout, FunLabel, ?BorderOpt),
566  _ = wxSizer:add(FunLayout,FunText, ?BorderOpt),
567  _ = wxSizer:add(ArLayout, ArLabel, ?BorderOpt),
568  _ = wxSizer:add(ArLayout,ArText, ?BorderOpt),
569  _ = wxSizer:add(Buttons, SearchButton, ?BorderOpt),
570  _ = wxSizer:add(Buttons,Cancel, ?BorderOpt),
571
572  _ = wxSizer:add(Top, ModLayout),
573  _ = wxSizer:add(Top, FunLayout),
574  _ = wxSizer:add(Top, ArLayout),
575  _ = wxSizer:add(Layout, Top,[{flag, ?wxALIGN_CENTER}]),
576  _ = wxSizer:add(Layout, Buttons,[{flag, ?wxALIGN_CENTER bor ?wxBOTTOM}]),
577  wxFrame:connect(Dialog, close_window),
578  wxWindow:setSizer(Dialog, Layout),
579  wxFrame:show(Dialog),
580  search_plt_loop(State, Dialog, ModText, FunText, ArText, SearchButton, Cancel).
581
582search_plt_loop(State= #gui_state{doc_plt = DocPlt, frame = Frame}, Win, ModText, FunText, ArText, Search, Cancel) ->
583  receive
584    #wx{id = ?Search_Cancel,
585	event = #wxCommand{type = command_button_clicked}} ->
586      wxWindow:destroy(Win);
587    #wx{id = ?SearchPltDialog, event = #wxClose{type = close_window}} ->
588      wxWindow:destroy(Win);
589    #wx{event = #wxClose{type = close_window}} ->
590      wxWindow:destroy(Win),
591      wxWindow:destroy(Frame);
592    #wx{id = ?SearchButton,
593	event = #wxCommand{type = command_button_clicked}} ->
594      M = format_search(wxTextCtrl:getValue(ModText)),
595      F = format_search(wxTextCtrl:getValue(FunText)),
596      A = format_search(wxTextCtrl:getValue(ArText)),
597
598      if
599	(M =:= '_') orelse (F =:= '_') orelse (A =:= '_') ->
600	  error_sms(State, "Please give:\n Module (atom)\n Function (atom)\n Arity (integer)\n"),
601	  search_plt_loop(State, Win, ModText, FunText, ArText, Search, Cancel);
602	 true ->
603	  case dialyzer_plt:get_specs(DocPlt, M, F, A) of
604	    none ->
605	      error_sms(State, "No such function"),
606	      search_plt_loop(State, Win, ModText, FunText, ArText, Search, Cancel);
607	    NonEmptyString ->
608	      wxWindow:destroy(Win),
609	      free_editor(State, "Content of PLT", NonEmptyString)
610	  end
611      end
612  end.
613
614format_search([]) ->
615  '_';
616format_search(String) ->
617  try list_to_integer(String)
618  catch error:_ -> list_to_atom(String)
619  end.
620
621show_doc_plt(#gui_state{doc_plt = DocPLT} = State) ->
622  case dialyzer_plt:get_specs(DocPLT) of
623    "" -> error_sms(State, "No analysis has been made yet!\n");
624    NonEmptyString -> free_editor(State, "Content of PLT", NonEmptyString)
625  end.
626
627message(State, Message) ->
628  output_sms(State, ?DIALYZER_MESSAGE_TITLE, Message, info).
629
630error_sms(State, Message) ->
631  output_sms(State, ?DIALYZER_ERROR_TITLE, Message, error).
632
633output_sms(#gui_state{frame = Frame}, Title, Message, Type) ->
634  Style = case Type of
635	    error -> ?wxOK bor ?wxICON_ERROR;
636	    info  -> ?wxOK bor ?wxICON_INFORMATION
637	  end,
638  Options = [{caption, Title}, {style, Style}],
639  MessageWin = wxMessageDialog:new(Frame, Message, Options),
640  wxWindow:setSizeHints(MessageWin, {350,100}),
641  wxDialog:showModal(MessageWin),
642  ok.
643
644free_editor(#gui_state{gui = Wx, frame = Frame}, Title, Contents0) ->
645  Contents = lists:flatten(Contents0),
646  Tokens = string:lexemes(Contents, "\n"),
647  NofLines = length(Tokens),
648  LongestLine = lists:max([length(X) || X <- Tokens]),
649  Height0 = NofLines * 25 + 80,
650  Height = if Height0 > 500 -> 500; true -> Height0 end,
651  Width0 = LongestLine * 7 + 60,
652  Width = if Width0 > 800 -> 800; true -> Width0 end,
653  Size = {size,{Width, Height}},
654  Win = wxFrame:new(Wx, ?Message, Title, [{size,{Width+4, Height+50}}]),
655
656  Editor = wxTextCtrl:new(Win, ?Message_Info,
657			  [Size,
658			   {style, ?wxTE_MULTILINE
659			    bor ?wxTE_READONLY bor ?wxVSCROLL bor ?wxEXPAND}]),
660  wxTextCtrl:appendText(Editor, Contents),
661  wxFrame:connect(Win, close_window),
662  Ok = wxButton:new(Win, ?Message_Ok, [{label, "OK"}]),
663  wxButton:connect(Ok, command_button_clicked),
664  Layout = wxBoxSizer:new(?wxVERTICAL),
665
666  _ = wxSizer:add(Layout, Editor, ?BorderOpt),
667  Flag = ?wxALIGN_CENTER bor ?wxBOTTOM bor ?wxALL,
668  _ = wxSizer:add(Layout, Ok, [{flag, Flag}, ?Border]),
669  wxWindow:setSizer(Win, Layout),
670  wxWindow:show(Win),
671  show_info_loop(Frame, Win).
672
673show_info_loop(Frame, Win) ->
674  receive
675    #wx{id = ?Message_Ok, event = #wxCommand{type = command_button_clicked}} ->
676      wxWindow:destroy(Win);
677    #wx{id = ?Message, event = #wxClose{type = close_window}} ->
678      wxWindow:destroy(Win);
679    #wx{event = #wxClose{type = close_window}} ->
680      wxWindow:destroy(Frame)
681  end.
682
683handle_add_files(#gui_state{chosen_box = ChosenBox, file_box = FileBox,
684			    files_to_analyze = FileList,
685			    mode = Mode} = State) ->
686  case wxFilePickerCtrl:getPath(FileBox) of
687    "" ->
688      State;
689    File ->
690      NewFile = ordsets:new(),
691      NewFile1 = ordsets:add_element(File,NewFile),
692      Ext =
693	case wxRadioBox:getSelection(Mode) of
694	  0 -> ".beam";
695	  1-> ".erl"
696	end,
697      State#gui_state{files_to_analyze = add_files(filter_mods(NewFile1, Ext), FileList, ChosenBox, Ext)}
698  end.
699
700handle_add_dir(#gui_state{chosen_box = ChosenBox, dir_entry = DirBox,
701			  files_to_analyze = FileList, mode = Mode} = State) ->
702  case wxDirPickerCtrl:getPath(DirBox) of
703    "" ->
704      State;
705    Dir ->
706      NewDir = ordsets:new(),
707      NewDir1 = ordsets:add_element(Dir,NewDir),
708      Ext = case wxRadioBox:getSelection(Mode) of
709	      0 -> ".beam";
710	      1-> ".erl"
711	    end,
712      State#gui_state{files_to_analyze = add_files(filter_mods(NewDir1,Ext), FileList, ChosenBox, Ext)}
713  end.
714
715handle_add_rec(#gui_state{chosen_box = ChosenBox, dir_entry = DirBox,
716			  files_to_analyze = FileList, mode = Mode} = State) ->
717  case wxDirPickerCtrl:getPath(DirBox) of
718    "" ->
719      State;
720    Dir ->
721      NewDir = ordsets:new(),
722      NewDir1 = ordsets:add_element(Dir,NewDir),
723      TargetDirs = ordsets:union(NewDir1, all_subdirs(NewDir1)),
724      Ext = case wxRadioBox:getSelection(Mode) of
725	      0 -> ".beam";
726	      1 -> ".erl"
727	    end,
728      State#gui_state{files_to_analyze = add_files(filter_mods(TargetDirs, Ext), FileList, ChosenBox, Ext)}
729  end.
730
731handle_file_delete(#gui_state{chosen_box = ChosenBox,
732			      files_to_analyze = FileList} = State) ->
733  {_, List} = wxListBox:getSelections(ChosenBox),
734  Set = ordsets:from_list([wxControlWithItems:getString(ChosenBox, X) || X <- List]),
735  FileList1 = ordsets:subtract(FileList,Set),
736  lists:foreach(fun (X) -> wxListBox:delete(ChosenBox, X) end, List),
737  State#gui_state{files_to_analyze = FileList1}.
738
739handle_file_delete_all(#gui_state{chosen_box = ChosenBox} = State) ->
740  wxListBox:clear(ChosenBox),
741  State#gui_state{files_to_analyze = ordsets:new()}.
742
743add_files(File, FileList, ChosenBox, Ext) ->
744  Set = filter_mods(FileList, Ext),
745  Files = ordsets:union(File, Set),
746  Files1 = ordsets:to_list(Files),
747  wxListBox:set(ChosenBox, Files1),
748  Files.
749
750filter_mods(Mods, Extension) ->
751  Fun = fun(X) ->
752	    filename:extension(X) =:= Extension
753	      orelse
754		(filelib:is_dir(X) andalso
755		 contains_files(X, Extension))
756	end,
757  ordsets:filter(Fun, Mods).
758
759contains_files(Dir, Extension) ->
760  {ok, Files} = file:list_dir(Dir),
761  lists:any(fun(X) -> filename:extension(X) =:= Extension end, Files).
762
763all_subdirs(Dirs) ->
764  all_subdirs(Dirs, []).
765
766all_subdirs([Dir|T], Acc) ->
767  {ok, Files} = file:list_dir(Dir),
768  SubDirs = lists:zf(fun(F) ->
769                       SubDir = filename:join(Dir, F),
770                       case filelib:is_dir(SubDir) of
771                         true -> {true, SubDir};
772                         false -> false
773                       end
774                   end, Files),
775  NewAcc = ordsets:union(ordsets:from_list(SubDirs), Acc),
776  all_subdirs(T ++ SubDirs, NewAcc);
777all_subdirs([], Acc) ->
778  Acc.
779
780start_analysis(State) ->
781  Analysis = build_analysis_record(State),
782  case get_anal_files(State, Analysis#analysis.start_from) of
783    error ->
784      Msg = "You must choose one or more files or dirs\n"
785	"before starting the analysis!",
786      error_sms(State, Msg),
787      config_gui_stop(State),
788      State;
789    {ok, Files} ->
790      Msg = "\n========== Starting Analysis ==========\n\n",
791      update_editor(State#gui_state.log, Msg),
792      NewAnalysis = Analysis#analysis{files = Files},
793      run_analysis(State, NewAnalysis)
794  end.
795
796build_analysis_record(#gui_state{mode = Mode, menu = Menu, options = Options,
797				 init_plt = InitPlt0}) ->
798  StartFrom =
799    case wxRadioBox:getSelection(Mode) of
800      0 -> byte_code;
801      1 -> src_code
802    end,
803  InitPlt =
804    case wxMenu:isChecked(Menu#menu.plt, ?menuID_PLT_INIT_EMPTY) of
805      true -> dialyzer_plt:new();
806      false -> InitPlt0
807    end,
808  #analysis{defines = Options#options.defines,
809	    include_dirs = Options#options.include_dirs,
810	    plt = InitPlt,
811	    start_from = StartFrom,
812	    solvers = Options#options.solvers}.
813
814get_anal_files(#gui_state{files_to_analyze = Files}, StartFrom) ->
815  FilteredMods =
816    case StartFrom of
817      src_code -> filter_mods(Files, ".erl");
818      byte_code -> filter_mods(Files, ".beam")
819    end,
820  FilteredDirs = [X || X <- Files, filelib:is_dir(X)],
821  case ordsets:union(FilteredMods, FilteredDirs) of
822    [] -> error;
823    Set -> {ok, Set}
824  end.
825
826run_analysis(State, Analysis) ->
827  config_gui_start(State),
828  Self = self(),
829  NewAnalysis = Analysis#analysis{doc_plt = dialyzer_plt:new()},
830  LegalWarnings = find_legal_warnings(State),
831  Fun =
832    fun() ->
833	dialyzer_analysis_callgraph:start(Self, LegalWarnings, NewAnalysis)
834    end,
835  BackendPid = spawn_link(Fun),
836  State#gui_state{backend_pid = BackendPid}.
837
838find_legal_warnings(#gui_state{menu = #menu{warnings = MenuWarnings},
839			       wantedWarnings = Warnings }) ->
840  ordsets:from_list([Tag || {Tag, MenuItem} <- Warnings,
841			    wxMenu:isChecked(MenuWarnings, MenuItem)]).
842
843update_editor(Editor, Msg) ->
844  wxTextCtrl:appendText(Editor,Msg).
845
846config_gui_stop(State) ->
847  wxWindow:disable(State#gui_state.stop),
848  wxWindow:enable(State#gui_state.run),
849  wxWindow:enable(State#gui_state.del_file),
850  wxWindow:enable(State#gui_state.clear_chosen),
851  wxWindow:enable(State#gui_state.add),
852  wxWindow:enable(State#gui_state.add_dir),
853  wxWindow:enable(State#gui_state.add_rec),
854  wxWindow:enable(State#gui_state.clear_warn),
855  wxWindow:enable(State#gui_state.clear_log),
856  Menu = State#gui_state.menu,
857  wxMenu:enable(Menu#menu.file,?menuID_FILE_SAVE_WARNINGS,true),
858  wxMenu:enable(Menu#menu.file,?menuID_FILE_SAVE_LOG,true),
859  wxMenu:enable(Menu#menu.options,?menuID_OPTIONS_MACRO,true),
860  wxMenu:enable(Menu#menu.options,?menuID_OPTIONS_INCLUDE_DIR,true),
861  wxMenu:enable(Menu#menu.plt,?menuID_PLT_INIT_EMPTY,true),
862  wxMenu:enable(Menu#menu.plt,?menuID_PLT_SHOW_CONTENTS,true),
863  wxMenu:enable(Menu#menu.plt,?menuID_PLT_SEARCH_CONTENTS,true),
864  wxRadioBox:enable(State#gui_state.mode).
865
866config_gui_start(State) ->
867  wxWindow:enable(State#gui_state.stop),
868  wxWindow:disable(State#gui_state.run),
869  wxWindow:disable(State#gui_state.del_file),
870  wxWindow:disable(State#gui_state.clear_chosen),
871  wxWindow:disable(State#gui_state.add),
872  wxWindow:disable(State#gui_state.add_dir),
873  wxWindow:disable(State#gui_state.add_rec),
874  wxWindow:disable(State#gui_state.clear_warn),
875  wxWindow:disable(State#gui_state.clear_log),
876  Menu = State#gui_state.menu,
877  wxMenu:enable(Menu#menu.file,?menuID_FILE_SAVE_WARNINGS, false),
878  wxMenu:enable(Menu#menu.file,?menuID_FILE_SAVE_LOG, false),
879  wxMenu:enable(Menu#menu.options,?menuID_OPTIONS_MACRO, false),
880  wxMenu:enable(Menu#menu.options,?menuID_OPTIONS_INCLUDE_DIR, false),
881  wxMenu:enable(Menu#menu.plt,?menuID_PLT_INIT_EMPTY, false),
882  wxMenu:enable(Menu#menu.plt,?menuID_PLT_SHOW_CONTENTS, false),
883  wxMenu:enable(Menu#menu.plt,?menuID_PLT_SEARCH_CONTENTS, false),
884  wxRadioBox:disable(State#gui_state.mode).
885
886save_file(#gui_state{frame = Frame, warnings_box = WBox, log = Log} = State, Type) ->
887  {Message, Box} = case Type of
888		     warnings -> {"Save Warnings", WBox};
889		     log -> {"Save Log", Log}
890		   end,
891  case wxTextCtrl:getValue(Box) of
892    "" -> error_sms(State,"There is nothing to save...\n");
893    _ ->
894      DefaultPath = code:root_dir(),
895      FileDialog = wxFileDialog:new(Frame,
896				    [{defaultDir, DefaultPath},
897				     {message, Message},
898				     {style,?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT}]),
899      case wxFileDialog:showModal(FileDialog) of
900	?wxID_OK ->
901	  Path = wxFileDialog:getPath(FileDialog),
902	  case wxTextCtrl:saveFile(Box,[{file,Path}]) of
903	    true -> ok;
904	    false -> error_sms(State, "Could not write to file:\n" ++ Path)
905	  end;
906	?wxID_CANCEL -> wxWindow:destroy(FileDialog);
907	_ -> error_sms(State, "Could not write to file:\n")
908      end
909  end.
910
911include_dialog(#gui_state{gui = Wx, frame = Frame, options = Options}) ->
912  Size = {size,{300,480}},
913  Dialog = wxFrame:new(Wx, ?IncludeDir, "Include Directories",[Size]),
914  DirLabel = wxStaticText:new(Dialog, ?InclLabel, "Directory: "),
915  DefaultPath = code:root_dir(),
916  DirPicker = wxDirPickerCtrl:new(Dialog, ?InclPicker,
917				   [{path, DefaultPath},
918				    {message, "Choose Directory to Include"},
919				    {style,?wxDIRP_DIR_MUST_EXIST bor ?wxDIRP_USE_TEXTCTRL}]),
920  Box = wxListBox:new(Dialog, ?InclBox,
921			[{size, {200,300}},
922			 {style, ?wxLB_EXTENDED bor ?wxLB_HSCROLL
923			  bor ?wxLB_NEEDED_SB}]),
924  AddButton = wxButton:new(Dialog, ?InclAdd, [{label, "Add"}]),
925  DeleteButton = wxButton:new(Dialog, ?InclDel, [{label, "Delete"}]),
926  DeleteAllButton = wxButton:new(Dialog, ?InclDelAll, [{label, "Delete All"}]),
927  Ok = wxButton:new(Dialog, ?InclOk, [{label, "OK"}]),
928  Cancel = wxButton:new(Dialog, ?InclCancel, [{label, "Cancel"}]),
929  wxButton:connect(AddButton, command_button_clicked),
930  wxButton:connect(DeleteButton, command_button_clicked),
931  wxButton:connect(DeleteAllButton, command_button_clicked),
932  wxButton:connect(Ok, command_button_clicked),
933  wxButton:connect(Cancel, command_button_clicked),
934  Dirs = [io_lib:format("~ts", [X]) || X <- Options#options.include_dirs],
935  wxListBox:set(Box, Dirs),
936  Layout = wxBoxSizer:new(?wxVERTICAL),
937  Buttons = wxBoxSizer:new(?wxHORIZONTAL),
938  Buttons1 = wxBoxSizer:new(?wxHORIZONTAL),
939
940  _ = wxSizer:add(Layout, DirLabel, [{flag, ?wxALIGN_CENTER_HORIZONTAL}]),
941  _ = wxSizer:add(Layout, DirPicker, [{flag, ?wxALIGN_CENTER_HORIZONTAL}]),
942  _ = wxSizer:add(Layout,AddButton, [{flag, ?wxALIGN_CENTER_HORIZONTAL bor ?wxALL}, ?Border]),
943  _ = wxSizer:add(Layout,Box, [{flag, ?wxALIGN_CENTER_HORIZONTAL bor ?wxALL}, ?Border]),
944  _ = wxSizer:add(Buttons, DeleteButton, ?BorderOpt),
945  _ = wxSizer:add(Buttons, DeleteAllButton, ?BorderOpt),
946  _ = wxSizer:add(Layout,Buttons, [{flag, ?wxALIGN_CENTER_HORIZONTAL}]),
947  _ = wxSizer:add(Buttons1, Ok, ?BorderOpt),
948  _ = wxSizer:add(Buttons1,Cancel, ?BorderOpt),
949  _ = wxSizer:add(Layout,Buttons1,[{flag, ?wxALIGN_RIGHT bor ?wxBOTTOM}]),
950
951  wxFrame:connect(Dialog, close_window),
952  wxWindow:setSizer(Dialog, Layout),
953  wxFrame:show(Dialog),
954  include_loop(Options, Dialog, Box, DirPicker, Frame).
955
956include_loop(Options, Win, Box, DirPicker, Frame) ->
957  receive
958    #wx{id = ?InclCancel,
959	event = #wxCommand{type = command_button_clicked}} ->
960      wxWindow:destroy(Win),
961      Options;
962    #wx{id = ?IncludeDir, event = #wxClose{type = close_window}} ->
963      wxWindow:destroy(Win),
964      Options;
965    #wx{event = #wxClose{type = close_window}} ->
966      wxWindow:destroy(Win),
967      wxWindow:destroy(Frame);
968    #wx{id = ?InclOk,
969	event = #wxCommand{type = command_button_clicked}} ->
970     wxWindow:destroy(Win),
971     Options;
972    #wx{id = ?InclAdd,
973	event = #wxCommand{type = command_button_clicked}} ->
974      Dirs = Options#options.include_dirs,
975       NewDirs =
976	case wxDirPickerCtrl:getPath(DirPicker) of
977	  "" -> Dirs;
978	  Add -> [Add|Dirs]
979	end,
980      NewOptions = Options#options{include_dirs = NewDirs},
981      wxListBox:set(Box, NewDirs),
982      include_loop(NewOptions, Win, Box, DirPicker, Frame);
983     #wx{id = ?InclDel,
984	event = #wxCommand{type = command_button_clicked}} ->
985      NewOptions =
986	case wxListBox:getSelections(Box) of
987	  {0,_} -> Options;
988	  {_,List} ->
989	    DelList = [wxControlWithItems:getString(Box,X) || X <- List],
990	    NewDirs = Options#options.include_dirs -- DelList,
991	    lists:foreach(fun (X) -> wxListBox:delete(Box, X) end, List),
992	    Options#options{include_dirs = NewDirs}
993	end,
994      include_loop(NewOptions, Win, Box, DirPicker, Frame);
995    #wx{id = ?InclDelAll,
996	event = #wxCommand{type = command_button_clicked}} ->
997      wxListBox:clear(Box),
998      NewOptions = Options#options{include_dirs = []},
999      include_loop(NewOptions, Win, Box, DirPicker, Frame)
1000  end.
1001
1002macro_dialog(#gui_state{gui = Wx, frame = Frame, options = Options}) ->
1003  Size = {size,{300,480}},
1004  Size1 = {size,{120,30}},
1005  Dialog = wxFrame:new(Wx, ?MacroDir, "Macro Definitions",[Size]),
1006  MacroLabel = wxStaticText:new(Dialog, ?MacroLabel, "Macro"),
1007  TermLabel = wxStaticText:new(Dialog, ?TermLabel, "Term"),
1008  MacroText = wxTextCtrl:new(Dialog, ?MacroText, [Size1]),
1009  TermText = wxTextCtrl:new(Dialog, ?TermText, [Size1]),
1010  Box = wxListBox:new(Dialog, ?MacroBox,
1011			[{size, {250,300}},
1012			 {style, ?wxLB_EXTENDED bor ?wxLB_HSCROLL
1013			  bor ?wxLB_NEEDED_SB}]),
1014
1015  AddButton = wxButton:new(Dialog, ?MacroAdd, [{label, "Add"}]),
1016  DeleteButton = wxButton:new(Dialog, ?MacroDel, [{label, "Delete"}]),
1017  DeleteAllButton = wxButton:new(Dialog, ?MacroDelAll, [{label, "Delete All"}]),
1018  Ok = wxButton:new(Dialog, ?MacroOk, [{label, "OK"}]),
1019  Cancel = wxButton:new(Dialog, ?MacroCancel, [{label, "Cancel"}]),
1020  wxButton:connect(AddButton, command_button_clicked),
1021  wxButton:connect(DeleteButton, command_button_clicked),
1022  wxButton:connect(DeleteAllButton, command_button_clicked),
1023  wxButton:connect(Ok, command_button_clicked),
1024  wxButton:connect(Cancel, command_button_clicked),
1025
1026  Macros = [io_lib:format("~p = ~p", [X, Y])
1027	    || {X,Y} <- Options#options.defines],
1028
1029  wxListBox:set(Box, Macros),
1030  Layout = wxBoxSizer:new(?wxVERTICAL),
1031  Item = wxBoxSizer:new(?wxHORIZONTAL),
1032  MacroItem = wxBoxSizer:new(?wxVERTICAL),
1033  TermItem = wxBoxSizer:new(?wxVERTICAL),
1034  Buttons = wxBoxSizer:new(?wxHORIZONTAL),
1035  Buttons1 = wxBoxSizer:new(?wxHORIZONTAL),
1036
1037  _ = wxSizer:add(MacroItem, MacroLabel, ?BorderOpt),
1038  _ = wxSizer:add(MacroItem, MacroText, ?BorderOpt),
1039  _ = wxSizer:add(TermItem, TermLabel, ?BorderOpt),
1040  _ = wxSizer:add(TermItem, TermText, ?BorderOpt),
1041  _ = wxSizer:add(Item, MacroItem),
1042  _ = wxSizer:add(Item, TermItem),
1043  _ = wxSizer:add(Layout, Item, [{flag, ?wxALIGN_CENTER_HORIZONTAL}]),
1044  _ = wxSizer:add(Layout, AddButton, [{flag, ?wxALIGN_CENTER_HORIZONTAL bor ?wxALL}, ?Border]),
1045  _ = wxSizer:add(Layout, Box, [{flag, ?wxALIGN_CENTER_HORIZONTAL bor ?wxALL}, ?Border]),
1046  _ = wxSizer:add(Buttons, DeleteButton, ?BorderOpt),
1047  _ = wxSizer:add(Buttons, DeleteAllButton, ?BorderOpt),
1048  _ = wxSizer:add(Layout, Buttons, [{flag, ?wxALIGN_CENTER_HORIZONTAL}]),
1049  _ = wxSizer:add(Buttons1, Ok, ?BorderOpt),
1050  _ = wxSizer:add(Buttons1, Cancel, ?BorderOpt),
1051  _ = wxSizer:add(Layout, Buttons1, [{flag, ?wxALIGN_RIGHT bor ?wxBOTTOM}]),
1052
1053  wxFrame:connect(Dialog, close_window),
1054  wxWindow:setSizer(Dialog, Layout),
1055  wxFrame:show(Dialog),
1056  macro_loop(Options, Dialog, Box, MacroText, TermText, Frame).
1057
1058macro_loop(Options, Win, Box, MacroText, TermText, Frame) ->
1059  receive
1060    #wx{id = ?MacroCancel,
1061	event = #wxCommand{type = command_button_clicked}} ->
1062      wxWindow:destroy(Win),
1063      Options;
1064    #wx{id = ?MacroDir, event = #wxClose{type = close_window}} ->
1065      wxWindow:destroy(Win),
1066      Options;
1067    #wx{event = #wxClose{type = close_window}} ->
1068      wxWindow:destroy(Win),
1069      wxWindow:destroy(Frame);
1070    #wx{id = ?MacroOk,
1071	event = #wxCommand{type = command_button_clicked}} ->
1072     wxWindow:destroy(Win),
1073     Options;
1074    #wx{id = ?MacroAdd,
1075	event = #wxCommand{type = command_button_clicked}} ->
1076      Defines = Options#options.defines,
1077       NewDefines =
1078	case wxTextCtrl:getValue(MacroText) of
1079	  "" -> Defines;
1080	  Macro ->
1081	    case wxTextCtrl:getValue(TermText) of
1082	      "" ->
1083		orddict:store(list_to_atom(Macro), true, Defines);
1084	      String ->
1085		orddict:store(list_to_atom(Macro), String, Defines)
1086	    end
1087	end,
1088      NewOptions = Options#options{defines = NewDefines},
1089      NewEntries = [io_lib:format("~p = ~p", [X, Y]) || {X, Y} <- NewDefines],
1090      wxListBox:set(Box, NewEntries),
1091      macro_loop(NewOptions, Win, Box, MacroText, TermText, Frame);
1092     #wx{id = ?MacroDel,
1093	event = #wxCommand{type = command_button_clicked}} ->
1094      NewOptions =
1095	case wxListBox:getSelections(Box) of
1096	  {0, _} -> Options;
1097	  {_, List} ->
1098	    Fun =
1099	      fun(X) ->
1100		  Val = wxControlWithItems:getString(Box,X),
1101		  [MacroName|_] = re:split(Val, " ", [{return, list}, unicode]),
1102		  list_to_atom(MacroName)
1103	      end,
1104	    Delete = [Fun(X) || X <- List],
1105	    lists:foreach(fun (X) -> wxListBox:delete(Box, X) end, List),
1106	    Defines = Options#options.defines,
1107	    NewDefines = lists:foldl(fun(X, Acc) ->
1108					 orddict:erase(X, Acc)
1109				     end,
1110				     Defines, Delete),
1111	    Options#options{defines = NewDefines}
1112	end,
1113      macro_loop(NewOptions, Win, Box, MacroText, TermText, Frame);
1114    #wx{id = ?MacroDelAll,
1115	event = #wxCommand{type = command_button_clicked}} ->
1116      wxListBox:clear(Box),
1117      NewOptions = Options#options{defines = []},
1118      macro_loop(NewOptions, Win, Box,  MacroText, TermText, Frame)
1119  end.
1120
1121handle_help(State, Title, Txt) ->
1122  FileName = filename:join([code:lib_dir(dialyzer), "doc", Txt]),
1123  case file:open(FileName, [read]) of
1124    {error, Reason} ->
1125      error_sms(State,
1126		io_lib:format("Could not find doc/~ts file!\n\n ~tp",
1127			      [Txt, Reason]));
1128    {ok, _Handle} ->
1129      case file:read_file(FileName) of
1130	{error, Reason} ->
1131	  error_sms(State,
1132		    io_lib:format("Could not read doc/~ts file!\n\n ~tp",
1133				  [Txt, Reason]));
1134	{ok, Binary} ->
1135	  Contents = binary_to_list(Binary),
1136	  free_editor(State, Title, Contents)
1137      end
1138  end.
1139
1140add_warnings(#gui_state{warnings_box = WarnBox,
1141			rawWarnings = RawWarns} = State, Warnings) ->
1142  NewRawWarns = RawWarns ++ Warnings,
1143  %% The indentation cannot be turned off.
1144  %% The column numbers of locations are always displayed.
1145  WarnList = [string:trim(dialyzer:format_warning(W), trailing) ||
1146               W <- NewRawWarns],
1147  wxListBox:set(WarnBox, WarnList),
1148  State#gui_state{rawWarnings = NewRawWarns}.
1149
1150handle_explanation(#gui_state{rawWarnings = RawWarns,
1151			      warnings_box = WarnBox,
1152			      expl_pid = ExplPid} = State) ->
1153  case wxListBox:isEmpty(WarnBox) of
1154    true ->
1155      error_sms(State, "\nThere are no warnings.\nRun the dialyzer first.");
1156    false ->
1157      case wxListBox:getSelections(WarnBox)of
1158	{0, []} ->
1159	  error_sms(State,"\nYou must choose a warning to be explained\n");
1160	{_, [WarnNumber]} ->
1161	  Warn = lists:nth(WarnNumber+1,RawWarns),
1162	  Self = self(),
1163	  ExplPid ! {Self, warning, Warn},
1164	  explanation_loop(State)
1165      end
1166  end.
1167
1168explanation_loop(#gui_state{expl_pid = ExplPid} = State) ->
1169  receive
1170    {ExplPid, explanation, Explanation} ->
1171      show_explanation(State, Explanation);
1172    _ -> io:format("Unknown message\n"),
1173	 explanation_loop(State)
1174  end.
1175
1176show_explanation(#gui_state{gui = Wx} = State, Explanation) ->
1177  case Explanation of
1178    none ->
1179      output_sms(State, ?DIALYZER_MESSAGE_TITLE,
1180		 "There is not any explanation for this error!\n", info);
1181    Expl ->
1182      ExplString = format_explanation(Expl),
1183      Size = {size,{700, 300}},
1184      Win = wxFrame:new(Wx, ?ExplWin, "Dialyzer Explanation", [{size,{740, 350}}]),
1185
1186      Editor = wxTextCtrl:new(Win, ?ExplText,
1187			      [Size,
1188			       {style, ?wxTE_MULTILINE
1189				bor ?wxTE_READONLY bor ?wxVSCROLL bor ?wxEXPAND}]),
1190      wxTextCtrl:appendText(Editor, ExplString),
1191      wxFrame:connect(Win, close_window),
1192      ExplButton = wxButton:new(Win, ?ExplButton, [{label, "Further Explain"}]),
1193      wxButton:connect(ExplButton, command_button_clicked),
1194      Ok = wxButton:new(Win, ?ExplOk, [{label, "OK"}]),
1195      wxButton:connect(Ok, command_button_clicked),
1196      Layout = wxBoxSizer:new(?wxVERTICAL),
1197      Buttons = wxBoxSizer:new(?wxHORIZONTAL),
1198      _ = wxSizer:add(Buttons, ExplButton, ?BorderOpt),
1199      _ = wxSizer:add(Buttons, Ok, ?BorderOpt),
1200      _ = wxSizer:add(Layout, Editor, [{flag, ?wxALIGN_CENTER_HORIZONTAL bor ?wxALL}, ?Border]),
1201      _ = wxSizer:add(Layout, Buttons,[{flag, ?wxALIGN_CENTER_HORIZONTAL}]),
1202      wxWindow:setSizer(Win, Layout),
1203      wxWindow:show(Win),
1204      NewState = State#gui_state{explanation_box = Editor},
1205      show_explanation_loop(NewState, Win, Explanation)
1206  end.
1207
1208show_explanation_loop(#gui_state{frame = Frame, expl_pid = ExplPid} = State, Win, Explanation) ->
1209  receive
1210    {ExplPid, none, _} ->
1211      output_sms(State, ?DIALYZER_MESSAGE_TITLE,
1212		       "There is not any other explanation for this error!\n", info),
1213      show_explanation_loop(State, Win,  Explanation);
1214    {ExplPid, further, NewExplanation} ->
1215      update_explanation(State, NewExplanation),
1216      show_explanation_loop(State, Win,  NewExplanation);
1217    #wx{id = ?ExplButton, event = #wxCommand{type = command_button_clicked}} ->
1218      ExplPid ! {self(), further, Explanation},
1219      show_explanation_loop(State, Win, Explanation);
1220    #wx{id = ?ExplOk, event = #wxCommand{type = command_button_clicked}} ->
1221      wxWindow:destroy(Win);
1222    #wx{id = ?ExplWin, event = #wxClose{type = close_window}} ->
1223       wxWindow:destroy(Win);
1224    #wx{event = #wxClose{type = close_window}} ->
1225      wxWindow:destroy(Frame)
1226  end.
1227
1228update_explanation(#gui_state{explanation_box = Box}, Explanation) ->
1229  ExplString = format_explanation(Explanation),
1230  wxTextCtrl:appendText(Box, "\n --------------------------- \n"),
1231  wxTextCtrl:appendText(Box, ExplString).
1232
1233format_explanation({function_return, {M, F, A}, NewList}) ->
1234  io_lib:format("The function ~w:~tw/~w returns ~ts\n",
1235		[M, F, A, erl_types:t_to_string(NewList)]);
1236format_explanation(Explanation) ->
1237  io_lib:format("~p\n", [Explanation]).
1238