diff --git a/src/gd_con.erl b/src/gd_con.erl index 3f8f64e..8ad4b93 100644 --- a/src/gd_con.erl +++ b/src/gd_con.erl @@ -19,6 +19,7 @@ deploy/3, make_key/6, recover_key/1, mnemonic/1, rename_key/2, drop_key/1, list_keys/0, add_node/1, set_sole_node/1]). +-export([tic/1, update_balance/2]). -export([encrypt/2, decrypt/2]). -export([save/2]). -export([start_link/0, stop/0]). @@ -41,7 +42,7 @@ -record(s, {version = 1 :: integer(), window = none :: none | wx:wx_object(), - timer = none :: none | reference(), + timer = none :: none | {Timer :: reference(), MS :: pos_integer()}, tasks = [] :: [#ui{}], selected = 0 :: non_neg_integer(), wallet = none :: none | #wallet{}, @@ -73,7 +74,7 @@ show_ui(Name) -> Result :: ok | {error, Reason :: term()}. open_wallet(Path, Phrase) -> - gen_server:call(?MODULE, {open_wallet, Path, Phrase}). + gen_server:call(?MODULE, {open_wallet, Path, Phrase}, infinity). -spec close_wallet() -> ok. @@ -278,6 +279,8 @@ list_keys() -> gen_server:call(?MODULE, list_keys). +%%% Network functions + -spec add_node(New) -> ok when New :: #node{}. @@ -292,6 +295,25 @@ set_sole_node(TheOneTrueNode) -> gen_server:cast(?MODULE, {set_sole_node, TheOneTrueNode}). +-spec tic(Interval) -> ok + when Interval :: pos_integer() | stop. + +tic(stop) -> + gen_server:cast(?MODULE, {tic, stop}); +tic(0) -> + gen_server:cast(?MODULE, {tic, stop}); +tic(Interval) when Interval -> + gen_server:cast(?MODULE, {tic, Interval}). + + +-spec update_balance(AccountID, Pucks) -> ok + when AccountID :: gajudesk:id(), + Pucks :: non_neg_integer(). + +update_balance(AccountID, Pucks) -> + gen_server:cast(?MODULE, {update_balance, AccountID, Pucks}). + + %%% Lifecycle functions -spec stop() -> ok. @@ -330,25 +352,30 @@ start_link() -> init(none) -> ok = log(info, "Starting"), process_flag(sensitive, true), - Prefs = read_prefs(), + {FirstRun, Prefs} = read_prefs(), GUI_Prefs = maps:get(gd_gui, Prefs, #{}), Window = gd_gui:start_link(GUI_Prefs), Wallets = get_prefs(wallets, Prefs, []), - T = erlang:send_after(tic(), self(), tic), - State = #s{window = Window, timer = T, wallets = Wallets, prefs = Prefs}, + MS = default_tic(), + T = erlang:send_after(MS, self(), tic), + State = #s{window = Window, timer = {T, MS}, wallets = Wallets, prefs = Prefs}, NewState = do_show_ui(gd_v_wallman, State), - ok = gd_v_wallman:first_run(), + ok = + case FirstRun of + false -> gd_v_wallman:to_front(); + true -> gd_v_wallman:first_run() + end, {ok, NewState}. read_prefs() -> case file:consult(prefs_path()) of - {ok, Prefs} -> proplists:to_map(Prefs); - _ -> #{} + {ok, Prefs} -> {false, proplists:to_map(Prefs)}; + _ -> {true, #{}} end. -tic() -> +default_tic() -> 6000. @@ -472,6 +499,9 @@ handle_cast({add_node, New}, State) -> handle_cast({set_sole_node, TheOneTrueNode}, State) -> NewState = do_set_sole_node(TheOneTrueNode, State), {noreply, NewState}; +handle_cast({tic, Interval}, State) -> + NewState = do_tic(Interval, State), + {noreply, NewState}; handle_cast(stop, State) -> NewState = do_stop(State), {noreply, NewState}; @@ -489,7 +519,7 @@ handle_cast(Unexpected, State) -> %% See: http://erlang.org/doc/man/gen_server.html#Module:handle_info-2 handle_info(tic, State) -> - NewState = do_tic(State), + NewState = handle_tic(State), {noreply, NewState}; handle_info({show_ui, Name}, State) -> NewState = do_show_ui(Name, State), @@ -524,13 +554,7 @@ code_change(_, State, _) -> terminate(Reason, _) -> ok = log(info, "Reason: ~p,", [Reason]), - case whereis(gmc_con) of - undefined -> - zx:stop(); - PID -> - ok = log(info, "gd_con found at: ~p", [PID]), - application:stop(gajumine) - end. + zx:stop(). @@ -617,11 +641,12 @@ do_refresh2(ChainID, State = #s{wallet = W = #wallet{poas = POAs}}) -> check_balance(ChainID) -> - fun(This = #poa{id = ID}) -> + fun(This = #poa{id = ID, balances = [#balance{coin = "gaju", total = OldBalance}]}) -> Pucks = case hz:acc(ID) of {ok, #{"balance" := P}} -> P; - {error, "Account not found"} -> 0 + {error, "Account not found"} -> 0; + {error, timeout} -> OldBalance end, Dist = [{ChainID, Pucks}], Gaju = #balance{coin = "gaju", total = Pucks, dist = Dist}, @@ -629,6 +654,7 @@ check_balance(ChainID) -> end. + ensure_hz_set(Node = #node{ip = IP, external = Port}) -> case hz:chain_nodes() of [{IP, Port}] -> @@ -833,7 +859,7 @@ do_spend(#spend_tx{sender_id = SenderID, Nonce, Payload, NetworkID), - tell(info, "Outcome: ~p", [Outcome]); + tell(info, "SpendTX Outcome: ~p", [Outcome]); false -> log(warning, "Tried do_spend with a bad key: ~p", [SenderID]) end. @@ -856,7 +882,7 @@ do_network(#s{wallet = #wallet{chain_id = ChainID}}) -> -%%% State Operations +%%% Stateless Operations encrypt(Pass, Binary) -> Flags = [{encrypt, true}, {padding, pkcs_padding}], @@ -1052,14 +1078,15 @@ do_drop_key(ID, State = #s{wallet = W, wallets = Wallets, pass = Pass}) -> do_open_wallet(Path, Phrase, State) -> Pass = pass(Phrase), case read(Path, Pass) of - {ok, Recovered = #wallet{name = Name, poas = POAs, endpoint = Node}} -> + {ok, Recovered = #wallet{name = Name, poas = POAs, endpoint = _Node}} -> ok = gd_gui:show(POAs), ok = gd_gui:wallet(Name), - ok = - case ensure_hz_set(Node) of - {ok, ChainID} -> gd_gui:chain(ChainID, Node); - Error -> gd_gui:trouble(Error) - end, +% TODO: set_hz/1 should dispatch async to the gd_netman. +% ok = set_hz(Node), +% case ensure_hz_set(Node) of +% {ok, ChainID} -> gd_gui:chain(ChainID, Node); +% Error -> gd_gui:trouble(Error) +% end, {ok, State#s{pass = Pass, wallet = Recovered}}; Error -> {Error, State} @@ -1300,7 +1327,24 @@ read3(T) -> end. -do_tic(State = #s{wallet = This = #wallet{poas = POAs, endpoint = Node}, selected = Selected}) when Selected > 0 -> +do_tic(stop, State = #s{timer = none}) -> + State; +do_tic(stop, State = #s{timer = {T, _}}) -> + _ = erlang:cancel_timer(T), + State#s{timer = none}; +do_tic(MS, State = #s{timer = none}) -> + T = erlang:send_after(MS, self(), tic), + State#s{timer = {T, MS}}; +do_tic(MS, State = #s{timer = {T, _}}) -> + _ = erlang:cancel_timer(T), + T = erlang:send_after(MS, self(), tic), + State#s{timer = {T, MS}}. + +handle_tic(State = #s{wallet = This = #wallet{poas = POAs, endpoint = Node}, + timer = {T, MS}, + selected = Selected}) + when Selected > 0 -> + NewState = case ensure_hz_set(Node) of {ok, ChainID} -> @@ -1314,12 +1358,21 @@ do_tic(State = #s{wallet = This = #wallet{poas = POAs, endpoint = Node}, selecte ok = log(info, "Balance update on tic failed with: ~p", [Error]), State end, - T = erlang:send_after(tic(), self(), tic), - NewState#s{timer = T}; -do_tic(State) -> - T = erlang:send_after(tic(), self(), tic), - State#s{timer = T}. - + ok = cancel_timer(T), + NewT = erlang:send_after(MS, self(), tic), + NewState#s{timer = {NewT, MS}}; +handle_tic(State = #s{timer = {T, MS}}) -> + ok = cancel_timer(T), + NewT = erlang:send_after(MS, self(), tic), + State#s{timer = {NewT, MS}}; +handle_tic(State) -> + State. + +cancel_timer(T) -> + case erlang:cancel_timer(T) of + false -> ok; + R -> log(warning, "Tic timers are doubled up. Remaining: ~wms", [R]) + end. persist(Prefs) -> diff --git a/src/gd_gui.erl b/src/gd_gui.erl index df5137d..e152c2a 100644 --- a/src/gd_gui.erl +++ b/src/gd_gui.erl @@ -38,7 +38,7 @@ accounts = [] :: [gajudesk:poa()], picker = none :: none | wx:wx_object(), id = {#w{}, #w{}} :: labeled(), - balance = {#w{}, #w{}} :: labeled(), + balance = #w{} :: #w{}, buttons = [] :: [widget()], history = #h{} :: #h{}}). @@ -92,21 +92,24 @@ init(Prefs) -> VSN = proplists:get_value(vsn, ?MODULE:module_info(attributes)), Wx = wx:new(), Frame = wxFrame:new(Wx, ?wxID_ANY, AppName ++ " v" ++ VSN), + Panel = wxWindow:new(Frame, ?wxID_ANY), + TopSz = wxBoxSizer:new(?wxVERTICAL), + _ = wxBoxSizer:add(TopSz, Panel, zxw:flags(wide)), MainSz = wxBoxSizer:new(?wxVERTICAL), - Picker = wxListBox:new(Frame, ?wxID_ANY, [{style, ?wxLC_SINGLE_SEL}]), + Picker = wxListBox:new(Panel, ?wxID_ANY, [{style, ?wxLC_SINGLE_SEL}]), - WallB = wxButton:new(Frame, ?wxID_ANY, [{label, "[none]"}, {style, ?wxBORDER_NONE}]), + WallB = wxButton:new(Panel, ?wxID_ANY, [{label, "[none]"}, {style, ?wxBORDER_NONE}]), WallW = #w{name = wallet, id = wxButton:getId(WallB), wx = WallB}, - ChainB = wxButton:new(Frame, ?wxID_ANY, [{label, "[ChainID]"}, {style, ?wxBORDER_NONE}]), + ChainB = wxButton:new(Panel, ?wxID_ANY, [{label, "[ChainID]"}, {style, ?wxBORDER_NONE}]), _ = wxButton:disable(ChainB), ChainW = #w{name = chain, id = wxButton:getId(ChainB), wx = ChainB}, - NodeB = wxButton:new(Frame, ?wxID_ANY, [{label, "[Node]"}, {style, ?wxBORDER_NONE}]), + NodeB = wxButton:new(Panel, ?wxID_ANY, [{label, "[Node]"}, {style, ?wxBORDER_NONE}]), NodeW = #w{name = node, id = wxButton:getId(NodeB), wx = NodeB}, - DevB = wxButton:new(Frame, ?wxID_ANY, [{label, "𝑓 () →"}]), + DevB = wxButton:new(Panel, ?wxID_ANY, [{label, "𝑓 () →"}]), DevW = #w{name = dev, id = wxButton:getId(DevB), wx = DevB}, - ID_L = wxStaticText:new(Frame, ?wxID_ANY, J("Account ID: ")), - ID_T = wxStaticText:new(Frame, ?wxID_ANY, ""), + ID_L = wxStaticText:new(Panel, ?wxID_ANY, J("Account ID: ")), + ID_T = wxStaticText:new(Panel, ?wxID_ANY, ""), ID_W = {#w{id = wxStaticText:getId(ID_L), wx = ID_L}, #w{id = wxStaticText:getId(ID_T), wx = ID_T}}, @@ -114,13 +117,9 @@ init(Prefs) -> _ = wxSizer:add(ID_Sz, ID_L, zxw:flags(base)), _ = wxSizer:add(ID_Sz, ID_T, zxw:flags(wide)), - BalanceL = wxStaticText:new(Frame, ?wxID_ANY, "木"), - BalanceT = wxStaticText:new(Frame, ?wxID_ANY, price_to_string(0)), - Balance = - {#w{id = wxStaticText:getId(BalanceL), wx = BalanceL}, - #w{id = wxStaticText:getId(BalanceT), wx = BalanceT}}, + BalanceT = wxStaticText:new(Panel, ?wxID_ANY, hz_format:amount(0)), + Balance = #w{id = wxStaticText:getId(BalanceT), wx = BalanceT}, BalanceSz = wxBoxSizer:new(?wxHORIZONTAL), - _ = wxSizer:add(BalanceSz, BalanceL, zxw:flags(base)), _ = wxSizer:add(BalanceSz, BalanceT, zxw:flags(wide)), NumbersSz = wxBoxSizer:new(?wxVERTICAL), @@ -142,7 +141,7 @@ init(Prefs) -> MakeButton = fun({Name, Label}) -> - B = wxButton:new(Frame, ?wxID_ANY, [{label, Label}]), + B = wxButton:new(Panel, ?wxID_ANY, [{label, Label}]), #w{name = Name, id = wxButton:getId(B), wx = B} end, @@ -190,7 +189,7 @@ init(Prefs) -> #w{wx = Refresh} = lists:keyfind(refresh, #w.name, Buttons), -% HistoryWin = wxScrolledWindow:new(Frame), +% HistoryWin = wxScrolledWindow:new(Panel), % HistorySz = wxBoxSizer:new(?wxVERTICAL), % ok = wxScrolledWindow:setSizerAndFit(HistoryWin, HistorySz), % ok = wxScrolledWindow:setScrollRate(HistoryWin, 5, 5), @@ -202,7 +201,8 @@ init(Prefs) -> _ = wxSizer:add(MainSz, ActionsSz, zxw:flags(base)), _ = wxSizer:add(MainSz, Refresh, zxw:flags(base)), % _ = wxSizer:add(MainSz, HistoryWin, zxw:flags(wide)), - ok = wxFrame:setSizer(Frame, MainSz), + ok = wxWindow:setSizer(Panel, MainSz), + ok = wxFrame:setSizer(Frame, TopSz), ok = wxSizer:layout(MainSz), ok = gd_v:safe_size(Frame, Prefs), @@ -578,8 +578,8 @@ show_mnemonic(Selected, State = #s{frame = Frame, j = J, accounts = Accounts}) - MnemTx = wxTextCtrl:new(Dialog, ?wxID_ANY, Options), _ = wxStaticBoxSizer:add(MnemSz, MnemTx, zxw:flags(wide)), ButtSz = wxBoxSizer:new(?wxHORIZONTAL), - CloseB = wxButton:new(Dialog, ?wxID_CANCEL, [{label, J("Close")}]), - CopyB = wxButton:new(Dialog, ?wxID_OK, [{label, J("Copy to Clipboard")}]), + CloseB = wxButton:new(Dialog, ?wxID_CANCEL, [{label, J("Close")}]), + CopyB = wxButton:new(Dialog, ?wxID_OK, [{label, J("Copy to Clipboard")}]), _ = wxBoxSizer:add(ButtSz, CloseB, zxw:flags(wide)), _ = wxBoxSizer:add(ButtSz, CopyB, zxw:flags(wide)), _ = wxBoxSizer:add(Sizer, MnemSz, zxw:flags(wide)), @@ -607,33 +607,15 @@ rename_key(State = #s{picker = Picker}) -> rename_key(Selected, State = #s{frame = Frame, j = J, accounts = Accounts}) -> #poa{id = ID, name = Name} = lists:nth(Selected, Accounts), - Dialog = wxDialog:new(Frame, ?wxID_ANY, J("New Key")), - Sizer = wxBoxSizer:new(?wxVERTICAL), - NameSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Name (Optional)")}]), - NameTx = wxTextCtrl:new(Dialog, ?wxID_ANY), - ok = wxTextCtrl:setValue(NameTx, Name), - _ = wxStaticBoxSizer:add(NameSz, NameTx, zxw:flags(base)), - 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)), - _ = wxBoxSizer:add(Sizer, NameSz, zxw:flags(base)), - _ = wxBoxSizer:add(Sizer, ButtSz, zxw:flags(wide)), - ok = wxDialog:setSizer(Dialog, Sizer), - ok = wxBoxSizer:layout(Sizer), - ok = wxDialog:setSize(Dialog, {500, 130}), - ok = wxDialog:center(Dialog), - ok = wxStyledTextCtrl:setFocus(NameTx), + Title = J("Rename Key"), + Label = J("New Name"), + Options = [{label, Label}, {init, Name}, selected, empty], ok = - case wxDialog:showModal(Dialog) of - ?wxID_OK -> - NewName = wxTextCtrl:getValue(NameTx), - gd_con:rename_key(ID, NewName); - ?wxID_CANCEL -> - ok + case zxw_modal_text:show(Frame, Title, Options) of + {ok, ""} -> gd_con:rename_key(ID, binary_to_list(ID)); + {ok, NewName} -> gd_con:rename_key(ID, NewName); + cancel -> ok end, - ok = wxDialog:destroy(Dialog), State. @@ -743,133 +725,29 @@ spend(Selected, State = #s{accounts = Accounts}) -> end. spend2(#poa{id = ID, name = Name}, Nonce, Height, State = #s{frame = Frame, j = J}) -> - Dialog = wxDialog:new(Frame, ?wxID_ANY, J("Transfer"), [{size, {500, 400}}]), - Sizer = wxBoxSizer:new(?wxVERTICAL), - Account = [Name, " (", ID, ")"], - FromTx = wxStaticText:new(Dialog, ?wxID_ANY, Account), - FromSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("From")}]), - _ = wxStaticBoxSizer:add(FromSz, FromTx, zxw:flags(wide)), - - ToTx = wxTextCtrl:new(Dialog, ?wxID_ANY), - ToSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("To")}]), - _ = wxStaticBoxSizer:add(ToSz, ToTx, zxw:flags(wide)), - - AmtTx = wxTextCtrl:new(Dialog, ?wxID_ANY), - AmtSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Amount")}]), - AmtInSz = wxBoxSizer:new(?wxHORIZONTAL), - AmtLabel = wxStaticText:new(Dialog, ?wxID_ANY, "木"), - _ = wxStaticBoxSizer:add(AmtInSz, AmtLabel, zxw:flags(base)), - _ = wxStaticBoxSizer:add(AmtInSz, AmtTx, zxw:flags(wide)), - _ = wxStaticBoxSizer:add(AmtSz, AmtInSz, zxw:flags(wide)), - - DataTx = wxTextCtrl:new(Dialog, ?wxID_ANY, [{style, ?wxTE_MULTILINE}]), - DataSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Message (optional)")}]), - _ = wxStaticBoxSizer:add(DataSz, DataTx, zxw:flags(wide)), - - Style = [{style, ?wxSL_HORIZONTAL bor ?wxSL_LABELS}], - - TTL_Sl = wxSlider:new(Dialog, ?wxID_ANY, 100, 10, 1000, Style), - TTL_Sz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("TTL")}]), - _ = wxStaticBoxSizer:add(TTL_Sz, TTL_Sl, zxw:flags(wide)), - - Min = hz:min_gas_price(), - Max = Min * 2, - GasSl = wxSlider:new(Dialog, ?wxID_ANY, Min, Min, Max, Style), - GasSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Gas Price")}]), - _ = wxStaticBoxSizer:add(GasSz, GasSl, zxw:flags(wide)), - - 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)), - - _ = wxBoxSizer:add(Sizer, FromSz, zxw:flags(base)), - _ = wxBoxSizer:add(Sizer, ToSz, zxw:flags(base)), - _ = wxBoxSizer:add(Sizer, AmtSz, zxw:flags(base)), - _ = wxBoxSizer:add(Sizer, DataSz, zxw:flags(wide)), - _ = wxBoxSizer:add(Sizer, TTL_Sz, zxw:flags(wide)), - _ = wxBoxSizer:add(Sizer, GasSz, zxw:flags(base)), - _ = wxBoxSizer:add(Sizer, ButtSz, zxw:flags(base)), - ok = wxDialog:setSizer(Dialog, Sizer), - ok = wxBoxSizer:layout(Sizer), - ok = wxFrame:setSize(Dialog, {500, 450}), - ok = wxFrame:center(Dialog), + Args = {Account, J}, ok = - case wxDialog:showModal(Dialog) of - ?wxID_OK -> + case gd_m_spend:show(Frame, Args) of + {ok, Partial = #spend_tx{ttl = TTL}} -> TX = - #spend_tx{sender_id = ID, - recipient_id = wxTextCtrl:getValue(ToTx), - amount = wxTextCtrl:getValue(AmtTx), - gas_price = wxSlider:getValue(GasSl), - gas = 20000, - ttl = Height + wxSlider:getValue(TTL_Sl), - nonce = Nonce, - payload = wxTextCtrl:getValue(DataTx)}, - clean_spend(TX); - ?wxID_CANCEL -> + Partial#spend_tx{sender_id = ID, + gas = 20000, + ttl = Height + TTL, + nonce = Nonce}, + gd_con:spend(TX); + cancel -> ok end, - ok = wxDialog:destroy(Dialog), State. -clean_spend(#spend_tx{recipient_id = ""}) -> - ok; -clean_spend( TX = #spend_tx{amount = S}) when is_list(S) -> - case string_to_price(S) of - {ok, Amount} -> clean_spend(TX#spend_tx{amount = Amount}); - {error, _} -> ok - end; -clean_spend(TX = #spend_tx{gas_price = S}) when is_list(S) -> - case is_int(S) of - true -> clean_spend(TX#spend_tx{gas_price = list_to_integer(S)}); - false -> ok - end; -clean_spend(TX = #spend_tx{gas = S}) when is_list(S) -> - case is_int(S) of - true -> clean_spend(TX#spend_tx{gas = list_to_integer(S)}); - false -> ok - end; -clean_spend(TX = #spend_tx{payload = S}) when is_list(S) -> - clean_spend(TX#spend_tx{payload = list_to_binary(S)}); -clean_spend(TX) -> - gd_con:spend(TX). - - -is_int(S) -> - lists:all(fun(C) -> $0 =< C andalso C =< $9 end, S). - grids_dialogue(State = #s{frame = Frame, j = J}) -> - Dialog = wxDialog:new(Frame, ?wxID_ANY, J("GRIDS URL")), - Sizer = wxBoxSizer:new(?wxVERTICAL), - Label = J("GRIDS URL"), - URL_Sz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J(Label)}]), - URL_Tx = wxTextCtrl:new(Dialog, ?wxID_ANY), - _ = wxStaticBoxSizer:add(URL_Sz, URL_Tx, zxw:flags(wide)), - 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)), - _ = wxBoxSizer:add(Sizer, URL_Sz, zxw:flags(base)), - _ = wxBoxSizer:add(Sizer, ButtSz, zxw:flags(wide)), - ok = wxDialog:setSizer(Dialog, Sizer), - ok = wxBoxSizer:layout(Sizer), - ok = wxDialog:setSize(Dialog, {500, 130}), - ok = wxDialog:center(Dialog), - ok = wxStyledTextCtrl:setFocus(URL_Tx), + Title = J("GRIDS URL"), ok = - case wxDialog:showModal(Dialog) of - ?wxID_OK -> - case wxTextCtrl:getValue(URL_Tx) of - "" -> ok; - String -> gd_con:grids(String) - end; - ?wxID_CANCEL -> - ok + case zxw_modal_text:show(Frame, Title) of + {ok, String} -> gd_con:grids(String); + cancel -> ok end, State. @@ -882,13 +760,13 @@ handle_button(Name, State) -> do_selection(Selected, State = #s{prefs = Prefs, accounts = Accounts, - balance = {_, #w{wx = B}}, id = {_, #w{wx = I}}}) + balance = #w{wx = B}, id = {_, #w{wx = I}}}) when Selected < length(Accounts) -> OneBasedIndex = Selected + 1, #poa{id = ID, balances = Balances} = lists:nth(OneBasedIndex, Accounts), [#balance{total = Pucks}] = Balances, ok = wxStaticText:setLabel(I, ID), - ok = wxStaticText:setLabel(B, price_to_string(Pucks)), + ok = wxStaticText:setLabel(B, hz_format:amount(Pucks)), ok = gd_con:selected(OneBasedIndex), NewPrefs = maps:put(selected, Selected, Prefs), State#s{prefs = NewPrefs}; @@ -915,7 +793,7 @@ do_show(Accounts, State = #s{sizer = Sizer, prefs = Prefs, picker = Picker}) -> ok = wxSizer:layout(Sizer), NewState. -clear_account(State = #s{balance = {_, #w{wx = B}}, id = {_, #w{wx = I}}}) -> +clear_account(State = #s{balance = #w{wx = B}, id = {_, #w{wx = I}}}) -> ok = wxStaticText:setLabel(I, ""), ok = wxStaticText:setLabel(B, ""), State. @@ -1160,37 +1038,3 @@ do_grids_mess_sig2(Request = #{"grids" := 1, wxDialog:destroy(Dialog); do_grids_mess_sig2(BadRequest, _) -> tell("Bad request: ~tp", [BadRequest]). - - - - -%%% Helpers - - -price_to_string(Pucks) -> - Gaju = 1_000_000_000_000_000_000, - H = integer_to_list(Pucks div Gaju), - R = Pucks rem Gaju, - case string:strip(lists:flatten(io_lib:format("~18..0w", [R])), right, $0) of - [] -> H; - T -> string:join([H, T], ".") - end. - -string_to_price(String) -> - case string:split(String, ".") of - [H] -> join_price(H, "0"); - [H, T] -> join_price(H, T); - _ -> {error, bad_price} - end. - -join_price(H, T) -> - try - Parts = [H, string:pad(T, 18, trailing, $0)], - Price = list_to_integer(unicode:characters_to_list(Parts)), - case Price < 0 of - false -> {ok, Price}; - true -> {error, negative_price} - end - catch - error:R -> {error, R} - end. diff --git a/src/gd_lib.erl b/src/gd_lib.erl new file mode 100644 index 0000000..4d9d983 --- /dev/null +++ b/src/gd_lib.erl @@ -0,0 +1,15 @@ +%%% @doc +%%% GajuDesk Helper Functions +%%% @end + +-module(gd_lib). + +-export([is_int/1]). + +-spec is_int(string()) -> boolean(). +%% @doc +%% A simple boolean check over whether every character in a string is part of an integer value +%% without the trashy `try .. catch' way. + +is_int(S) -> + lists:all(fun(C) -> $0 =< C andalso C =< $9 end, S). diff --git a/src/gd_m_spend.erl b/src/gd_m_spend.erl new file mode 100644 index 0000000..40690b6 --- /dev/null +++ b/src/gd_m_spend.erl @@ -0,0 +1,284 @@ +%%% @doc +%%% A live modal for creating a SpendTX +%%% @end + +-module(gd_m_spend). +-vsn("0.8.0"). +-author("Craig Everett "). +-copyright("Craig Everett "). +-license("GPL-3.0-or-later"). + +-behavior(zxw_modal). + +-export([show/2]). +-export([init/1, handle_info/2, handle_event/2]). + +-include_lib("wx/include/wx.hrl"). +-include("$zx_include/zx_logger.hrl"). +-include("gd.hrl"). + +-record(s, + {frame = none :: none | wx:wx_object(), + parent = none :: none | wx:wx_object(), + caller = none :: none | pid(), + j = j() :: fun(), + to_tx = none :: none | wx:wx_object(), + amount_tx = none :: none | wx:wx_object(), + payload_tx = none :: none | wx:wx_object(), + ttl_sl = none :: none | wx:wx_object(), + gas_sl = none :: none | wx:wx_object(), + affirm = none :: none | wx:wx_object(), + cancel = none :: none | wx:wx_object()}). + +j() -> + fun(X) -> X end. + + +%%% Interface + +-spec show(Parent, Args) -> {ok, #spend_tx{}} | cancel + when Parent :: wxFrame:wxFrame(), + Args :: {Account :: iolist(), + Nonce :: pos_integer(), + Height :: pos_integer(), + J :: fun()}. + +show(Parent, Args) -> + zxw_modal:show(Parent, ?MODULE, Args). + + + +%% Init + +init({Parent, Caller, {Account, J}}) -> + Frame = wxFrame:new(Parent, ?wxID_ANY, J("Transfer Gajus")), + Panel = wxWindow:new(Frame, ?wxID_ANY), + TopSz = wxBoxSizer:new(?wxVERTICAL), + _ = wxBoxSizer:add(TopSz, Panel, zxw:flags(wide)), + MainSz = wxBoxSizer:new(?wxVERTICAL), + + FromTx = wxStaticText:new(Panel, ?wxID_ANY, Account), + FromSz = wxStaticBoxSizer:new(?wxVERTICAL, Panel, [{label, J("From")}]), + _ = wxStaticBoxSizer:add(FromSz, FromTx, zxw:flags({wide, 5})), + ToTx = wxTextCtrl:new(Panel, ?wxID_ANY), + ToSz = wxStaticBoxSizer:new(?wxVERTICAL, Panel, [{label, J("To")}]), + _ = wxStaticBoxSizer:add(ToSz, ToTx, zxw:flags({wide, 5})), + AmtTx = wxTextCtrl:new(Panel, ?wxID_ANY), + AmtSz = wxStaticBoxSizer:new(?wxVERTICAL, Panel, [{label, J("Amount")}]), + AmtInSz = wxBoxSizer:new(?wxHORIZONTAL), + _ = wxStaticBoxSizer:add(AmtInSz, AmtTx, zxw:flags({wide, 5})), + _ = wxStaticBoxSizer:add(AmtSz, AmtInSz, zxw:flags({wide, 5})), + PayloadTx = wxTextCtrl:new(Panel, ?wxID_ANY, [{style, ?wxTE_MULTILINE}]), + PayloadSz = wxStaticBoxSizer:new(?wxVERTICAL, Panel, [{label, J("Message")}]), + _ = wxStaticBoxSizer:add(PayloadSz, PayloadTx, zxw:flags({wide, 5})), + Style = [{style, ?wxSL_HORIZONTAL bor ?wxSL_LABELS}], + TTL_Sl = wxSlider:new(Panel, ?wxID_ANY, 100, 10, 1000, Style), + TTL_Sz = wxStaticBoxSizer:new(?wxVERTICAL, Panel, [{label, J("TTL")}]), + _ = wxStaticBoxSizer:add(TTL_Sz, TTL_Sl, zxw:flags({wide, 5})), + Min = hz:min_gas_price(), + Max = Min * 2, + GasSl = wxSlider:new(Panel, ?wxID_ANY, Min, Min, Max, Style), + GasSz = wxStaticBoxSizer:new(?wxVERTICAL, Panel, [{label, J("Gas Price")}]), + _ = wxStaticBoxSizer:add(GasSz, GasSl, zxw:flags({wide, 5})), + ButtSz = wxBoxSizer:new(?wxHORIZONTAL), + Affirm = wxButton:new(Panel, ?wxID_ANY, [{label, J("OK")}]), + Cancel = wxButton:new(Panel, ?wxID_ANY, [{label, J("Cancel")}]), + _ = wxBoxSizer:add(ButtSz, Affirm, zxw:flags({wide, 5})), + _ = wxBoxSizer:add(ButtSz, Cancel, zxw:flags({wide, 5})), + _ = wxBoxSizer:add(MainSz, FromSz, zxw:flags({base, 5})), + _ = wxBoxSizer:add(MainSz, ToSz, zxw:flags({base, 5})), + _ = wxBoxSizer:add(MainSz, AmtSz, zxw:flags({base, 5})), + _ = wxBoxSizer:add(MainSz, PayloadSz, zxw:flags({wide, 5})), + _ = wxBoxSizer:add(MainSz, TTL_Sz, zxw:flags({base, 5})), + _ = wxBoxSizer:add(MainSz, GasSz, zxw:flags({base, 5})), + _ = wxBoxSizer:add(MainSz, ButtSz, zxw:flags({base, 5})), + HandleKey = key_handler(ToTx, AmtTx, PayloadTx, TTL_Sl, GasSl, Affirm, Cancel), + ok = wxFrame:connect(Frame, key_down, [{callback, HandleKey}]), + ok = wxTextCtrl:connect(ToTx, key_down, [{callback, HandleKey}]), + ok = wxTextCtrl:connect(AmtTx, key_down, [{callback, HandleKey}]), + ok = wxTextCtrl:connect(PayloadTx, key_down, [{callback, HandleKey}]), + ok = wxTextCtrl:connect(TTL_Sl, key_down, [{callback, HandleKey}]), + ok = wxTextCtrl:connect(GasSl, key_down, [{callback, HandleKey}]), + ok = wxTextCtrl:connect(Affirm, key_down, [{callback, HandleKey}]), + ok = wxTextCtrl:connect(Cancel, key_down, [{callback, HandleKey}]), + ok = wxFrame:connect(Frame, command_button_clicked), + ok = wxFrame:connect(Frame, close_window), + ok = wxFrame:setSize(Frame, {500, 650}), + ok = wxFrame:center(Frame), + ok = wxWindow:setSizer(Panel, MainSz), + ok = wxFrame:setSizer(Frame, TopSz), + ok = wxBoxSizer:layout(MainSz), + ok = wxFrame:centerOnParent(Frame), + true = wxFrame:show(Frame), + self() ! {focus, to_tx}, + State = + #s{frame = Frame, parent = Parent, caller = Caller, + to_tx = ToTx, amount_tx = AmtTx, payload_tx = PayloadTx, + ttl_sl = TTL_Sl, gas_sl = GasSl, + affirm = Affirm, cancel = Cancel}, + {Frame, State}. + + +key_handler(ToTx, AmtTx, PayloadTx, TTL_Sl, GasSl, Affirm, Cancel) -> + Me = self(), + ToID = wxTextCtrl:getId(ToTx), + AmtID = wxTextCtrl:getId(AmtTx), + PL_ID = wxTextCtrl:getId(PayloadTx), + TTL_ID = wxSlider:getId(TTL_Sl), + GasID = wxSlider:getId(GasSl), + AffirmID = wxButton:getId(Affirm), + CancelID = wxButton:getId(Cancel), + fun(#wx{id = ID, event = #wxKey{type = key_down, keyCode = Code}}, KeyPress) -> + case {Code, wxKeyEvent:shiftDown(KeyPress)} of + {9, false} -> + case ID of + ToID -> Me ! {focus, amount_tx}; + AmtID -> Me ! {focus, payload_tx}; + PL_ID -> Me ! {focus, ttl_sl}; + TTL_ID -> Me ! {focus, gas_sl}; + GasID -> Me ! {focus, affirm}; + AffirmID -> Me ! {focus, cancel}; + CancelID -> Me ! {focus, to_tx}; + _ -> wxEvent:skip(KeyPress) + end; + {9, true} -> + case ID of + ToID -> Me ! {focus, cancel}; + AmtID -> Me ! {focus, to_tx}; + PL_ID -> Me ! {focus, amount_tx}; + TTL_ID -> Me ! {focus, payload_tx}; + GasID -> Me ! {focus, ttl_sl}; + AffirmID -> Me ! {focus, gas_sl}; + CancelID -> Me ! {focus, affirm}; + _ -> wxEvent:skip(KeyPress) + end; + {13, _} -> + case ID of + ToID -> Me ! {tab, to_tx}; + AmtID -> Me ! {tab, amount_tx}; + PL_ID -> Me ! {tab, payload_tx}; + TTL_ID -> Me ! {tab, ttl_sl}; + GasID -> Me ! {tab, gas_sl}; + AffirmID -> Me ! {enter, affirm}; + CancelID -> Me ! {enter, cancel}; + _ -> wxEvent:skip(KeyPress) + end; + {27, _} -> + Me ! esc; + {_, _} -> + wxEvent:skip(KeyPress) + end + end. + + + +handle_info({focus, Element}, State) -> + ok = focus(Element, State), + {noreply, State}; +handle_info({enter, affirm}, State) -> + check(State); +handle_info({enter, cancel}, State) -> + cancel(State); +handle_info(esc, State) -> + ok = cancel(State), + {noreply, State}; +handle_info(Message, State) -> + ok = log(warning, "Message: ~p~nState: ~p~n", [Message, State]), + {noreply, State}. + + +handle_event(#wx{event = #wxCommand{type = command_button_clicked}, id = ID}, + State = #s{affirm = Affirm, cancel = Cancel}) -> + AffirmID = wxButton:getId(Affirm), + CancelID = wxButton:getId(Cancel), + NewState = + case ID of + AffirmID -> check(State); + CancelID -> cancel(State) + end, + {noreply, NewState}; +handle_event(#wx{event = #wxClose{}}, State) -> + NewState = cancel(State), + {noreply, NewState}; +handle_event(Event, State) -> + ok = log(warning, "Unexpected event ~tp State: ~tp~n", [Event, State]), + {noreply, State}. + + +%%% Doers + +focus(to_tx, #s{to_tx = ToTX}) -> wxTextCtrl:setFocus(ToTX); +focus(amount_tx, #s{amount_tx = AmountTX}) -> wxTextCtrl:setFocus(AmountTX); +focus(payload_tx, #s{payload_tx = PayloadTX}) -> wxTextCtrl:setFocus(PayloadTX); +focus(ttl_sl, #s{ttl_sl = TTL_SL}) -> wxSlider:setFocus(TTL_SL); +focus(gas_sl, #s{gas_sl = GasSL}) -> wxSlider:setFocus(GasSL); +focus(affirm, #s{affirm = Affirm}) -> wxButton:setFocus(Affirm); +focus(cancel, #s{cancel = Cancel}) -> wxButton:setFocus(Cancel). + + +cancel(#s{frame = Frame, caller = Caller}) -> + ok = wxFrame:destroy(Frame), + zxw_modal:done(Caller, cancel). + + +check(State =#s{frame = Frame, caller = Caller, j = J, + to_tx = ToTx, amount_tx = AmountTx, payload_tx = PayloadTx, + ttl_sl = TTL_Sl, gas_sl = GasSl}) -> + DirtyTX = + [{recipient_id, wxTextCtrl:getValue(ToTx)}, + {amount, wxTextCtrl:getValue(AmountTx)}, + {gas_price, wxSlider:getValue(GasSl)}, + {ttl, wxSlider:getValue(TTL_Sl)}, + {payload, wxTextCtrl:getValue(PayloadTx)}], + ok = + case clean_spend(DirtyTX, #spend_tx{}, J, []) of + {ok, CleanTX} -> + ok = wxFrame:destroy(Frame), + zxw_modal:done(Caller, {ok, CleanTX}); + {error, Errors} -> + DerpyDerpDerp = form_message(Errors, J), + ok = zxw:show_message(Frame, DerpyDerpDerp), + State + end. + + +% TODO: There should be some suggestive logic around gas prices, both based on how large +% the payload and TTL are, but also the ingoing current common gas rate. This will require +% a "gas station" sort of analysis app to query, though. +clean_spend([{recipient_id, ""} | Rest], TX, J, Errors) -> + NewErrors = [{recipient_id, J("Recipient field is empty.")} | Errors], + clean_spend(Rest, TX, J, NewErrors); +clean_spend([{recipient_id, Recipient} | Rest], TX, J, Errors) -> + clean_spend(Rest, TX#spend_tx{recipient_id = list_to_binary(Recipient)}, J, Errors); +clean_spend([{amount, ""} | Rest], TX, J, Errors) -> + clean_spend(Rest, TX#spend_tx{amount = 0}, J, Errors); +clean_spend([{amount, S} | Rest], TX, J, Errors) -> + {NewTX, NewErrors} = + case hz_format:read(S) of + {ok, Amount} -> + {TX#spend_tx{amount = Amount}, Errors}; + error -> + Derp = J("Amount field is not properly formatted."), + {TX, [{amount, Derp} | Errors]} + end, + clean_spend(Rest, NewTX, J, NewErrors); +clean_spend([{gas_price, GasPrice} | Rest], TX, J, Errors) -> + clean_spend(Rest, TX#spend_tx{gas_price = GasPrice}, J, Errors); +clean_spend([{ttl, TTL} | Rest], TX, J, Errors) -> + clean_spend(Rest, TX#spend_tx{ttl = TTL}, J, Errors); +clean_spend([{payload, S} | Rest], TX, J, Errors) -> + clean_spend(Rest, TX#spend_tx{payload = list_to_binary(S)}, J, Errors); +clean_spend([], TX, _, []) -> + {ok, TX}; +clean_spend([], _, _, Errors) -> + {error, Errors}. + + +form_message(Errors, J) -> + Header = J("The following errors were encountered:"), + unicode:characters_to_list([Header, "\n", assemble(Errors)]). + +% TODO: Highlight the fields since we know them. +assemble([{_, M} | T]) -> [M, "\n" | assemble(T)]; +assemble([]) -> []. + diff --git a/src/gd_m_wallet_importer.erl b/src/gd_m_wallet_importer.erl new file mode 100644 index 0000000..84cd103 --- /dev/null +++ b/src/gd_m_wallet_importer.erl @@ -0,0 +1,184 @@ +%%% @doc +%%% A live modal for importing a wallet. +%%% +%%% Interprets [ENTER] as an "OK" button click and [ESC] as a "Cancel" button click. +%%% @end + +-module(gd_m_wallet_importer). +-vsn("0.8.0"). +-author("Craig Everett "). +-copyright("Craig Everett "). +-license("GPL-3.0-or-later"). + +-behavior(zxw_modal). + +-export([show/6]). +-export([init/1, handle_info/2, handle_event/2]). + +-include_lib("wx/include/wx.hrl"). +-include("$zx_include/zx_logger.hrl"). + +-record(s, + {frame = none :: none | wx:wx_object(), + parent = none :: none | wx:wx_object(), + caller = none :: none | pid(), + name_tx = none :: none | wx:wx_object(), + pass_tx = none :: none | wx:wx_object(), + ok = none :: none | wx:wx_object(), + cancel = none :: none | wx:wx_object()}). + + +%%% Interface + +-spec show(Parent, Title, NameLabel, PassLabel, OK_L, Cancel_L) -> {ok, Name, Pass} | cancel + when Parent :: wxFrame:wxFrame(), + Title :: string(), + NameLabel :: string(), + PassLabel :: string(), + OK_L :: string(), + Cancel_L :: string(), + Name :: string(), + Pass :: string() | none. + +show(Parent, Title, NameLabel, PassLabel, OK_L, Cancel_L) -> + zxw_modal:show(Parent, ?MODULE, {Title, NameLabel, PassLabel, OK_L, Cancel_L}). + + + +%% Init + +init({Parent, Caller, {Title, NameLabel, PassLabel, OK_L, Cancel_L}}) -> + Frame = wxFrame:new(Parent, ?wxID_ANY, Title), + Panel = wxWindow:new(Frame, ?wxID_ANY), + TopSz = wxBoxSizer:new(?wxVERTICAL), + _ = wxBoxSizer:add(TopSz, Panel, zxw:flags(wide)), + MainSz = wxBoxSizer:new(?wxVERTICAL), + NameSz = wxStaticBoxSizer:new(?wxVERTICAL, Panel, [{label, NameLabel}]), + NameTx = wxTextCtrl:new(Panel, ?wxID_ANY, [{style, ?wxTE_PROCESS_TAB}]), + _ = wxSizer:add(NameSz, NameTx, zxw:flags({wide, 5})), + PassSz = wxStaticBoxSizer:new(?wxVERTICAL, Panel, [{label, PassLabel}]), + PassTx = wxTextCtrl:new(Panel, ?wxID_ANY, [{style, ?wxTE_PROCESS_TAB bor ?wxTE_PASSWORD}]), + _ = wxSizer:add(PassSz, PassTx, zxw:flags({wide, 5})), + OK = wxButton:new(Panel, ?wxID_ANY, [{label, OK_L}]), + Cancel = wxButton:new(Panel, ?wxID_ANY, [{label, Cancel_L}]), + ButtSz = wxBoxSizer:new(?wxHORIZONTAL), + _ = wxBoxSizer:add(ButtSz, OK, zxw:flags({wide, 5})), + _ = wxBoxSizer:add(ButtSz, Cancel, zxw:flags({wide, 5})), + _ = wxBoxSizer:add(MainSz, NameSz, zxw:flags({wide, 5})), + _ = wxBoxSizer:add(MainSz, PassSz, zxw:flags({wide, 5})), + _ = wxBoxSizer:add(MainSz, ButtSz, zxw:flags({wide, 5})), + HandleKey = key_handler(NameTx, PassTx, OK, Cancel), + ok = wxFrame:connect(Frame, key_down, [{callback, HandleKey}]), + ok = wxTextCtrl:connect(NameTx, key_down, [{callback, HandleKey}]), + ok = wxTextCtrl:connect(PassTx, key_down, [{callback, HandleKey}]), + ok = wxTextCtrl:connect(OK, key_down, [{callback, HandleKey}]), + ok = wxTextCtrl:connect(Cancel, key_down, [{callback, HandleKey}]), + ok = wxFrame:connect(Frame, command_button_clicked), + ok = wxFrame:connect(Frame, close_window), + ok = wxFrame:setSize(Frame, {500, 220}), + ok = wxWindow:setSizer(Panel, MainSz), + ok = wxFrame:setSizer(Frame, TopSz), + ok = wxBoxSizer:layout(MainSz), + ok = wxFrame:centerOnParent(Frame), + ok = wxTextCtrl:setFocus(NameTx), + true = wxFrame:show(Frame), + State = + #s{frame = Frame, parent = Parent, caller = Caller, + name_tx = NameTx, pass_tx = PassTx, + ok = OK, cancel = Cancel}, + {Frame, State}. + + +key_handler(NameTx, PassTx, OK, Cancel) -> + Me = self(), + NameID = wxTextCtrl:getId(NameTx), + PassID = wxTextCtrl:getId(PassTx), + OK_ID = wxButton:getId(OK), + CancelID = wxButton:getId(Cancel), + fun(#wx{id = ID, event = #wxKey{type = key_down, keyCode = Code}}, KeyPress) -> + case Code of + 9 -> + case ID of + NameID -> Me ! {tab, name}; + PassID -> Me ! {tab, pass}; + OK_ID -> Me ! {tab, ok}; + CancelID -> Me ! {tab, cancel}; + _ -> wxEvent:skip(KeyPress) + end; + 13 -> + case ID of + NameID -> Me ! {enter, name}; + PassID -> Me ! {enter, pass}; + _ -> wxEvent:skip(KeyPress) + end; + 27 -> + Me ! esc; + _ -> + wxEvent:skip(KeyPress) + end + end. + + + +handle_info({tab, Element}, State) -> + ok = tab_traverse(Element, State), + {noreply, State}; +handle_info({enter, name}, State = #s{pass_tx = PassTx}) -> + ok = wxTextCtrl:setFocus(PassTx), + {noreply, State}; +handle_info({enter, pass}, State) -> + done(State); +handle_info(esc, State) -> + ok = cancel(State), + {noreply, State}; +handle_info(Message, State) -> + ok = log(warning, "Message: ~p~nState: ~p~n", [Message, State]), + {noreply, State}. + + +handle_event(#wx{event = #wxCommand{type = command_button_clicked}, id = ID}, + State = #s{ok = OK, cancel = Cancel}) -> + OK_ID = wxButton:getId(OK), + CancelID = wxButton:getId(Cancel), + case ID of + OK_ID -> done(State); + CancelID -> cancel(State) + end; +handle_event(#wx{event = #wxClose{}}, State) -> + NewState = cancel(State), + {noreply, NewState}; +handle_event(Event, State) -> + ok = log(warning, "Unexpected event ~tp State: ~tp~n", [Event, State]), + {noreply, State}. + + +%%% Doers + +tab_traverse(name, #s{pass_tx = PassTx}) -> + wxTextCtrl:setFocus(PassTx); +tab_traverse(pass, #s{ok = OK}) -> + wxButton:setFocus(OK); +tab_traverse(ok, #s{cancel = Cancel}) -> + wxButton:setFocus(Cancel); +tab_traverse(cancel, #s{name_tx = NameTx}) -> + wxTextCtrl:setFocus(NameTx). + + +cancel(#s{frame = Frame, caller = Caller}) -> + ok = wxFrame:destroy(Frame), + zxw_modal:done(Caller, cancel). + + +done(#s{frame = Frame, caller = Caller, name_tx = NameTx, pass_tx = PassTx}) -> + Result = + case wxTextCtrl:getValue(NameTx) of + "" -> + cancel; + Name -> + case wxTextCtrl:getValue(PassTx) of + "" -> {ok, Name, none}; + Pass -> {ok, Name, Pass} + end + end, + ok = wxFrame:destroy(Frame), + zxw_modal:done(Caller, Result). diff --git a/src/gd_sophia_editor.erl b/src/gd_sophia_editor.erl index 1455ac7..9f0604f 100644 --- a/src/gd_sophia_editor.erl +++ b/src/gd_sophia_editor.erl @@ -1,6 +1,6 @@ -module(gd_sophia_editor). -vsn("0.8.0"). --export([new/1, update/2, +-export([new/1, update/1, update/2, get_text/1, set_text/2]). -include("$zx_include/zx_logger.hrl"). @@ -124,6 +124,9 @@ set_colors(STC) -> update(_Event, STC) -> + update(STC). + +update(STC) -> Text = wxStyledTextCtrl:getText(STC), case so_scan:scan(Text) of {ok, Tokens} -> diff --git a/src/gd_v_devman.erl b/src/gd_v_devman.erl index 610491f..e4c2906 100644 --- a/src/gd_v_devman.erl +++ b/src/gd_v_devman.erl @@ -15,7 +15,6 @@ -include("$zx_include/zx_logger.hrl"). -include("gd.hrl"). --define(editorMode, sophia). % Widgets -record(w, @@ -28,7 +27,7 @@ {name = <<"">> :: binary(), call = #w{} :: #w{}, dryrun = #w{} :: none | #w{}, - args = [] :: [{wx:wx_object(), wx:wx_object(), argt()}]}). + args = [] :: [argt()]}). % Code book pages -record(p, @@ -57,7 +56,9 @@ code = {none, []} :: {Codebook :: none | wx:wx_object(), Pages :: [#p{}]}, cons = {none, []} :: {Consbook :: none | wx:wx_object(), Pages :: [#c{}]}}). --type argt() :: int | string | address | list(argt()). +% TODO: Spec HZ AACIs. +-type argt() :: term(). % FIXME: Whatever HZ returns in the AACI as an arg type. + %%% Interface @@ -427,10 +428,9 @@ call_params([{L, C} | T], A) -> end. clicked4(State, - #c{id = ConID, build = #{aci := ACI}, funs = {_, Funs}}, + #c{id = ConID, build = #{aaci := AACI}, funs = {_, Funs}}, {Name, Type}, {PK, Nonce, TTL, GasP, Gas, Amt}) -> - AACI = hz:prepare_aaci(ACI), #f{args = ArgFields} = maps:get(Name, Funs), Args = lists:map(fun get_arg/1, ArgFields), case hz:contract_call(PK, Nonce, Gas, GasP, Amt, TTL, AACI, ConID, Name, Args) of @@ -590,39 +590,34 @@ deploy(State = #s{code = {Codebook, Pages}}) -> State; Index -> #p{code = CodeTx} = lists:nth(Index + 1, Pages), - Source = wxStyledTextCtrl:getText(CodeTx), + Source = gd_sophia_editor:get_text(CodeTx), deploy2(State, Source) end. deploy2(State, Source) -> case compile(Source) of -% Options = sophia_options(), -% case so_compiler:from_string(Source, Options) of % TODO: Make hz accept either the aaci or the aci, preferring the aaci if present {ok, Build} -> - ACI = maps:get(aci, Build), - RawAACI = {aaci, ContractName, Funs, NS} = hz:prepare_aaci(ACI), - {InitSpec, Callable} = maps:take("init", Funs), - AACI = setelement(3, RawAACI, Callable), - Complete = maps:put(aaci, AACI, Build), + {aaci, ContractName, Funs, _} = maps:get(aaci, Build), ok = tell(info, "Deploying Contract: ~p", [ContractName]), - deploy3(State, InitSpec, Complete, NS); + InitSpec = maps:get("init", Funs), + deploy3(State, InitSpec, Build); Other -> ok = tell(info, "Compilation Failed!~n~tp", [Other]), State end. -deploy3(State, InitSpec, Build, NS) -> +deploy3(State, InitSpec, Build) -> case gd_con:list_keys() of {ok, 0, []} -> handle_troubling(State, "No keys exist in the current wallet."); {ok, Selected, Keys} -> - deploy4(State, InitSpec, Build, NS, Selected, Keys); + deploy4(State, InitSpec, Build, Selected, Keys); error -> handle_troubling(State, "No wallet is selected!") end. -deploy4(State = #s{frame = Frame, j = J}, InitSpec, Build, NS, Selected, Keys) -> +deploy4(State = #s{frame = Frame, j = J}, InitSpec, Build, Selected, Keys) -> InitArgs = element(1, InitSpec), Dialog = wxDialog:new(Frame, ?wxID_ANY, J("Deploy Contract")), Sizer = wxBoxSizer:new(?wxVERTICAL), @@ -645,7 +640,7 @@ deploy4(State = #s{frame = Frame, j = J}, InitSpec, Build, NS, Selected, Keys) - GridSz = wxFlexGridSizer:new(2, 4, 4), ok = wxFlexGridSizer:setFlexibleDirection(GridSz, ?wxHORIZONTAL), ok = wxFlexGridSizer:addGrowableCol(GridSz, 1), - ArgFields = make_arg_fields(ScrollWin, GridSz, InitArgs, NS, J), + ArgFields = make_arg_fields(ScrollWin, GridSz, InitArgs), _ = wxStaticBoxSizer:add(FunSz, GridSz, zxw:flags(wide)), _ = wxStaticBoxSizer:add(ScrollSz, FunSz, [{proportion, 1}, {flag, ?wxEXPAND}]), _ = wxStaticBoxSizer:add(ScrollSz, KeySz, [{proportion, 0}, {flag, ?wxEXPAND}]), @@ -691,6 +686,19 @@ deploy5(State, Build, Params, Args) -> State. +make_arg_fields(ScrollWin, GridSz, Args) -> + MakeArgField = + fun({AN, T}) -> + tell(info, "~p: Arg: ~p, Type: ~p", [?LINE, AN, T]), + ANT = wxStaticText:new(ScrollWin, ?wxID_ANY, AN), + TCT = wxTextCtrl:new(ScrollWin, ?wxID_ANY), + _ = wxFlexGridSizer:add(GridSz, ANT, [{proportion, 0}, {flag, ?wxEXPAND}]), + _ = wxFlexGridSizer:add(GridSz, TCT, [{proportion, 0}, {flag, ?wxEXPAND}]), + {T, TCT} + end, + lists:map(MakeArgField, Args). + + open(State = #s{frame = Frame, j = J}) -> Dialog = wxDialog:new(Frame, ?wxID_ANY, J("Open Contract Source")), Sizer = wxBoxSizer:new(?wxVERTICAL), @@ -835,12 +843,9 @@ open_hash2(State, Address) -> open_hash3(State, Address, Source) -> % TODO: Compile on load and verify the deployed hash for validity. - Options = sophia_options(), - case so_compiler:from_string(Source, Options) of - {ok, Build = #{aaci := AACI}} -> - {Defs = #{functions := Funs}, ConIfaces} = find_main(AACI), - Callable = maps:remove("init", Funs), - FunDefs = {maps:put(functions, Callable, Defs), ConIfaces}, + case compile(Source) of + {ok, Build} -> + {aaci, _, FunDefs, _} = maps:get(aaci, Build), ok = tell(info, "Compilation Succeeded!~n~tp~n~n~tp", [Build, FunDefs]), add_code_page(State, {hash, Address}, Source); Other -> @@ -903,7 +908,7 @@ save(State = #s{frame = Frame, j = J, prefs = Prefs, code = {Codebook, Pages}}) _ -> Name ++ ".aes" end, Path = filename:join(Dir, File), - Source = get_source(Widget), + Source = gd_sophia_editor:get_text(Widget), case filelib:ensure_dir(Path) of ok -> case file:write_file(Path, Source) of @@ -931,18 +936,6 @@ save(State = #s{frame = Frame, j = J, prefs = Prefs, code = {Codebook, Pages}}) end end. -get_source(Widget) -> - case ?editorMode of - plain -> wxTextCtrl:getValue(Widget); - sophia -> gd_sophia_editor:get_text(Widget) - end. - -set_source(Widget, Src) -> - case ?editorMode of - plain -> wxTextCtrl:setValue(Widget, Src); - sophia -> gd_sophia_editor:set_text(Widget, Src) - end. - % TODO: Break this down -- tons of things in here recur. rename(State = #s{frame = Frame, j = J, prefs = Prefs, code = {Codebook, Pages}}) -> @@ -974,7 +967,7 @@ rename(State = #s{frame = Frame, j = J, prefs = Prefs, code = {Codebook, Pages}} _ -> Name ++ ".aes" end, NewPath = filename:join(Dir, File), - Source = get_source(Widget), + Source = gd_sophia_editor:get_text(Widget), case filelib:ensure_dir(NewPath) of ok -> case file:write_file(NewPath, Source) of @@ -1020,41 +1013,16 @@ load(State = #s{frame = Frame, j = J}) -> % TODO: Extract the exact compiler version, load it, and use only that or fail if % the specific version is unavailable. % TODO: Compile on load and verify the deployed hash for validity. - Dialog = wxDialog:new(Frame, ?wxID_ANY, J("Retrieve Contract Source")), - Sizer = wxBoxSizer:new(?wxVERTICAL), - AddressSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Address Hash")}]), - AddressTx = wxTextCtrl:new(Dialog, ?wxID_ANY), - _ = wxSizer:add(AddressSz, AddressTx, zxw:flags(wide)), - 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(wide)), - _ = wxSizer:add(Sizer, ButtSz, zxw:flags(wide)), - ok = wxDialog:setSizer(Dialog, Sizer), - ok = wxBoxSizer:layout(Sizer), - ok = wxDialog:setSize(Dialog, {500, 200}), - ok = wxDialog:center(Dialog), - ok = wxTextCtrl:setFocus(AddressTx), - Choice = - case wxDialog:showModal(Dialog) of - ?wxID_OK -> - case wxTextCtrl:getValue(AddressTx) of - "" -> cancel; - A -> {ok, A} - end; - ?wxID_CANCEL -> - cancel - end, - ok = wxDialog:destroy(Dialog), - case Choice of + Title = J("Retrieve Contract Source"), + Label = J("Address Hash"), + case zxw_modal_text:show(Frame, Title, [{label, Label}]) of {ok, Address = "ct_" ++ _} -> load2(State, Address); {ok, Address = "th_" ++ _} -> load_from_tx(State, Address); {ok, Turd} -> handle_troubling(State, {bad_address, Turd}); cancel -> State end. + load_from_tx(State, Address) -> case hz:tx_info(Address) of {ok, #{"call_info" := #{"contract_id" := Contract}}} -> @@ -1081,16 +1049,9 @@ load3(State = #s{tabs = TopBook, cons = {Consbook, Pages}, buttons = Buttons, j PageSz = wxBoxSizer:new(?wxVERTICAL), ProgSz = wxBoxSizer:new(?wxHORIZONTAL), CodeSz = wxStaticBoxSizer:new(?wxVERTICAL, Window, [{label, J("Contract Source")}]), - CodeTxStyle = {style, ?wxTE_MULTILINE - bor ?wxTE_PROCESS_TAB - bor ?wxTE_DONTWRAP - bor ?wxTE_READONLY}, - CodeTx = wxTextCtrl:new(Window, ?wxID_ANY, [CodeTxStyle]), - Mono = wxFont:new(10, ?wxMODERN, ?wxNORMAL, ?wxNORMAL, [{face, "Monospace"}]), - TextAt = wxTextAttr:new(), - ok = wxTextAttr:setFont(TextAt, Mono), - true = wxTextCtrl:setDefaultStyle(CodeTx, TextAt), - ok = set_source(CodeTx, Source), + CodeTx = gd_sophia_editor:new(Window), + ok = gd_sophia_editor:set_text(CodeTx, Source), + ok = gd_sophia_editor:update(CodeTx), _ = wxSizer:add(CodeSz, CodeTx, zxw:flags(wide)), ScrollWin = wxScrolledWindow:new(Window), FunSz = wxStaticBoxSizer:new(?wxVERTICAL, ScrollWin, [{label, J("Function Interfaces")}]), @@ -1100,22 +1061,19 @@ load3(State = #s{tabs = TopBook, cons = {Consbook, Pages}, buttons = Buttons, j ConsTxStyle = {style, ?wxTE_MULTILINE bor ?wxTE_READONLY}, ConsTx = wxTextCtrl:new(Window, ?wxID_ANY, [ConsTxStyle]), _ = wxSizer:add(ConsSz, ConsTx, zxw:flags(wide)), - _ = wxSizer:add(ProgSz, CodeSz, [{proportion, 2}, {flag, ?wxEXPAND}]), + _ = wxSizer:add(ProgSz, CodeSz, [{proportion, 3}, {flag, ?wxEXPAND}]), _ = wxSizer:add(ProgSz, ScrollWin, [{proportion, 1}, {flag, ?wxEXPAND}]), _ = wxSizer:add(PageSz, ProgSz, [{proportion, 3}, {flag, ?wxEXPAND}]), _ = wxSizer:add(PageSz, ConsSz, [{proportion, 1}, {flag, ?wxEXPAND}]), {Out, IFaces, Build, NewButtons} = case compile(Source) of {ok, Output} -> - ACI = maps:get(aci, Output), - AACI = {aaci, ContractName, Funs, NS} = hz:prepare_aaci(ACI), - Complete = maps:put(aaci, AACI, Output), - ok = tell(info, "Loading Contract: ~p", [ContractName]), + {aaci, _, Funs, _} = maps:get(aaci, Output), Callable = maps:remove("init", Funs), tell(info, "Callable: ~p", [Callable]), - {NB, IFs} = fun_interfaces(ScrollWin, FunSz, Buttons, Callable, NS, J), - O = io_lib:format("Compilation Succeeded!~n~tp~n~nDone!~n", [Complete]), - {O, IFs, Complete, NB}; + {NB, IFs} = fun_interfaces(ScrollWin, FunSz, Buttons, Callable, J), + O = io_lib:format("Compilation Succeeded!~n~tp~n~nDone!~n", [Output]), + {O, IFs, Output, NB}; Other -> O = io_lib:format("Compilation Failed!~n~tp~n", [Other]), {O, [], none, Buttons} @@ -1139,183 +1097,37 @@ get_arg({record, AFs}) -> get_record(AFs); get_arg({tuple, AFs}) -> list_to_tuple(lists:map(fun get_arg/1, AFs)); +get_arg({map, [Key, Value]}) -> + #{get_arg(Key) => get_arg(Value)}; +get_arg({variant, AFs, [VariantOne | _]}) -> + Elems = lists:map(fun get_arg/1, AFs), + list_to_tuple([VariantOne | Elems]); get_arg({_, TextCtrl}) -> wxTextCtrl:getValue(TextCtrl). get_record([{L, A} | T]) -> [{L, get_arg(A)} | get_record(T)]. -find_main(ACI) -> - find_main(ACI, none, []). - -find_main([#{contract := I = #{kind := contract_interface}} | T], M, Is) -> - find_main(T, M, [I | Is]); -find_main([#{contract := M = #{kind := contract_main}} | T], _, Is) -> - find_main(T, M, Is); -find_main([#{namespace := _} | T], M, Is) -> - find_main(T, M, Is); -find_main([C | T], M, Is) -> - ok = tell("Surprising ACI element: ~p", [C]), - find_main(T, M, Is); -find_main([], M, Is) -> - {M, Is}. - -fun_interfaces(ScrollWin, FunSz, Buttons, Funs, NS, J) -> +fun_interfaces(ScrollWin, FunSz, Buttons, Funs, J) -> MakeIface = fun(Name, {Args, _}) -> - tell(info, "Fun: ~p, Args: ~p", [Name, Args]), FunName = unicode:characters_to_list([Name, "/", integer_to_list(length(Args))]), - FN = wxStaticBoxSizer:new(?wxVERTICAL, ScrollWin, [{label, FunName}]), - GridSz = wxFlexGridSizer:new(2, 4, 4), - ok = wxFlexGridSizer:setFlexibleDirection(GridSz, ?wxHORIZONTAL), - ok = wxFlexGridSizer:addGrowableCol(GridSz, 1), - ArgFields = make_arg_fields(ScrollWin, GridSz, Args, NS, J), - ButtSz = wxBoxSizer:new(?wxHORIZONTAL), + FS = wxBoxSizer:new(?wxHORIZONTAL), + FLabel = wxStaticText:new(ScrollWin, ?wxID_ANY, FunName), CallBn = wxButton:new(ScrollWin, ?wxID_ANY, [{label, J("Call")}]), DryRBn = wxButton:new(ScrollWin, ?wxID_ANY, [{label, J("Dry Run")}]), - _ = wxBoxSizer:add(ButtSz, CallBn, zxw:flags(wide)), - _ = wxBoxSizer:add(ButtSz, DryRBn, zxw:flags(wide)), + _ = wxBoxSizer:add(FS, FLabel, [{proportion, 1}, {flag, ?wxEXPAND}]), + _ = wxBoxSizer:add(FS, CallBn, [{proportion, 0}, {flag, ?wxEXPAND}]), + _ = wxBoxSizer:add(FS, DryRBn, [{proportion, 0}, {flag, ?wxEXPAND}]), CallButton = #w{name = {Name, call}, id = wxButton:getId(CallBn), wx = CallBn}, DryRButton = #w{name = {Name, dryr}, id = wxButton:getId(DryRBn), wx = DryRBn}, - _ = wxStaticBoxSizer:add(FN, GridSz, zxw:flags(wide)), - _ = wxStaticBoxSizer:add(FN, ButtSz, zxw:flags(base)), - _ = wxSizer:add(FunSz, FN, zxw:flags(base)), - #f{name = Name, call = CallButton, dryrun = DryRButton, args = ArgFields} + _ = wxSizer:add(FunSz, FS, zxw:flags(base)), + #f{name = Name, call = CallButton, dryrun = DryRButton, args = Args} end, IFaces = maps:map(MakeIface, Funs), - tell(info, "IFaces: ~p", [IFaces]), NewButtons = maps:fold(fun map_iface_buttons/3, Buttons, IFaces), {NewButtons, IFaces}. -% FIXME: This can be simplified and needs to provide better widgets for types. -% "variant" types should be wxChoice -% Booleans should either be wxChoice or check boxes -% The sizer expansion direction for vertical elements is stupid -make_arg_fields(ScrollWin, GridSz, Args, NS, J) -> - MakeArgField = - fun - ({AN, {T, already_normalized, T}}) -> -% tell(info, "~p Arg: ~p, Type: ~p", [?LINE, AN, T]), - ANT = wxStaticText:new(ScrollWin, ?wxID_ANY, AN), - TCT = wxTextCtrl:new(ScrollWin, ?wxID_ANY), - _ = wxFlexGridSizer:add(GridSz, ANT, fill()), - _ = wxFlexGridSizer:add(GridSz, TCT, fill()), - {T, TCT}; - ({T, already_normalized, T}) -> -% tell(info, "~p Type: ~p", [?LINE, T]), - ANT = wxStaticText:new(ScrollWin, ?wxID_ANY, atom_to_list(T)), - TCT = wxTextCtrl:new(ScrollWin, ?wxID_ANY), - _ = wxFlexGridSizer:add(GridSz, ANT, fill()), - _ = wxFlexGridSizer:add(GridSz, TCT, fill()), - {T, TCT}; - ({AN, {_TypeName, T, T}}) -> -% tell(info, "~p Arg: ~p, ~p: ~p", [?LINE, AN, TypeName, T]), - ANT = wxStaticText:new(ScrollWin, ?wxID_ANY, AN), - TCT = wxTextCtrl:new(ScrollWin, ?wxID_ANY), - _ = wxFlexGridSizer:add(GridSz, ANT, fill()), - _ = wxFlexGridSizer:add(GridSz, TCT, fill()), - {T, TCT}; - ({AN, {_TypeName, already_normalized, {record, InnerArgs}}}) -> -% tell(info, "~p Arg: ~p, ~p: ~p", [?LINE, AN, TypeName, InnerArgs]), - ANT = wxStaticText:new(ScrollWin, ?wxID_ANY, AN), - InnerSz = wxFlexGridSizer:new(2, 4, 4), - ok = wxFlexGridSizer:setFlexibleDirection(InnerSz, ?wxHORIZONTAL), - ok = wxFlexGridSizer:addGrowableCol(InnerSz, 1), - AFs = make_arg_fields(ScrollWin, InnerSz, InnerArgs, NS, J), - _ = wxFlexGridSizer:add(GridSz, ANT, fill()), - _ = wxFlexGridSizer:add(GridSz, InnerSz, fill()), - {record, AFs}; - ({AN, {_TypeName, already_normalized, {tuple, InnerArgs}}}) -> -% tell(info, "~p Arg: ~p, ~p: ~p", [?LINE, AN, TypeName, InnerArgs]), - ANT = wxStaticText:new(ScrollWin, ?wxID_ANY, AN), - InnerSz = wxFlexGridSizer:new(2, 4, 4), - ok = wxFlexGridSizer:setFlexibleDirection(InnerSz, ?wxHORIZONTAL), - ok = wxFlexGridSizer:addGrowableCol(InnerSz, 1), - AFs = make_arg_fields(ScrollWin, InnerSz, InnerArgs, NS, J), - _ = wxFlexGridSizer:add(GridSz, ANT, fill()), - _ = wxFlexGridSizer:add(GridSz, InnerSz, fill()), - {tuple, AFs}; - ({AN, {_TypeName, already_normalized, {list, InnerArgs}}}) -> -% tell(info, "~p Arg: ~p, ~p: ~p", [?LINE, AN, TypeName, InnerArgs]), - ANT = wxStaticText:new(ScrollWin, ?wxID_ANY, AN), - ArgSz = wxBoxSizer:new(?wxVERTICAL), - InnerSz = wxFlexGridSizer:new(2, 4, 4), - ok = wxFlexGridSizer:setFlexibleDirection(InnerSz, ?wxHORIZONTAL), - ok = wxFlexGridSizer:addGrowableCol(InnerSz, 1), - AFs = make_arg_fields(ScrollWin, InnerSz, InnerArgs, NS, J), - B = wxButton:new(ScrollWin, ?wxID_ANY, [{label, J("Add")}]), - AB = #w{name = {AN, add}, id = wxButton:getId(B), wx = B}, - _ = wxBoxSizer:add(ArgSz, InnerSz, fill()), - _ = wxBoxSizer:add(ArgSz, B, fill()), - _ = wxFlexGridSizer:add(GridSz, ANT, fill()), - _ = wxFlexGridSizer:add(GridSz, ArgSz, fill()), - {list, AFs, AB}; - ({AN, {_TypeName, already_normalized, T}}) -> -% tell(info, "~p Arg: ~p, ~p: ~p", [?LINE, AN, TypeName, T]), - ANT = wxStaticText:new(ScrollWin, ?wxID_ANY, AN), - InnerSz = wxFlexGridSizer:new(2, 4, 4), - ok = wxFlexGridSizer:setFlexibleDirection(InnerSz, ?wxHORIZONTAL), - ok = wxFlexGridSizer:addGrowableCol(InnerSz, 1), - AFs = make_arg_fields(ScrollWin, InnerSz, T, NS, J), - _ = wxFlexGridSizer:add(GridSz, ANT, fill()), - _ = wxFlexGridSizer:add(GridSz, InnerSz, fill()), - {tuple, AFs}; - ({AN, {{tuple, _}, already_normalized, {tuple, InnerArgs}}}) -> -% tell(info, "~p Arg: ~p, Tuple: ~p", [?LINE, AN, InnerArgs]), - ANT = wxStaticText:new(ScrollWin, ?wxID_ANY, AN), - InnerSz = wxFlexGridSizer:new(2, 4, 4), - ok = wxFlexGridSizer:setFlexibleDirection(InnerSz, ?wxHORIZONTAL), - ok = wxFlexGridSizer:addGrowableCol(InnerSz, 1), - AFs = make_arg_fields(ScrollWin, InnerSz, InnerArgs, NS, J), - _ = wxFlexGridSizer:add(GridSz, ANT, fill()), - _ = wxFlexGridSizer:add(GridSz, InnerSz, fill()), - {tuple, AFs}; - ({AN, {{list, _}, already_normalized, {list, InnerArgs}}}) -> -% tell(info, "~p Arg: ~p, List: ~p", [?LINE, AN, InnerArgs]), - ANT = wxStaticText:new(ScrollWin, ?wxID_ANY, AN), - ArgSz = wxBoxSizer:new(?wxHORIZONTAL), - InnerSz = wxFlexGridSizer:new(2, 4, 4), - ok = wxFlexGridSizer:setFlexibleDirection(InnerSz, ?wxHORIZONTAL), - ok = wxFlexGridSizer:addGrowableCol(InnerSz, 1), - AFs = make_arg_fields(ScrollWin, InnerSz, InnerArgs, NS, J), - B = wxButton:new(ScrollWin, ?wxID_ANY, [{label, J("Add")}]), - AB = #w{name = {AN, add}, id = wxButton:getId(B), wx = B}, - _ = wxBoxSizer:add(ArgSz, InnerSz, fill()), - _ = wxBoxSizer:add(ArgSz, B, fill()), - _ = wxFlexGridSizer:add(GridSz, ANT, fill()), - _ = wxFlexGridSizer:add(GridSz, ArgSz, fill()), - {list, AFs, AB}; - ({{tuple, _}, already_normalized, {tuple, InnerArgs}}) -> -% tell(info, "~p Tuple: ~p", [?LINE, InnerArgs]), - ANT = wxStaticText:new(ScrollWin, ?wxID_ANY, "tuple"), - InnerSz = wxFlexGridSizer:new(2, 4, 4), - ok = wxFlexGridSizer:setFlexibleDirection(InnerSz, ?wxHORIZONTAL), - ok = wxFlexGridSizer:addGrowableCol(InnerSz, 1), - AFs = make_arg_fields(ScrollWin, InnerSz, InnerArgs, NS, J), - _ = wxFlexGridSizer:add(GridSz, ANT, fill()), - _ = wxFlexGridSizer:add(GridSz, InnerSz, fill()), - {tuple, AFs}; - ({{list, _}, already_normalized, {list, InnerArgs}}) -> -% tell(info, "~p List: ~p", [?LINE, InnerArgs]), - ANT = wxStaticText:new(ScrollWin, ?wxID_ANY, "list"), - ArgSz = wxBoxSizer:new(?wxHORIZONTAL), - InnerSz = wxFlexGridSizer:new(2, 4, 4), - ok = wxFlexGridSizer:setFlexibleDirection(InnerSz, ?wxHORIZONTAL), - ok = wxFlexGridSizer:addGrowableCol(InnerSz, 1), - AFs = make_arg_fields(ScrollWin, InnerSz, InnerArgs, NS, J), - B = wxButton:new(ScrollWin, ?wxID_ANY, [{label, J("Add")}]), - AB = #w{name = {list, add}, id = wxButton:getId(B), wx = B}, - _ = wxBoxSizer:add(ArgSz, InnerSz, [{proportion, 1}, {flag, ?wxEXPAND}]), - _ = wxBoxSizer:add(ArgSz, B, fill()), - _ = wxFlexGridSizer:add(GridSz, ANT, fill()), - _ = wxFlexGridSizer:add(GridSz, ArgSz, fill()), - {list, AFs, AB} - end, - lists:map(MakeArgField, Args). - -fill() -> - [{proportion, 0}, {flag, ?wxEXPAND}]. - map_iface_buttons(_, #f{call = C = #w{id = CID}, dryrun = D = #w{id = DID}}, A) -> maps:merge(#{CID => C, DID => D}, A); @@ -1330,7 +1142,7 @@ edit(State = #s{cons = {Consbook, Pages}}) -> Index -> #c{code = CodeTx} = lists:nth(Index + 1, Pages), Address = wxNotebook:getPageText(Consbook, Index), - Source = wxTextCtrl:getValue(CodeTx), + Source = gd_sophia_editor:get_text(CodeTx), add_code_page(State, {hash, Address}, Source) end. @@ -1359,7 +1171,16 @@ list_iface_buttons(#f{call = #w{id = CID}, dryrun = #w{id = DID}}, A) -> compile(Source) -> Options = sophia_options(), - so_compiler:from_string(Source, Options). + case so_compiler:from_string(Source, Options) of + {ok, Build} -> + ACI = maps:get(aci, Build), + AACI = hz:prepare_aaci(ACI), + Complete = maps:put(aaci, AACI, Build), + {ok, Complete}; + Other -> + Other + end. + sophia_options() -> [{aci, json}]. diff --git a/src/gd_v_wallman.erl b/src/gd_v_wallman.erl index 1237005..790d9b0 100644 --- a/src/gd_v_wallman.erl +++ b/src/gd_v_wallman.erl @@ -317,41 +317,15 @@ do_open2(Selected, State = #s{wallets = Wallets}) -> end. do_open3(Path, State = #s{frame = Frame, j = J}) -> - Label = J("Password"), - Dialog = wxDialog:new(Frame, ?wxID_ANY, Label), - Sizer = wxBoxSizer:new(?wxVERTICAL), - PassSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, Label}]), - PassTx = wxTextCtrl:new(Dialog, ?wxID_ANY, [{style, ?wxTE_PASSWORD}]), - _ = wxStaticBoxSizer:add(PassSz, PassTx, zxw:flags(wide)), - ButtSz = wxBoxSizer:new(?wxHORIZONTAL), - Affirm = wxButton:new(Dialog, ?wxID_OK), - _ = wxBoxSizer:add(ButtSz, Affirm, zxw:flags(wide)), - _ = wxBoxSizer:add(Sizer, PassSz, zxw:flags(base)), - _ = wxBoxSizer:add(Sizer, ButtSz, zxw:flags(wide)), - ok = wxDialog:setSizer(Dialog, Sizer), - ok = wxBoxSizer:layout(Sizer), - ok = wxDialog:setSize(Dialog, {500, 130}), - ok = wxDialog:center(Dialog), - ok = wxStyledTextCtrl:setFocus(PassTx), - case wxDialog:showModal(Dialog) of - ?wxID_OK -> - case wxTextCtrl:getValue(PassTx) of - "" -> - ok = wxDialog:destroy(Dialog), - ensure_shown(Frame); - Phrase -> - ok = wxDialog:destroy(Dialog), - case gd_con:open_wallet(Path, Phrase) of - ok -> - do_close(State); - Error -> - ok = ensure_shown(Frame), - trouble(Error) - end + Title = J("Passphrase"), + case zxw_modal_text:show(Frame, Title, [{password, true}]) of + {ok, Phrase} -> + case gd_con:open_wallet(Path, Phrase) of + ok -> do_close(State); + Error -> trouble(Error) end; - ?wxID_CANCEL -> - ok = wxDialog:destroy(Dialog), - ensure_shown(Frame) + cancel -> + ok end. @@ -538,51 +512,15 @@ do_import2(_, "", _, _) -> abort; do_import2(Dir, File, J, Frame) -> Path = filename:join(Dir, File), - Dialog = wxDialog:new(Frame, ?wxID_ANY, J("Import Wallet")), - Sizer = wxBoxSizer:new(?wxVERTICAL), - - NameSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Name")}]), - NameTx = wxTextCtrl:new(Dialog, ?wxID_ANY), - _ = wxSizer:add(NameSz, NameTx, zxw:flags(wide)), - PassSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Passphrase")}]), - PassTx = wxTextCtrl:new(Dialog, ?wxID_ANY), - _ = wxSizer:add(PassSz, PassTx, zxw:flags(wide)), - - 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, NameSz, zxw:flags(base)), - _ = wxSizer:add(Sizer, PassSz, 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(NameTx), - - Result = - case wxDialog:showModal(Dialog) of - ?wxID_OK -> - Name = - case wxTextCtrl:getValue(NameTx) of - "" -> Path; - N -> N - end, - Pass = - case wxTextCtrl:getValue(PassTx) of - "" -> none; - P -> P - end, - gd_con:import_wallet(Name, Path, Pass); - ?wxID_CANCEL -> - abort - end, - ok = wxDialog:destroy(Dialog), - Result. + Title = J("Import Wallet"), + NameL = J("Wallet Name"), + PassL = J("Passphrase (leave blank if none)"), + OK_L = J("OK"), + CancelL = J("Cancel"), + case gd_m_wallet_importer:show(Frame, Title, NameL, PassL, OK_L, CancelL) of + {ok, Name, Pass} -> gd_con:import_wallet(Name, Path, Pass); + cancel -> abort + end. do_drop(State = #s{picker = Picker}) -> diff --git a/zomp.meta b/zomp.meta index 7f20576..f1f4f59 100644 --- a/zomp.meta +++ b/zomp.meta @@ -2,10 +2,11 @@ {type,gui}. {modules,[]}. {prefix,"gd"}. -{desc,"A desktop client for the Gajumaru network of blockchain networks"}. {author,"Craig Everett"}. +{desc,"A desktop client for the Gajumaru network of blockchain networks"}. {package_id,{"otpr","gajudesk",{0,8,0}}}. -{deps,[{"otpr","hakuzaru",{0,7,0}}, +{deps,[{"otpr","hakuzaru",{0,8,2}}, + {"otpr","zxwidgets",{1,1,0}}, {"otpr","eblake2",{1,0,1}}, {"otpr","base58",{0,1,1}}, {"otpr","gmserialization",{0,1,3}}, @@ -13,8 +14,7 @@ {"otpr","gmbytecode",{3,4,1}}, {"otpr","lom",{1,0,0}}, {"otpr","zj",{1,1,0}}, - {"otpr","ec_utils",{1,0,0}}, - {"otpr","zxwidgets",{1,0,1}}]}. + {"otpr","ec_utils",{1,0,0}}]}. {key_name,none}. {a_email,"craigeverett@qpq.swiss"}. {c_email,"info@qpq.swiss"}.