-module(gmc_v_netman). -vsn("0.3.1"). -author("Craig Everett "). -copyright("QPQ AG "). -license("GPL-3.0-or-later"). -behavior(wx_object). %-behavior(gmc_v). -include_lib("wx/include/wx.hrl"). -export([to_front/1]). -export([set_manifest/1]). -export([start_link/1]). -export([init/1, terminate/2, code_change/3, handle_call/3, handle_cast/2, handle_info/2, handle_event/2]). -include("$zx_include/zx_logger.hrl"). -include("gmc.hrl"). -record(w, {name = none :: atom(), id = 0 :: integer(), wx = none :: none | wx:wx_object()}). -record(s, {wx = none :: none | wx:wx_object(), frame = none :: none | wx:wx_object(), lang = en :: en | jp, j = none :: none | fun(), prefs = #{} :: map(), buttons = [] :: [#w{}], nets = [] :: [#net{}], book = #w{} :: #w{}}). %%% Interface -spec to_front(Win) -> ok when Win :: wx:wx_object(). to_front(Win) -> wx_object:cast(Win, to_front). -spec set_manifest(Entries) -> ok when Entries :: [ael:conf_meta()]. set_manifest(Entries) -> case is_pid(whereis(?MODULE)) of true -> wx_object:cast(?MODULE, {set_manifest, Entries}); false -> ok end. %%% Startup Functions start_link(Args) -> wx_object:start_link({local, ?MODULE}, ?MODULE, Args, []). init({Prefs, Manifest}) -> Lang = maps:get(lang, Prefs, en_us), Trans = gmc_jt:read_translations(?MODULE), J = gmc_jt:j(Lang, Trans), Wx = wx:new(), Frame = wxFrame:new(Wx, ?wxID_ANY, J("Networks")), MainSz = wxBoxSizer:new(?wxVERTICAL), ButtSz = wxBoxSizer:new(?wxHORIZONTAL), ButtonTemplates = [{node, J("Add Node")}, {drop, J("Delete")}], MakeButton = fun({Name, Label}) -> B = wxButton:new(Frame, ?wxID_ANY, [{label, Label}]), #w{name = Name, id = wxButton:getId(B), wx = B} end, Buttons = lists:map(MakeButton, ButtonTemplates), % FIXME Disable = fun(#w{wx = B}) -> wxButton:disable(B) end, ok = lists:foreach(Disable, Buttons), AddButton = fun(#w{wx = B}) -> wxSizer:add(ButtSz, B, zxw:flags(wide)) end, Notebook = wxNotebook:new(Frame, ?wxID_ANY, [{style, ?wxBK_DEFAULT}]), ok = add_pages(Notebook, J, Manifest), ok = lists:foreach(AddButton, Buttons), _ = wxSizer:add(MainSz, ButtSz, zxw:flags(base)), _ = wxSizer:add(MainSz, Notebook, zxw:flags(wide)), _ = wxFrame:setSizer(Frame, MainSz), _ = wxSizer:layout(MainSz), ok = gmc_v:safe_size(Frame, Prefs), ok = wxFrame:connect(Frame, close_window), ok = wxFrame:connect(Frame, command_button_clicked), ok = wxFrame:center(Frame), true = wxFrame:show(Frame), State = #s{wx = Wx, frame = Frame, j = J, prefs = Prefs, buttons = Buttons, nets = Manifest, book = Notebook}, {Frame, State}. add_pages(Notebook, J, [#net{id = ID, chains = Chains} | Rest]) -> Page = wxScrolledWindow:new(Notebook), PageSz = wxBoxSizer:new(?wxVERTICAL), ChainPanels = draw_chain_panels(Page, J, Chains), AddPanel = fun(Panel) -> wxSizer:add(PageSz, Panel, zxw:flags(wide)) end, ok = lists:foreach(AddPanel, ChainPanels), ok = wxWindow:setSizer(Page, PageSz), ok = wxSizer:layout(PageSz), true = wxNotebook:addPage(Notebook, Page, ID, []), add_pages(Notebook, J, Rest); add_pages(_, _, []) -> ok. draw_chain_panels(Page, J, [#chain{id = ID, coins = Coins, nodes = Nodes} | Rest]) -> Sizer = wxStaticBoxSizer:new(?wxVERTICAL, Page, [{label, ID}]), CurrencySizer = wxBoxSizer:new(?wxHORIZONTAL), CurrencyLabel = wxStaticText:new(Page, ?wxID_ANY, J("Currencies: ")), Currencies = wxStaticText:new(Page, ?wxID_ANY, string:join(Coins, " ")), _ = wxSizer:add(CurrencySizer, CurrencyLabel, zxw:flags(base)), _ = wxSizer:add(CurrencySizer, Currencies, zxw:flags(base)), List = wxListCtrl:new(Page, [{style, ?wxLC_REPORT bor ?wxLC_SINGLE_SEL}]), Labels = [J("Address"), J("External"), J("Internal"), J("Rosetta"), J("Channel"), J("Middleware")], Columns = indexify(Labels), AddColumn = fun({I, L}) -> wxListCtrl:insertColumn(List, I, L, []) end, ok = lists:foreach(AddColumn, Columns), IndexedNodes = indexify(Nodes), AddNodes = fun({Index, #node{ip = IP, external = E, internal = I, rosetta = R, channel = C, mdw = M}}) -> Address = case is_list(IP) of false -> inet:ntoa(IP); true -> IP end, Elements = indexify([Address | stringify_ports([E, I, R, C, M])]), _ = wxListCtrl:insertItem(List, Index, ""), AddText = fun({K, L}) -> wxListCtrl:setItem(List, Index, K, L) end, lists:foreach(AddText, Elements) end, ok = lists:foreach(AddNodes, IndexedNodes), _ = wxListCtrl:setColumnWidth(List, 0, ?wxLIST_AUTOSIZE), _ = wxSizer:add(Sizer, CurrencySizer, zxw:flags(base)), _ = wxSizer:add(Sizer, List, zxw:flags(wide)), [Sizer | draw_chain_panels(Page, J, Rest)]; draw_chain_panels(_, _, []) -> []. indexify(List) -> lists:zip(lists:seq(0, length(List) -1), List). stringify_ports([none | T]) -> ["" | stringify_ports(T)]; stringify_ports([Port | T]) -> [integer_to_list(Port) | stringify_ports(T)]; stringify_ports([]) -> []. %%% OTP callbacks handle_call(Unexpected, From, State) -> ok = log(warning, "Unexpected call from ~tp: ~tp~n", [From, Unexpected]), {noreply, State}. handle_cast(to_front, State = #s{frame = Frame}) -> ok = wxFrame:raise(Frame), {noreply, State}; handle_cast(Unexpected, State) -> ok = log(warning, "Unexpected cast: ~tp~n", [Unexpected]), {noreply, State}. handle_info(Unexpected, State) -> ok = log(warning, "Unexpected info: ~tp~n", [Unexpected]), {noreply, State}. handle_event(E = #wx{event = #wxCommand{type = command_button_clicked}, id = ID}, State = #s{buttons = Buttons}) -> NewState = case lists:keyfind(ID, #w.id, Buttons) of #w{name = node} -> add_node(State); #w{name = drop} -> drop_network(State); false -> tell("Received message: ~w", [E]), State end, {noreply, NewState}; handle_event(#wx{event = #wxClose{}}, State = #s{frame = Frame, prefs = Prefs}) -> Geometry = case wxTopLevelWindow:isMaximized(Frame) of true -> max; false -> {X, Y} = wxWindow:getPosition(Frame), {W, H} = wxWindow:getSize(Frame), {X, Y, W, H} end, NewPrefs = maps:put(geometry, Geometry, Prefs), ok = gmc_con:save(?MODULE, NewPrefs), ok = wxWindow:destroy(Frame), {noreply, State}; handle_event(Event, State) -> ok = tell(info, "Unexpected event ~tp State: ~tp~n", [Event, State]), {noreply, State}. code_change(_, State, _) -> {ok, State}. terminate(Reason, State) -> ok = log(info, "Reason: ~tp, State: ~tp", [Reason, State]), wx:destroy(). %%% Doers add_node(State = #s{frame = Frame, j = J}) -> Dialog = wxDialog:new(Frame, ?wxID_ANY, J("Add Node")), Sizer = wxBoxSizer:new(?wxVERTICAL), AddressSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Address")}]), AddressTx = wxTextCtrl:new(Dialog, ?wxID_ANY), _ = wxSizer:add(AddressSz, AddressTx, zxw:flags(wide)), PortSz = wxBoxSizer:new(?wxHORIZONTAL), Labels = [J("External"), J("Internal"), J("Rosetta"), J("Channel"), J("Middleware")], MakePortCtrl = fun(L) -> Sz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, L}]), Tx = wxTextCtrl:new(Dialog, ?wxID_ANY), _ = wxSizer:add(Sz, Tx, zxw:flags(wide)), _ = wxSizer:add(PortSz, Sz, zxw:flags(wide)), Tx end, PortCtrls = lists:map(MakePortCtrl, Labels), ButtSz = wxBoxSizer:new(?wxHORIZONTAL), Affirm = wxButton:new(Dialog, ?wxID_OK), Cancel = wxButton:new(Dialog, ?wxID_CANCEL), _ = wxBoxSizer:add(ButtSz, Affirm, zxw:flags(wide)), _ = wxBoxSizer:add(ButtSz, Cancel, zxw:flags(wide)), _ = wxSizer:add(Sizer, AddressSz, zxw:flags(base)), _ = wxSizer:add(Sizer, PortSz, zxw:flags(base)), _ = wxSizer:add(Sizer, ButtSz, zxw:flags(wide)), ok = wxDialog:setSizer(Dialog, Sizer), ok = wxBoxSizer:layout(Sizer), ok = wxFrame:setSize(Dialog, {500, 200}), ok = wxFrame:center(Dialog), ok = wxStyledTextCtrl:setFocus(AddressTx), ok = case wxDialog:showModal(Dialog) of ?wxID_OK -> case wxTextCtrl:getValue(AddressTx) of "" -> ok; Address -> add_node2(Address, PortCtrls) end; ?wxID_CANCEL -> ok end, ok = wxDialog:destroy(Dialog), State. add_node2(Address, PortCtrls) -> IP = case inet:parse_address(Address) of {ok, N} -> N; {error, einval} -> Address end, [E, I, R, C, M] = lists:map(fun numerify_port/1, PortCtrls), New = #node{ip = IP, external = E, internal = I, rosetta = R, channel = C, mdw = M}, gmc_con:add_node(New). numerify_port(Ctrl) -> case wxTextCtrl:getValue(Ctrl) of "" -> none; String -> case s_to_i(String) of {ok, PortNum} -> PortNum; error -> none end end. s_to_i(S) -> try I = list_to_integer(S), {ok, I} catch error:badarg -> error end. drop_network(State = #s{nets = Nets, book = Book}) -> ok = case wxNotebook:getSelection(Book) of ?wxNOT_FOUND -> ok; Index -> #net{id = ID} = lists:nth(Index + 1, Nets), gmc_con:drop_network(ID) end, State.