diff options
Diffstat (limited to 'lib/reltool/src/reltool_sys_win.erl')
-rw-r--r-- | lib/reltool/src/reltool_sys_win.erl | 1292 |
1 files changed, 1292 insertions, 0 deletions
diff --git a/lib/reltool/src/reltool_sys_win.erl b/lib/reltool/src/reltool_sys_win.erl new file mode 100644 index 0000000000..ea80ab7e85 --- /dev/null +++ b/lib/reltool/src/reltool_sys_win.erl @@ -0,0 +1,1292 @@ +%% +%% %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% + +-module(reltool_sys_win). + +%% Public +-export([start_link/1, get_server/1, set_app/2, open_app/2]). + +%% Internal +-export([init/1, loop/1]). + +%% sys callback functions +-export([ + system_continue/3, + system_terminate/4, + system_code_change/4 + ]). + +-include_lib("wx/include/wx.hrl"). +-include("reltool.hrl"). + +-record(state, + {parent_pid, + server_pid, + app_wins, + sys, + common, + config_file, + target_dir, + boot_dir, + frame, + panel, + book, + rel_book, + lib_tree, + status_bar, + popup_menu, + source, + whitelist, + blacklist, + derived, + fgraph_wins + }). + +-define(WIN_WIDTH, 800). +-define(WIN_HEIGHT, 600). + +-define(CLOSE_ITEM, ?wxID_EXIT). %% Use OS specific version if available +-define(ABOUT_ITEM, ?wxID_ABOUT). %% Use OS specific +-define(CONTENTS_ITEM, 300). +-define(APP_GRAPH_ITEM, 301). +-define(MOD_GRAPH_ITEM, 302). +-define(LOAD_CONFIG_ITEM, 303). +-define(SAVE_CONFIG_NODEF_NODER_ITEM, 304). +-define(SAVE_CONFIG_NODEF_DER_ITEM, 305). +-define(SAVE_CONFIG_DEF_NODER_ITEM, 306). +-define(SAVE_CONFIG_DEF_DER_ITEM, 307). +-define(UNDO_CONFIG_ITEM, 308). +-define(RESET_CONFIG_ITEM, 309). +-define(GEN_REL_FILES_ITEM, 310). +-define(GEN_TARGET_ITEM, 311). + +-define(APP_PAGE, "Applications"). +-define(LIB_PAGE, "Libraries"). +-define(SYS_PAGE, "System settings"). +-define(REL_PAGE, "Releases"). + +-define(APPS_APP_COL, 0). +-define(source, "Available"). +-define(whitelist, "Included"). +-define(blacklist, "Excluded"). +-define(derived, "Derived"). + +-record(root_data, {dir}). +-record(lib_data, {dir, tree, item}). +-record(escript_data, {file, tree, item}). +-record(app_data, {name, dir}). +-record(app_win, {name, pid}). +-record(fgraph_win, {frame, pid}). +-record(root_popup, {dir, choices, tree, item}). +-record(lib_popup, {dir, choices, tree, item}). +-record(escript_popup, {file, choices, tree, item}). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Client + +start_link(Opts) -> + proc_lib:start_link(?MODULE, init, [[{parent, self()} | Opts]], infinity, []). + +get_server(Pid) -> + reltool_utils:call(Pid, get_server). + +set_app(Pid, App) -> + reltool_utils:call(Pid, {set_app, App}). + +open_app(Pid, AppName) -> + reltool_utils:call(Pid, {open_app, AppName}). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Server + +init(Options) -> + try + do_init(Options) + catch + error:Reason -> + exit({Reason, erlang:get_stacktrace()}) + end. + +do_init([{parent, Parent} | Options]) -> + case reltool_server:start_link(Options) of + {ok, ServerPid, C, Sys} -> + process_flag(trap_exit, C#common.trap_exit), + S = #state{parent_pid = Parent, + server_pid = ServerPid, + common = C, + config_file = filename:absname("config.reltool"), + target_dir = filename:absname("reltool_target_dir"), + app_wins = [], + sys = Sys, + fgraph_wins = []}, + wx:new(), + wx:debug(C#common.wx_debug), + S2 = create_window(S), + + %% wx_misc:beginBusyCursor(), + case reltool_server:get_status(ServerPid) of + {ok, Warnings} -> + exit_dialog(Warnings), + {ok, Sys2} = reltool_server:get_sys(ServerPid), + S3 = S2#state{sys = Sys2}, + S5 = wx:batch(fun() -> + Title = atom_to_list(?APPLICATION), + wxFrame:setTitle(S3#state.frame, Title), + %% wxFrame:setMinSize(Frame, {?WIN_WIDTH, ?WIN_HEIGHT}), + wxStatusBar:setStatusText(S3#state.status_bar, "Done."), + S4 = redraw_apps(S3), + redraw_libs(S4) + end), + %% wx_misc:endBusyCursor(), + %% wxFrame:destroy(Frame), + proc_lib:init_ack(S#state.parent_pid, {ok, self()}), + loop(S5); + {error, Reason} -> + io:format("~p(~p): <ERROR> ~p\n", [?MODULE, ?LINE, Reason]), + exit(Reason) + end; + {error, Reason} -> + io:format("~p(~p): <ERROR> ~p\n", [?MODULE, ?LINE, Reason]), + exit(Reason) + end. + +exit_dialog([]) -> + ok; +exit_dialog(Warnings) -> + Question = "Do you want to continue despite these warnings?", + Details = lists:flatten([[W, $\n] || W <- Warnings]), + case question_dialog(Question, Details) of + ?wxID_OK -> + ok; + ?wxID_CANCEL -> + io:format("~p(~p): <ERROR> ~s\n", [?MODULE, ?LINE, Details]), + exit(Details) + end. + +loop(S) -> + receive + {system, From, Msg} -> + Common = S#state.common, + sys:handle_system_msg(Msg, From, S#state.parent_pid, ?MODULE, Common#common.sys_debug, S); + #wx{obj = ObjRef, + event = #wxClose{type = close_window}} = Msg -> + if + ObjRef =:= S#state.frame -> + wxFrame:destroy(ObjRef), + exit(shutdown); + true -> + FWs = S#state.fgraph_wins, + case lists:keysearch(ObjRef, #fgraph_win.frame, FWs) of + {value, FW} -> + reltool_fgraph_win:stop(FW#fgraph_win.pid, shutdown), + wxFrame:destroy(ObjRef), + FWs2 = lists:keydelete(ObjRef, #fgraph_win.frame, FWs), + ?MODULE:loop(S#state{fgraph_wins = FWs2}); + false -> + error_logger:format("~p~p got unexpected message:\n\t~p\n", + [?MODULE, self(), Msg]), + ?MODULE:loop(S) + end + end; + #wx{id = ?CLOSE_ITEM, event = #wxCommand{type = command_menu_selected}, userData = main_window} -> + wxFrame:destroy(S#state.frame), + exit(shutdown); + #wx{event = #wxSize{}} = Wx -> + Wx2 = reltool_utils:get_latest_resize(Wx), + S2 = handle_event(S, Wx2), + ?MODULE:loop(S2); + #wx{} = Wx -> + S2 = handle_event(S, Wx), + ?MODULE:loop(S2); + {call, ReplyTo, Ref, get_server} -> + reltool_utils:reply(ReplyTo, Ref, {ok, S#state.server_pid}), + ?MODULE:loop(S); + {call, ReplyTo, Ref, {set_app, NewApp}} -> + {ok, AnalysedApp, S2} = do_set_app(S, NewApp), + reltool_utils:reply(ReplyTo, Ref, {ok, AnalysedApp}), + ?MODULE:loop(S2); + {call, ReplyTo, Ref, {open_app, AppName}} -> + S2 = do_open_app(S, AppName), + {value, #app_win{pid = AppPid}} = lists:keysearch(AppName, #app_win.name, S2#state.app_wins), + reltool_utils:reply(ReplyTo, Ref, {ok, AppPid}), + ?MODULE:loop(S2); + {'EXIT', Pid, Reason} when Pid =:= S#state.parent_pid -> + [reltool_fgraph_win:stop(FW#fgraph_win.pid, Reason) || FW <- S#state.fgraph_wins], + exit(Reason); + {'EXIT', _Pid, _Reason} = Exit -> + {FWs, AWs} = handle_child_exit(Exit, S#state.fgraph_wins, S#state.app_wins), + ?MODULE:loop(S#state{fgraph_wins = FWs, app_wins = AWs}); + Msg -> + error_logger:format("~p~p got unexpected message:\n\t~p\n", + [?MODULE, self(), Msg]), + ?MODULE:loop(S) + end. + +handle_child_exit({'EXIT', Pid, _Reason} = Exit, FWs, AWs) -> + case lists:keymember(Pid, #fgraph_win.pid, FWs) of + true -> + msg_warning(Exit, forcegraph_window), + {lists:keydelete(Pid, #fgraph_win.pid, FWs), AWs}; + false -> + case lists:keymember(Pid, #app_win.pid, AWs) of + true -> + msg_warning(Exit, application_window), + {FWs, lists:keydelete(Pid, #app_win.pid, AWs)}; + false -> + msg_warning(Exit, unknown), + {FWs, AWs} + end + end. + +msg_warning({'EXIT', _Pid, shutdown}, Type) when Type =/= unknown -> + ok; +msg_warning(Exit, Type) -> + error_logger:format("~p~p got unexpected message (~p):\n\t~p\n", + [?MODULE, self(), Type, Exit]). + +create_window(S) -> + Title = lists:concat([?APPLICATION, " - starting up"]), + Frame = wxFrame:new(wx:null(), ?wxID_ANY, Title, [{size, {?WIN_WIDTH, ?WIN_HEIGHT}}]), + %%wxFrame:setSize(Frame, {?WIN_WIDTH, ?WIN_HEIGHT}), + %% wxFrame:setMinSize(Frame, {?WIN_WIDTH, ?WIN_HEIGHT}), + Bar = wxFrame:createStatusBar(Frame,[]), + wxStatusBar:setStatusText(Bar, "Processing libraries..."), + %% Label = wxStaticText:new(Panel, ?wxID_ANY, Text, [{style, ?wxTE_READONLY}]), + %% Sizer = wxBoxSizer:new(?wxVERTICAL), + %% wxSizer:add(Sizer, Label, [{flag, ?wxEXPAND}, {proportion, 1}]), + %% wxPanel:setSizer(Panel, Sizer), + %% wxSizer:fit(Sizer, Frame), + %% wxSizer:setSizeHints(Sizer, Frame), + + %% Frame = wxFrame:new(wx:null(), ?wxID_ANY, Title, []), + %% Frame = S#state.frame, + wxToolTip:setDelay(3000), + Panel = wxPanel:new(Frame, []), + %% Bar = wxFrame:createStatusBar(Frame,[]), + create_menubar(Frame), + + Book = wxNotebook:new(Panel, ?wxID_ANY, []), + S2 = S#state{frame = Frame, panel = Panel, book = Book, status_bar = Bar}, + S3 = lists:foldl(fun(Fun, Acc) -> Fun(Acc) end, + S2, + [ + fun create_app_page/1, + fun create_lib_page/1, + fun create_main_release_page/1, + fun create_config_page/1 + ]), + Sizer = wxBoxSizer:new(?wxVERTICAL), + wxSizer:add(Sizer, Book, [{flag, ?wxEXPAND}, {proportion, 1}]), + + wxPanel:setSizer(Panel, Sizer), + wxSizer:fit(Sizer, Frame), + wxSizer:setSizeHints(Sizer, Frame), + wxFrame:connect(Frame, close_window), + + wxFrame:show(Frame), + S3. + +create_menubar(Frame) -> + MenuBar = wxMenuBar:new(), + File = wxMenu:new([]), + Help = wxMenu:new([]), + wxMenuBar:append(MenuBar, File, "File" ), + wxMenu:append(File, ?APP_GRAPH_ITEM, "Display application dependency graph" ), + wxMenu:append(File, ?MOD_GRAPH_ITEM, "Display module dependency graph" ), + wxMenu:appendSeparator(File), + wxMenu:append(File, ?RESET_CONFIG_ITEM, "Reset configuration to default" ), + wxMenu:append(File, ?UNDO_CONFIG_ITEM, "Undo configuration (toggle)" ), + wxMenu:append(File, ?LOAD_CONFIG_ITEM, "Load configuration" ), + Save = wxMenu:new(), + wxMenu:append(Save, ?SAVE_CONFIG_NODEF_NODER_ITEM, "Save explicit configuration (neither defaults nor derivates)"), + wxMenu:append(Save, ?SAVE_CONFIG_DEF_NODER_ITEM , "Save configuration defaults (defaults only)"), + wxMenu:append(Save, ?SAVE_CONFIG_NODEF_DER_ITEM, "Save configuration derivates (derivates only))"), + wxMenu:append(Save, ?SAVE_CONFIG_DEF_DER_ITEM, "Save extended configuration (both defaults and derivates)"), + + wxMenu:append(File, ?wxID_ANY, "Save configuration", Save), + wxMenu:appendSeparator(File), + wxMenu:append(File, ?GEN_REL_FILES_ITEM, "Generate rel, script and boot files" ), + wxMenu:append(File, ?GEN_TARGET_ITEM, "Generate target system" ), + wxMenu:appendSeparator(File), + wxMenu:append(File, ?CLOSE_ITEM, "Close" ), + wxMenuBar:append(MenuBar, Help, "Help" ), + wxMenu:append(Help, ?CONTENTS_ITEM, "Contents" ), + wxMenu:append(Help, ?ABOUT_ITEM, "About" ), + wxFrame:setMenuBar(Frame, MenuBar), + wxEvtHandler:connect(Frame, + command_menu_selected, + [{userData, main_window}]), + wxEvtHandler:connect(File, menu_close), + wxEvtHandler:connect(Help, menu_close), + MenuBar. + +create_app_page(#state{book = Book} = S) -> + Panel = wxPanel:new(Book, []), + Sizer = wxBoxSizer:new(?wxHORIZONTAL), + + SourceCtrl = create_app_list_ctrl(Panel, Sizer, ?source, + whitelist_add, blacklist_add), + wxSizer:add(Sizer, + wxStaticLine:new(Panel, [{style, ?wxLI_VERTICAL}]), + [{border, 2}, {flag, ?wxALL bor ?wxEXPAND}]), + WhiteCtrl = create_app_list_ctrl(Panel, Sizer, ?whitelist, + whitelist_del, blacklist_add), + wxSizer:add(Sizer, + wxStaticLine:new(Panel, [{style, ?wxLI_VERTICAL}]), + [{border, 2}, {flag, ?wxALL bor ?wxEXPAND}]), + BlackCtrl = create_app_list_ctrl(Panel, Sizer, ?blacklist, + whitelist_add, blacklist_del), + wxSizer:add(Sizer, + wxStaticLine:new(Panel, [{style, ?wxLI_VERTICAL}]), + [{border, 2}, {flag, ?wxALL bor ?wxEXPAND}]), + DerivedCtrl = create_app_list_ctrl(Panel, Sizer, ?derived, + whitelist_add, blacklist_add), + %% S3 = redraw_apps(S2), + wxPanel:setSizer(Panel, Sizer), + wxNotebook:addPage(Book, Panel, ?APP_PAGE, []), + S#state{source = SourceCtrl, + whitelist = WhiteCtrl, + blacklist = BlackCtrl, + derived = DerivedCtrl}. + +create_app_list_ctrl(Panel, OuterSz, Title, Tick, Cross) -> + %% Create list control + Width = lists:max([100, ?WIN_WIDTH - 40]) div 4, + Height = lists:max([100, ?WIN_HEIGHT - 100]), + ListCtrl = wxListCtrl:new(Panel, + [{style, + ?wxLC_REPORT bor + %% ?wxLC_SORT_ASCENDING bor + %% ?wxLC_SINGLE_SEL bor + ?wxVSCROLL}, + {size, {Width, Height}}]), + ToolTip = "Select application(s) or open separate application window with a double click.", + wxListCtrl:setToolTip(ListCtrl, ToolTip), + + %% Prep images + reltool_utils:assign_image_list(ListCtrl), + + %% Prep column label + ListItem = wxListItem:new(), + wxListItem:setAlign(ListItem, ?wxLIST_FORMAT_LEFT), + wxListItem:setText(ListItem, Title), + wxListCtrl:insertColumn(ListCtrl, ?APPS_APP_COL, ListItem), + wxListItem:destroy(ListItem), + + %% Create button + ButtonSz = wxBoxSizer:new(?wxHORIZONTAL), + create_button(Panel, ButtonSz, ListCtrl, Title, "wxART_TICK_MARK", Tick), + create_button(Panel, ButtonSz, ListCtrl, Title, "wxART_CROSS_MARK", Cross), + + + InnerSz = wxBoxSizer:new(?wxVERTICAL), + wxSizer:add(InnerSz, + ListCtrl, + [{border, 2}, + {flag, ?wxALL bor ?wxEXPAND}, + {proportion, 1}]), + wxSizer:add(InnerSz, + ButtonSz, + [{flag, ?wxEXPAND}]), + wxSizer:add(OuterSz, + InnerSz, + [{flag, ?wxEXPAND}, {proportion, 1}]), + + %% Subscribe on events + wxEvtHandler:connect(ListCtrl, size, [{skip, true}, {userData, app_list_ctrl}]), + wxEvtHandler:connect(ListCtrl, command_list_item_activated), + wxWindow:connect(ListCtrl, enter_window), + + ListCtrl. + +%% create_button(_Panel, Sizer, _ListCtrl, _BitMapName, _Tag, undefined) -> +%% wxSizer:addStretchSpacer(Sizer); +create_button(Panel, Sizer, ListCtrl, Title, BitMapName, Action) -> + BitMap = wxArtProvider:getBitmap(BitMapName), + Button = wxBitmapButton:new(Panel, ?wxID_ANY, BitMap, []), + ToolTip = action_to_tool_tip(Title, Action), + wxBitmapButton:setToolTip(Button, ToolTip), + Options = [{userData, {app_button, Action, ListCtrl}}], + wxEvtHandler:connect(Button, command_button_clicked, Options), + wxSizer:add(Sizer, + Button, + [{border, 2}, + {flag, ?wxALL}, + {proportion, 1}]). + +action_to_tool_tip(Label, Action) -> + case Action of + whitelist_add when Label =:= ?whitelist -> + "Remove selected application(s) from whitelist."; + whitelist_add -> + "Add selected application(s) to whitelist."; + whitelist_del -> + "Remove selected application(s)from whitelist."; + blacklist_add when Label =:= ?blacklist -> + "Remove selected application(s) from blacklist."; + blacklist_add -> + "Add selected application(s) to blacklist."; + blacklist_del -> + "Remove selected application(s) from blacklist." + end. + +create_lib_page(#state{book = Book} = S) -> + Panel = wxPanel:new(Book, []), + Sizer = wxBoxSizer:new(?wxHORIZONTAL), + + Tree = wxTreeCtrl:new(Panel, [{style , ?wxTR_HAS_BUTTONS bor ?wxTR_HIDE_ROOT}]), + ToolTip = "Edit application sources.", + wxBitmapButton:setToolTip(Tree, ToolTip), + + wxFrame:connect(Tree, command_tree_item_activated), + wxFrame:connect(Tree, command_tree_item_right_click), + + wxSizer:add(Sizer, + Tree, + [{border, 2}, + {flag, ?wxALL bor ?wxEXPAND}, + {proportion, 1}]), + wxPanel:setSizer(Panel, Sizer), + wxNotebook:addPage(Book, Panel, ?LIB_PAGE, []), + S#state{lib_tree = Tree}. + +redraw_libs(#state{lib_tree = Tree, sys = Sys} = S) -> + wxTreeCtrl:deleteAllItems(Tree), + + Top = wxTreeCtrl:addRoot(Tree, "Sources", []), + {ok, Erts} = reltool_server:get_app(S#state.server_pid, erts), + append_root(Tree, Top, Sys#sys.root_dir, Erts), + + LibItem = wxTreeCtrl:appendItem(Tree, Top, "Library directories", []), + LibData = #lib_data{dir = undefined, tree = Tree, item = LibItem}, + wxTreeCtrl:setItemData(Tree, LibItem, LibData), + [append_lib(Tree, LibItem, Dir) || Dir <- Sys#sys.lib_dirs], + + EscriptItem = append_item(Tree, Top, "Escript files", undefined), + EscriptData = #escript_data{file = undefined, tree = Tree, item = EscriptItem}, + wxTreeCtrl:setItemData(Tree,EscriptItem, EscriptData), + [append_escript(Tree, EscriptItem, File) || File <- Sys#sys.escripts], + wxTreeCtrl:expand(Tree, LibItem), + wxTreeCtrl:expand(Tree, EscriptItem), + S. + +append_root(Tree, Parent, Dir, Erts) -> + Top = append_item(Tree, Parent, "Root directory", undefined), + Data = #root_data{dir = Dir}, + RootItem = append_item(Tree, Top, Dir, Data), + ErtsItem = append_item(Tree, RootItem, "erts", undefined), + [append_app(Tree, ErtsItem, filename:basename(filename:dirname(D)), D) + || D <- Erts#app.sorted_dirs], + LibItem = append_item(Tree, RootItem, "lib", undefined), + LibDir = filename:join([Dir, "lib"]), + LibDirs = reltool_utils:lib_dirs(LibDir), + AppDirs = lists:sort(fun reltool_utils:app_dir_test/2, LibDirs), + [append_app(Tree, LibItem, D, LibDir) || D <- AppDirs], + wxTreeCtrl:expand(Tree, Top), + RootItem. + +append_lib(Tree, Parent, Dir) -> + Item = wxTreeCtrl:appendItem(Tree, Parent, Dir, []), + Data = #lib_data{dir = Dir, tree = Tree, item = Item}, + wxTreeCtrl:setItemData(Tree, Item, Data), + append_apps(Tree, Item, Dir). + +append_apps(Tree, Item, Dir) -> + AppDirs = lists:sort(fun reltool_utils:app_dir_test/2, + reltool_utils:lib_dirs(Dir)), + [append_app(Tree, Item, D, Dir) || D <- AppDirs], + Item. + +append_app(Tree, Parent, Base, Dir) -> + Data = #app_data{name = Base, dir = Dir}, + append_item(Tree, Parent, Base, Data). + +append_escript(Tree, Parent, File) -> + Data = #escript_data{file = File}, + append_item(Tree, Parent, File, Data). + +append_item(Tree, Parent, Label, Data) -> + Item = wxTreeCtrl:appendItem(Tree, Parent, Label, []), + wxTreeCtrl:setItemData(Tree, Item, Data), + Item. + +create_config_page(#state{sys = Sys, book = Book} = S) -> + Panel = wxPanel:new(Book, []), + Sizer = wxBoxSizer:new(?wxHORIZONTAL), + AppConds = reltool_utils:incl_conds(), + AppBox = wxRadioBox:new(Panel, + ?wxID_ANY, + "Application inclusion policy", + ?wxDefaultPosition, + ?wxDefaultSize, + AppConds, + []), + AppToolTip = "Choose default policy for inclusion of applications. ", + wxBitmapButton:setToolTip(AppBox, AppToolTip), + AppChoice = reltool_utils:incl_cond_to_index(Sys#sys.incl_cond), + wxRadioBox:setSelection(AppBox, AppChoice), + wxEvtHandler:connect(AppBox, command_radiobox_selected, + [{userData, config_incl_cond}]), + ModConds = reltool_utils:mod_conds(), + ModBox = wxRadioBox:new(Panel, + ?wxID_ANY, + "Module inclusion policy", + ?wxDefaultPosition, + ?wxDefaultSize, + ModConds, + []), + ModToolTip = "Choose default policy for module inclusion.", + wxBitmapButton:setToolTip(ModBox, ModToolTip), + + ModChoice = reltool_utils:mod_cond_to_index(Sys#sys.mod_cond), + wxRadioBox:setSelection(ModBox, ModChoice), + wxEvtHandler:connect(ModBox, command_radiobox_selected, + [{userData, config_mod_cond}]), + + wxSizer:add(Sizer, + AppBox, + [{border, 2}, + {flag, ?wxALL bor ?wxEXPAND}, + {proportion, 1}]), + wxSizer:add(Sizer, + ModBox, + [{border, 2}, + {flag, ?wxALL bor ?wxEXPAND}, + {proportion, 1}]), + wxPanel:setSizer(Panel, Sizer), + wxNotebook:addPage(Book, Panel, ?SYS_PAGE, []), + S. + +create_main_release_page(#state{book = Book} = S) -> + Panel = wxPanel:new(Book, []), + RelBook = wxNotebook:new(Panel, ?wxID_ANY, []), + Sizer = wxBoxSizer:new(?wxVERTICAL), + ButtonSizer = wxBoxSizer:new(?wxHORIZONTAL), + + Create = wxButton:new(Panel, ?wxID_ANY, [{label, "Create"}]), + wxButton:setToolTip(Create, "Create a new release."), + wxButton:connect(Create, command_button_clicked, [{userData, create_rel}]), + wxSizer:add(ButtonSizer, Create), + + Delete = wxButton:new(Panel, ?wxID_ANY, [{label, "Delete"}]), + wxButton:setToolTip(Delete, "Delete a release."), + wxButton:connect(Delete, command_button_clicked, [{userData, delete_rel}]), + wxSizer:add(ButtonSizer, Delete), + + View = wxButton:new(Panel, ?wxID_ANY, [{label, "View script"}]), + wxButton:setToolTip(View, "View generated script file."), + wxButton:connect(View, command_button_clicked, [{userData, view_script}]), + wxSizer:add(ButtonSizer, View), + + [add_release_page(RelBook, Rel) || Rel <- (S#state.sys)#sys.rels], + + wxSizer:add(Sizer, RelBook, [{flag, ?wxEXPAND}, {proportion, 1}]), + wxSizer:add(Sizer, ButtonSizer, [{flag, ?wxEXPAND}]), + wxPanel:setSizer(Panel, Sizer), + wxNotebook:addPage(Book, Panel, ?REL_PAGE, []), + S#state{rel_book = RelBook}. + +add_release_page(Book, #rel{name = RelName, rel_apps = RelApps}) -> + Panel = wxPanel:new(Book, []), + Sizer = wxBoxSizer:new(?wxHORIZONTAL), + RelBox = wxRadioBox:new(Panel, + ?wxID_ANY, + "Applications included in the release " ++ RelName, + ?wxDefaultPosition, + ?wxDefaultSize, + [atom_to_list(RA#rel_app.name) || RA <- RelApps], + []), + %% wxRadioBox:setSelection(RelBox, 2), % mandatory + wxEvtHandler:connect(RelBox, command_radiobox_selected, [{userData, {config_rel_cond, RelName}}]), + RelToolTip = "Choose which applications that shall be included in the release resource file.", + wxBitmapButton:setToolTip(RelBox, RelToolTip), + + wxSizer:add(Sizer, + RelBox, + [{border, 2}, + {flag, ?wxALL bor ?wxEXPAND}, + {proportion, 1}]), + wxPanel:setSizer(Panel, Sizer), + wxNotebook:addPage(Book, Panel, RelName, []). + +do_open_app(S, AppBase) when is_list(AppBase) -> + {AppName, _AppVsn} = reltool_utils:split_app_name(AppBase), + do_open_app(S, AppName); +do_open_app(S, '') -> + S; +do_open_app(#state{server_pid = ServerPid, common = C, app_wins = AppWins} = S, AppName) when is_atom(AppName) -> + case lists:keysearch(AppName, #app_win.name, AppWins) of + false -> + WxEnv = wx:get_env(), + {ok, Pid} = reltool_app_win:start_link(WxEnv, ServerPid, C, AppName), + AW = #app_win{name = AppName, pid = Pid}, + S#state{app_wins = [AW | AppWins]}; + {value, AW} -> + reltool_app_win:raise(AW#app_win.pid), + S + end. + +root_popup(S, Root, Tree, Item) -> + PopupMenu = wxMenu:new(), + wxMenu:append(PopupMenu, 0, "Root dir"), + wxMenu:appendSeparator(PopupMenu), + wxMenu:append(PopupMenu, 1, "Edit"), + Choices = [edit], + wxEvtHandler:connect(PopupMenu, command_menu_selected), + wxEvtHandler:connect(PopupMenu, menu_close), + wxWindow:popupMenu(S#state.frame, PopupMenu), + + Popup = #root_popup{dir = Root, choices = Choices, tree = Tree, item = Item}, + S#state{popup_menu = Popup}. + +lib_popup(S, Lib, Tree, Item) -> + PopupMenu = wxMenu:new(), + wxMenu:append(PopupMenu, 0, "Library dir"), + wxMenu:appendSeparator(PopupMenu), + wxMenu:append(PopupMenu, 1, "Add"), + Choices = + case wxTreeCtrl:getItemData(Tree, Item) of + #lib_data{dir = undefined} -> + [add]; + #lib_data{} -> + wxMenu:append(PopupMenu, 2, "Edit"), + wxMenu:append(PopupMenu, 3, "Delete"), + [add, edit, delete] + end, + wxEvtHandler:connect(PopupMenu, command_menu_selected), + wxEvtHandler:connect(PopupMenu, menu_close), + wxWindow:popupMenu(S#state.frame, PopupMenu), + + Popup = #lib_popup{dir = Lib, choices = Choices, tree = Tree, item = Item}, + S#state{popup_menu = Popup}. + +escript_popup(S, File, Tree, Item) -> + PopupMenu = wxMenu:new(), + wxMenu:append(PopupMenu, 0, "Escript file"), + wxMenu:appendSeparator(PopupMenu), + wxMenu:append(PopupMenu, 1, "Add"), + Choices = + case wxTreeCtrl:getItemData(Tree, Item) of + #escript_data{file = undefined} -> + [add]; + #escript_data{} -> + wxMenu:append(PopupMenu, 2, "Edit"), + wxMenu:append(PopupMenu, 3, "Delete"), + [add, edit, delete] + end, + wxEvtHandler:connect(PopupMenu, command_menu_selected), + wxEvtHandler:connect(PopupMenu, menu_close), + wxWindow:popupMenu(S#state.frame, PopupMenu), + + Popup = #escript_popup{file = File, choices = Choices, tree = Tree, item = Item}, + S#state{popup_menu = Popup}. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +handle_event(S, #wx{id = Id, obj= ObjRef, userData = UserData, event = Event} = _Wx) -> + %% io:format("wx: ~p\n", [Wx]), + case Event of + #wxSize{type = size, size = {W, _H}} when UserData =:= app_list_ctrl -> + wxListCtrl:setColumnWidth(ObjRef, ?APPS_APP_COL, W), + S; + #wxCommand{type = command_menu_selected} when Id =:= ?APP_GRAPH_ITEM -> + update_app_graph(S); + #wxCommand{type = command_menu_selected} when Id =:= ?MOD_GRAPH_ITEM -> + update_mod_graph(S); + #wxCommand{type = command_menu_selected} when Id =:= ?RESET_CONFIG_ITEM -> + reset_config(S); + #wxCommand{type = command_menu_selected} when Id =:= ?UNDO_CONFIG_ITEM -> + undo_config(S); + #wxCommand{type = command_menu_selected} when Id =:= ?LOAD_CONFIG_ITEM -> + load_config(S); + #wxCommand{type = command_menu_selected} when Id =:= ?SAVE_CONFIG_NODEF_NODER_ITEM -> + save_config(S, false, false); + #wxCommand{type = command_menu_selected} when Id =:= ?SAVE_CONFIG_NODEF_DER_ITEM -> + save_config(S, false, true); + #wxCommand{type = command_menu_selected} when Id =:= ?SAVE_CONFIG_DEF_NODER_ITEM -> + save_config(S, true, false); + #wxCommand{type = command_menu_selected} when Id =:= ?SAVE_CONFIG_DEF_DER_ITEM -> + save_config(S, true, true); + #wxCommand{type = command_menu_selected} when Id =:= ?GEN_REL_FILES_ITEM -> + gen_rel_files(S); + #wxCommand{type = command_menu_selected} when Id =:= ?GEN_TARGET_ITEM -> + gen_target(S); + #wxCommand{type = command_menu_selected} when UserData =:= main_window, Id =:= ?CONTENTS_ITEM -> + {file, BeamFile} = code:is_loaded(?MODULE), + EbinDir = filename:dirname(BeamFile), + AppDir = filename:dirname(EbinDir), + HelpFile = filename:join([AppDir, "doc", "html", "index.html"]), + Url = "file://" ++ filename:absname(HelpFile), + wx_misc:launchDefaultBrowser(Url), + S; + #wxCommand{type = command_menu_selected} when UserData =:= main_window, Id =:= ?ABOUT_ITEM -> + AboutStr = "Reltool is a release management tool. It analyses a given" + " Erlang/OTP installation and determines various dependencies" + " between applications. The graphical frontend depicts the" + " dependencies and enables interactive customization of a" + " target system. The backend provides a batch interface" + " for generation of customized target systems.", + MD = wxMessageDialog:new(S#state.frame, + AboutStr, + [{style, ?wxOK bor ?wxICON_INFORMATION}, + {caption, "About Reltool"}]), + wxMessageDialog:showModal(MD), + wxMessageDialog:destroy(MD), + S; + #wxMenu{type = menu_close} -> + S#state{popup_menu = undefined}; + #wxCommand{type = command_menu_selected = Type, cmdString = Str} + when S#state.popup_menu =/= undefined -> + handle_popup_event(S, Type, Id, ObjRef, UserData, Str); + #wxMouse{type = enter_window} -> + wxWindow:setFocus(ObjRef), + S; + _ -> + case wxNotebook:getPageText(S#state.book, wxNotebook:getSelection(S#state.book)) of + ?APP_PAGE -> handle_app_event(S, Event, ObjRef, UserData); + ?LIB_PAGE -> handle_source_event(S, Event, ObjRef, UserData); + ?SYS_PAGE -> handle_system_event(S, Event, ObjRef, UserData); + ?REL_PAGE -> handle_release_event(S, Event, ObjRef, UserData) + end + end. + +handle_popup_event(S, _Type, 0, _ObjRef, _UserData, _Str) -> + S#state{popup_menu = undefined}; +handle_popup_event(#state{popup_menu = #root_popup{dir = OldDir, choices = Choices}, + sys = Sys} = S, + _Type, Pos, _ObjRef, _UserData, _Str) -> + case lists:nth(Pos, Choices) of + edit -> + Style = ?wxFD_OPEN bor ?wxFD_FILE_MUST_EXIST, + case select_dir(S#state.frame, "Change root directory", OldDir, Style) of + {ok, NewDir} when NewDir =:= OldDir -> + %% Same dir.Ignore. + S#state{popup_menu = undefined}; + {ok, NewDir} -> + Sys2 = Sys#sys{root_dir = NewDir}, + do_set_sys(S#state{popup_menu = undefined, sys = Sys2}); + cancel -> + S#state{popup_menu = undefined} + end + end; +handle_popup_event(#state{popup_menu = #lib_popup{dir = OldDir, choices = Choices}, + sys = Sys} = S, + _Type, Pos, _ObjRef, _UserData, _Str) -> + case lists:nth(Pos, Choices) of + add -> + {ok, Cwd} = file:get_cwd(), + Style = ?wxFD_OPEN bor ?wxFD_FILE_MUST_EXIST, + case select_dir(S#state.frame, "Select a library directory to add", Cwd, Style) of + {ok, NewDir} -> + case lists:member(NewDir, Sys#sys.lib_dirs) of + true -> + %% Ignore duplicate. Keep old. + S#state{popup_menu = undefined}; + false -> + LibDirs = Sys#sys.lib_dirs ++ [NewDir], + Sys2 = Sys#sys{lib_dirs = LibDirs}, + do_set_sys(S#state{popup_menu = undefined, sys = Sys2}) + end; + cancel -> + S#state{popup_menu = undefined} + end; + edit -> + Style = ?wxFD_OPEN bor ?wxFD_FILE_MUST_EXIST, + case select_dir(S#state.frame, "Change library directory", OldDir, Style) of + {ok, NewDir} -> + case lists:member(NewDir, Sys#sys.lib_dirs) of + true -> + %% Ignore duplicate. Keep old. + S#state{popup_menu = undefined}; + false -> + Pred = fun(E) -> E =/= OldDir end, + {Before, [_| After]} = + lists:splitwith(Pred, Sys#sys.lib_dirs), + LibDirs2 = Before ++ [NewDir | After], + Sys2 = Sys#sys{lib_dirs = LibDirs2}, + do_set_sys(S#state{popup_menu = undefined, sys = Sys2}) + end; + cancel -> + S#state{popup_menu = undefined} + end; + delete -> + LibDirs = Sys#sys.lib_dirs -- [OldDir], + Sys2 = Sys#sys{lib_dirs = LibDirs}, + do_set_sys(S#state{popup_menu = undefined, sys = Sys2}) + end; +handle_popup_event(#state{popup_menu = #escript_popup{file = OldFile, choices = Choices}, + sys = Sys} = S, + _Type, Pos, _ObjRef, _UserData, _Str) -> + case lists:nth(Pos, Choices) of + add -> + OldFile2 = + case OldFile of + undefined -> + {ok, Cwd} = file:get_cwd(), + filename:join([Cwd, "myEscript"]); + _ -> + OldFile + end, + Style = ?wxFD_OPEN bor ?wxFD_FILE_MUST_EXIST, + case select_file(S#state.frame, "Select an escript file to add", OldFile2, Style) of + {ok, NewFile} -> + case lists:member(NewFile, Sys#sys.escripts) of + true -> + %% Ignore duplicate. Keep old. + S#state{popup_menu = undefined}; + false -> + Escripts = Sys#sys.escripts ++ [NewFile], + Sys2 = Sys#sys{escripts = Escripts}, + do_set_sys(S#state{popup_menu = undefined, sys = Sys2}) + end; + cancel -> + S#state{popup_menu = undefined} + end; + edit -> + Style = ?wxFD_OPEN bor ?wxFD_FILE_MUST_EXIST, + case select_file(S#state.frame, "Change escript file name", OldFile, Style) of + {ok, NewFile} -> + case lists:member(NewFile, Sys#sys.escripts) of + true -> + %% Ignore duplicate. Keep old. + S#state{popup_menu = undefined}; + false -> + Pred = fun(E) -> E =/= OldFile end, + {Before, [_| After]} = lists:splitwith(Pred, Sys#sys.escripts), + Escripts2 = Before ++ [NewFile | After], + Sys2 = Sys#sys{escripts = Escripts2}, + do_set_sys(S#state{popup_menu = undefined, sys = Sys2}) + end; + cancel -> + S#state{popup_menu = undefined} + end; + delete -> + Escripts = Sys#sys.escripts -- [OldFile], + Sys2 = Sys#sys{escripts = Escripts}, + do_set_sys(S#state{popup_menu = undefined, sys = Sys2}) + end. + +handle_system_event(#state{sys = Sys} = S, + #wxCommand{type = command_radiobox_selected, cmdString = Choice}, + _ObjRef, + config_mod_cond) -> + ModCond = reltool_utils:list_to_mod_cond(Choice), + Sys2 = Sys#sys{mod_cond = ModCond}, + do_set_sys(S#state{sys = Sys2}); +handle_system_event(#state{sys = Sys} = S, + #wxCommand{type = command_radiobox_selected, cmdString = Choice}, + _ObjRef, + config_incl_cond) -> + AppCond = reltool_utils:list_to_incl_cond(Choice), + Sys2 = Sys#sys{incl_cond = AppCond}, + do_set_sys(S#state{sys = Sys2}); +handle_system_event(S, Event, ObjRef, UserData) -> + error_logger:format("~p~p got unexpected wx sys event to ~p with user data: ~p\n\t ~p\n", + [?MODULE, self(), ObjRef, UserData, Event]), + S. + +handle_release_event(S, _Event, _ObjRef, UserData) -> + io:format("Release data: ~p\n", [UserData]), + S. + +handle_source_event(S, #wxTree{type = command_tree_item_activated, item = Item}, ObjRef, _UserData) -> + case wxTreeCtrl:getItemData(ObjRef, Item) of + #root_data{dir = _Dir} -> + %% io:format("Root dialog: ~p\n", [Dir]), + S; + #lib_data{dir = _Dir} -> + %% io:format("Lib dialog: ~p\n", [Dir]), + S; + #escript_data{file = _File} -> + %% io:format("Escript dialog: ~p\n", [File]), + S; + #app_data{name = Name} -> + do_open_app(S, Name); + undefined -> + S + end; +handle_source_event(S, #wxTree{type = command_tree_item_right_click, item = Item}, Tree, _UserData) -> + case wxTreeCtrl:getItemData(Tree, Item) of + #root_data{dir = Dir} -> + wx:batch(fun() -> root_popup(S, Dir, Tree, Item) end); + #lib_data{dir = Dir} -> + wx:batch(fun() -> lib_popup(S, Dir, Tree, Item) end); + #escript_data{file = File} -> + wx:batch(fun() -> escript_popup(S, File, Tree, Item) end); + #app_data{name = Name} -> + io:format("App menu: ~p\n", [Name]), + S; + undefined -> + S + end. + +handle_app_event(S, #wxList{type = command_list_item_activated, itemIndex = Pos}, ListCtrl, _UserData) -> + AppName = wxListCtrl:getItemText(ListCtrl, Pos), + do_open_app(S, AppName); +handle_app_event(S, #wxCommand{type = command_button_clicked}, _ObjRef, {app_button, Action, ListCtrl}) -> + Items = reltool_utils:get_items(ListCtrl), + handle_app_button(S, Items, Action); +handle_app_event(S, Event, ObjRef, UserData) -> + error_logger:format("~p~p got unexpected wx app event to ~p with user data: ~p\n\t ~p\n", + [?MODULE, self(), ObjRef, UserData, Event]), + S. + +handle_app_button(#state{server_pid = ServerPid, app_wins = AppWins} = S, Items, Action) -> + NewApps = [move_app(S, Item, Action) || Item <- Items], + case reltool_server:set_apps(ServerPid, NewApps) of + {ok, []} -> + ok; + {ok, Warnings} -> + Msg = lists:flatten([[W, $\n] || W <- Warnings]), + display_message(Msg, ?wxICON_WARNING); + {error, Reason} -> + display_message(Reason, ?wxICON_ERROR) + end, + [ok = reltool_app_win:refresh(AW#app_win.pid) || AW <- AppWins], + redraw_apps(S). + +do_set_sys(#state{sys = Sys, server_pid = ServerPid, status_bar = Bar} = S) -> + wxStatusBar:setStatusText(Bar, "Processing libraries..."), + Status = reltool_server:set_sys(ServerPid, Sys), + check_and_refresh(S, Status). + +move_app(S, {_ItemNo, AppBase}, Action) -> + {AppName, _Vsn} = reltool_utils:split_app_name(AppBase), + {ok, OldApp} = reltool_server:get_app(S#state.server_pid, AppName), + AppCond = + case Action of + whitelist_add -> + case OldApp#app.incl_cond of + include -> undefined; + exclude -> include; + undefined -> include + end; + whitelist_del -> + undefined; + blacklist_add -> + exclude; + blacklist_del -> + undefined; + _ -> + error_logger:format("~p~p got unexpected app button event: ~p ~p\n", + [?MODULE, self(), Action, AppBase]), + OldApp#app.incl_cond + end, + OldApp#app{incl_cond = AppCond}. + +do_set_app(#state{server_pid = ServerPid, app_wins = AppWins} = S, NewApp) -> + {ok, AnalysedApp, Warnings} = reltool_server:set_app(ServerPid, NewApp), + [ok = reltool_app_win:refresh(AW#app_win.pid) || AW <- AppWins], + S2 = redraw_apps(S), + case Warnings of + [] -> + ignore; + _ -> + Msg = lists:flatten([[W, $\n] || W <- Warnings]), + display_message(Msg, ?wxICON_WARNING) + end, + {ok, AnalysedApp, S2}. + +redraw_apps(#state{server_pid = ServerPid, + source = SourceCtrl, + whitelist = WhiteCtrl, + blacklist = BlackCtrl, + derived = DerivedCtrl} = S) -> + {ok, SourceApps} = reltool_server:get_apps(ServerPid, source), + {ok, WhiteApps} = reltool_server:get_apps(ServerPid, whitelist), + {ok, BlackApps} = reltool_server:get_apps(ServerPid, blacklist), + {ok, DerivedApps} = reltool_server:get_apps(ServerPid, derived), + + BadApps = fun(#app{used_by_apps = UsedBy} = A) when UsedBy =/= [] -> + A#app{status = missing}; + (A) -> + A + end, + BlackApps2 = lists:map(BadApps, BlackApps), + redraw_apps(SourceApps, SourceCtrl, ?CROSS_IMAGE, ?WARN_IMAGE), + WhiteN = redraw_apps(WhiteApps, WhiteCtrl, ?TICK_IMAGE, ?ERR_IMAGE), + redraw_apps(BlackApps2, BlackCtrl, ?CROSS_IMAGE, ?WARN_IMAGE), + DerivedN = redraw_apps(DerivedApps, DerivedCtrl, ?TICK_IMAGE, ?ERR_IMAGE), + Status = lists:concat([WhiteN, " whitelisted modules and ", + DerivedN, " derived modules."]), + wxStatusBar:setStatusText(S#state.status_bar, Status), + S. + +redraw_apps(Apps, ListCtrl, OkImage, ErrImage) -> + do_redraw_apps(ListCtrl, Apps, OkImage, ErrImage). + +do_redraw_apps(ListCtrl, [], _OkImage, _ErrImage) -> + wxListCtrl:deleteAllItems(ListCtrl), + 0; +do_redraw_apps(ListCtrl, Apps, OkImage, ErrImage) -> + OldItems = reltool_utils:get_items(ListCtrl), + wxListCtrl:deleteAllItems(ListCtrl), + AddImage = + fun(App) -> + case App#app.status of + ok -> {OkImage, App#app.label, App}; + missing -> {ErrImage, App#app.label, App} + end + end, + ImageApps = lists:map(AddImage, Apps), + Show = + fun({ImageId, Text, App}, {Row, ModCount, Items}) -> + wxListCtrl:insertItem(ListCtrl, Row, ""), + if (Row rem 2) =:= 0 -> + wxListCtrl:setItemBackgroundColour(ListCtrl, Row, {240,240,255}); + true -> + ignore + end, + wxListCtrl:setItem(ListCtrl, Row, ?APPS_APP_COL, Text, [{imageId, ImageId}]), + N = length([M || M <- App#app.mods, M#mod.is_included =:= true]), + {Row + 1, ModCount + N, [{Row, Text} | Items]} + end, + {_, N, NewItems} = wx:foldl(Show, {0, 0, []}, lists:sort(ImageApps)), + reltool_utils:select_items(ListCtrl, OldItems, lists:reverse(NewItems)), + N. + +update_app_graph(S) -> + {ok, WhiteApps} = reltool_server:get_apps(S#state.server_pid, whitelist), + {ok, DerivedApps} = reltool_server:get_apps(S#state.server_pid, derived), + + WhiteNames = [A#app.name || A <- WhiteApps], + DerivedNames = [A#app.name || A <- DerivedApps], + Nodes = WhiteNames ++ DerivedNames, + %% WhiteUses = [N || A <- WhiteApps, N <- A#app.uses_apps, lists:member(N, Nodes)], + %% DerivedUses = [N || A <- DerivedApps, N <- A#app.uses_apps, lists:member(N, Nodes)], + + WhiteLinks = [[A#app.name, U] || A <- WhiteApps, + U <- A#app.uses_apps, + U =/= A#app.name, + lists:member(U, Nodes)], + DerivedLinks = [[A#app.name, U] || A <- DerivedApps, + U <- A#app.uses_apps, + U =/= A#app.name, + lists:member(U, Nodes)], + Links = lists:usort(WhiteLinks ++ DerivedLinks), + %% io:format("Links: ~p\n", [Links]), + Title = lists:concat([?APPLICATION, " - application graph"]), + create_fgraph_window(S, Title, Nodes, Links). + +update_mod_graph(S) -> + {ok, WhiteApps} = reltool_server:get_apps(S#state.server_pid, whitelist), + {ok, DerivedApps} = reltool_server:get_apps(S#state.server_pid, derived), + WhiteMods = lists:usort([M || A <- WhiteApps, M <- A#app.mods, M#mod.is_included =:= true]), + DerivedMods = lists:usort([M || A <- DerivedApps, M <- A#app.mods, M#mod.is_included =:= true]), + + WhiteNames = [M#mod.name || M <- WhiteMods], + DerivedNames = [M#mod.name || M <- DerivedMods], + Nodes = WhiteNames ++ DerivedNames, + + WhiteLinks = [[M#mod.name, U] || M <- WhiteMods, + U <- M#mod.uses_mods, + U =/= M#mod.name, + lists:member(U, Nodes)], + DerivedLinks = [[M#mod.name, U] || M <- DerivedMods, + U <- M#mod.uses_mods, + U =/= M#mod.name, + lists:member(U, Nodes)], + Links = lists:usort(WhiteLinks ++ DerivedLinks), + %% io:format("Links: ~p\n", [Links]), + Title = lists:concat([?APPLICATION, " - module graph"]), + create_fgraph_window(S, Title, Nodes, Links). + +create_fgraph_window(S, Title, Nodes, Links) -> + Frame = wxFrame:new(wx:null(), ?wxID_ANY, Title, []), + wxFrame:setSize(Frame, {?WIN_WIDTH, ?WIN_HEIGHT}), + Panel = wxPanel:new(Frame, []), + Options = [{size, {lists:max([100, ?WIN_WIDTH - 100]), ?WIN_HEIGHT}}], + {Server, Fgraph} = reltool_fgraph_win:new(Panel, Options), + Choose = fun(?MISSING_APP) -> alternate; + (_) -> default + end, + [reltool_fgraph_win:add_node(Server, N, Choose(N)) || N <- Nodes], + [reltool_fgraph_win:add_link(Server, {From, To}) || [From, To] <- Links], + + Sizer = wxBoxSizer:new(?wxVERTICAL), + wxSizer:add(Sizer, Fgraph, [{flag, ?wxEXPAND}, {proportion, 1}]), + wxPanel:setSizer(Panel, Sizer), + %% wxSizer:fit(Sizer, Frame), + %% wxSizer:setSizeHints(Sizer, Frame), + wxFrame:connect(Frame, close_window), + wxFrame:show(Frame), + FW = #fgraph_win{frame = Frame, pid = Server}, + S#state{fgraph_wins = [FW | S#state.fgraph_wins]}. + +reset_config(#state{status_bar = Bar} = S) -> + wxStatusBar:setStatusText(Bar, "Processing libraries..."), + Status = reltool_server:reset_config(S#state.server_pid), + check_and_refresh(S, Status). + +undo_config(#state{status_bar = Bar} = S) -> + wxStatusBar:setStatusText(Bar, "Processing libraries..."), + ok = reltool_server:undo_config(S#state.server_pid), + refresh(S). + +load_config(#state{status_bar = Bar, config_file = OldFile} = S) -> + Style = ?wxFD_OPEN bor ?wxFD_FILE_MUST_EXIST, + case select_file(S#state.frame, "Select a file to load the configuration from", OldFile, Style) of + {ok, NewFile} -> + wxStatusBar:setStatusText(Bar, "Processing libraries..."), + Status = reltool_server:load_config(S#state.server_pid, NewFile), + check_and_refresh(S#state{config_file = NewFile}, Status); + cancel -> + S + end. + +save_config(#state{config_file = OldFile} = S, InclDefaults, InclDerivates) -> + Style = ?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT, + case select_file(S#state.frame, "Select a file to save the configuration to", OldFile, Style) of + {ok, NewFile} -> + Status = reltool_server:save_config(S#state.server_pid, NewFile, InclDefaults, InclDerivates), + check_and_refresh(S#state{config_file = NewFile}, Status); + cancel -> + S + end. + +gen_rel_files(#state{target_dir = OldDir} = S) -> + Style = ?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT, + case select_dir(S#state.frame, "Select a directory to generate rel, script and boot files to", OldDir, Style) of + {ok, NewDir} -> + Status = reltool_server:gen_rel_files(S#state.server_pid, NewDir), + check_and_refresh(S, Status); + cancel -> + S + end. + +gen_target(#state{target_dir = OldDir} = S) -> + Style = ?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT, + case select_dir(S#state.frame, "Select a directory to generate a target system to", OldDir, Style) of + {ok, NewDir} -> + Status = reltool_server:gen_target(S#state.server_pid, NewDir), + check_and_refresh(S#state{target_dir = NewDir}, Status); + cancel -> + S + end. + +select_file(Frame, Message, DefaultFile, Style) -> + Dialog = wxFileDialog:new(Frame, + [{message, Message}, + {defaultDir, filename:dirname(DefaultFile)}, + {defaultFile, filename:basename(DefaultFile)}, + {style, Style}]), + Choice = + case wxMessageDialog:showModal(Dialog) of + ?wxID_CANCEL -> cancel; + ?wxID_OK -> {ok, wxFileDialog:getPath(Dialog)} + end, + wxFileDialog:destroy(Dialog), + Choice. + +select_dir(Frame, Message, DefaultDir, Style) -> + Dialog = wxDirDialog:new(Frame, + [{title, Message}, + {defaultPath, DefaultDir}, + {style, Style}]), + Choice = + case wxMessageDialog:showModal(Dialog) of + ?wxID_CANCEL -> cancel; + ?wxID_OK -> {ok, wxDirDialog:getPath(Dialog)} + end, + wxDirDialog:destroy(Dialog), + Choice. + +check_and_refresh(S, Status) -> + case Status of + ok -> + true; + {ok, Warnings} -> + undo_dialog(S, Warnings); + {error, Reason} when is_list(Reason) -> + display_message(Reason, ?wxICON_ERROR), + false; + {error, Reason} -> + Msg = lists:flatten(io_lib:format("Error:\n\n~p\n", [Reason])), + display_message(Msg, ?wxICON_ERROR), + false + end, + refresh(S). + +refresh(S) -> + {ok, Sys} = reltool_server:get_sys(S#state.server_pid), + [ok = reltool_app_win:refresh(AW#app_win.pid) || AW <- S#state.app_wins], + S2 = S#state{sys = Sys}, + S3 = redraw_libs(S2), + redraw_apps(S3). + +question_dialog(Question, Details) -> + %% Parent = S#state.frame, + Parent = wx:typeCast(wx:null(), wxWindow), + %% [{style, ?wxYES_NO bor ?wxICON_ERROR bor ?wx}]), + DialogStyle = ?wxRESIZE_BORDER bor ?wxCAPTION bor ?wxSYSTEM_MENU bor + ?wxMINIMIZE_BOX bor ?wxMAXIMIZE_BOX bor ?wxCLOSE_BOX, + Dialog = wxDialog:new(Parent, ?wxID_ANY, "Undo dialog", [{style, DialogStyle}]), + Color = wxWindow:getBackgroundColour(Dialog), + TextStyle = ?wxTE_READONLY bor ?wxTE_MULTILINE bor ?wxHSCROLL, + Text1 = wxTextCtrl:new(Dialog, ?wxID_ANY, [{style, ?wxTE_READONLY bor ?wxBORDER_NONE}]), + wxWindow:setBackgroundColour(Text1, Color), + wxTextCtrl:appendText(Text1, Question), + Text2 = wxTextCtrl:new(Dialog, ?wxID_ANY, [{size, {600, 400}}, {style, TextStyle}]), + wxWindow:setBackgroundColour(Text2, Color), + wxTextCtrl:appendText(Text2, Details), + %% wxDialog:setAffirmativeId(Dialog, ?wxID_YES), + %% wxDialog:setEscapeId(Dialog, ?wxID_NO), + Sizer = wxBoxSizer:new(?wxVERTICAL), + wxSizer:add(Sizer, Text1, [{border, 2}, {flag, ?wxEXPAND}]), + wxSizer:add(Sizer, Text2, [{border, 2}, {flag, ?wxEXPAND}, {proportion, 1}]), + ButtSizer = wxDialog:createStdDialogButtonSizer(Dialog, ?wxOK bor ?wxCANCEL), + wxSizer:add(Sizer, ButtSizer, [{border, 2}, {flag, ?wxEXPAND}]), + wxPanel:setSizer(Dialog, Sizer), + wxSizer:fit(Sizer, Dialog), + wxSizer:setSizeHints(Sizer, Dialog), + Answer = wxDialog:showModal(Dialog), + wxDialog:destroy(Dialog), + Answer. + +undo_dialog(_S, []) -> + true; +undo_dialog(S, Warnings) -> + Question = "Do you want to perform the update despite these warnings?", + Details = lists:flatten([[W, $\n] || W <- Warnings]), + case question_dialog(Question, Details) of + ?wxID_OK -> + true; + ?wxID_CANCEL -> + reltool_server:undo_config(S#state.server_pid), + false + end. + +display_message(Message, Icon) -> + Dialog = wxMessageDialog:new(wx:null(), + Message, + [{style, ?wxOK bor Icon}]), + wxMessageDialog:showModal(Dialog), + wxMessageDialog:destroy(Dialog). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% sys callbacks + +system_continue(_Parent, _Debug, S) -> + ?MODULE:loop(S). + +system_terminate(Reason, _Parent, _Debug, _S) -> + exit(Reason). + +system_code_change(S,_Module,_OldVsn,_Extra) -> + {ok, S}. |