From 5ebb532b86d20eeb73b85cf81b73a4f2d716d7d2 Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Thu, 14 Aug 2025 14:00:47 +0900 Subject: [PATCH 01/25] Stop trying to pull the AACI from the build directly --- src/gd_v_devman.erl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/gd_v_devman.erl b/src/gd_v_devman.erl index a5dad28..3c4d1a6 100644 --- a/src/gd_v_devman.erl +++ b/src/gd_v_devman.erl @@ -837,7 +837,8 @@ 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}} -> + {ok, Build} -> + AACI = hz:prepare_aaci(Build), {Defs = #{functions := Funs}, ConIfaces} = find_main(AACI), Callable = maps:remove("init", Funs), FunDefs = {maps:put(functions, Callable, Defs), ConIfaces}, From 2ecde986bfaa0a747fcbcc29cddff0f9cb4aa98e Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Fri, 15 Aug 2025 13:24:39 +0900 Subject: [PATCH 02/25] WIP --- src/gd_v_devman.erl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gd_v_devman.erl b/src/gd_v_devman.erl index 3c4d1a6..adec361 100644 --- a/src/gd_v_devman.erl +++ b/src/gd_v_devman.erl @@ -837,9 +837,9 @@ 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 = hz:prepare_aaci(Build), - {Defs = #{functions := Funs}, ConIfaces} = find_main(AACI), + {ok, Build = #{aci := ACI}} -> +% AACI = hz:prepare_aaci(ACI), + {Defs = #{functions := Funs}, ConIfaces} = find_main(ACI), Callable = maps:remove("init", Funs), FunDefs = {maps:put(functions, Callable, Defs), ConIfaces}, ok = tell(info, "Compilation Succeeded!~n~tp~n~n~tp", [Build, FunDefs]), From 2952822a8f168f66a37b0d32af758a1ebc7c7609 Mon Sep 17 00:00:00 2001 From: Jarvis Carroll Date: Fri, 22 Aug 2025 14:21:25 +1000 Subject: [PATCH 03/25] Debug print AACI instead --- src/gd_v_devman.erl | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/gd_v_devman.erl b/src/gd_v_devman.erl index adec361..051334d 100644 --- a/src/gd_v_devman.erl +++ b/src/gd_v_devman.erl @@ -838,10 +838,7 @@ open_hash3(State, Address, Source) -> Options = sophia_options(), case so_compiler:from_string(Source, Options) of {ok, Build = #{aci := ACI}} -> -% AACI = hz:prepare_aaci(ACI), - {Defs = #{functions := Funs}, ConIfaces} = find_main(ACI), - Callable = maps:remove("init", Funs), - FunDefs = {maps:put(functions, Callable, Defs), ConIfaces}, + {aaci, _Name, FunDefs, _TypeDefs} = hz:prepare_aaci(ACI), ok = tell(info, "Compilation Succeeded!~n~tp~n~n~tp", [Build, FunDefs]), add_code_page(State, {hash, Address}, Source); Other -> From dc0b620a4cfbe656fdefe20f26513903d466be67 Mon Sep 17 00:00:00 2001 From: Jarvis Carroll Date: Fri, 22 Aug 2025 15:22:39 +1000 Subject: [PATCH 04/25] Make styled text control when loading --- src/gd_v_devman.erl | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/gd_v_devman.erl b/src/gd_v_devman.erl index 051334d..f2a6881 100644 --- a/src/gd_v_devman.erl +++ b/src/gd_v_devman.erl @@ -929,6 +929,24 @@ save(State = #s{frame = Frame, j = J, prefs = Prefs, code = {Codebook, Pages}}) end end. +make_plain_text_control(Parent) -> + CodeTxStyle = {style, ?wxTE_MULTILINE + bor ?wxTE_PROCESS_TAB + bor ?wxTE_DONTWRAP + bor ?wxTE_READONLY}, + CodeTx = wxTextCtrl:new(Parent, ?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), + CodeTx. + +make_source_text_control(Parent) -> + case ?editorMode of + plain -> make_plain_text_control(Parent); + sophia -> gd_sophia_editor:new(Parent) + end. + get_source(Widget) -> case ?editorMode of plain -> wxTextCtrl:getValue(Widget); @@ -940,7 +958,6 @@ set_source(Widget, Src) -> 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}}) -> @@ -1079,15 +1096,7 @@ 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), + CodeTx = make_source_text_control(Window), ok = set_source(CodeTx, Source), _ = wxSizer:add(CodeSz, CodeTx, zxw:flags(wide)), ScrollWin = wxScrolledWindow:new(Window), From f78d929fb9dbecd634998fbbbbbb9acdf356405b Mon Sep 17 00:00:00 2001 From: Jarvis Carroll Date: Fri, 22 Aug 2025 18:38:57 +1000 Subject: [PATCH 05/25] Sketchy list, map, variant interface None of these are quite working right, but the hive contract can open in the 'open from chain' button now. --- src/gd_v_devman.erl | 103 ++++++++++++++++---------------------------- 1 file changed, 36 insertions(+), 67 deletions(-) diff --git a/src/gd_v_devman.erl b/src/gd_v_devman.erl index f2a6881..7a968dc 100644 --- a/src/gd_v_devman.erl +++ b/src/gd_v_devman.erl @@ -1146,6 +1146,11 @@ 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). @@ -1201,28 +1206,14 @@ fun_interfaces(ScrollWin, FunSz, Buttons, Funs, NS, J) -> make_arg_fields(ScrollWin, GridSz, Args, NS, J) -> MakeArgField = fun - ({AN, {T, already_normalized, T}}) -> + ({AN, {_O, _N, T}}) when is_atom(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}}}) -> + ({AN, {_O, _N, {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), @@ -1232,24 +1223,24 @@ make_arg_fields(ScrollWin, GridSz, Args, NS, J) -> _ = wxFlexGridSizer:add(GridSz, ANT, fill()), _ = wxFlexGridSizer:add(GridSz, InnerSz, fill()), {record, AFs}; - ({AN, {_TypeName, already_normalized, {tuple, InnerArgs}}}) -> + ({AN, {_O, _N, {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), + AFs = make_unnamed_fields(ScrollWin, InnerSz, InnerArgs, NS, J), _ = wxFlexGridSizer:add(GridSz, ANT, fill()), _ = wxFlexGridSizer:add(GridSz, InnerSz, fill()), {tuple, AFs}; - ({AN, {_TypeName, already_normalized, {list, InnerArgs}}}) -> + ({AN, {_O, _N, {list, [InnerType]}}}) -> % 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), + AFs = make_unnamed_fields(ScrollWin, InnerSz, [InnerType], 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()), @@ -1257,69 +1248,47 @@ make_arg_fields(ScrollWin, GridSz, Args, NS, J) -> _ = wxFlexGridSizer:add(GridSz, ANT, fill()), _ = wxFlexGridSizer:add(GridSz, ArgSz, fill()), {list, AFs, AB}; - ({AN, {_TypeName, already_normalized, T}}) -> + ({AN, {_O, _N, {map, [Key, Value]}}}) -> % 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), + AFs = make_unnamed_fields(ScrollWin, InnerSz, [Key, Value], 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]), + {map, AFs}; + ({AN, {_O, _N, {variant, Variants = [{_, VariantElements} | _]}}}) -> +% 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), + AFs = make_unnamed_fields(ScrollWin, InnerSz, VariantElements, 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} + VariantNames = lists:map(fun({Name, _}) -> Name end, Variants), + {variant, AFs, VariantNames} end, lists:map(MakeArgField, Args). +make_unnamed_fields(ScrollWin, InnerSz, Elems, NS, J) -> + tell(info, "ta", []), + ChooseName = + fun + (Ann = {_, _, T}) when is_atom(T) -> + {atom_to_list(T), Ann}; + (Ann = {Name, _, {record, _}}) when is_list(Name) -> + io:format("record: ~p~n", [Ann]), + {Name, Ann}; + (Ann = {_, _, {T, _}}) when is_atom(T) -> + {atom_to_list(T), Ann} + end, + tell(info, "tb", []), + WithNames = lists:map(ChooseName, Elems), + tell(info, "tc", []), + make_arg_fields(ScrollWin, InnerSz, WithNames, NS, J). + fill() -> [{proportion, 0}, {flag, ?wxEXPAND}]. From 51463b8b747a2463c7c9b0d2d0020f804f9eb474 Mon Sep 17 00:00:00 2001 From: Jarvis Carroll Date: Fri, 22 Aug 2025 19:52:42 +1000 Subject: [PATCH 06/25] Make deploy thingy break less --- src/gd_v_devman.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gd_v_devman.erl b/src/gd_v_devman.erl index 7a968dc..80cdd56 100644 --- a/src/gd_v_devman.erl +++ b/src/gd_v_devman.erl @@ -590,7 +590,7 @@ deploy(State = #s{code = {Codebook, Pages}}) -> State; Index -> #p{code = CodeTx} = lists:nth(Index + 1, Pages), - Source = wxTextCtrl:getValue(CodeTx), + Source = get_source(CodeTx), deploy2(State, Source) end. From 1d1e735b1680348594f1572318e478d873e0f68c Mon Sep 17 00:00:00 2001 From: Jarvis Carroll Date: Fri, 22 Aug 2025 19:59:47 +1000 Subject: [PATCH 07/25] Remove some trace tells d'oh --- src/gd_v_devman.erl | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/gd_v_devman.erl b/src/gd_v_devman.erl index 80cdd56..244efee 100644 --- a/src/gd_v_devman.erl +++ b/src/gd_v_devman.erl @@ -1273,7 +1273,6 @@ make_arg_fields(ScrollWin, GridSz, Args, NS, J) -> lists:map(MakeArgField, Args). make_unnamed_fields(ScrollWin, InnerSz, Elems, NS, J) -> - tell(info, "ta", []), ChooseName = fun (Ann = {_, _, T}) when is_atom(T) -> @@ -1284,9 +1283,7 @@ make_unnamed_fields(ScrollWin, InnerSz, Elems, NS, J) -> (Ann = {_, _, {T, _}}) when is_atom(T) -> {atom_to_list(T), Ann} end, - tell(info, "tb", []), WithNames = lists:map(ChooseName, Elems), - tell(info, "tc", []), make_arg_fields(ScrollWin, InnerSz, WithNames, NS, J). fill() -> From b8a31e994f323c98de16ccfdacd5ad8deed66ef4 Mon Sep 17 00:00:00 2001 From: Jarvis Carroll Date: Fri, 22 Aug 2025 22:57:12 +1000 Subject: [PATCH 08/25] factor compile/aaci logic Instead of removing `init` and changing the definition of what the AACI is, we can just remove it in the one case that needs `init` removed, which is load3. --- src/gd_v_devman.erl | 59 ++++++++++++++++++--------------------------- 1 file changed, 23 insertions(+), 36 deletions(-) diff --git a/src/gd_v_devman.erl b/src/gd_v_devman.erl index 244efee..2f70e44 100644 --- a/src/gd_v_devman.erl +++ b/src/gd_v_devman.erl @@ -427,10 +427,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 @@ -596,17 +595,12 @@ deploy(State = #s{code = {Codebook, Pages}}) -> 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, NS} = 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, NS); Other -> ok = tell(info, "Compilation Failed!~n~tp", [Other]), State @@ -835,10 +829,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 = #{aci := ACI}} -> - {aaci, _Name, FunDefs, _TypeDefs} = hz:prepare_aaci(ACI), + 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 -> @@ -1114,15 +1107,12 @@ load3(State = #s{tabs = TopBook, cons = {Consbook, Pages}, buttons = Buttons, j {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, NS} = 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}; + 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} @@ -1157,21 +1147,6 @@ get_arg({_, 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) -> MakeIface = fun(Name, {Args, _}) -> @@ -1203,6 +1178,9 @@ fun_interfaces(ScrollWin, FunSz, Buttons, Funs, NS, J) -> % "variant" types should be wxChoice % Booleans should either be wxChoice or check boxes % The sizer expansion direction for vertical elements is stupid +% FIXME: We probably don't need the opaque type definitions 'NS', since the +% AACI already caches the useful expansions that might be needed for error +% messages. make_arg_fields(ScrollWin, GridSz, Args, NS, J) -> MakeArgField = fun @@ -1332,7 +1310,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}]. From 6a1c8ecf62ecd6dfb138498765c634afe3e7497d Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Fri, 17 Oct 2025 19:35:55 +0900 Subject: [PATCH 09/25] WIP --- src/gd_v_wallman.erl | 78 ++++++++++++++++++++++++++------------------ 1 file changed, 46 insertions(+), 32 deletions(-) diff --git a/src/gd_v_wallman.erl b/src/gd_v_wallman.erl index cdf8514..ffb60f6 100644 --- a/src/gd_v_wallman.erl +++ b/src/gd_v_wallman.erl @@ -318,41 +318,55 @@ do_open2(Selected, State = #s{wallets = Wallets}) -> 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 + case zxw_text_modal:show(Frame, Label, [{password, true}]) of + {ok, ""} -> + ensure_shown(Frame); + {ok, Phrase} -> + case gd_con:open_wallet(Path, Phrase) of + ok -> + do_close(State); + Error -> + ok = ensure_shown(Frame), + trouble(Error) end; - ?wxID_CANCEL -> - ok = wxDialog:destroy(Dialog), + cancel -> ensure_shown(Frame) end. +% 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 +% end; +% ?wxID_CANCEL -> +% ok = wxDialog:destroy(Dialog), +% ensure_shown(Frame) +% end. do_wiz(State = #s{wx = WX, lang = Lang, wiz = none}) -> From c452604f4bbacdda37935fed18f10e44f6deb520 Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Wed, 29 Oct 2025 14:05:56 +0900 Subject: [PATCH 10/25] WIP --- src/gd_v_wallman.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gd_v_wallman.erl b/src/gd_v_wallman.erl index ffb60f6..002009e 100644 --- a/src/gd_v_wallman.erl +++ b/src/gd_v_wallman.erl @@ -318,7 +318,7 @@ do_open2(Selected, State = #s{wallets = Wallets}) -> do_open3(Path, State = #s{frame = Frame, j = J}) -> Label = J("Password"), - case zxw_text_modal:show(Frame, Label, [{password, true}]) of + case zxw_modal_text:show(Frame, Label, [{password, true}]) of {ok, ""} -> ensure_shown(Frame); {ok, Phrase} -> From bd047a6a46732e87c388b1f225600b6eca7d8d27 Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Fri, 7 Nov 2025 13:13:27 +0900 Subject: [PATCH 11/25] WIP --- src/gd_con.erl | 12 ++- src/gd_gui.erl | 62 +++++------- src/gd_m_wallet_importer.erl | 184 +++++++++++++++++++++++++++++++++++ src/gd_v_wallman.erl | 104 +++----------------- 4 files changed, 228 insertions(+), 134 deletions(-) create mode 100644 src/gd_m_wallet_importer.erl diff --git a/src/gd_con.erl b/src/gd_con.erl index 3f8f64e..103668c 100644 --- a/src/gd_con.erl +++ b/src/gd_con.erl @@ -330,21 +330,25 @@ 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}, NewState = do_show_ui(gd_v_wallman, State), - ok = gd_v_wallman:first_run(), + ok = + case FirstRun of + false -> ok; + 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. diff --git a/src/gd_gui.erl b/src/gd_gui.erl index df5137d..d1eb6b6 100644 --- a/src/gd_gui.erl +++ b/src/gd_gui.erl @@ -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,8 +117,8 @@ 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)), + BalanceL = wxStaticText:new(Panel, ?wxID_ANY, "木"), + BalanceT = wxStaticText:new(Panel, ?wxID_ANY, price_to_string(0)), Balance = {#w{id = wxStaticText:getId(BalanceL), wx = BalanceL}, #w{id = wxStaticText:getId(BalanceT), wx = BalanceT}}, @@ -142,7 +145,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 +193,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 +205,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 +582,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)), @@ -843,33 +847,11 @@ is_int(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. 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_v_wallman.erl b/src/gd_v_wallman.erl index d3aeb54..790d9b0 100644 --- a/src/gd_v_wallman.erl +++ b/src/gd_v_wallman.erl @@ -317,56 +317,16 @@ do_open2(Selected, State = #s{wallets = Wallets}) -> end. do_open3(Path, State = #s{frame = Frame, j = J}) -> - Label = J("Password"), - case zxw_modal_text:show(Frame, Label, [{password, true}]) of - {ok, ""} -> - ensure_shown(Frame); + 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 -> - ok = ensure_shown(Frame), - trouble(Error) + ok -> do_close(State); + Error -> trouble(Error) end; cancel -> - ensure_shown(Frame) + ok end. -% 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 -% end; -% ?wxID_CANCEL -> -% ok = wxDialog:destroy(Dialog), -% ensure_shown(Frame) -% end. do_wiz(State = #s{wx = WX, lang = Lang, wiz = none}) -> @@ -552,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}) -> From c59ef4b14eb354d22d0c74725c41e29d62ea7655 Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Fri, 7 Nov 2025 15:45:57 +0900 Subject: [PATCH 12/25] WIP --- src/gd_con.erl | 2 +- src/gd_sophia_editor.erl | 5 +- src/gd_v_devman.erl | 223 +++++++-------------------------------- 3 files changed, 46 insertions(+), 184 deletions(-) diff --git a/src/gd_con.erl b/src/gd_con.erl index 103668c..563da3d 100644 --- a/src/gd_con.erl +++ b/src/gd_con.erl @@ -339,7 +339,7 @@ init(none) -> NewState = do_show_ui(gd_v_wallman, State), ok = case FirstRun of - false -> ok; + false -> gd_v_wallman:to_front(); true -> gd_v_wallman:first_run() end, {ok, NewState}. 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 4ca6291..a0cec25 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, @@ -589,7 +588,7 @@ deploy(State = #s{code = {Codebook, Pages}}) -> State; Index -> #p{code = CodeTx} = lists:nth(Index + 1, Pages), - Source = get_source(CodeTx), + Source = gd_sophia_editor:get_text(CodeTx), deploy2(State, Source) end. @@ -597,26 +596,26 @@ deploy2(State, Source) -> case compile(Source) of % TODO: Make hz accept either the aaci or the aci, preferring the aaci if present {ok, Build} -> - {aaci, ContractName, Funs, NS} = maps:get(aaci, Build), + {aaci, ContractName, Funs, _} = maps:get(aaci, Build), ok = tell(info, "Deploying Contract: ~p", [ContractName]), InitSpec = maps:get("init", Funs), - deploy3(State, InitSpec, Build, NS); + 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), @@ -639,7 +638,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}]), @@ -685,6 +684,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), @@ -894,7 +906,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 @@ -922,35 +934,6 @@ save(State = #s{frame = Frame, j = J, prefs = Prefs, code = {Codebook, Pages}}) end end. -make_plain_text_control(Parent) -> - CodeTxStyle = {style, ?wxTE_MULTILINE - bor ?wxTE_PROCESS_TAB - bor ?wxTE_DONTWRAP - bor ?wxTE_READONLY}, - CodeTx = wxTextCtrl:new(Parent, ?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), - CodeTx. - -make_source_text_control(Parent) -> - case ?editorMode of - plain -> make_plain_text_control(Parent); - sophia -> gd_sophia_editor:new(Parent) - 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}}) -> @@ -982,7 +965,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 @@ -1028,41 +1011,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}}} -> @@ -1089,8 +1047,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")}]), - CodeTx = make_source_text_control(Window), - 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,17 +1059,17 @@ 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} -> - {aaci, _, Funs, NS} = maps:get(aaci, Output), + {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), + {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 -> @@ -1147,126 +1106,26 @@ get_arg({_, TextCtrl}) -> get_record([{L, A} | T]) -> [{L, get_arg(A)} | get_record(T)]. -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 -% FIXME: We probably don't need the opaque type definitions 'NS', since the -% AACI already caches the useful expansions that might be needed for error -% messages. -make_arg_fields(ScrollWin, GridSz, Args, NS, J) -> - MakeArgField = - fun - ({AN, {_O, _N, T}}) when is_atom(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}; - ({AN, {_O, _N, {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, {_O, _N, {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_unnamed_fields(ScrollWin, InnerSz, InnerArgs, NS, J), - _ = wxFlexGridSizer:add(GridSz, ANT, fill()), - _ = wxFlexGridSizer:add(GridSz, InnerSz, fill()), - {tuple, AFs}; - ({AN, {_O, _N, {list, [InnerType]}}}) -> -% 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_unnamed_fields(ScrollWin, InnerSz, [InnerType], 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, {_O, _N, {map, [Key, Value]}}}) -> -% 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_unnamed_fields(ScrollWin, InnerSz, [Key, Value], NS, J), - _ = wxFlexGridSizer:add(GridSz, ANT, fill()), - _ = wxFlexGridSizer:add(GridSz, InnerSz, fill()), - {map, AFs}; - ({AN, {_O, _N, {variant, Variants = [{_, VariantElements} | _]}}}) -> -% 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_unnamed_fields(ScrollWin, InnerSz, VariantElements, NS, J), - _ = wxFlexGridSizer:add(GridSz, ANT, fill()), - _ = wxFlexGridSizer:add(GridSz, InnerSz, fill()), - VariantNames = lists:map(fun({Name, _}) -> Name end, Variants), - {variant, AFs, VariantNames} - end, - lists:map(MakeArgField, Args). - -make_unnamed_fields(ScrollWin, InnerSz, Elems, NS, J) -> - ChooseName = - fun - (Ann = {_, _, T}) when is_atom(T) -> - {atom_to_list(T), Ann}; - (Ann = {Name, _, {record, _}}) when is_list(Name) -> - io:format("record: ~p~n", [Ann]), - {Name, Ann}; - (Ann = {_, _, {T, _}}) when is_atom(T) -> - {atom_to_list(T), Ann} - end, - WithNames = lists:map(ChooseName, Elems), - make_arg_fields(ScrollWin, InnerSz, WithNames, NS, J). - -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); @@ -1281,7 +1140,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. From 5a4c934d10704dc34a0667237fd932f67c607dc4 Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Fri, 7 Nov 2025 18:51:18 +0900 Subject: [PATCH 13/25] WIP --- src/gd_gui.erl | 32 +++++++------------------------- src/gd_v_devman.erl | 6 ++++-- 2 files changed, 11 insertions(+), 27 deletions(-) diff --git a/src/gd_gui.erl b/src/gd_gui.erl index d1eb6b6..ad2d0cb 100644 --- a/src/gd_gui.erl +++ b/src/gd_gui.erl @@ -611,33 +611,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. diff --git a/src/gd_v_devman.erl b/src/gd_v_devman.erl index a0cec25..e4c2906 100644 --- a/src/gd_v_devman.erl +++ b/src/gd_v_devman.erl @@ -27,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, @@ -56,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 From d7a8a81fa9b3094f87210c203f8099d2b77afee4 Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Fri, 7 Nov 2025 19:56:31 +0900 Subject: [PATCH 14/25] Update zxw dep --- zomp.meta | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/zomp.meta b/zomp.meta index 7f20576..71fdb54 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","zxwidgets",{1,1,0}}, + {"otpr","hakuzaru",{0,7,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"}. From d70f5bb3897ec65715c5cf9137251f7e03a4dda8 Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Wed, 19 Nov 2025 21:18:18 +0900 Subject: [PATCH 15/25] WIP --- src/gd_con.erl | 8 +- src/gd_gui.erl | 100 ++--------------- src/gd_m_spend.erl | 274 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 287 insertions(+), 95 deletions(-) create mode 100644 src/gd_m_spend.erl diff --git a/src/gd_con.erl b/src/gd_con.erl index 563da3d..f2a5119 100644 --- a/src/gd_con.erl +++ b/src/gd_con.erl @@ -528,13 +528,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(). diff --git a/src/gd_gui.erl b/src/gd_gui.erl index ad2d0cb..311d513 100644 --- a/src/gd_gui.erl +++ b/src/gd_gui.erl @@ -729,104 +729,28 @@ 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), - + Title = J("Transfer Gajus"), 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, Nonce, Height}, + Labels = {J("From"), J("To"), J("Amount"), J("Message (optional)"), J("TTL"), J("Gas Price")}, ok = - case wxDialog:showModal(Dialog) of - ?wxID_OK -> + case gd_m_spend:show(Frame, Title, Args, Labels) of + {ok, RecipientID, Amount, GasPrice, TTL, Payload} -> TX = #spend_tx{sender_id = ID, - recipient_id = wxTextCtrl:getValue(ToTx), - amount = wxTextCtrl:getValue(AmtTx), - gas_price = wxSlider:getValue(GasSl), + recipient_id = RecipientID, + amount = Amount, + gas_price = GasPrice, gas = 20000, - ttl = Height + wxSlider:getValue(TTL_Sl), + ttl = Height + TTL, nonce = Nonce, - payload = wxTextCtrl:getValue(DataTx)}, - clean_spend(TX); - ?wxID_CANCEL -> + payload = Payload}, + 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}) -> Title = J("GRIDS URL"), diff --git a/src/gd_m_spend.erl b/src/gd_m_spend.erl new file mode 100644 index 0000000..969f293 --- /dev/null +++ b/src/gd_m_spend.erl @@ -0,0 +1,274 @@ +%%% @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(), + 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(), + ok = none :: none | wx:wx_object(), + cancel = none :: none | wx:wx_object()}). + + +%%% Interface + +-spec show(Parent, Title, Args, Labels) -> {ok, ReceipientID, Amount, GasPrice, TTL, Payload} | cancel + when Parent :: wxFrame:wxFrame(), + Title :: string(), + Args :: {Account :: iolist(), + Nonce :: pos_integer(), + Height :: pos_integer()}, + Labels :: {FromL :: string(), + ToL :: string(), + AmountL :: string(), + MessageL :: string(), + TTL_L :: string(), + GasL :: string(), + AffirmL :: string(), + CancelL :: string()}, + RecipientID :: binary(), % <<"ak_...">> | <<"ct_...">> + Amount :: non_neg_integer(), % Pucks + GasPrice :: pos_integer(), % Pucks + TTL :: non_neg_integer(), % Generations + Payload :: binary(). + +show(Parent, Title, Args, Labels) -> + zxw_modal:show(Parent, ?MODULE, {Title, Args, Labels}). + + + +%% Init + +init({Parent, Caller, {Title, Args, Labels}}) -> + {Account, Nonce, Height} = Args, + {FromL, ToL, AmountL, MessageL, TTL_L, GasL, AffirmL, CancelL} = Labels, + 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), + + FromTx = wxStaticText:new(Panel, ?wxID_ANY, Account), + FromSz = wxStaticBoxSizer:new(?wxVERTICAL, Panel, [{label, FromL}]), + _ = wxStaticBoxSizer:add(FromSz, FromTx, zxw:flags({wide, 5})), + ToTx = wxTextCtrl:new(Panel, ?wxID_ANY), + ToSz = wxStaticBoxSizer:new(?wxVERTICAL, Panel, [{label, ToL}]), + _ = wxStaticBoxSizer:add(ToSz, ToTx, zxw:flags({wide, 5})), + AmtTx = wxTextCtrl:new(Panel, ?wxID_ANY), + AmtSz = wxStaticBoxSizer:new(?wxVERTICAL, Panel, [{label, AmountL}]), + AmtInSz = wxBoxSizer:new(?wxHORIZONTAL), + AmtLabel = wxStaticText:new(Panel, ?wxID_ANY, "木"), + _ = wxStaticBoxSizer:add(AmtInSz, AmtLabel, zxw:flags(base)), + _ = 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, Dialog, [{label, MessageL}]), + _ = 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, TTL_L}]), + _ = 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, GasL}]), + _ = wxStaticBoxSizer:add(GasSz, GasSl, zxw:flags({wide, 5})), + ButtSz = wxBoxSizer:new(?wxHORIZONTAL), + Affirm = wxButton:new(Panel, ?wxID_ANY, [{label, AffirmL}]), + Cancel = wxButton:new(Panel, ?wxID_ANY, [{label, CancelL}]), + _ = 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({wide, 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, OK, 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(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, 450}), + ok = wxFrame:center(Frame), + 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, + to_tx = ToTx, amount_tx = AmtTx, payload_tx = PayloadTx, + ttl_sl = TTL_Sl, gas_sl = GasSl, + ok = OK, cancel = Cancel}, + {Frame, State}. + + +key_handler(ToTx, AmtTx, PayloadTx, TTL_Sl, GasSl, OK, 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), + 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 +% 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}; +% 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, + to_tx = ToTx, amount_tx = AmountTx, payload_tx = PayloadTx, + ttl_sl = TTL_Sl, gas_sl = GasSl}) -> + DirtyTX = + #spend_tx{recipient_id = wxTextCtrl:getValue(ToTx), + amount = wxTextCtrl:getValue(AmountTx), + gas_price = wxSlider:getValue(GasSl), + ttl = wxSlider:getValue(TTL_Sl), + payload = wxTextCtrl:getValue(PayloadTX)}, + Result = + case clean_spend(TX) of + {ok, CleanTX} -> + + + 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). + + +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) -> + {ok, CleanTX}. + + +is_int(S) -> + lists:all(fun(C) -> $0 =< C andalso C =< $9 end, S). From 6a8c4fe1e11e6db5b194369ff51b30718a26a0bf Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Sat, 29 Nov 2025 14:33:15 +0900 Subject: [PATCH 16/25] WIP: Add formatter and some other stuff --- src/gd_gui.erl | 34 ------ src/gd_lib.erl | 279 +++++++++++++++++++++++++++++++++++++++++++++ src/gd_m_spend.erl | 13 ++- 3 files changed, 286 insertions(+), 40 deletions(-) create mode 100644 src/gd_lib.erl diff --git a/src/gd_gui.erl b/src/gd_gui.erl index 311d513..033de68 100644 --- a/src/gd_gui.erl +++ b/src/gd_gui.erl @@ -1048,37 +1048,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..1c43139 --- /dev/null +++ b/src/gd_lib.erl @@ -0,0 +1,279 @@ +%%% @doc +%%% GajuDesk Helper Functions +%%% @end + +-module(gd_lib). + +-export([format_price/1, format_price/3, format_price/4, % read_price/1, + price_to_string/1, string_to_price/1, + is_int/1]). + + +format_price(Pucks) -> + format_price(gaju, {west, $,, 3},Pucks). + + +format_price(puck, {west, Separator, Period}, Pucks) -> + western_format(Separator, Period, Pucks); +format_price(gaju, {west, Separator = $., Period}, Pucks) -> + western_format(Separator, $,, Period, all, Pucks); +format_price(gaju, {west, Separator, Period}, Pucks) -> + western_format(Separator, $., Period, all, Pucks); +format_price(Unit, jp, Pucks) -> + jp_format(Unit, all, Pucks). + + + +-spec format_price(Unit, Style, Precision, Pucks) -> Serialized + when Unit :: gaju | puck, + Style :: jp | jp_pucks | {Separator, Period}, + Precision :: all | 0..18, + Separator :: $, | $. | $_, + Period :: 3 | 4, + Pucks :: integer(), + Serialized :: string(). +%% @doc +%% A formatting function that accepts a price value in pucks as an integer, and outputs a string +%% formatted in the desired way. +%% +%% Consider, for example, a few input and output pairs: +%% ``` +%% format_price({$,, 3}, 3, 123456789123456789123456789) -> +%% "木123,456,789.123". +%% +%% format_price({$,, 3}, 6, 123456789123456789123456789) -> +%% "木123,456,789.123,456" +%% +%% format_price({$,, 3}, all, 123456789123456789123456789) -> +%% "木123.456.789,123.456.789.123.456.789" +%% +%% format_price({$_, 4}, 10, 123456789123456789123456789) -> +%% "木1_2345_6789.1234_5678_91" +%% +%% format_price(jp, 3, 123456789123456789123456789) -> +%% "1億2345万6789木12京3000兆本" +%% +%% format_price(jp, 6, 123456789123456789123456789) -> +%% "1億2345万6789木12京3456兆本" +%% +%% format_price(jp, 0, 123456789123456789123456789) -> +%% "1億2345万6789木" +%% +%% format_price(jp_pucks, all, 123456789123456789123456789) -> +%% "123秭4567垓8912京3456兆7891億2345万6789本" +%% ''' + + +format_price(puck, {west, Separator, Period}, _, Pucks) -> + western_format(Separator, Period, Pucks); +format_price(gaju, {west, Separator = $., Period}, Precision, Pucks) -> + western_format(Separator, $,, Period, Precision, Pucks); +format_price(gaju, {west, Separator, Period}, Precision, Pucks) -> + western_format(Separator, $., Period, Precision, Pucks); +format_price(Unit, jp, Precision, Pucks) -> + jp_format(Unit, Precision, Pucks). + + + + +western_format(Separator, Period, Pucks) -> + P = lists:reverse(integer_to_list(Pucks)), + [puck_mark() | separate(Separator, Period, P)]. + + +western_format(Separator, _, Period, 0, Pucks) -> + G = lists:reverse(integer_to_list(Pucks div one_gaju())), + [gaju_mark() | separate(Separator, Period, G)]; +western_format(Separator, Break, Period, Precision, Pucks) -> + G = lists:reverse(integer_to_list(Pucks div one_gaju())), + H = [gaju_mark() | separate(Separator, Period, G)], + case decimal_pucks(Precision, Pucks rem one_gaju()) of + {"", ""} -> + H; + {P, E} -> + T = lists:reverse(separate(Separator, Period, P)), + lists:flatten([H, Break, T, E]) + end. + +decimal_pucks(_, 0) -> + {"", ""}; +decimal_pucks(all, Pucks) -> + RTrailing = lists:reverse(integer_to_list(Pucks)), + {lists:reverse(lists:dropwhile(fun(C) -> C =:= $0 end, RTrailing)), ""}; +decimal_pucks(Precision, Pucks) -> + {Significant, Rest} = lists:split(min(Precision, 18), integer_to_list(Pucks)), + RTrailing = lists:reverse(Significant), + Trailing = lists:reverse(lists:dropwhile(fun(C) -> C =:= $0 end, RTrailing)), + case lists:all(fun(C) -> C =:= $0 end, Rest) of + true -> {Trailing, ""}; + false -> {Trailing, "..."} + end. + +separate(_, _, "") -> + ""; +separate(S, P, G) -> + separate(S, P, 1, G, []). + +separate(_, _, _, [H], A) -> + [H | A]; +separate(S, P, P, [H | T], A) -> + separate(S, P, 1, T, [S, H | A]); +separate(S, P, N, [H | T], A) -> + separate(S, P, N + 1, T, [H | A]). + + +jp_format(gaju, 0, Pucks) -> + G = lists:reverse(integer_to_list(Pucks div one_gaju())), + rank(gaju_mark(), G); +jp_format(gaju, all, Pucks) -> + H = jp_format(gaju, 0, Pucks), + T = jp_format(puck, all, Pucks rem one_gaju()), + H ++ T; +jp_format(gaju, Precision, Pucks) -> + H = jp_format(gaju, 0, Pucks), + T = jp_format(puck, Precision, Pucks rem one_gaju()), + H ++ T; +jp_format(puck, all, Pucks) -> + P = lists:reverse(integer_to_list(Pucks)), + case lists:all(fun(C) -> C =:= $0 end, P) of + false -> rank(puck_mark(), P); + true -> [$0, puck_mark()] + end; +jp_format(puck, Precision, Pucks) -> + Digits = min(Precision, 18), + P = lists:reverse(integer_to_list(Pucks)), + case length(P) > Digits of + true -> + [$0, puck_mark()]; + false -> + Significant = lists:nthtail(Digits, P), + Filler = tuple_to_list(erlang:make_tuple(Digits, $0)), + PuckingString = Filler ++ Significant, + case lists:all(fun(C) -> C =:= $0 end, PuckingString) of + false -> rank(puck_mark(), PuckingString); + true -> [$0, puck_mark()] + end + end. + + +rank(Symbol, [$0, $0, $0, $0 | PT]) -> + rank(jp_ranks(), PT, [Symbol]); +rank(Symbol, [P4, $0, $0, $0 | PT]) -> + rank(jp_ranks(), PT, [P4, Symbol]); +rank(Symbol, [P4, P3, $0, $0 | PT]) -> + rank(jp_ranks(), PT, [P3, P4, Symbol]); +rank(Symbol, [P4, P3, P2, $0 | PT]) -> + rank(jp_ranks(), PT, [P2, P3, P4, Symbol]); +rank(Symbol, [P4, P3, P2, P1 | PT]) -> + rank(jp_ranks(), PT, [P1, P2, P3, P4, Symbol]); +rank(Symbol, [P4]) -> + [P4, Symbol]; +rank(Symbol, [P4, P3]) -> + [P3, P4, Symbol]; +rank(Symbol, [P4, P3, P2]) -> + [P2, P3, P4, Symbol]. + +jp_ranks() -> +% "万億兆京垓秭穣溝澗正載極". + "ABCDEFG". + +rank([_ | RT], [$0, $0, $0, $0 | PT], A) -> + rank(RT, PT, A); +rank([RH | RT], [P4, $0, $0, $0 | PT], A) -> + rank(RT, PT, [P4, RH | A]); +rank([RH | RT], [P4, P3, $0, $0 | PT], A) -> + rank(RT, PT, [P3, P4, RH | A]); +rank([RH | RT], [P4, P3, P2, $0 | PT], A) -> + rank(RT, PT, [P2, P3, P4, RH | A]); +rank([RH | RT], [P4, P3, P2, P1 | PT], A) -> + rank(RT, PT, [P1, P2, P3, P4, RH | A]); +rank(_, [$0, $0, $0, $0], A) -> + A; +rank(_, [$0, $0, $0], A) -> + A; +rank(_, [$0, $0], A) -> + A; +rank(_, [$0], A) -> + A; +rank(_, [], A) -> + A; +rank([RH | _], [P4, $0, $0, $0], A) -> + [P4, RH | A]; +rank([RH | _], [P4, $0, $0], A) -> + [P4, RH | A]; +rank([RH | _], [P4, $0], A) -> + [P4, RH | A]; +rank([RH | _], [P4], A) -> + [P4, RH | A]; +rank([RH | _], [P4, P3, $0, $0], A) -> + [P3, P4, RH | A]; +rank([RH | _], [P4, P3, $0], A) -> + [P3, P4, RH | A]; +rank([RH | _], [P4, P3], A) -> + [P3, P4, RH | A]; +rank([RH | _], [P4, P3, P2, $0], A) -> + [P2, P3, P4, RH | A]; +rank([RH | _], [P4, P3, P2], A) -> + [P2, P3, P4, RH | A]; +rank([RH | _], [P4, P3, P2, P1], A) -> + [P1, P2, P3, P4, RH | A]. + + +gaju_mark() -> $G. + +puck_mark() -> $P. + +one_gaju() -> 1_000_000_000_000_000_000. + + +-spec price_to_string(Pucks) -> Gajus + when Pucks :: integer(), + Gajus :: string(). +%% @doc +%% A simplified formatting function that converts an integer value in Pucks to a string representation +%% in Gajus. Useful for formatting generic output for UI elements + +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. + + +-spec string_to_price(Gajus) -> Pucks + when Gajus :: string(), + Pucks :: integer(). +%% @doc +%% A simplified formatting function that converts a Gaju value represented as a string to an +%% integer value in Pucks. + +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. + + +-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 index 969f293..5040444 100644 --- a/src/gd_m_spend.erl +++ b/src/gd_m_spend.erl @@ -17,6 +17,7 @@ -include_lib("wx/include/wx.hrl"). -include("$zx_include/zx_logger.hrl"). +-include("gd.hrl"). -record(s, {frame = none :: none | wx:wx_object(), @@ -232,7 +233,7 @@ done(#s{frame = Frame, caller = Caller, case clean_spend(TX) of {ok, CleanTX} -> - + end, Result = case wxTextCtrl:getValue(NameTx) of "" -> @@ -246,10 +247,12 @@ done(#s{frame = Frame, caller = Caller, ok = wxFrame:destroy(Frame), zxw_modal:done(Caller, Result). +clean_spend(TX) -> + clean_spend(TX, []). -clean_spend(#spend_tx{recipient_id = ""}) -> - ok; -clean_spend( TX = #spend_tx{amount = S}) when is_list(S) -> +clean_spend(TX = #spend_tx{recipient_id = ""}, Errors) -> + clean_spend(TX#spend_tx{recipient_id = none}, [recipient_id | Errors]); +clean_spend(TX = #spend_tx{amount = S}, Errors) when is_list(S) -> case string_to_price(S) of {ok, Amount} -> clean_spend(TX#spend_tx{amount = Amount}); {error, _} -> ok @@ -270,5 +273,3 @@ clean_spend(TX) -> {ok, CleanTX}. -is_int(S) -> - lists:all(fun(C) -> $0 =< C andalso C =< $9 end, S). From d9acde1b83644ffef7373cfca2f69d72fa9fdf98 Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Mon, 1 Dec 2025 11:52:00 +0900 Subject: [PATCH 17/25] WIP :Move formatting functions --- src/gd_lib.erl | 266 +------------------------------------------------ 1 file changed, 1 insertion(+), 265 deletions(-) diff --git a/src/gd_lib.erl b/src/gd_lib.erl index 1c43139..4d9d983 100644 --- a/src/gd_lib.erl +++ b/src/gd_lib.erl @@ -4,271 +4,7 @@ -module(gd_lib). --export([format_price/1, format_price/3, format_price/4, % read_price/1, - price_to_string/1, string_to_price/1, - is_int/1]). - - -format_price(Pucks) -> - format_price(gaju, {west, $,, 3},Pucks). - - -format_price(puck, {west, Separator, Period}, Pucks) -> - western_format(Separator, Period, Pucks); -format_price(gaju, {west, Separator = $., Period}, Pucks) -> - western_format(Separator, $,, Period, all, Pucks); -format_price(gaju, {west, Separator, Period}, Pucks) -> - western_format(Separator, $., Period, all, Pucks); -format_price(Unit, jp, Pucks) -> - jp_format(Unit, all, Pucks). - - - --spec format_price(Unit, Style, Precision, Pucks) -> Serialized - when Unit :: gaju | puck, - Style :: jp | jp_pucks | {Separator, Period}, - Precision :: all | 0..18, - Separator :: $, | $. | $_, - Period :: 3 | 4, - Pucks :: integer(), - Serialized :: string(). -%% @doc -%% A formatting function that accepts a price value in pucks as an integer, and outputs a string -%% formatted in the desired way. -%% -%% Consider, for example, a few input and output pairs: -%% ``` -%% format_price({$,, 3}, 3, 123456789123456789123456789) -> -%% "木123,456,789.123". -%% -%% format_price({$,, 3}, 6, 123456789123456789123456789) -> -%% "木123,456,789.123,456" -%% -%% format_price({$,, 3}, all, 123456789123456789123456789) -> -%% "木123.456.789,123.456.789.123.456.789" -%% -%% format_price({$_, 4}, 10, 123456789123456789123456789) -> -%% "木1_2345_6789.1234_5678_91" -%% -%% format_price(jp, 3, 123456789123456789123456789) -> -%% "1億2345万6789木12京3000兆本" -%% -%% format_price(jp, 6, 123456789123456789123456789) -> -%% "1億2345万6789木12京3456兆本" -%% -%% format_price(jp, 0, 123456789123456789123456789) -> -%% "1億2345万6789木" -%% -%% format_price(jp_pucks, all, 123456789123456789123456789) -> -%% "123秭4567垓8912京3456兆7891億2345万6789本" -%% ''' - - -format_price(puck, {west, Separator, Period}, _, Pucks) -> - western_format(Separator, Period, Pucks); -format_price(gaju, {west, Separator = $., Period}, Precision, Pucks) -> - western_format(Separator, $,, Period, Precision, Pucks); -format_price(gaju, {west, Separator, Period}, Precision, Pucks) -> - western_format(Separator, $., Period, Precision, Pucks); -format_price(Unit, jp, Precision, Pucks) -> - jp_format(Unit, Precision, Pucks). - - - - -western_format(Separator, Period, Pucks) -> - P = lists:reverse(integer_to_list(Pucks)), - [puck_mark() | separate(Separator, Period, P)]. - - -western_format(Separator, _, Period, 0, Pucks) -> - G = lists:reverse(integer_to_list(Pucks div one_gaju())), - [gaju_mark() | separate(Separator, Period, G)]; -western_format(Separator, Break, Period, Precision, Pucks) -> - G = lists:reverse(integer_to_list(Pucks div one_gaju())), - H = [gaju_mark() | separate(Separator, Period, G)], - case decimal_pucks(Precision, Pucks rem one_gaju()) of - {"", ""} -> - H; - {P, E} -> - T = lists:reverse(separate(Separator, Period, P)), - lists:flatten([H, Break, T, E]) - end. - -decimal_pucks(_, 0) -> - {"", ""}; -decimal_pucks(all, Pucks) -> - RTrailing = lists:reverse(integer_to_list(Pucks)), - {lists:reverse(lists:dropwhile(fun(C) -> C =:= $0 end, RTrailing)), ""}; -decimal_pucks(Precision, Pucks) -> - {Significant, Rest} = lists:split(min(Precision, 18), integer_to_list(Pucks)), - RTrailing = lists:reverse(Significant), - Trailing = lists:reverse(lists:dropwhile(fun(C) -> C =:= $0 end, RTrailing)), - case lists:all(fun(C) -> C =:= $0 end, Rest) of - true -> {Trailing, ""}; - false -> {Trailing, "..."} - end. - -separate(_, _, "") -> - ""; -separate(S, P, G) -> - separate(S, P, 1, G, []). - -separate(_, _, _, [H], A) -> - [H | A]; -separate(S, P, P, [H | T], A) -> - separate(S, P, 1, T, [S, H | A]); -separate(S, P, N, [H | T], A) -> - separate(S, P, N + 1, T, [H | A]). - - -jp_format(gaju, 0, Pucks) -> - G = lists:reverse(integer_to_list(Pucks div one_gaju())), - rank(gaju_mark(), G); -jp_format(gaju, all, Pucks) -> - H = jp_format(gaju, 0, Pucks), - T = jp_format(puck, all, Pucks rem one_gaju()), - H ++ T; -jp_format(gaju, Precision, Pucks) -> - H = jp_format(gaju, 0, Pucks), - T = jp_format(puck, Precision, Pucks rem one_gaju()), - H ++ T; -jp_format(puck, all, Pucks) -> - P = lists:reverse(integer_to_list(Pucks)), - case lists:all(fun(C) -> C =:= $0 end, P) of - false -> rank(puck_mark(), P); - true -> [$0, puck_mark()] - end; -jp_format(puck, Precision, Pucks) -> - Digits = min(Precision, 18), - P = lists:reverse(integer_to_list(Pucks)), - case length(P) > Digits of - true -> - [$0, puck_mark()]; - false -> - Significant = lists:nthtail(Digits, P), - Filler = tuple_to_list(erlang:make_tuple(Digits, $0)), - PuckingString = Filler ++ Significant, - case lists:all(fun(C) -> C =:= $0 end, PuckingString) of - false -> rank(puck_mark(), PuckingString); - true -> [$0, puck_mark()] - end - end. - - -rank(Symbol, [$0, $0, $0, $0 | PT]) -> - rank(jp_ranks(), PT, [Symbol]); -rank(Symbol, [P4, $0, $0, $0 | PT]) -> - rank(jp_ranks(), PT, [P4, Symbol]); -rank(Symbol, [P4, P3, $0, $0 | PT]) -> - rank(jp_ranks(), PT, [P3, P4, Symbol]); -rank(Symbol, [P4, P3, P2, $0 | PT]) -> - rank(jp_ranks(), PT, [P2, P3, P4, Symbol]); -rank(Symbol, [P4, P3, P2, P1 | PT]) -> - rank(jp_ranks(), PT, [P1, P2, P3, P4, Symbol]); -rank(Symbol, [P4]) -> - [P4, Symbol]; -rank(Symbol, [P4, P3]) -> - [P3, P4, Symbol]; -rank(Symbol, [P4, P3, P2]) -> - [P2, P3, P4, Symbol]. - -jp_ranks() -> -% "万億兆京垓秭穣溝澗正載極". - "ABCDEFG". - -rank([_ | RT], [$0, $0, $0, $0 | PT], A) -> - rank(RT, PT, A); -rank([RH | RT], [P4, $0, $0, $0 | PT], A) -> - rank(RT, PT, [P4, RH | A]); -rank([RH | RT], [P4, P3, $0, $0 | PT], A) -> - rank(RT, PT, [P3, P4, RH | A]); -rank([RH | RT], [P4, P3, P2, $0 | PT], A) -> - rank(RT, PT, [P2, P3, P4, RH | A]); -rank([RH | RT], [P4, P3, P2, P1 | PT], A) -> - rank(RT, PT, [P1, P2, P3, P4, RH | A]); -rank(_, [$0, $0, $0, $0], A) -> - A; -rank(_, [$0, $0, $0], A) -> - A; -rank(_, [$0, $0], A) -> - A; -rank(_, [$0], A) -> - A; -rank(_, [], A) -> - A; -rank([RH | _], [P4, $0, $0, $0], A) -> - [P4, RH | A]; -rank([RH | _], [P4, $0, $0], A) -> - [P4, RH | A]; -rank([RH | _], [P4, $0], A) -> - [P4, RH | A]; -rank([RH | _], [P4], A) -> - [P4, RH | A]; -rank([RH | _], [P4, P3, $0, $0], A) -> - [P3, P4, RH | A]; -rank([RH | _], [P4, P3, $0], A) -> - [P3, P4, RH | A]; -rank([RH | _], [P4, P3], A) -> - [P3, P4, RH | A]; -rank([RH | _], [P4, P3, P2, $0], A) -> - [P2, P3, P4, RH | A]; -rank([RH | _], [P4, P3, P2], A) -> - [P2, P3, P4, RH | A]; -rank([RH | _], [P4, P3, P2, P1], A) -> - [P1, P2, P3, P4, RH | A]. - - -gaju_mark() -> $G. - -puck_mark() -> $P. - -one_gaju() -> 1_000_000_000_000_000_000. - - --spec price_to_string(Pucks) -> Gajus - when Pucks :: integer(), - Gajus :: string(). -%% @doc -%% A simplified formatting function that converts an integer value in Pucks to a string representation -%% in Gajus. Useful for formatting generic output for UI elements - -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. - - --spec string_to_price(Gajus) -> Pucks - when Gajus :: string(), - Pucks :: integer(). -%% @doc -%% A simplified formatting function that converts a Gaju value represented as a string to an -%% integer value in Pucks. - -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. - +-export([is_int/1]). -spec is_int(string()) -> boolean(). %% @doc From 638c88f900d7573fe5e1992ac0f4283956f020a9 Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Tue, 23 Dec 2025 15:59:48 +0900 Subject: [PATCH 18/25] Dekajigger the kajiggerlicious SpendTX --- src/gd_gui.erl | 36 +++--- src/gd_m_spend.erl | 265 +++++++++++++++++++++++---------------------- zomp.meta | 4 +- 3 files changed, 152 insertions(+), 153 deletions(-) diff --git a/src/gd_gui.erl b/src/gd_gui.erl index 033de68..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{}}). @@ -117,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(Panel, ?wxID_ANY, "木"), - BalanceT = wxStaticText:new(Panel, ?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), @@ -729,22 +725,16 @@ spend(Selected, State = #s{accounts = Accounts}) -> end. spend2(#poa{id = ID, name = Name}, Nonce, Height, State = #s{frame = Frame, j = J}) -> - Title = J("Transfer Gajus"), Account = [Name, " (", ID, ")"], - Args = {Account, Nonce, Height}, - Labels = {J("From"), J("To"), J("Amount"), J("Message (optional)"), J("TTL"), J("Gas Price")}, + Args = {Account, J}, ok = - case gd_m_spend:show(Frame, Title, Args, Labels) of - {ok, RecipientID, Amount, GasPrice, TTL, Payload} -> + case gd_m_spend:show(Frame, Args) of + {ok, Partial = #spend_tx{ttl = TTL}} -> TX = - #spend_tx{sender_id = ID, - recipient_id = RecipientID, - amount = Amount, - gas_price = GasPrice, - gas = 20000, - ttl = Height + TTL, - nonce = Nonce, - payload = Payload}, + Partial#spend_tx{sender_id = ID, + gas = 20000, + ttl = Height + TTL, + nonce = Nonce}, gd_con:spend(TX); cancel -> ok @@ -770,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}; @@ -803,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. diff --git a/src/gd_m_spend.erl b/src/gd_m_spend.erl index 5040444..66b56ef 100644 --- a/src/gd_m_spend.erl +++ b/src/gd_m_spend.erl @@ -4,7 +4,7 @@ %%% Interprets [ENTER] as an "OK" button click and [ESC] as a "Cancel" button click. %%% @end --module(gd_m_wallet_importer). +-module(gd_m_spend). -vsn("0.8.0"). -author("Craig Everett "). -copyright("Craig Everett "). @@ -12,7 +12,7 @@ -behavior(zxw_modal). --export([show/6]). +-export([show/2]). -export([init/1, handle_info/2, handle_event/2]). -include_lib("wx/include/wx.hrl"). @@ -23,145 +23,139 @@ {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(), - ok = 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, Title, Args, Labels) -> {ok, ReceipientID, Amount, GasPrice, TTL, Payload} | cancel +-spec show(Parent, Args) -> {ok, #spend_tx{}} | cancel when Parent :: wxFrame:wxFrame(), - Title :: string(), Args :: {Account :: iolist(), Nonce :: pos_integer(), - Height :: pos_integer()}, - Labels :: {FromL :: string(), - ToL :: string(), - AmountL :: string(), - MessageL :: string(), - TTL_L :: string(), - GasL :: string(), - AffirmL :: string(), - CancelL :: string()}, - RecipientID :: binary(), % <<"ak_...">> | <<"ct_...">> - Amount :: non_neg_integer(), % Pucks - GasPrice :: pos_integer(), % Pucks - TTL :: non_neg_integer(), % Generations - Payload :: binary(). + Height :: pos_integer(), + J :: fun()}. -show(Parent, Title, Args, Labels) -> - zxw_modal:show(Parent, ?MODULE, {Title, Args, Labels}). +show(Parent, Args) -> + zxw_modal:show(Parent, ?MODULE, Args). %% Init -init({Parent, Caller, {Title, Args, Labels}}) -> - {Account, Nonce, Height} = Args, - {FromL, ToL, AmountL, MessageL, TTL_L, GasL, AffirmL, CancelL} = Labels, - Frame = wxFrame:new(Parent, ?wxID_ANY, Title), +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, FromL}]), + 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, ToL}]), + 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, AmountL}]), + AmtSz = wxStaticBoxSizer:new(?wxVERTICAL, Panel, [{label, J("Amount")}]), AmtInSz = wxBoxSizer:new(?wxHORIZONTAL), AmtLabel = wxStaticText:new(Panel, ?wxID_ANY, "木"), _ = wxStaticBoxSizer:add(AmtInSz, AmtLabel, zxw:flags(base)), _ = 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, Dialog, [{label, MessageL}]), + 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, TTL_L}]), + 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, GasL}]), + 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, AffirmL}]), - Cancel = wxButton:new(Panel, ?wxID_ANY, [{label, CancelL}]), + 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({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, OK, Cancel), + 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(OK, 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, 450}), + 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), - ok = wxTextCtrl:setFocus(NameTx), true = wxFrame:show(Frame), + self() ! {tab, cancel}, State = - #s{frame = Frame, parent = Parent, caller = Caller, - to_tx = ToTx, amount_tx = AmtTx, payload_tx = PayloadTx, - ttl_sl = TTL_Sl, gas_sl = GasSl, - ok = OK, cancel = Cancel}, + #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, OK, Cancel) -> +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), - OK_ID = wxButton:getId(OK), + AffirmID = wxButton:getId(Affirm), CancelID = wxButton:getId(Cancel), fun(#wx{id = ID, event = #wxKey{type = key_down, keyCode = Code}}, KeyPress) -> case Code of -% 9 -> -% 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}; -% 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; + 9 -> + 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 ! {tab, affirm}; + CancelID -> Me ! {tab, cancel}; + _ -> 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; _ -> @@ -174,11 +168,11 @@ key_handler(ToTx, AmtTx, PayloadTx, TTL_Sl, GasSl, OK, Cancel) -> 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({enter, affirm}, State) -> + NewState = check(State), + {noreply, NewState}; +handle_info({enter, cancel}, State) -> + cancel(State); handle_info(esc, State) -> ok = cancel(State), {noreply, State}; @@ -188,13 +182,15 @@ handle_info(Message, State) -> handle_event(#wx{event = #wxCommand{type = command_button_clicked}, id = ID}, - State = #s{ok = OK, cancel = Cancel}) -> - OK_ID = wxButton:getId(OK), + State = #s{affirm = Affirm, cancel = Cancel}) -> + AffirmID = wxButton:getId(Affirm), CancelID = wxButton:getId(Cancel), - case ID of - OK_ID -> done(State); - CancelID -> cancel(State) - end; + NewState = + case ID of + AffirmID -> check(State); + CancelID -> cancel(State) + end, + {noreply, NewState}; handle_event(#wx{event = #wxClose{}}, State) -> NewState = cancel(State), {noreply, NewState}; @@ -205,14 +201,20 @@ handle_event(Event, 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). +tab_traverse(to_tx, #s{amount_tx = AmountTX}) -> + wxTextCtrl:setFocus(AmountTX); +tab_traverse(amount_tx, #s{payload_tx = PayloadTX}) -> + wxTextCtrl:setFocus(PayloadTX); +tab_traverse(payload_tx, #s{ttl_sl = TTL_SL}) -> + wxSlider:setFocus(TTL_SL); +tab_traverse(ttl_sl, #s{gas_sl = GasSL}) -> + wxSlider:setFocus(GasSL); +tab_traverse(gas_sl, #s{affirm = Affirm}) -> + wxButton:setFocus(Affirm); +tab_traverse(affirm, #s{cancel = Cancel}) -> + wxButton:setFocus(Cancel); +tab_traverse(cancel, #s{to_tx = ToTX}) -> + wxTextCtrl:setFocus(ToTX). cancel(#s{frame = Frame, caller = Caller}) -> @@ -220,56 +222,63 @@ cancel(#s{frame = Frame, caller = Caller}) -> zxw_modal:done(Caller, cancel). -done(#s{frame = Frame, caller = Caller, - to_tx = ToTx, amount_tx = AmountTx, payload_tx = PayloadTx, - ttl_sl = TTL_Sl, gas_sl = GasSl}) -> +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 = - #spend_tx{recipient_id = wxTextCtrl:getValue(ToTx), - amount = wxTextCtrl:getValue(AmountTx), - gas_price = wxSlider:getValue(GasSl), - ttl = wxSlider:getValue(TTL_Sl), - payload = wxTextCtrl:getValue(PayloadTX)}, - Result = - case clean_spend(TX) of - {ok, CleanTX} -> + [{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} -> + 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, - 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). - -clean_spend(TX) -> - clean_spend(TX, []). - -clean_spend(TX = #spend_tx{recipient_id = ""}, Errors) -> - clean_spend(TX#spend_tx{recipient_id = none}, [recipient_id | Errors]); -clean_spend(TX = #spend_tx{amount = S}, Errors) 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) -> - {ok, CleanTX}. + 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/zomp.meta b/zomp.meta index 71fdb54..f1f4f59 100644 --- a/zomp.meta +++ b/zomp.meta @@ -5,8 +5,8 @@ {author,"Craig Everett"}. {desc,"A desktop client for the Gajumaru network of blockchain networks"}. {package_id,{"otpr","gajudesk",{0,8,0}}}. -{deps,[{"otpr","zxwidgets",{1,1,0}}, - {"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}}, From 39b2e48f68ecf1c88ef6f1b230992951f8389de1 Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Tue, 23 Dec 2025 17:02:52 +0900 Subject: [PATCH 19/25] WIP --- src/gd_m_spend.erl | 66 ++++++++++++++++++++++++---------------------- 1 file changed, 34 insertions(+), 32 deletions(-) diff --git a/src/gd_m_spend.erl b/src/gd_m_spend.erl index 66b56ef..f36c901 100644 --- a/src/gd_m_spend.erl +++ b/src/gd_m_spend.erl @@ -68,8 +68,6 @@ init({Parent, Caller, {Account, J}}) -> AmtTx = wxTextCtrl:new(Panel, ?wxID_ANY), AmtSz = wxStaticBoxSizer:new(?wxVERTICAL, Panel, [{label, J("Amount")}]), AmtInSz = wxBoxSizer:new(?wxHORIZONTAL), - AmtLabel = wxStaticText:new(Panel, ?wxID_ANY, "木"), - _ = wxStaticBoxSizer:add(AmtInSz, AmtLabel, zxw:flags(base)), _ = 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}]), @@ -114,7 +112,7 @@ init({Parent, Caller, {Account, J}}) -> ok = wxBoxSizer:layout(MainSz), ok = wxFrame:centerOnParent(Frame), true = wxFrame:show(Frame), - self() ! {tab, cancel}, + self() ! {focus, to_tx}, State = #s{frame = Frame, parent = Parent, caller = Caller, to_tx = ToTx, amount_tx = AmtTx, payload_tx = PayloadTx, @@ -133,19 +131,30 @@ key_handler(ToTx, AmtTx, PayloadTx, TTL_Sl, GasSl, Affirm, Cancel) -> AffirmID = wxButton:getId(Affirm), CancelID = wxButton:getId(Cancel), fun(#wx{id = ID, event = #wxKey{type = key_down, keyCode = Code}}, KeyPress) -> - case Code of - 9 -> + case {Code, wxKeyEvent:shiftDown(KeyPress)} of + {9, false} -> 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 ! {tab, affirm}; - CancelID -> Me ! {tab, cancel}; + 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; - 13 -> + {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}; @@ -156,17 +165,17 @@ key_handler(ToTx, AmtTx, PayloadTx, TTL_Sl, GasSl, Affirm, Cancel) -> CancelID -> Me ! {enter, cancel}; _ -> wxEvent:skip(KeyPress) end; - 27 -> + {27, _} -> Me ! esc; - _ -> + {_, _} -> wxEvent:skip(KeyPress) end end. -handle_info({tab, Element}, State) -> - ok = tab_traverse(Element, State), +handle_info({focus, Element}, State) -> + ok = focus(Element, State), {noreply, State}; handle_info({enter, affirm}, State) -> NewState = check(State), @@ -201,20 +210,13 @@ handle_event(Event, State) -> %%% Doers -tab_traverse(to_tx, #s{amount_tx = AmountTX}) -> - wxTextCtrl:setFocus(AmountTX); -tab_traverse(amount_tx, #s{payload_tx = PayloadTX}) -> - wxTextCtrl:setFocus(PayloadTX); -tab_traverse(payload_tx, #s{ttl_sl = TTL_SL}) -> - wxSlider:setFocus(TTL_SL); -tab_traverse(ttl_sl, #s{gas_sl = GasSL}) -> - wxSlider:setFocus(GasSL); -tab_traverse(gas_sl, #s{affirm = Affirm}) -> - wxButton:setFocus(Affirm); -tab_traverse(affirm, #s{cancel = Cancel}) -> - wxButton:setFocus(Cancel); -tab_traverse(cancel, #s{to_tx = ToTX}) -> - wxTextCtrl:setFocus(ToTX). +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}) -> @@ -266,7 +268,7 @@ 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([{payload, S} | Rest], TX, J, Errors) -> clean_spend(Rest, TX#spend_tx{payload = list_to_binary(S)}, J, Errors); clean_spend([], TX, _, []) -> {ok, TX}; From 8a8bee0bbdba5747131d2c3982e9f25fe7fe3743 Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Sat, 27 Dec 2025 21:04:30 +0900 Subject: [PATCH 20/25] WIP --- src/gd_m_spend.erl | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/gd_m_spend.erl b/src/gd_m_spend.erl index f36c901..587da5b 100644 --- a/src/gd_m_spend.erl +++ b/src/gd_m_spend.erl @@ -1,7 +1,5 @@ %%% @doc -%%% A live modal for importing a wallet. -%%% -%%% Interprets [ENTER] as an "OK" button click and [ESC] as a "Cancel" button click. +%%% A live modal for creating a SpendTX %%% @end -module(gd_m_spend). From 52994a12cb9e53697f0f4272a830c2b14e201dd7 Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Tue, 30 Dec 2025 16:27:29 +0900 Subject: [PATCH 21/25] Monkeypatch net crash on open --- src/gd_con.erl | 86 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 68 insertions(+), 18 deletions(-) diff --git a/src/gd_con.erl b/src/gd_con.erl index f2a5119..7f85229 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. @@ -334,8 +356,9 @@ init(none) -> 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 = case FirstRun of @@ -352,7 +375,7 @@ read_prefs() -> end. -tic() -> +default_tic() -> 6000. @@ -476,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}; @@ -493,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), @@ -627,7 +653,9 @@ check_balance(ChainID) -> end. + ensure_hz_set(Node = #node{ip = IP, external = Port}) -> + tell("Ensuring hz is set..."), case hz:chain_nodes() of [{IP, Port}] -> case hz:status() of @@ -854,7 +882,7 @@ do_network(#s{wallet = #wallet{chain_id = ChainID}}) -> -%%% State Operations +%%% Stateless Operations encrypt(Pass, Binary) -> Flags = [{encrypt, true}, {padding, pkcs_padding}], @@ -1053,11 +1081,12 @@ do_open_wallet(Path, Phrase, State) -> {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} @@ -1298,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, _}}) -> + ok = erlang:cancel_timer(T, [{async, true}]), + 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, _}}) -> + ok = erlang:cancel_timer(T, [{async, true}]), + 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} -> @@ -1312,11 +1358,15 @@ 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 = erlang:cancel_timer(T, [{async, true}]), + NewT = erlang:send_after(MS, self(), tic), + NewState#s{timer = {NewT, MS}}; +handle_tic(State = #s{timer = {T, MS}}) -> + ok = erlang:cancel_timer(T, [{async, true}]), + NewT = erlang:send_after(MS, self(), tic), + State#s{timer = {NewT, MS}}; +handle_tic(State) -> + State. From 99669a50c731a369a9a8c591d24c48f9726eb50d Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Tue, 30 Dec 2025 16:33:32 +0900 Subject: [PATCH 22/25] Catch HZ timeout on balance check --- src/gd_con.erl | 8 +-- src/gd_net.erl | 81 ++++++++++++++++++++++++++++++ src/gd_net_man.erl | 119 +++++++++++++++++++++++++++++++++++++++++++++ src/gd_net_sup.erl | 48 ++++++++++++++++++ src/gd_nets.erl | 50 +++++++++++++++++++ 5 files changed, 302 insertions(+), 4 deletions(-) create mode 100644 src/gd_net.erl create mode 100644 src/gd_net_man.erl create mode 100644 src/gd_net_sup.erl create mode 100644 src/gd_nets.erl diff --git a/src/gd_con.erl b/src/gd_con.erl index 7f85229..cdd203f 100644 --- a/src/gd_con.erl +++ b/src/gd_con.erl @@ -641,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 = [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}, @@ -655,7 +656,6 @@ check_balance(ChainID) -> ensure_hz_set(Node = #node{ip = IP, external = Port}) -> - tell("Ensuring hz is set..."), case hz:chain_nodes() of [{IP, Port}] -> case hz:status() of @@ -1078,7 +1078,7 @@ 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), % TODO: set_hz/1 should dispatch async to the gd_netman. diff --git a/src/gd_net.erl b/src/gd_net.erl new file mode 100644 index 0000000..6bd05eb --- /dev/null +++ b/src/gd_net.erl @@ -0,0 +1,81 @@ +%%% @doc +%%% GajuDesk: Net Worker +%%% @end + +-module(gd_net). +-vsn("0.8.0"). +-behavior(gen_server). +-author("Craig Everett "). +-copyright("QPQ AG "). +-license("GPL-3.0-or-later"). + +%% gen_server +-export([start_link/0]). +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + code_change/3, terminate/2]). +-include("$zx_include/zx_logger.hrl"). + + +%%% Type and Record Definitions + + +-record(s, + {}). + + +-type state() :: #s{}. + + + +%%% Interface + + + +%%% gen_server + +-spec start_link() -> Result + when Result :: {ok, pid()} + | {error, Reason :: term()}. + +start_link() -> + gen_server:start_link(?MODULE, none, []). + + +init(none) -> + State = #s{}, + {ok, State}. + + +handle_call(Unexpected, From, State) -> + ok = log(warning, "Unexpected call from ~tp: ~tp", [From, Unexpected]), + {noreply, State}. + + +handle_cast(Unexpected, State) -> + ok = log(warning, "Unexpected cast: ~tp", [Unexpected]), + {noreply, State}. + + +handle_info(Unexpected, State) -> + ok = log(warning, "Unexpected info: ~tp", [Unexpected]), + {noreply, State}. + + +-spec code_change(OldVersion, State, Extra) -> {ok, NewState} | {error, Reason} + when OldVersion :: Version | {old, Version}, + Version :: term(), + State :: state(), + Extra :: term(), + NewState :: term(), + Reason :: term(). + +code_change(_, State, _) -> + {ok, State}. + + +terminate(_, _) -> + ok. + + + +%%% Doer Functions diff --git a/src/gd_net_man.erl b/src/gd_net_man.erl new file mode 100644 index 0000000..1c7715e --- /dev/null +++ b/src/gd_net_man.erl @@ -0,0 +1,119 @@ +%%% @doc +%%% GajuDesk: Net Worker Manager +%%% @end + +-module(gd_net_man). +-vsn("0.8.0"). +-behavior(gen_server). +-author("Craig Everett "). +-copyright("QPQ AG "). +-license("GPL-3.0-or-later"). + +%% Worker interface +-export([enroll/0]). +%% gen_server +-export([start_link/0]). +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + code_change/3, terminate/2]). +-include("$zx_include/zx_logger.hrl"). + + +%%% Type and Record Definitions + + +-record(s, + {nets = [] :: [pid()]}). + + +-type state() :: #s{}. + + + +%%% Service Interface + + + +%%% Worker Interface + +-spec enroll() -> ok. +%% @doc +%% Workers register here after they initialize. + +enroll() -> + gen_server:cast(?MODULE, {enroll, self()}). + + + +%%% gen_server + +-spec start_link() -> Result + when Result :: {ok, pid()} + | {error, Reason :: term()}. + +start_link() -> + gen_server:start_link({local, ?MODULE}, ?MODULE, none, []). + + +init(none) -> + State = #s{}, + {ok, State}. + + +handle_call(Unexpected, From, State) -> + ok = log(warning, "Unexpected call from ~tp: ~tp", [From, Unexpected]), + {noreply, State}. + + +handle_cast({enroll, PID}, State) -> + NewState = do_enroll(PID, State), + {noreply, NewState}; +handle_cast(Unexpected, State) -> + ok = log(warning, "Unexpected cast: ~tp", [Unexpected]), + {noreply, State}. + + +handle_info({'DOWN', Mon, process, PID, Reason}, State) -> + NewState = handle_down(Mon, PID, Reason, State), + {noreply, NewState}; +handle_info(Unexpected, State) -> + ok = log(warning, "Unexpected info: ~tp", [Unexpected]), + {noreply, State}. + + +handle_down(Mon, PID, Reason, State = #s{nets = Nets}) -> + case lists:member(PID, Nets) of + true -> + NewNets = lists:delete(PID, Nets), + State#s{nets = NewNets}; + false -> + Unexpected = {'DOWN', Mon, process, PID, Reason}, + ok = log(warning, "Unexpected info: ~tp", [Unexpected]), + State + end. + + +code_change(_, State, _) -> + {ok, State}. + + +terminate(_, _) -> + ok. + + + +%%% Doer Functions + +-spec do_enroll(PID, State) -> NewState + when PID :: pid(), + State :: state(), + NewState :: state(). + +do_enroll(PID, State = #s{nets = Nets}) -> + case lists:member(PID, Nets) of + false -> + Mon = monitor(process, PID), + ok = log(info, "Enroll: ~tp @ ~tp", [PID, Mon]), + State#s{nets = [PID | Nets]}; + true -> + State + end. diff --git a/src/gd_net_sup.erl b/src/gd_net_sup.erl new file mode 100644 index 0000000..33405ee --- /dev/null +++ b/src/gd_net_sup.erl @@ -0,0 +1,48 @@ +%%% @doc +%%% GajuDesk : Net Worker Supervisor +%%% @end + +-module(gd_net_sup). +-vsn("0.8.0"). +-behaviour(supervisor). +-author("Craig Everett "). +-copyright("QPQ AG "). +-license("GPL-3.0-or-later"). + + +-export([start_net/0]). +-export([start_link/0]). +-export([init/1]). + + +-spec start_net() -> Result + when Result :: {ok, pid()} + | {error, Reason}, + Reason :: {already_started, pid()} + | {shutdown, term()} + | term(). + +start_net() -> + supervisor:start_child(?MODULE, []). + + +-spec start_link() -> {ok, pid()}. + +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, none). + + +-spec init(none) -> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}. +%% @private +%% The OTP init/1 function. + +init(none) -> + RestartStrategy = {simple_one_for_one, 1, 60}, + Net = + {gd_net, + {gd_net, start_link, []}, + temporary, + brutal_kill, + worker, + [gd_net]}, + {ok, {RestartStrategy, [Net]}}. diff --git a/src/gd_nets.erl b/src/gd_nets.erl new file mode 100644 index 0000000..bd11e6f --- /dev/null +++ b/src/gd_nets.erl @@ -0,0 +1,50 @@ +%%% @doc +%%% GajuDesk: Net Service Supervisor +%%% +%%% This is the service-level supervisor of the system. It is the parent of both the +%%% client connection handlers and the client manager (which manages the client +%%% connection handlers). This is the child of gd_sup. +%%% +%%% See: http://erlang.org/doc/apps/kernel/application.html +%%% @end + +-module(gd_nets). +-vsn("0.8.0"). +-behavior(supervisor). +-author("Craig Everett "). +-copyright("QPQ AG "). +-license("GPL-3.0-or-later"). + +-export([start_link/0]). +-export([init/1]). + + +-spec start_link() -> {ok, pid()}. +%% @private +%% This supervisor's own start function. + +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, none). + +-spec init(none) -> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}. +%% @private +%% The OTP init/1 function. + +init(none) -> + RestartStrategy = {rest_for_one, 1, 60}, + NetMan = + {gd_net_man, + {gd_net_man, start_link, []}, + permanent, + 5000, + worker, + [gd_net_man]}, + NetSup = + {gd_net_sup, + {gd_net_sup, start_link, []}, + permanent, + 5000, + supervisor, + [gd_net_sup]}, + Children = [NetSup, NetMan], + {ok, {RestartStrategy, Children}}. From 03b1f3193596c8a7cb380b1d93c9ee6cef7699fb Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Tue, 30 Dec 2025 17:05:56 +0900 Subject: [PATCH 23/25] Fix balance check --- src/gd_con.erl | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/gd_con.erl b/src/gd_con.erl index cdd203f..f4faa6e 100644 --- a/src/gd_con.erl +++ b/src/gd_con.erl @@ -641,7 +641,7 @@ do_refresh2(ChainID, State = #s{wallet = W = #wallet{poas = POAs}}) -> check_balance(ChainID) -> - fun(This = #poa{id = ID, balances = [OldBalance]}) -> + fun(This = #poa{id = ID, balances = [#balance{coin = "gaju", total = OldBalance}]}) -> Pucks = case hz:acc(ID) of {ok, #{"balance" := P}} -> P; @@ -1330,13 +1330,13 @@ read3(T) -> do_tic(stop, State = #s{timer = none}) -> State; do_tic(stop, State = #s{timer = {T, _}}) -> - ok = erlang:cancel_timer(T, [{async, true}]), + _ = 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, _}}) -> - ok = erlang:cancel_timer(T, [{async, true}]), + _ = erlang:cancel_timer(T), T = erlang:send_after(MS, self(), tic), State#s{timer = {T, MS}}. @@ -1358,16 +1358,21 @@ handle_tic(State = #s{wallet = This = #wallet{poas = POAs, endpoint = Node}, ok = log(info, "Balance update on tic failed with: ~p", [Error]), State end, - ok = erlang:cancel_timer(T, [{async, true}]), + ok = cancel_timer(T), NewT = erlang:send_after(MS, self(), tic), NewState#s{timer = {NewT, MS}}; handle_tic(State = #s{timer = {T, MS}}) -> - ok = erlang:cancel_timer(T, [{async, true}]), + 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) -> From 07875ab0e08dc567a70c68b893c22e3c9f3deecd Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Tue, 30 Dec 2025 18:00:37 +0900 Subject: [PATCH 24/25] Kill spend window --- src/gd_con.erl | 2 +- src/gd_m_spend.erl | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gd_con.erl b/src/gd_con.erl index f4faa6e..8ad4b93 100644 --- a/src/gd_con.erl +++ b/src/gd_con.erl @@ -859,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. diff --git a/src/gd_m_spend.erl b/src/gd_m_spend.erl index 587da5b..40690b6 100644 --- a/src/gd_m_spend.erl +++ b/src/gd_m_spend.erl @@ -176,8 +176,7 @@ handle_info({focus, Element}, State) -> ok = focus(Element, State), {noreply, State}; handle_info({enter, affirm}, State) -> - NewState = check(State), - {noreply, NewState}; + check(State); handle_info({enter, cancel}, State) -> cancel(State); handle_info(esc, State) -> @@ -234,6 +233,7 @@ check(State =#s{frame = Frame, caller = Caller, j = J, 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), From d59278a26cb7af37f21c276cea5258659e3a75d3 Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Tue, 30 Dec 2025 18:42:37 +0900 Subject: [PATCH 25/25] Drop net* --- src/gd_net.erl | 81 ------------------------------ src/gd_net_man.erl | 119 --------------------------------------------- src/gd_net_sup.erl | 48 ------------------ src/gd_nets.erl | 50 ------------------- 4 files changed, 298 deletions(-) delete mode 100644 src/gd_net.erl delete mode 100644 src/gd_net_man.erl delete mode 100644 src/gd_net_sup.erl delete mode 100644 src/gd_nets.erl diff --git a/src/gd_net.erl b/src/gd_net.erl deleted file mode 100644 index 6bd05eb..0000000 --- a/src/gd_net.erl +++ /dev/null @@ -1,81 +0,0 @@ -%%% @doc -%%% GajuDesk: Net Worker -%%% @end - --module(gd_net). --vsn("0.8.0"). --behavior(gen_server). --author("Craig Everett "). --copyright("QPQ AG "). --license("GPL-3.0-or-later"). - -%% gen_server --export([start_link/0]). --export([init/1, handle_call/3, handle_cast/2, handle_info/2, - code_change/3, terminate/2]). --include("$zx_include/zx_logger.hrl"). - - -%%% Type and Record Definitions - - --record(s, - {}). - - --type state() :: #s{}. - - - -%%% Interface - - - -%%% gen_server - --spec start_link() -> Result - when Result :: {ok, pid()} - | {error, Reason :: term()}. - -start_link() -> - gen_server:start_link(?MODULE, none, []). - - -init(none) -> - State = #s{}, - {ok, State}. - - -handle_call(Unexpected, From, State) -> - ok = log(warning, "Unexpected call from ~tp: ~tp", [From, Unexpected]), - {noreply, State}. - - -handle_cast(Unexpected, State) -> - ok = log(warning, "Unexpected cast: ~tp", [Unexpected]), - {noreply, State}. - - -handle_info(Unexpected, State) -> - ok = log(warning, "Unexpected info: ~tp", [Unexpected]), - {noreply, State}. - - --spec code_change(OldVersion, State, Extra) -> {ok, NewState} | {error, Reason} - when OldVersion :: Version | {old, Version}, - Version :: term(), - State :: state(), - Extra :: term(), - NewState :: term(), - Reason :: term(). - -code_change(_, State, _) -> - {ok, State}. - - -terminate(_, _) -> - ok. - - - -%%% Doer Functions diff --git a/src/gd_net_man.erl b/src/gd_net_man.erl deleted file mode 100644 index 1c7715e..0000000 --- a/src/gd_net_man.erl +++ /dev/null @@ -1,119 +0,0 @@ -%%% @doc -%%% GajuDesk: Net Worker Manager -%%% @end - --module(gd_net_man). --vsn("0.8.0"). --behavior(gen_server). --author("Craig Everett "). --copyright("QPQ AG "). --license("GPL-3.0-or-later"). - -%% Worker interface --export([enroll/0]). -%% gen_server --export([start_link/0]). --export([init/1, handle_call/3, handle_cast/2, handle_info/2, - code_change/3, terminate/2]). --include("$zx_include/zx_logger.hrl"). - - -%%% Type and Record Definitions - - --record(s, - {nets = [] :: [pid()]}). - - --type state() :: #s{}. - - - -%%% Service Interface - - - -%%% Worker Interface - --spec enroll() -> ok. -%% @doc -%% Workers register here after they initialize. - -enroll() -> - gen_server:cast(?MODULE, {enroll, self()}). - - - -%%% gen_server - --spec start_link() -> Result - when Result :: {ok, pid()} - | {error, Reason :: term()}. - -start_link() -> - gen_server:start_link({local, ?MODULE}, ?MODULE, none, []). - - -init(none) -> - State = #s{}, - {ok, State}. - - -handle_call(Unexpected, From, State) -> - ok = log(warning, "Unexpected call from ~tp: ~tp", [From, Unexpected]), - {noreply, State}. - - -handle_cast({enroll, PID}, State) -> - NewState = do_enroll(PID, State), - {noreply, NewState}; -handle_cast(Unexpected, State) -> - ok = log(warning, "Unexpected cast: ~tp", [Unexpected]), - {noreply, State}. - - -handle_info({'DOWN', Mon, process, PID, Reason}, State) -> - NewState = handle_down(Mon, PID, Reason, State), - {noreply, NewState}; -handle_info(Unexpected, State) -> - ok = log(warning, "Unexpected info: ~tp", [Unexpected]), - {noreply, State}. - - -handle_down(Mon, PID, Reason, State = #s{nets = Nets}) -> - case lists:member(PID, Nets) of - true -> - NewNets = lists:delete(PID, Nets), - State#s{nets = NewNets}; - false -> - Unexpected = {'DOWN', Mon, process, PID, Reason}, - ok = log(warning, "Unexpected info: ~tp", [Unexpected]), - State - end. - - -code_change(_, State, _) -> - {ok, State}. - - -terminate(_, _) -> - ok. - - - -%%% Doer Functions - --spec do_enroll(PID, State) -> NewState - when PID :: pid(), - State :: state(), - NewState :: state(). - -do_enroll(PID, State = #s{nets = Nets}) -> - case lists:member(PID, Nets) of - false -> - Mon = monitor(process, PID), - ok = log(info, "Enroll: ~tp @ ~tp", [PID, Mon]), - State#s{nets = [PID | Nets]}; - true -> - State - end. diff --git a/src/gd_net_sup.erl b/src/gd_net_sup.erl deleted file mode 100644 index 33405ee..0000000 --- a/src/gd_net_sup.erl +++ /dev/null @@ -1,48 +0,0 @@ -%%% @doc -%%% GajuDesk : Net Worker Supervisor -%%% @end - --module(gd_net_sup). --vsn("0.8.0"). --behaviour(supervisor). --author("Craig Everett "). --copyright("QPQ AG "). --license("GPL-3.0-or-later"). - - --export([start_net/0]). --export([start_link/0]). --export([init/1]). - - --spec start_net() -> Result - when Result :: {ok, pid()} - | {error, Reason}, - Reason :: {already_started, pid()} - | {shutdown, term()} - | term(). - -start_net() -> - supervisor:start_child(?MODULE, []). - - --spec start_link() -> {ok, pid()}. - -start_link() -> - supervisor:start_link({local, ?MODULE}, ?MODULE, none). - - --spec init(none) -> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}. -%% @private -%% The OTP init/1 function. - -init(none) -> - RestartStrategy = {simple_one_for_one, 1, 60}, - Net = - {gd_net, - {gd_net, start_link, []}, - temporary, - brutal_kill, - worker, - [gd_net]}, - {ok, {RestartStrategy, [Net]}}. diff --git a/src/gd_nets.erl b/src/gd_nets.erl deleted file mode 100644 index bd11e6f..0000000 --- a/src/gd_nets.erl +++ /dev/null @@ -1,50 +0,0 @@ -%%% @doc -%%% GajuDesk: Net Service Supervisor -%%% -%%% This is the service-level supervisor of the system. It is the parent of both the -%%% client connection handlers and the client manager (which manages the client -%%% connection handlers). This is the child of gd_sup. -%%% -%%% See: http://erlang.org/doc/apps/kernel/application.html -%%% @end - --module(gd_nets). --vsn("0.8.0"). --behavior(supervisor). --author("Craig Everett "). --copyright("QPQ AG "). --license("GPL-3.0-or-later"). - --export([start_link/0]). --export([init/1]). - - --spec start_link() -> {ok, pid()}. -%% @private -%% This supervisor's own start function. - -start_link() -> - supervisor:start_link({local, ?MODULE}, ?MODULE, none). - --spec init(none) -> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}. -%% @private -%% The OTP init/1 function. - -init(none) -> - RestartStrategy = {rest_for_one, 1, 60}, - NetMan = - {gd_net_man, - {gd_net_man, start_link, []}, - permanent, - 5000, - worker, - [gd_net_man]}, - NetSup = - {gd_net_sup, - {gd_net_sup, start_link, []}, - permanent, - 5000, - supervisor, - [gd_net_sup]}, - Children = [NetSup, NetMan], - {ok, {RestartStrategy, Children}}.