1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2009-2018. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19
20-module(reltool_app_win).
21
22%% Public
23-export([start_link/4, raise/1, refresh/1, open_mod/2]).
24
25%% Internal
26-export([init/5, loop/1]).
27
28%% sys callback functions
29-export([
30         system_continue/3,
31         system_terminate/4,
32         system_code_change/4
33        ]).
34
35-include_lib("wx/include/wx.hrl").
36-include("reltool.hrl").
37
38-record(state,
39        {parent_pid,
40         xref_pid,
41	 mod_wins,
42         sys,
43         common,
44         app,
45         frame,
46         panel,
47         book,
48         status_bar,
49         %% page, % apps | source | config
50         config_app_global, config_app_local, config_app_local_box,
51         config_mod_global, config_mod_local, config_mod_local_box,
52	 config_latest, config_selected, config_source_box,
53
54         app_used_by_ctrl, app_required_ctrl, app_incl_ctrl, app_uses_ctrl,
55         mods_source_ctrl, mods_white_ctrl, mods_black_ctrl, mods_derived_ctrl,
56         deps_used_by_ctrl, deps_uses_ctrl,
57         popup_menu}).
58-record(mod_win, {name, pid}).
59
60-define(WIN_WIDTH, 800).
61-define(WIN_HEIGHT, 600).
62%% -define(MODS_MOD_COL_WIDTH, 250).
63%% -define(MODS_APP_COL_WIDTH, 250).
64%% -define(APPS_APP_COL_WIDTH, 250).
65
66-define(CLOSE_ITEM, ?wxID_EXIT).    %% Use OS specific version if available
67-define(ABOUT_ITEM, ?wxID_ABOUT).   %% Use OS specific
68-define(CONTENTS_ITEM, 300).
69
70-define(MODS_MOD_COL, 0).
71-define(MODS_APP_COL, 1).
72-define(APPS_APP_COL, 0).
73
74-define(source, "Available").
75-define(whitelist, "Included").
76-define(blacklist, "Excluded").
77-define(derived, "Derived").
78
79%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
80%% Client
81
82start_link(WxEnv, Xref, Common, AppName) ->
83    proc_lib:start_link(?MODULE,
84			init,
85			[self(), WxEnv, Xref, Common, AppName],
86			infinity,
87			[]).
88
89raise(Pid) ->
90    reltool_utils:cast(Pid, raise).
91
92refresh(Pid) ->
93    reltool_utils:cast(Pid, refresh).
94
95open_mod(Pid, ModName) ->
96    reltool_utils:call(Pid, {open_mod, ModName}).
97
98%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
99%% Server
100
101init(Parent, WxEnv, Xref, C, AppName) ->
102    try
103	do_init(Parent, WxEnv, Xref, C, AppName)
104    catch
105	error:Reason:Stacktrace ->
106	    exit({Reason, Stacktrace})
107    end.
108
109do_init(Parent, WxEnv, Xref, C, AppName) ->
110    process_flag(trap_exit, C#common.trap_exit),
111    {ok, App} = reltool_server:get_app(Xref, AppName),
112    {ok, Sys} = reltool_server:get_sys(Xref),
113    S = #state{parent_pid = Parent,
114	       xref_pid = Xref,
115	       mod_wins = [],
116	       sys = Sys,
117	       common = C,
118	       app = App},
119    proc_lib:init_ack(Parent, {ok, self()}),
120    wx:set_env(WxEnv),
121    wx:debug(C#common.wx_debug),
122    S2 = wx:batch(fun() -> create_window(S) end),
123    loop(S2).
124
125loop(#state{xref_pid = Xref, common = C, app = App} = S) ->
126    receive
127        {system, From, Msg} ->
128            Dbg = C#common.sys_debug,
129            sys:handle_system_msg(Msg,
130				  From,
131				  S#state.parent_pid,
132				  ?MODULE,
133				  Dbg,
134				  S);
135        {cast, _From, raise} ->
136            wxFrame:raise(S#state.frame),
137            wxFrame:setFocus(S#state.frame),
138            ?MODULE:loop(S);
139        {cast, _From, refresh} ->
140	    case reltool_server:get_app(Xref, App#app.name) of
141		{ok, App2} ->
142		    {ok, Sys} = reltool_server:get_sys(Xref),
143		    S2 = redraw_window(S#state{sys = Sys, app = App2}),
144		    [ok = reltool_mod_win:refresh(MW#mod_win.pid) ||
145			MW <- S2#state.mod_wins],
146		    ?MODULE:loop(S2);
147		{error, _Reason} ->
148		    wxFrame:destroy(S#state.frame),
149		    exit(shutdown)
150	    end;
151        {call, ReplyTo, Ref, {open_mod, ModName}} ->
152	    S2 = create_mod_window(S, ModName),
153	    {value, #mod_win{pid = ModPid}} =
154		lists:keysearch(ModName, #mod_win.name, S2#state.mod_wins),
155	    reltool_utils:reply(ReplyTo, Ref, {ok, ModPid}),
156	    ?MODULE:loop(S2);
157	#wx{event = #wxSize{}} = Wx ->
158	    Wx2 = reltool_utils:get_latest_resize(Wx),
159	    S2 = handle_event(S, Wx2),
160	    ?MODULE:loop(S2);
161        #wx{obj = ObjRef,
162            event = #wxClose{type = close_window}} ->
163            wxFrame:destroy(ObjRef),
164            exit(shutdown);
165        #wx{} = Wx ->
166            S2 = handle_event(S, Wx),
167            ?MODULE:loop(S2);
168        {'EXIT', Pid, Reason} when Pid =:= S#state.parent_pid ->
169            exit(Reason);
170        {'EXIT', Pid, _Reason} = Exit ->
171	    exit_warning(Exit),
172            S2 = S#state{mod_wins = lists:keydelete(Pid,
173						    #mod_win.pid,
174						    S#state.mod_wins)},
175            ?MODULE:loop(S2);
176        Msg ->
177            error_logger:format("~w~w got unexpected message:\n\t~tp\n",
178                                [?MODULE, self(), Msg]),
179            ?MODULE:loop(S)
180    end.
181
182exit_warning({'EXIT', _Pid, shutdown}) ->
183    ok;
184exit_warning({'EXIT', _Pid, _Reason} = Msg) ->
185    error_logger:format("~w~w got unexpected message:\n\t~tp\n",
186			[?MODULE, self(), Msg]).
187
188create_window(#state{app = App} = S) ->
189    Title = app_title(App),
190    Frame = wxFrame:new(wx:null(), ?wxID_ANY, Title, []),
191    %% wxFrame:setSize(Frame, {?WIN_WIDTH, ?WIN_HEIGHT}),
192    Panel = wxPanel:new(Frame, []),
193    StatusBar = wxFrame:createStatusBar(Frame,[]),
194
195    Book = wxNotebook:new(Panel, ?wxID_ANY, []),
196
197    S2 = S#state{frame = Frame,
198                 panel = Panel,
199                 book = Book,
200                 status_bar = StatusBar},
201    Derived = app_to_mods(S2),
202    S3 = create_mods_page(S2, Derived),
203    S4 = create_apps_page(S3, Derived),
204    S5 = create_deps_page(S4, Derived),
205    S6 = create_config_page(S5),
206    Sizer = wxBoxSizer:new(?wxVERTICAL),
207    wxSizer:add(Sizer, Book, [{flag, ?wxEXPAND}, {proportion, 1}]),
208
209    wxPanel:setSizer(Panel, Sizer),
210    wxSizer:fit(Sizer, Frame),
211    wxSizer:setSizeHints(Sizer, Frame),
212    wxFrame:show(Frame),
213
214    wxFrame:connect(Frame, close_window),
215    S6.
216
217app_title(App) ->
218    lists:concat([?APPLICATION, " - ", App#app.label]).
219
220create_apps_page(S, Derived) ->
221    Panel = wxPanel:new(S#state.book, []),
222    Main = wxBoxSizer:new(?wxVERTICAL),
223    Upper = wxBoxSizer:new(?wxHORIZONTAL),
224    Lower = wxBoxSizer:new(?wxHORIZONTAL),
225
226    UsedByCtrl = create_apps_list_ctrl(Panel, Upper, "Used by"),
227    wxSizer:add(Upper,
228		wxStaticLine:new(Panel, [{style, ?wxLI_VERTICAL}]),
229		[{border, 2}, {flag, ?wxALL bor ?wxEXPAND}]),
230
231    RequiredCtrl = create_apps_list_ctrl(Panel, Upper, "Required"),
232    wxSizer:add(Upper, wxStaticLine:new(Panel, [{style, ?wxLI_VERTICAL}]),
233		[{border, 2}, {flag, ?wxALL bor ?wxEXPAND}]),
234    InclCtrl = create_apps_list_ctrl(Panel, Upper, "Included"),
235    wxSizer:add(Upper, wxStaticLine:new(Panel, [{style, ?wxLI_VERTICAL}]),
236		[{border, 2}, {flag, ?wxALL bor ?wxEXPAND}]),
237    UsesCtrl = create_apps_list_ctrl(Panel, Upper, "Uses"),
238    S2 = S#state{app_required_ctrl = RequiredCtrl,
239		 app_used_by_ctrl = UsedByCtrl,
240		 app_incl_ctrl = InclCtrl,
241		 app_uses_ctrl = UsesCtrl},
242    redraw_apps(S2, Derived),
243    wxSizer:add(Main, Upper,
244                [{border, 2},
245                 {flag, ?wxALL bor ?wxEXPAND},
246                 {proportion, 1}]),
247
248    wxSizer:add(Main, Lower,
249                [{border, 2},
250                 {flag, ?wxALL bor ?wxEXPAND}]),
251    wxPanel:setSizer(Panel, Main),
252    wxNotebook:addPage(S2#state.book, Panel, "Application dependencies", []),
253    S2.
254
255create_apps_list_ctrl(Panel, Sizer, Text) ->
256    Width = lists:max([100, ?WIN_WIDTH - 40]) div 4,
257    Height = lists:max([100, ?WIN_HEIGHT - 100]),
258    ListCtrl = wxListCtrl:new(Panel,
259			      [{style,
260				?wxLC_REPORT bor
261				%% ?wxLC_SORT_ASCENDING bor
262				?wxLC_SINGLE_SEL bor
263				?wxHSCROLL bor
264				?wxVSCROLL},
265			       {size, {Width, Height}}
266			      ]),
267
268    %% Prep images
269    reltool_utils:assign_image_list(ListCtrl),
270
271    %% Prep column label
272    ListItem  = wxListItem:new(),
273    wxListItem:setAlign(ListItem, ?wxLIST_FORMAT_LEFT),
274    wxListItem:setText(ListItem, Text),
275    wxListItem:setWidth(ListItem, reltool_utils:get_column_width(ListCtrl)),
276    wxListCtrl:insertColumn(ListCtrl, ?APPS_APP_COL, ListItem),
277    wxListItem:destroy(ListItem),
278
279    wxSizer:add(Sizer, ListCtrl,
280                [{border, 2},
281                 {flag, ?wxALL bor ?wxEXPAND},
282                 {proportion, 1}]),
283    wxEvtHandler:connect(ListCtrl, size,
284			 [{skip, true}, {userData, apps_list_ctrl}]),
285    wxListCtrl:connect(ListCtrl, command_list_item_activated,
286		       [{userData, open_app}]),
287    wxWindow:connect(ListCtrl, enter_window),
288    ListCtrl.
289
290create_deps_page(S, Derived) ->
291    Panel = wxPanel:new(S#state.book, []),
292    Main = wxBoxSizer:new(?wxHORIZONTAL),
293
294    UsedByCtrl = create_mods_list_ctrl(Panel,
295				       Main,
296				       "Modules using this",
297				       " and their applications",
298				       undefined,
299				       undefined),
300    wxSizer:add(Main, wxStaticLine:new(Panel, [{style, ?wxLI_VERTICAL}]),
301		[{border, 2}, {flag, ?wxALL bor ?wxEXPAND}]),
302    UsesCtrl   = create_mods_list_ctrl(Panel,
303				       Main,
304				       "Used modules",
305				       " and their applications",
306				       undefined,
307				       undefined),
308    S2 = S#state{deps_used_by_ctrl = UsedByCtrl,
309                 deps_uses_ctrl = UsesCtrl},
310    redraw_mods(S2, Derived),
311    wxPanel:setSizer(Panel, Main),
312    wxNotebook:addPage(S2#state.book, Panel, "Module dependencies", []),
313    S2.
314
315create_mods_page(S, Derived) ->
316    Panel = wxPanel:new(S#state.book, []),
317    MainSz = wxBoxSizer:new(?wxHORIZONTAL),
318
319    SourceCtrl = create_mods_list_ctrl(Panel,
320				       MainSz,
321				       ?source,
322				       "",
323				       whitelist_add,
324				       blacklist_add),
325    wxSizer:add(MainSz, wxStaticLine:new(Panel, [{style, ?wxLI_VERTICAL}]),
326		[{border, 2}, {flag, ?wxALL bor ?wxEXPAND}]),
327    WhiteCtrl = create_mods_list_ctrl(Panel,
328				      MainSz,
329				      ?whitelist,
330				      "",
331				      whitelist_del,
332				      blacklist_add),
333    wxSizer:add(MainSz, wxStaticLine:new(Panel, [{style, ?wxLI_VERTICAL}]),
334		[{border, 2}, {flag, ?wxALL bor ?wxEXPAND}]),
335    BlackCtrl = create_mods_list_ctrl(Panel,
336				      MainSz,
337				      ?blacklist,
338				      "",
339				      whitelist_add,
340				      blacklist_del),
341    wxSizer:add(MainSz, wxStaticLine:new(Panel, [{style, ?wxLI_VERTICAL}]),
342		[{border, 2}, {flag, ?wxALL bor ?wxEXPAND}]),
343    DerivedCtrl = create_mods_list_ctrl(Panel,
344					MainSz,
345					?derived,
346					"",
347					whitelist_add,
348					blacklist_add),
349    S2 = S#state{mods_source_ctrl  = SourceCtrl,
350                 mods_white_ctrl   = WhiteCtrl,
351                 mods_black_ctrl   = BlackCtrl,
352                 mods_derived_ctrl = DerivedCtrl},
353    redraw_mods(S2, Derived),
354    wxPanel:setSizer(Panel, MainSz),
355    wxNotebook:addPage(S2#state.book, Panel, "Modules", []),
356    S2.
357
358create_mods_list_ctrl(Panel, OuterSz, Title, AppText, Tick, Cross) ->
359    ListCtrl = wxListCtrl:new(Panel,
360                              [{style,
361                                ?wxLC_REPORT bor
362                                %% ?wxLC_SORT_ASCENDING bor
363                                %% ?wxLC_SINGLE_SEL bor
364                                ?wxHSCROLL bor
365				?wxVSCROLL}]),
366    ToolTip = "Select module(s) or open separate module "
367	"window with a double click.",
368    wxListCtrl:setToolTip(ListCtrl, ToolTip),
369
370    %% Prep images
371    reltool_utils:assign_image_list(ListCtrl),
372
373    %% Prep column label
374    ListItem  = wxListItem:new(),
375    wxListItem:setAlign(ListItem, ?wxLIST_FORMAT_LEFT),
376    wxListItem:setText(ListItem, Title),
377    wxListCtrl:insertColumn(ListCtrl, ?MODS_MOD_COL, ListItem),
378    %% wxListCtrl:setColumnWidth(ListCtrl, ?MODS_MOD_COL, ?MODS_MOD_COL_WIDTH),
379    Prop =
380        case AppText =/= "" of
381            true  ->
382                wxListItem:setText(ListItem, AppText),
383                wxListCtrl:insertColumn(ListCtrl, ?MODS_APP_COL, ListItem),
384                %% wxListCtrl:setColumnWidth(ListCtrl, ?MODS_APP_COL,
385		%% ?MODS_APP_COL_WIDTH),
386                2;
387            false ->
388                1
389        end,
390    wxListItem:destroy(ListItem),
391
392    ButtonSz = wxBoxSizer:new(?wxHORIZONTAL),
393    create_button(Panel, ButtonSz, ListCtrl, Title, "wxART_TICK_MARK", Tick),
394    create_button(Panel, ButtonSz, ListCtrl, Title, "wxART_CROSS_MARK", Cross),
395    wxEvtHandler:connect(ListCtrl, size,
396			 [{skip, true}, {userData, mods_list_ctrl}]),
397    wxListCtrl:connect(ListCtrl, command_list_item_activated,
398		       [{userData, open_mod}]),
399    wxWindow:connect(ListCtrl, enter_window),
400    InnerSz = wxBoxSizer:new(?wxVERTICAL),
401    wxSizer:add(InnerSz, ListCtrl,
402                [{border, 2},
403                 {flag, ?wxALL bor ?wxEXPAND},
404                 {proportion, 1}]),
405    wxSizer:add(InnerSz, ButtonSz,
406                [{flag, ?wxALL bor ?wxEXPAND}]),
407    wxSizer:add(OuterSz, InnerSz,
408                [{flag, ?wxALL bor ?wxEXPAND},
409                 {proportion, Prop}]),
410    ListCtrl.
411
412create_button(_Panel, Sizer, _ListCtrl, _Title, _BitMapName, undefined) ->
413    wxSizer:addStretchSpacer(Sizer);
414create_button(Panel, Sizer, ListCtrl, Title, BitMapName, Action) ->
415    %% InnerSz = wxBoxSizer:new(?wxVERTICAL),
416    BitMap = wxArtProvider:getBitmap(BitMapName),
417    Button = wxBitmapButton:new(Panel, ?wxID_ANY, BitMap, []),
418    ToolTip = action_to_tool_tip(Title, Action),
419    wxBitmapButton:setToolTip(Button, ToolTip),
420    %% wxSizer:add(InnerSz, Button, [{flag, ?wxALIGN_CENTER_HORIZONTAL}]),
421    Opts = [{userData, {mod_button, Action, ListCtrl}}],
422    wxEvtHandler:connect(Button, command_button_clicked, Opts),
423    wxSizer:add(Sizer, Button,
424                [{border, 2},
425                 {flag, ?wxALL bor ?wxALIGN_CENTER_HORIZONTAL},
426                 {proportion, 1}]).
427
428action_to_tool_tip(Label, Action) ->
429    case Action of
430	whitelist_add when Label =:= ?whitelist ->
431	    "Remove selected module(s) from whitelist.";
432	whitelist_add ->
433	    "Add selected module(s) to whitelist.";
434	whitelist_del ->
435	    "Remove selected module(s)from whitelist.";
436	blacklist_add when Label =:= ?blacklist ->
437	    "Remove selected module(s) from blacklist.";
438	blacklist_add ->
439	    "Add selected module(s) to blacklist.";
440	blacklist_del ->
441	    "Remove selected module(s) from blacklist."
442    end.
443
444create_config_page(#state{app = App} = S) ->
445    Panel = wxPanel:new(S#state.book, []),
446    TopSizer = wxBoxSizer:new(?wxVERTICAL),
447
448    %% Source dirs
449    {LatestRadio, SelectedRadio, SourceBox} =
450	create_double_box(Panel,
451			  TopSizer,
452			  "Source selection policy",
453			  "Use latest version",
454			  use_latest_vsn,
455			  "Use selected version",
456			  use_selected_vsn,
457			  "Directories",
458			  App#app.sorted_dirs,
459			  version),
460
461    InclSizer = wxBoxSizer:new(?wxHORIZONTAL),
462
463    %% Application inclusion
464    {AppGlobalRadio, AppLocalRadio, AppLocalBox} =
465	create_double_box(Panel,
466			  InclSizer,
467			  "Application inclusion policy",
468			  "Use global config",
469			  global_incl_cond,
470			  "Use application specific config",
471			  local_incl_cond,
472			  "Application specific",
473			  reltool_utils:incl_conds(),
474			  incl_cond),
475
476    %% Module inclusion
477    {ModGlobalRadio, ModLocalRadio, ModLocalBox} =
478	create_double_box(Panel,
479			  InclSizer,
480			  "Module inclusion policy",
481			  "Use global config",
482			  global_mod_cond,
483			  "Use application specific config",
484			  local_mod_cond,
485			  "Application specific",
486			  reltool_utils:mod_conds(),
487			  mod_cond),
488    wxSizer:add(TopSizer, InclSizer,
489                [{border, 2}, {flag, ?wxALL bor ?wxEXPAND}, {proportion, 1}]),
490
491    S2 = S#state{config_app_global = AppGlobalRadio,
492                 config_app_local = AppLocalRadio,
493                 config_app_local_box = AppLocalBox,
494		 config_mod_global = ModGlobalRadio,
495                 config_mod_local = ModLocalRadio,
496                 config_mod_local_box = ModLocalBox,
497		 config_latest = LatestRadio,
498		 config_selected = SelectedRadio,
499		 config_source_box = SourceBox},
500    redraw_config(S2),
501    wxPanel:setSizer(Panel, TopSizer),
502    wxNotebook:addPage(S2#state.book, Panel, "Application settings", []),
503    S2.
504
505create_double_box(Panel, Sizer, TopLabel,
506		  OuterText, OuterData,
507		  InnerText, InnerData,
508		  InternalLabel, InternalChoices, InternalChoiceData) ->
509    TopSizer = wxStaticBoxSizer:new(?wxVERTICAL, Panel,
510				    [{label, TopLabel}]),
511    OuterSizer = wxBoxSizer:new(?wxVERTICAL),
512    OuterRadio = wxRadioButton:new(Panel, ?wxID_ANY, OuterText,
513				    [{style, ?wxRB_GROUP}]),
514    wxEvtHandler:connect(OuterRadio, command_radiobutton_selected,
515			 [{userData, OuterData}]),
516    InnerRadio = wxRadioButton:new(Panel, ?wxID_ANY, InnerText),
517    wxEvtHandler:connect(InnerRadio, command_radiobutton_selected,
518			 [{userData, InnerData}]),
519    InnerBox = wxRadioBox:new(Panel,
520			      ?wxID_ANY,
521			      InternalLabel,
522			      ?wxDefaultPosition,
523			      ?wxDefaultSize,
524			      InternalChoices,
525			      []),
526    wxEvtHandler:connect(InnerBox, command_radiobox_selected,
527			 [{userData, InternalChoiceData}]),
528    wxSizer:add(OuterSizer, OuterRadio,
529                [{border, 2}, {flag, ?wxALL bor ?wxEXPAND}]),
530    wxSizer:add(OuterSizer, InnerRadio,
531                [{border, 2}, {flag, ?wxALL bor ?wxEXPAND}]),
532    wxSizer:add(TopSizer, OuterSizer,
533                [{border, 2}, {flag, ?wxALL bor ?wxEXPAND}]),
534    wxSizer:add(TopSizer, InnerBox,
535                [{border, 2}, {flag, ?wxALL bor ?wxEXPAND}, {proportion, 1}]),
536    wxSizer:add(Sizer, TopSizer,
537                [{border, 2}, {flag, ?wxALL bor ?wxEXPAND}, {proportion, 1}]),
538    {OuterRadio, InnerRadio, InnerBox}.
539
540%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
541
542handle_event(#state{sys = Sys, app = App} = S, Wx) ->
543    %% io:format("wx: ~p\n", [Wx]),
544    case Wx of
545	#wx{obj = ObjRef, event = #wxMouse{type = enter_window}} ->
546	    wxWindow:setFocus(ObjRef),
547	    S;
548	#wx{obj= ListCtrl,
549	    userData = mods_list_ctrl,
550	    event = #wxSize{type = size, size = {W, _H}}} ->
551	    HasApps = (wxListCtrl:getColumnCount(ListCtrl) > 1),
552	    case HasApps of
553		false ->
554		    wxListCtrl:setColumnWidth(ListCtrl, ?MODS_MOD_COL, W);
555		true ->
556		    wxListCtrl:setColumnWidth(ListCtrl,
557					      ?MODS_MOD_COL,
558					      (2 * W) div 3),
559		    wxListCtrl:setColumnWidth(ListCtrl, ?MODS_APP_COL, W div 3)
560	    end,
561	    S;
562	#wx{obj = ListCtrl,
563	    userData = apps_list_ctrl,
564	    event = #wxSize{type = size, size = {W, _H}}} ->
565	    wxListCtrl:setColumnWidth(ListCtrl, ?APPS_APP_COL, W),
566	    S;
567        #wx{userData = open_app,
568            obj = ListCtrl,
569            event = #wxList{type = command_list_item_activated,
570			    itemIndex = Pos}} ->
571            AppBase = wxListCtrl:getItemText(ListCtrl, Pos),
572	    {AppName, _AppVsn} = reltool_utils:split_app_name(AppBase),
573            {ok, _AppPid} = reltool_sys_win:open_app(S#state.parent_pid,
574						     AppName),
575            S;
576        #wx{userData = open_mod,
577            obj = ListCtrl,
578            event = #wxList{type = command_list_item_activated,
579			    itemIndex = Pos}} ->
580            ModName = list_to_atom(wxListCtrl:getItemText(ListCtrl, Pos)),
581	    create_mod_window(S, ModName);
582        #wx{userData = global_incl_cond} ->
583            %% Use global setting
584            change_incl_cond(S, App, undefined);
585        #wx{userData = local_incl_cond} ->
586            %% Use app spec setting
587            change_incl_cond(S, App, Sys#sys.incl_cond);
588        #wx{userData = incl_cond,
589            %% Change app spec setting
590            event = #wxCommand{type = command_radiobox_selected,
591                               cmdString = Sel}} ->
592            AppCond = reltool_utils:list_to_incl_cond(Sel),
593            change_incl_cond(S, App, AppCond);
594
595        #wx{userData = global_mod_cond} ->
596            %% Use global setting
597            change_mod_cond(S, App, undefined);
598        #wx{userData = local_mod_cond} ->
599            %% Use app spec setting
600            change_mod_cond(S, App, Sys#sys.mod_cond);
601        #wx{userData = mod_cond,
602            %% Change app spec setting
603            event = #wxCommand{type = command_radiobox_selected,
604                               cmdString = Sel}} ->
605            ModCond = reltool_utils:list_to_mod_cond(Sel),
606            change_mod_cond(S, App, ModCond);
607
608        #wx{userData = use_latest_vsn} ->
609            %% Use latest version
610	    App2 = App#app{use_selected_vsn = undefined},
611	    S2 = change_version(S, App2, App#app.active_dir),
612            redraw_window(S2);
613        #wx{userData = use_selected_vsn} ->
614            %% Use selected version
615	    App2 = App#app{use_selected_vsn = dir},
616	    {ok, App3} = reltool_sys_win:set_app(S#state.parent_pid, App2),
617	    S2 = S#state{app = App3},
618	    redraw_window(S2);
619        #wx{userData = version,
620            event = #wxCommand{type = command_radiobox_selected,
621                               cmdString = ActiveDir}} ->
622            %% Change app source
623	    App2 = App#app{use_selected_vsn = dir},
624	    S2 = change_version(S, App2, ActiveDir),
625            redraw_window(S2);
626        #wx{userData = {mod_button, Action, ListCtrl},
627            event = #wxCommand{type = command_button_clicked}} ->
628            Items = reltool_utils:get_items(ListCtrl),
629	    handle_mod_button(S, Items, Action);
630        _ ->
631            error_logger:format("~w~w got unexpected app event from "
632				"wx:\n\t~tp\n",
633                                [?MODULE, self(), Wx]),
634            S
635    end.
636
637create_mod_window(#state{parent_pid = RelPid, xref_pid = Xref, common = C} = S,
638		  ModName) ->
639    case lists:keysearch(ModName, #mod_win.name, S#state.mod_wins) of
640        false ->
641            WxEnv = wx:get_env(),
642            {ok, Pid} =
643		reltool_mod_win:start_link(WxEnv, Xref, RelPid, C, ModName),
644            MW = #mod_win{name = ModName, pid = Pid},
645            S#state{mod_wins = [MW | S#state.mod_wins]};
646        {value, MW} ->
647            reltool_app_win:raise(MW#mod_win.pid),
648            S
649    end.
650
651handle_mod_button(#state{app = App} = S, Items, Action) ->
652    App2 = lists:foldl(fun(Item, A) -> move_mod(A, Item, Action) end,
653		       App,
654		       Items),
655    {ok, App3} = reltool_sys_win:set_app(S#state.parent_pid, App2),
656    S2 = S#state{app = App3},
657    redraw_window(S2).
658
659move_mod(App, {_ItemNo, ModStr}, Action) ->
660    ModName = list_to_atom(ModStr),
661    Mods = App#app.mods,
662    {value, M} = lists:keysearch(ModName, #mod.name, Mods),
663    AppCond =
664	case Action of
665	    whitelist_add ->
666		case M#mod.incl_cond of
667		    include   -> undefined;
668		    exclude   -> include;
669		    undefined -> include
670		end;
671	    whitelist_del ->
672		undefined;
673	    blacklist_add ->
674		exclude;
675	    blacklist_del ->
676		undefined;
677	    _ ->
678		error_logger:format("~w~w got unexpected mod "
679				    "button event: ~w\n\t ~tp\n",
680				    [?MODULE, self(), ModName, Action]),
681		M#mod.incl_cond
682	end,
683    M2 = M#mod{incl_cond = AppCond},
684    Mods2 = lists:keystore(ModName, #mod.name, Mods, M2),
685    App#app{mods = Mods2}.
686
687change_incl_cond(S, App, NewAppCond) ->
688    App2 = App#app{incl_cond = NewAppCond},
689    {ok, App3} = reltool_sys_win:set_app(S#state.parent_pid, App2),
690    S2 = S#state{app = App3},
691    redraw_window(S2).
692
693change_mod_cond(S, App, NewModCond) ->
694    App2 = App#app{mod_cond = NewModCond},
695    {ok, App3} = reltool_sys_win:set_app(S#state.parent_pid, App2),
696    S2 = S#state{app = App3},
697    redraw_window(S2).
698
699change_version(S, App, NewDir) ->
700    App2 = App#app{active_dir = NewDir,
701		   label = undefined,
702		   vsn = undefined,
703		   info = undefined},
704    {ok, App3} = reltool_sys_win:set_app(S#state.parent_pid, App2),
705    Title = app_title(App3),
706    wxFrame:setTitle(S#state.frame, Title),
707    S#state{app = App3}.
708
709redraw_apps(#state{app = #app{info = AppInfo},
710                   app_used_by_ctrl = UsedByCtrl,
711                   app_required_ctrl = RequiredCtrl,
712                   app_incl_ctrl = InclCtrl,
713                   app_uses_ctrl = UsesCtrl,
714		   xref_pid = Xref},
715	    {_SourceMods,
716	     _WhiteMods,
717	     _BlackMods,
718	     _DerivedMods,
719	     UsedByMods,
720	     UsesMods}) ->
721    UsedByApps =
722	lists:usort([{M#mod.app_name, Image} || {Image, _, M} <- UsedByMods]),
723    Select =
724	fun(AppName) ->
725		{ok, App} = reltool_server:get_app(Xref, AppName),
726		case App#app.status of
727		    missing -> {AppName, ?ERR_IMAGE};
728		    ok      -> {AppName, ?TICK_IMAGE}
729		end
730	end,
731    RequiredApps = lists:sort(lists:map(Select, AppInfo#app_info.applications)),
732    InclApps = lists:map(Select, AppInfo#app_info.incl_apps),
733    UsesApps =
734	lists:usort([{M#mod.app_name, Image} || {Image, _, M} <- UsesMods]),
735    do_redraw_apps(UsedByCtrl, UsedByApps),
736    do_redraw_apps(RequiredCtrl, RequiredApps),
737    do_redraw_apps(InclCtrl, InclApps),
738    do_redraw_apps(UsesCtrl, UsesApps),
739    ok.
740
741do_redraw_apps(ListCtrl, []) ->
742    wxListCtrl:deleteAllItems(ListCtrl);
743    %% wxListCtrl:setColumnWidth(ListCtrl, ?APPS_APP_COL,
744%% ?wxLIST_AUTOSIZE_USEHEADER);
745do_redraw_apps(ListCtrl, AppImages) ->
746    wxListCtrl:deleteAllItems(ListCtrl),
747    Add =
748        fun({AppName, ImageId}, {Row, Prev}) when AppName =/= Prev ->
749                wxListCtrl:insertItem(ListCtrl, Row, ""),
750                if (Row rem 2) =:= 0 ->
751                        wxListCtrl:setItemBackgroundColour(ListCtrl,
752							   Row,
753							   {240,240,255});
754                   true ->
755                        ignore
756                end,
757                Str = atom_to_list(AppName),
758                wxListCtrl:setItem(ListCtrl,
759				   Row,
760				   ?APPS_APP_COL,
761				   Str,
762				   [{imageId, ImageId}]),
763                {Row + 1, AppName};
764	   ({_, _}, Acc) ->
765		Acc
766        end,
767    wx:foldl(Add, {0, undefined}, AppImages).
768
769%% print(X, X, Format, Args) ->
770%%     io:format(Format, Args);
771%% print(_, _, _, _) ->
772%%     ok.
773
774redraw_mods(#state{mods_source_ctrl = SourceCtrl,
775                   mods_white_ctrl = WhiteCtrl,
776                   mods_black_ctrl = BlackCtrl,
777                   mods_derived_ctrl = DerivedCtrl,
778                   deps_used_by_ctrl = UsedByCtrl,
779                   deps_uses_ctrl = UsesCtrl,
780		   app = #app{is_pre_included = IsPre, is_included = IsIncl},
781		   status_bar = Bar},
782	    {SourceMods,
783	     WhiteMods,
784	     BlackMods,
785	     DerivedMods,
786	     UsedByMods,
787	     UsesMods}) ->
788    InclStatus =
789	case IsIncl of
790	    true when IsPre =:= true -> "Whitelist - ";
791	    true -> "Derived - ";
792	    false -> "Blacklist - ";
793	    undefined -> "Source - "
794	end,
795    Status = lists:concat([InclStatus,
796			   length(WhiteMods), " whitelisted modules and ",
797			   length(DerivedMods), " derived modules."]),
798    wxStatusBar:setStatusText(Bar, Status),
799    opt_redraw_mods(SourceCtrl, SourceMods),
800    opt_redraw_mods(WhiteCtrl, WhiteMods),
801    opt_redraw_mods(BlackCtrl, BlackMods),
802    opt_redraw_mods(DerivedCtrl, DerivedMods),
803    opt_redraw_mods(UsedByCtrl, UsedByMods),
804    opt_redraw_mods(UsesCtrl, UsesMods).
805
806app_to_mods(#state{xref_pid = Xref, app = App}) ->
807    SourceMods  = [M || M <- App#app.mods,
808			M#mod.is_included =/= true,
809			M#mod.is_pre_included =/= false],
810    WhiteMods = [M || M <- App#app.mods,
811                      M#mod.is_pre_included =:= true],
812    BlackMods = [M || M <- App#app.mods,
813                      M#mod.is_pre_included =:= false],
814    DerivedMods  = [M || M <- App#app.mods,
815                         M#mod.is_included =:= true,
816                         M#mod.is_pre_included =/= true],
817    GetMod =
818        fun(ModName) when is_atom(ModName) ->
819                {ok, M} = reltool_server:get_mod(Xref, ModName),
820		if
821		    M#mod.app_name =:= App#app.name,
822		    M#mod.is_included =:= true ->
823			false;
824		    true ->
825			{true, M}
826		end
827        end,
828    UsedByMods = lists:zf(GetMod, App#app.used_by_mods),
829    UsesMods = lists:zf(GetMod, App#app.uses_mods),
830    {
831     [select_image(source,    M) || M <- SourceMods],
832     [select_image(whitelist, M) || M <- WhiteMods],
833     [select_image(blacklist, M) || M <- BlackMods],
834     [select_image(derived,   M) || M <- DerivedMods],
835     [select_image(used_by,   M) || M <- UsedByMods],
836     [select_image(uses,      M) || M <- UsesMods]
837    }.
838
839select_image(Kind, M) ->
840    Image =
841	case Kind of
842	    blacklist when M#mod.status =:= missing ->
843		?WARN_IMAGE;
844	    source when M#mod.status =:= missing ->
845		?WARN_IMAGE;
846	    _ when M#mod.status =:= missing ->
847		?ERR_IMAGE;
848	    blacklist when M#mod.incl_cond =:= exclude ->
849		?CROSS_IMAGE;
850	    blacklist ->
851		?SOURCE_IMAGE;
852	    source ->
853		?CROSS_IMAGE;
854	    whitelist when M#mod.incl_cond =:= include ->
855		?TICK_IMAGE;
856	    whitelist ->
857		?SOURCE_IMAGE;
858	    derived ->
859		?TICK_IMAGE;
860	    used_by when M#mod.is_included =:= true ->
861		?TICK_IMAGE;
862	    used_by when M#mod.is_included =:= false ->
863		?WARN_IMAGE;
864	    used_by ->
865		?ERR_IMAGE;
866	    uses when M#mod.is_included =:= true ->
867		?TICK_IMAGE;
868	    uses when M#mod.is_included =:= false ->
869		?WARN_IMAGE;
870	    uses ->
871		?ERR_IMAGE
872        end,
873    {Image, M#mod.app_name, M}.
874
875opt_redraw_mods(undefined, _ImageMods) ->
876    ok;
877opt_redraw_mods(ListCtrl, ImageMods) ->
878    HasApps = (wxListCtrl:getColumnCount(ListCtrl) > 1),
879    do_redraw_mods(ListCtrl, ImageMods, HasApps).
880
881do_redraw_mods(ListCtrl, [], _HasApps) ->
882    wxListCtrl:deleteAllItems(ListCtrl);
883do_redraw_mods(ListCtrl, ImageMods, HasApps) ->
884    wxListCtrl:deleteAllItems(ListCtrl),
885    Add =
886        fun({ImageId, AppName, #mod{name = ModName}}, Row) ->
887                wxListCtrl:insertItem(ListCtrl, Row, ""),
888                if (Row rem 2) =:= 0 ->
889                        wxListCtrl:setItemBackgroundColour(ListCtrl,
890							   Row,
891							   {240,240,255});
892                   true ->
893                        ignore
894                end,
895                wxListCtrl:setItem(ListCtrl,
896				   Row,
897				   ?MODS_MOD_COL,
898				   atom_to_list(ModName),
899				   [{imageId, ImageId}]),
900                case HasApps of
901                    false ->
902                        ok;
903                    true ->
904                        wxListCtrl:setItem(ListCtrl,
905					   Row,
906					   ?MODS_APP_COL,
907					   atom_to_list(AppName),
908					   [{imageId, ImageId}])
909                end,
910                Row + 1
911        end,
912    wx:foldl(Add, 0, lists:sort(ImageMods)).
913
914redraw_config(#state{sys = #sys{incl_cond = GlobalIncl,
915				mod_cond = GlobalSource},
916		     app = #app{incl_cond = LocalIncl,
917				mod_cond = LocalSource,
918				use_selected_vsn = UseSelected,
919				active_dir = ActiveDir,
920				sorted_dirs = SortedDirs},
921		     config_app_global = AppGlobalRadio,
922		     config_app_local = AppLocalRadio,
923		     config_app_local_box = AppLocalBox,
924		     config_mod_global = ModGlobalRadio,
925		     config_mod_local = ModLocalRadio,
926		     config_mod_local_box = ModLocalBox,
927		     config_latest = LatestRadio,
928		     config_selected = SelectedRadio,
929		     config_source_box = SourceBox}) ->
930    redraw_double_box(GlobalIncl,
931		      LocalIncl,
932		      AppGlobalRadio,
933		      AppLocalRadio,
934		      AppLocalBox,
935		      fun reltool_utils:incl_cond_to_index/1),
936    redraw_double_box(GlobalSource,
937		      LocalSource,
938		      ModGlobalRadio,
939		      ModLocalRadio,
940		      ModLocalBox,
941		      fun reltool_utils:mod_cond_to_index/1),
942    redraw_double_box(false,
943		      UseSelected,
944		      LatestRadio,
945		      SelectedRadio,
946		      SourceBox,
947		      fun(false) ->
948			      0;
949			 (_) ->
950			      reltool_utils:elem_to_index(ActiveDir,
951							  SortedDirs) - 1
952		      end).
953
954redraw_double_box(Global, Local, GlobalRadio, LocalRadio, LocalBox, GetChoice) ->
955    AppCond =
956        case Local of
957            undefined ->
958                wxRadioButton:setValue(GlobalRadio, true),
959                wxRadioButton:setValue(LocalRadio, false),
960                wxRadioBox:disable(LocalBox),
961                Global;
962            _ ->
963                wxRadioButton:setValue(GlobalRadio, false),
964                wxRadioButton:setValue(LocalRadio, true),
965                wxRadioBox:enable(LocalBox),
966                Local
967        end,
968    Choice = GetChoice(AppCond),
969    wxRadioBox:setSelection(LocalBox, Choice).
970
971redraw_window(S) ->
972    %% wx_misc:beginBusyCursor(),
973    Derived = app_to_mods(S),
974    redraw_config(S),
975    redraw_mods(S, Derived),
976    redraw_apps(S, Derived),
977    %% wx_misc:endBusyCursor(),
978    S.
979
980%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
981%% sys callbacks
982
983system_continue(_Parent, _Debug, S) ->
984    ?MODULE:loop(S).
985
986system_terminate(Reason, _Parent, _Debug, _S) ->
987    exit(Reason).
988
989system_code_change(S,_Module,_OldVsn,_Extra) ->
990    {ok, S}.
991