From aa76d7609334ac6c5680bd0a9cd2a5a234b7d3e8 Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Wed, 6 Aug 2025 14:57:27 +0900 Subject: [PATCH 1/9] WIP --- src/:w | 527 +++++++++++++++++++++++++++++++++++++++++++ src/gd_v_wallman.erl | 99 +++++++- 2 files changed, 615 insertions(+), 11 deletions(-) create mode 100644 src/:w diff --git a/src/:w b/src/:w new file mode 100644 index 0000000..5e1a02e --- /dev/null +++ b/src/:w @@ -0,0 +1,527 @@ +-module(gd_v_wallman). +-vsn("0.6.6"). +-author("Craig Everett "). +-copyright("QPQ AG "). +-license("GPL-3.0-or-later"). + +-behavior(wx_object). +%-behavior(gd_v). +-include_lib("wx/include/wx.hrl"). +-export([to_front/1]). +-export([show/2]). +-export([start_link/1]). +-export([init/1, terminate/2, code_change/3, + handle_call/3, handle_cast/2, handle_info/2, handle_event/2]). +-include("$zx_include/zx_logger.hrl"). +-include("gd.hrl"). + + +-record(w, + {name = none :: atom(), + id = 0 :: integer(), + wx = none :: none | wx:wx_object()}). + +-record(s, + {wx = none :: none | wx:wx_object(), + frame = none :: none | wx:wx_object(), + lang = en :: en | jp, + j = none :: none | fun(), + prefs = #{} :: map(), + wallets = [] :: [#wr{}], + picker = none :: none | wx:wx_object(), + buttons = [] :: [#w{}], + wiz = none :: none | {wx:wx_object(), Buttons :: [#w{}]}). + + +%%% Interface + +-spec to_front(Win) -> ok + when Win :: wx:wx_object(). + +to_front(Win) -> + wx_object:cast(Win, to_front). + + +-spec show(Win, Manifest) -> ok + when Win :: wx:xw_object(), + Manifest :: [#wr{}]. + +show(Win, Manifest) -> + wx_object:cast(Win, {show, Manifest}). + + +%%% Startup + +start_link(Args) -> + wx_object:start_link({local, ?MODULE}, ?MODULE, Args, []). + + +init({Prefs, Manifest}) -> + Lang = maps:get(lang, Prefs, en_us), + Trans = gd_jt:read_translations(?MODULE), + J = gd_jt:j(Lang, Trans), + Wx = wx:new(), + Frame = wxFrame:new(Wx, ?wxID_ANY, J("Wallets")), + + MainSz = wxBoxSizer:new(?wxVERTICAL), + + Picker = wxListBox:new(Frame, ?wxID_ANY, [{style, ?wxLC_SINGLE_SEL}]), + Names = [Name || #wr{name = Name} <- Manifest], + ok = wxListBox:set(Picker, Names), + + ButtSz = wxBoxSizer:new(?wxHORIZONTAL), + ButtonTemplates = + [{open, J("Open")}, + {new, J("New")}, + {move, J("Move")}, + {import, J("Import")}, + {drop, J("Drop")}], + MakeButton = + fun({Name, Label}) -> + B = wxButton:new(Frame, ?wxID_ANY, [{label, Label}]), + _ = wxSizer:add(ButtSz, B, zxw:flags(wide)), + #w{name = Name, id = wxButton:getId(B), wx = B} + end, + Buttons = lists:map(MakeButton, ButtonTemplates), + + _ = wxSizer:add(MainSz, Picker, zxw:flags(wide)), + _ = wxSizer:add(MainSz, ButtSz, zxw:flags(base)), + + ok = wxFrame:setSizer(Frame, MainSz), + ok = wxSizer:layout(MainSz), + + NewPrefs = + case maps:is_key(geometry, Prefs) of + true -> + Prefs; + false -> + Display = wxDisplay:new(), + {_, _, W, H} = wxDisplay:getGeometry(Display), + ok = wxDisplay:destroy(Display), + WW = 500, + WH = 350, + X = (W div 2) - (WW div 2), + Y = (H div 2) - (WH div 2), + Prefs#{geometry => {X, Y, WW, WH}} + end, + ok = gd_v:safe_size(Frame, NewPrefs), + + ok = wxFrame:connect(Frame, command_button_clicked), + ok = wxFrame:connect(Frame, close_window), + ok = wxListBox:connect(Picker, command_listbox_doubleclicked), + Count = length(Manifest) + ok = + if + Count =:= 0 -> + self() ! wiz, + ok; + Count =:= 1 -> + self() ! open_only, + ok; + Count > 1 -> + true = wxFrame:show(Frame), + ok + end, + State = #s{wx = Wx, frame = Frame, lang = Lang, j = J, prefs = Prefs, + wallets = Manifest, + picker = Picker, + buttons = Buttons}, + {Frame, State}. + + + +%%% wx_object + +handle_call(Unexpected, From, State) -> + ok = log(warning, "Unexpected call from ~tp: ~tp~n", [From, Unexpected]), + {noreply, State}. + + +handle_cast(to_front, State = #s{frame = Frame}) -> + ok = wxFrame:raise(Frame), + {noreply, State}; +handle_cast({show, Manifest}, State) -> + NewState = do_show(Manifest, State), + {noreply, NewState}; +handle_cast(Unexpected, State) -> + ok = log(warning, "Unexpected cast: ~tp~n", [Unexpected]), + {noreply, State}. + + +handle_info(wiz, State) -> + NewState = do_wiz(State), + {noreply, NewState}; +handle_info(open_only, State) -> + NewState = do_open_only(State), + {noreply, NewState}; +handle_info(Unexpected, State) -> + ok = log(warning, "Unexpected info: ~tp~n", [Unexpected]), + {noreply, State}. + + +handle_event(#wx{event = #wxCommand{type = command_button_clicked}, + id = ID}, + State = #s{buttons = Buttons}) -> + NewState = + case lists:keyfind(ID, #w.id, Buttons) of + #w{name = open} -> do_open(State); + #w{name = new} -> do_new(State); + #w{name = import} -> do_import(State); + #w{name = drop} -> do_drop(State); + #w{name = Name} -> handle_button(Name, State); + false -> State + end, + {noreply, NewState}; +handle_event(#wx{event = #wxCommand{type = command_listbox_doubleclicked, + commandInt = Selected}}, + State) -> + NewState = do_open2(Selected + 1, State), + {noreply, NewState}; +handle_event(#wx{event = #wxClose{}}, State) -> + ok = do_close(State), + {noreply, State}; +handle_event(Event, State) -> + ok = tell(info, "Unexpected event ~tp State: ~tp~n", [Event, State]), + {noreply, State}. + + +code_change(_, State, _) -> + {ok, State}. + + +terminate(wx_deleted, _) -> + wx:destroy(); +terminate(Reason, State) -> + ok = log(info, "Reason: ~tp, State: ~tp", [Reason, State]), + wx:destroy(). + + + +%%% doers + +do_show(Manifest, State = #s{picker = Picker}) -> + Names = [Name || #wr{name = Name} <- Manifest], + ok = wxListBox:set(Picker, Names), + State#s{wallets = Manifest}. + + +do_close(#s{frame = Frame, prefs = Prefs}) -> + Geometry = + case wxTopLevelWindow:isMaximized(Frame) of + true -> + max; + false -> + {X, Y} = wxWindow:getPosition(Frame), + {W, H} = wxWindow:getSize(Frame), + {X, Y, W, H} + end, + NewPrefs = maps:put(geometry, Geometry, Prefs), + ok = gd_con:save(?MODULE, NewPrefs), + ok = wxWindow:destroy(Frame). + + +handle_button(Name, State) -> + ok = tell("Button Click: ~p", [Name]), + State. + + +do_open(State = #s{wallets = []}) -> + State; +do_open(State = #s{picker = Picker}) -> + case wxListBox:getSelection(Picker) of + -1 -> State; + Selected -> do_open2(Selected + 1, State) + end. + +do_open2(Selected, State = #s{wallets = Wallets}) -> + case lists:nth(Selected, Wallets) of + #wr{pass = true, path = Path} -> + do_open3(Path, State); + #wr{pass = false, path = Path} -> + ok = gd_con:open_wallet(Path, none), + ok = do_close(State), + State + end. + +do_open3(Path, State = #s{frame = Frame, j = J}) -> + Label = J("Password"), + Dialog = wxDialog:new(Frame, ?wxID_ANY, Label), + Sizer = wxBoxSizer:new(?wxVERTICAL), + PassSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, Label}]), + PassTx = wxTextCtrl:new(Dialog, ?wxID_ANY, [{style, ?wxTE_PASSWORD}]), + _ = wxStaticBoxSizer:add(PassSz, PassTx, zxw:flags(wide)), + ButtSz = wxBoxSizer:new(?wxHORIZONTAL), + Affirm = wxButton:new(Dialog, ?wxID_OK), + _ = wxBoxSizer:add(ButtSz, Affirm, zxw:flags(wide)), + _ = wxBoxSizer:add(Sizer, PassSz, zxw:flags(base)), + _ = wxBoxSizer:add(Sizer, ButtSz, zxw:flags(wide)), + ok = wxDialog:setSizer(Dialog, Sizer), + ok = wxBoxSizer:layout(Sizer), + ok = wxDialog:setSize(Dialog, {500, 130}), + ok = wxFrame:center(Dialog), + ok = wxStyledTextCtrl:setFocus(PassTx), + case wxDialog:showModal(Dialog) of + ?wxID_OK -> + case wxTextCtrl:getValue(PassTx) of + "" -> + ok = wxDialog:destroy(Dialog), + State; + Phrase -> + ok = wxDialog:destroy(Dialog), + ok = gd_con:open_wallet(Path, Phrase), + ok = do_close(State), + State + end; + ?wxID_CANCEL -> + ok = wxDialog:destroy(Dialog), + State + end. + + +do_wiz(State = #s{wx = WX, lang = Lang, wiz = none}) -> + Trans = gd_jt:read_translations(?MODULE), + J = gd_jt:j(Lang, Trans), + + Frame = wxFrame:new(WX, ?wxID_ANY, J("Initializing Wallet")), + MainSz = wxBoxSizer:new(?wxVERTICAL), + + ButtonTemplates = + [{noob, J("Create a default wallet with a new account for me.")}, + {l33t, J("I know what I'm doing.\nJust show me the wallet manager.")}], + + MakeButton = + fun({Name, Label}) -> + B = wxButton:new(Frame, ?wxID_ANY, [{label, Label}]), + #w{name = Name, id = wxButton:getId(B), wx = B} + end, + + Buttons = lists:map(MakeButton, ButtonTemplates), + + Add = fun(#w{wx = Button}) -> wxBoxSizer:add(MainSz, Button, zxw:flags(wide)) end, + ok = lists:foreach(Add, Buttons), + + ok = wxFrame:setSizer(Frame, MainSz), + ok = wxSizer:layout(MainSz), + + ok = wxFrame:connect(Frame, command_button_clicked), + ok = wxFrame:connect(Frame, close_window), + ok = wxFrame:setSize(Frame, {300, 300}), + ok = wxFrame:center(Frame), + true = wxFrame:show(Frame), + State#s{wiz = {Frame, Buttons}}. + + +do_new(State = #s{frame = Frame, j = J, prefs = Prefs}) -> + DefaultDir = maps:get(dir, Prefs, zx_lib:path(var, "otpr", "gajudesk")), + Options = + [{message, J("Save Location")}, + {defaultDir, DefaultDir}, + {defaultFile, "default.gaju"}, + {wildCard, "*.gaju"}, + {sz, {300, 270}}, + {style, ?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT}], + Dialog = wxFileDialog:new(Frame, Options), + case wxFileDialog:showModal(Dialog) of + ?wxID_OK -> + Dir = wxFileDialog:getDirectory(Dialog), + File = wxFileDialog:getFilename(Dialog), + Path = filename:join(Dir, File), + ok = wxFileDialog:destroy(Dialog), + case do_new2(Path, J, Frame) of + ok -> + NewPrefs = maps:put(dir, Dir, Prefs), + do_close(State#s{prefs = NewPrefs}); + abort -> + State + end; + ?wxID_CANCEL -> + ok = wxFileDialog:destroy(Dialog), + State + end. + +do_new2(Path, J, Frame) -> + Dialog = wxDialog:new(Frame, ?wxID_ANY, J("New Wallet"), [{size, {400, 250}}]), + Sizer = wxBoxSizer:new(?wxVERTICAL), + + Network = wxRadioBox:new(Dialog, ?wxID_ANY, J("Network"), {0, 0}, {50, 50}, [J("Mainnet"), J("Testnet")]), + NameSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Name")}]), + NameTx = wxTextCtrl:new(Dialog, ?wxID_ANY), + _ = wxSizer:add(NameSz, NameTx, zxw:flags(wide)), + Label1 = J("Passphrase (optional)"), + PassSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, Label1}]), + PassTx = wxTextCtrl:new(Dialog, ?wxID_ANY, [{style, ?wxTE_PASSWORD}]), + _ = wxSizer:add(PassSz, PassTx, zxw:flags(wide)), + Label2= J("Passphrase Confirmation"), + PassConSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, Label2}]), + PassConTx = wxTextCtrl:new(Dialog, ?wxID_ANY, [{style, ?wxTE_PASSWORD}]), + _ = wxSizer:add(PassConSz, PassConTx, 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, Network, zxw:flags(base)), + _ = wxSizer:add(Sizer, NameSz, zxw:flags(base)), + _ = wxSizer:add(Sizer, PassSz, zxw:flags(base)), + _ = wxSizer:add(Sizer, PassConSz, zxw:flags(base)), + _ = wxSizer:add(Sizer, ButtSz, zxw:flags(wide)), + + ok = wxDialog:setSizer(Dialog, Sizer), + ok = wxDialog:setSize(Dialog, {300, 270}), + ok = wxBoxSizer:layout(Sizer), + ok = wxDialog:center(Dialog), + ok = wxStyledTextCtrl:setFocus(NameTx), + + Result = + case wxDialog:showModal(Dialog) of + ?wxID_OK -> + Net = + case wxRadioBox:getSelection(Network) of + 0 -> mainnet; + 1 -> testnet; + _ -> mainnet + end, + Name = + case wxTextCtrl:getValue(NameTx) of + "" -> Path; + N -> N + end, + Pass = + case {wxTextCtrl:getValue(PassTx), wxTextCtrl:getValue(PassConTx)} of + {"", ""} -> none; + {P, P} -> P; + {_, _} -> bad + end, + do_new3(Net, Name, Path, Pass); + ?wxID_CANCEL -> + abort + end, + ok = wxDialog:destroy(Dialog), + Result. + +do_new3(_, _, _, bad) -> + abort; +do_new3(Net, Name, Path, Pass) -> + gd_con:new_wallet(Net, Name, Path, Pass). + + +do_import(State = #s{frame = Frame, j = J, prefs = Prefs}) -> + DefaultDir = maps:get(dir, Prefs, zx_lib:path(var, "otpr", "gajudesk")), + Options = + [{message, J("Select Wallet File")}, + {defaultDir, DefaultDir}, + {wildCard, "*.gaju"}, + {style, ?wxFD_OPEN}], + Dialog = wxFileDialog:new(Frame, Options), + case wxFileDialog:showModal(Dialog) of + ?wxID_OK -> + Dir = wxFileDialog:getDirectory(Dialog), + File = wxFileDialog:getFilename(Dialog), + ok = wxFileDialog:destroy(Dialog), + case do_import2(Dir, File, J, Frame) of + ok -> + NewPrefs = maps:put(dir, Dir, Prefs), + do_close(State#s{prefs = NewPrefs}); + abort -> + State + end; + ?wxID_CANCEL -> + ok = wxFileDialog:destroy(Dialog), + State + end. + +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. + + +do_drop(State = #s{picker = Picker}) -> + case wxListBox:getSelection(Picker) of + -1 -> State; + Selected -> do_drop(Selected + 1, State) + end. + +do_drop(Selected, State = #s{j = J, frame = Frame, wallets = Wallets}) -> + #wr{name = Name, path = Path} = lists:nth(Selected, Wallets), + Dialog = wxDialog:new(Frame, ?wxID_ANY, J("Drop Wallet")), + Sizer = wxBoxSizer:new(?wxVERTICAL), + + MessageM = J("REALLY delete wallet?"), + Message = ["\r\n", MessageM, "\r\n\r\n\"", Name, "\""], + MessageT = wxStaticText:new(Dialog, ?wxID_ANY, Message, + [{style, ?wxALIGN_CENTRE_HORIZONTAL}]), + DeleteM = J("Delete file data?"), + DeleteCheck = wxCheckBox:new(Dialog, ?wxID_ANY, DeleteM), + + 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, MessageT, zxw:flags(base)), + _ = wxSizer:add(Sizer, DeleteCheck, 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 = + case wxDialog:showModal(Dialog) of + ?wxID_OK -> + Delete = wxCheckBox:getValue(DeleteCheck), + gd_con:drop_wallet(Path, Delete); + ?wxID_CANCEL -> + ok + end, + State. diff --git a/src/gd_v_wallman.erl b/src/gd_v_wallman.erl index 49f007e..68bd09e 100644 --- a/src/gd_v_wallman.erl +++ b/src/gd_v_wallman.erl @@ -29,7 +29,8 @@ prefs = #{} :: map(), wallets = [] :: [#wr{}], picker = none :: none | wx:wx_object(), - buttons = [] :: [#w{}]}). + buttons = [] :: [#w{}], + wiz = none :: none | {wx:wx_object(), Buttons :: [#w{}]}}). %%% Interface @@ -107,14 +108,18 @@ init({Prefs, Manifest}) -> ok = wxFrame:connect(Frame, command_button_clicked), ok = wxFrame:connect(Frame, close_window), - true = wxFrame:show(Frame), ok = wxListBox:connect(Picker, command_listbox_doubleclicked), + Count = length(Manifest), ok = - case length(Manifest) =:= 0 of - false -> + if + Count =:= 0 -> + self() ! wiz, ok; - true -> - self() ! new, + Count =:= 1 -> + self() ! open, + ok; + Count > 1 -> + true = wxFrame:show(Frame), ok end, State = #s{wx = Wx, frame = Frame, lang = Lang, j = J, prefs = Prefs, @@ -143,8 +148,11 @@ handle_cast(Unexpected, State) -> {noreply, State}. -handle_info(new, State) -> - NewState = do_new(State), +handle_info(wiz, State) -> + NewState = do_wiz(State), + {noreply, NewState}; +handle_info(open, State) -> + NewState = do_open(State), {noreply, NewState}; handle_info(Unexpected, State) -> ok = log(warning, "Unexpected info: ~tp~n", [Unexpected]), @@ -153,7 +161,7 @@ handle_info(Unexpected, State) -> handle_event(#wx{event = #wxCommand{type = command_button_clicked}, id = ID}, - State = #s{buttons = Buttons}) -> + State = #s{buttons = Buttons, wiz = none}) -> NewState = case lists:keyfind(ID, #w.id, Buttons) of #w{name = open} -> do_open(State); @@ -166,12 +174,25 @@ handle_event(#wx{event = #wxCommand{type = command_button_clicked}, {noreply, NewState}; handle_event(#wx{event = #wxCommand{type = command_listbox_doubleclicked, commandInt = Selected}}, - State) -> + State = #s{wiz = none}) -> NewState = do_open2(Selected + 1, State), {noreply, NewState}; -handle_event(#wx{event = #wxClose{}}, State) -> +handle_event(#wx{event = #wxClose{}}, State = #s{wiz = none}) -> ok = do_close(State), {noreply, State}; +handle_event(#wx{event = #wxCommand{type = command_button_clicked}, + id = ID}, + State = #s{wiz = {_, WizButtons}}) -> + NewState = + case lists:keyfind(ID, #w.id, WizButtons) of + #w{name = noob} -> wiz_noob_assist(State); + #w{name = l33t} -> close_wiz(State); + false -> State + end, + {noreply, NewState}; +handle_event(#wx{event = #wxClose{}}, State = #s{wiz = {_, _}}) -> + ok = close_wiz(State), + {noreply, State}; handle_event(Event, State) -> ok = tell(info, "Unexpected event ~tp State: ~tp~n", [Event, State]), {noreply, State}. @@ -197,6 +218,12 @@ do_show(Manifest, State = #s{picker = Picker}) -> State#s{wallets = Manifest}. +close_wiz(State = #s{frame = Frame, wiz = {Wiz, _}}) -> + ok = wxWindow:destroy(Wiz), + true = wxFrame:show(Frame), + State#s{wiz = none}. + + do_close(#s{frame = Frame, prefs = Prefs}) -> Geometry = case wxTopLevelWindow:isMaximized(Frame) of @@ -219,6 +246,12 @@ handle_button(Name, State) -> do_open(State = #s{wallets = []}) -> State; +do_open(State = #s{wallets = [#wr{pass = true, path = Path}]}) -> + do_open3(Path, State); +do_open(State = #s{wallets = [#wr{pass = false, path = Path}]}) -> + ok = gd_con:open_wallet(Path, none), + ok = do_close(State), + State; do_open(State = #s{picker = Picker}) -> case wxListBox:getSelection(Picker) of -1 -> State; @@ -270,6 +303,50 @@ do_open3(Path, State = #s{frame = Frame, j = J}) -> end. +do_wiz(State = #s{wx = WX, lang = Lang, wiz = none}) -> + Trans = gd_jt:read_translations(?MODULE), + J = gd_jt:j(Lang, Trans), + + Frame = wxFrame:new(WX, ?wxID_ANY, J("Initializing Wallet")), + MainSz = wxBoxSizer:new(?wxVERTICAL), + + ButtonTemplates = + [{noob, J("Create a default wallet with a new account for me.")}, + {l33t, J("I know what I'm doing.\nJust show me the wallet manager.")}], + + MakeButton = + fun({Name, Label}) -> + B = wxButton:new(Frame, ?wxID_ANY, [{label, Label}]), + #w{name = Name, id = wxButton:getId(B), wx = B} + end, + + Buttons = lists:map(MakeButton, ButtonTemplates), + + Add = fun(#w{wx = Button}) -> wxBoxSizer:add(MainSz, Button, zxw:flags(wide)) end, + ok = lists:foreach(Add, Buttons), + + ok = wxFrame:setSizer(Frame, MainSz), + ok = wxSizer:layout(MainSz), + + ok = wxFrame:connect(Frame, command_button_clicked), + ok = wxFrame:connect(Frame, close_window), + ok = wxFrame:setSize(Frame, {300, 300}), + ok = wxFrame:center(Frame), + true = wxFrame:show(Frame), + State#s{wiz = {Frame, Buttons}}. + +wiz_noob_assist(State = #s{j = J, wiz = {Wiz, _}}) -> + DefaultDir = zx_lib:path(var, "otpr", "gajudesk"), + {{YY, MM, DD}, {Hr, Mn, Sc}} = calendar:local_time(), + Form = "~4.10.0B-~2.10.0B-~2.10.0B_~2.10.0B-~2.10.0B-~2.10.0B", + Name = io_lib:format(Form, [YY, MM, DD, Hr, Mn, Sc]), + Path = filename:join(DefaultDir, Name ++ ".gaju"), + case do_new2(Path, J, Wiz) of + ok -> do_close(State); + abort -> State + end. + + do_new(State = #s{frame = Frame, j = J, prefs = Prefs}) -> DefaultDir = maps:get(dir, Prefs, zx_lib:path(var, "otpr", "gajudesk")), Options = -- 2.30.2 From 127278f76ab8acb0c354ac399ab21c964551eff9 Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Wed, 6 Aug 2025 18:47:15 +0900 Subject: [PATCH 2/9] WIP --- src/gd_con.erl | 30 ++++++++++++-------- src/gd_v_wallman.erl | 67 +++++++++++++++++++++++++++++--------------- 2 files changed, 62 insertions(+), 35 deletions(-) diff --git a/src/gd_con.erl b/src/gd_con.erl index 75c20e4..c9e40cb 100644 --- a/src/gd_con.erl +++ b/src/gd_con.erl @@ -38,15 +38,15 @@ mon = none :: none | reference()}). -record(s, - {version = 1 :: integer(), - window = none :: none | wx:wx_object(), - timer = none :: none | reference(), - tasks = [] :: [#ui{}], - selected = 0 :: non_neg_integer(), - wallet = none :: none | #wallet{}, - pass = none :: none | binary(), - prefs = #{} :: #{module() := term()}, - wallets = [] :: [#wr{}]}). + {version = 1 :: integer(), + window = none :: none | wx:wx_object(), + timer = none :: none | reference(), + tasks = [] :: [#ui{}], + selected = 0 :: non_neg_integer(), + wallet = none :: none | #wallet{}, + pass = none :: none | binary(), + prefs = #{} :: #{module() := term()}, + wallets = [] :: [#wr{}]}). -type state() :: #s{}. @@ -477,6 +477,9 @@ handle_cast(Unexpected, State) -> handle_info(tic, State) -> NewState = do_tic(State), {noreply, NewState}; +handle_info({show_ui, Name}, State) -> + NewState = do_show_ui(Name, State), + {noreply, NewState}; handle_info({'DOWN', Mon, process, PID, Info}, State) -> NewState = handle_down(Mon, PID, Info, State), {noreply, NewState}; @@ -486,6 +489,7 @@ handle_info(Unexpected, State) -> handle_down(Mon, PID, Info, State = #s{tasks = Tasks}) -> + tell("Handling down"), case lists:keytake(Mon, #ui.mon, Tasks) of {value, #ui{}, NewTasks} -> State#s{tasks = NewTasks}; @@ -531,7 +535,7 @@ do_show_ui(Name, State = #s{tasks = Tasks, prefs = Prefs}) -> Win = Name:start_link({TaskPrefs, TaskData}), PID = wx_object:get_pid(Win), Mon = monitor(process, PID), - ok = Name:to_front(Win), +% ok = Name:to_front(Win), UI = #ui{name = Name, pid = PID, wx = Win, mon = Mon}, State#s{tasks = [UI | Tasks]} end. @@ -1023,8 +1027,10 @@ do_open_wallet(Path, Phrase, State) -> State#s{pass = Pass, wallet = Recovered}; Error -> ok = gd_gui:trouble(Error), - New = default_wallet(), - State#s{wallet = New} + % FIXME: This is chaotically stupid. + tell("Sending myself the show_ui message"), + self() ! {show_ui, gd_v_wallman}, + State end. diff --git a/src/gd_v_wallman.erl b/src/gd_v_wallman.erl index 68bd09e..dd81ee1 100644 --- a/src/gd_v_wallman.erl +++ b/src/gd_v_wallman.erl @@ -120,12 +120,12 @@ init({Prefs, Manifest}) -> ok; Count > 1 -> true = wxFrame:show(Frame), - ok + wxFrame:raise(Frame) end, State = #s{wx = Wx, frame = Frame, lang = Lang, j = J, prefs = Prefs, - wallets = Manifest, - picker = Picker, - buttons = Buttons}, + wallets = Manifest, + picker = Picker, + buttons = Buttons}, {Frame, State}. @@ -136,8 +136,8 @@ handle_call(Unexpected, From, State) -> ok = log(warning, "Unexpected call from ~tp: ~tp~n", [From, Unexpected]), {noreply, State}. - handle_cast(to_front, State = #s{frame = Frame}) -> + ok = ensure_shown(Frame), ok = wxFrame:raise(Frame), {noreply, State}; handle_cast({show, Manifest}, State) -> @@ -185,9 +185,9 @@ handle_event(#wx{event = #wxCommand{type = command_button_clicked}, State = #s{wiz = {_, WizButtons}}) -> NewState = case lists:keyfind(ID, #w.id, WizButtons) of - #w{name = noob} -> wiz_noob_assist(State); - #w{name = l33t} -> close_wiz(State); - false -> State + #w{name = noob} -> wiz_noob_assist(State); + #w{name = l33t} -> close_wiz(State); + false -> State end, {noreply, NewState}; handle_event(#wx{event = #wxClose{}}, State = #s{wiz = {_, _}}) -> @@ -236,7 +236,8 @@ do_close(#s{frame = Frame, prefs = Prefs}) -> end, NewPrefs = maps:put(geometry, Geometry, Prefs), ok = gd_con:save(?MODULE, NewPrefs), - ok = wxWindow:destroy(Frame). + ok = wxWindow:destroy(Frame), + exit(normal). handle_button(Name, State) -> @@ -283,13 +284,14 @@ do_open3(Path, State = #s{frame = Frame, j = J}) -> ok = wxDialog:setSizer(Dialog, Sizer), ok = wxBoxSizer:layout(Sizer), ok = wxDialog:setSize(Dialog, {500, 130}), - ok = wxFrame:center(Dialog), + ok = wxDialog:center(Dialog), ok = wxStyledTextCtrl:setFocus(PassTx), case wxDialog:showModal(Dialog) of ?wxID_OK -> case wxTextCtrl:getValue(PassTx) of "" -> ok = wxDialog:destroy(Dialog), + ok = ensure_shown(Frame), State; Phrase -> ok = wxDialog:destroy(Dialog), @@ -299,6 +301,7 @@ do_open3(Path, State = #s{frame = Frame, j = J}) -> end; ?wxID_CANCEL -> ok = wxDialog:destroy(Dialog), + ok = ensure_shown(Frame), State end. @@ -307,7 +310,7 @@ do_wiz(State = #s{wx = WX, lang = Lang, wiz = none}) -> Trans = gd_jt:read_translations(?MODULE), J = gd_jt:j(Lang, Trans), - Frame = wxFrame:new(WX, ?wxID_ANY, J("Initializing Wallet")), + Wiz = wxFrame:new(WX, ?wxID_ANY, J("Initializing Wallet")), MainSz = wxBoxSizer:new(?wxVERTICAL), ButtonTemplates = @@ -316,7 +319,7 @@ do_wiz(State = #s{wx = WX, lang = Lang, wiz = none}) -> MakeButton = fun({Name, Label}) -> - B = wxButton:new(Frame, ?wxID_ANY, [{label, Label}]), + B = wxButton:new(Wiz, ?wxID_ANY, [{label, Label}]), #w{name = Name, id = wxButton:getId(B), wx = B} end, @@ -325,15 +328,16 @@ do_wiz(State = #s{wx = WX, lang = Lang, wiz = none}) -> Add = fun(#w{wx = Button}) -> wxBoxSizer:add(MainSz, Button, zxw:flags(wide)) end, ok = lists:foreach(Add, Buttons), - ok = wxFrame:setSizer(Frame, MainSz), + ok = wxFrame:setSizer(Wiz, MainSz), ok = wxSizer:layout(MainSz), - ok = wxFrame:connect(Frame, command_button_clicked), - ok = wxFrame:connect(Frame, close_window), - ok = wxFrame:setSize(Frame, {300, 300}), - ok = wxFrame:center(Frame), - true = wxFrame:show(Frame), - State#s{wiz = {Frame, Buttons}}. + ok = wxFrame:connect(Wiz, command_button_clicked), + ok = wxFrame:connect(Wiz, close_window), + ok = wxFrame:setSize(Wiz, {300, 300}), + ok = wxFrame:center(Wiz), + true = wxFrame:show(Wiz), + State#s{wiz = {Wiz, Buttons}}. + wiz_noob_assist(State = #s{j = J, wiz = {Wiz, _}}) -> DefaultDir = zx_lib:path(var, "otpr", "gajudesk"), @@ -342,8 +346,11 @@ wiz_noob_assist(State = #s{j = J, wiz = {Wiz, _}}) -> Name = io_lib:format(Form, [YY, MM, DD, Hr, Mn, Sc]), Path = filename:join(DefaultDir, Name ++ ".gaju"), case do_new2(Path, J, Wiz) of - ok -> do_close(State); - abort -> State + ok -> + ok = do_close(State), + State; + abort -> + State end. @@ -366,7 +373,9 @@ do_new(State = #s{frame = Frame, j = J, prefs = Prefs}) -> case do_new2(Path, J, Frame) of ok -> NewPrefs = maps:put(dir, Dir, Prefs), - do_close(State#s{prefs = NewPrefs}); + NewState = State#s{prefs = NewPrefs}, + ok = do_close(NewState), + NewState; abort -> State end; @@ -459,7 +468,9 @@ do_import(State = #s{frame = Frame, j = J, prefs = Prefs}) -> case do_import2(Dir, File, J, Frame) of ok -> NewPrefs = maps:put(dir, Dir, Prefs), - do_close(State#s{prefs = NewPrefs}); + NewState = State#s{prefs = NewPrefs}, + ok = do_close(NewState), + NewState; abort -> State end; @@ -561,3 +572,13 @@ do_drop(Selected, State = #s{j = J, frame = Frame, wallets = Wallets}) -> ok end, State. + + +ensure_shown(Frame) -> + case wxWindow:isShown(Frame) of + true -> + ok; + false -> + true = wxFrame:show(Frame), + ok + end. -- 2.30.2 From 50190a5b5bd8a6b2830801e1f4c2e4ecb85faf2c Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Wed, 6 Aug 2025 18:47:42 +0900 Subject: [PATCH 3/9] WIP --- src/:w | 527 --------------------------------------------------------- 1 file changed, 527 deletions(-) delete mode 100644 src/:w diff --git a/src/:w b/src/:w deleted file mode 100644 index 5e1a02e..0000000 --- a/src/:w +++ /dev/null @@ -1,527 +0,0 @@ --module(gd_v_wallman). --vsn("0.6.6"). --author("Craig Everett "). --copyright("QPQ AG "). --license("GPL-3.0-or-later"). - --behavior(wx_object). -%-behavior(gd_v). --include_lib("wx/include/wx.hrl"). --export([to_front/1]). --export([show/2]). --export([start_link/1]). --export([init/1, terminate/2, code_change/3, - handle_call/3, handle_cast/2, handle_info/2, handle_event/2]). --include("$zx_include/zx_logger.hrl"). --include("gd.hrl"). - - --record(w, - {name = none :: atom(), - id = 0 :: integer(), - wx = none :: none | wx:wx_object()}). - --record(s, - {wx = none :: none | wx:wx_object(), - frame = none :: none | wx:wx_object(), - lang = en :: en | jp, - j = none :: none | fun(), - prefs = #{} :: map(), - wallets = [] :: [#wr{}], - picker = none :: none | wx:wx_object(), - buttons = [] :: [#w{}], - wiz = none :: none | {wx:wx_object(), Buttons :: [#w{}]}). - - -%%% Interface - --spec to_front(Win) -> ok - when Win :: wx:wx_object(). - -to_front(Win) -> - wx_object:cast(Win, to_front). - - --spec show(Win, Manifest) -> ok - when Win :: wx:xw_object(), - Manifest :: [#wr{}]. - -show(Win, Manifest) -> - wx_object:cast(Win, {show, Manifest}). - - -%%% Startup - -start_link(Args) -> - wx_object:start_link({local, ?MODULE}, ?MODULE, Args, []). - - -init({Prefs, Manifest}) -> - Lang = maps:get(lang, Prefs, en_us), - Trans = gd_jt:read_translations(?MODULE), - J = gd_jt:j(Lang, Trans), - Wx = wx:new(), - Frame = wxFrame:new(Wx, ?wxID_ANY, J("Wallets")), - - MainSz = wxBoxSizer:new(?wxVERTICAL), - - Picker = wxListBox:new(Frame, ?wxID_ANY, [{style, ?wxLC_SINGLE_SEL}]), - Names = [Name || #wr{name = Name} <- Manifest], - ok = wxListBox:set(Picker, Names), - - ButtSz = wxBoxSizer:new(?wxHORIZONTAL), - ButtonTemplates = - [{open, J("Open")}, - {new, J("New")}, - {move, J("Move")}, - {import, J("Import")}, - {drop, J("Drop")}], - MakeButton = - fun({Name, Label}) -> - B = wxButton:new(Frame, ?wxID_ANY, [{label, Label}]), - _ = wxSizer:add(ButtSz, B, zxw:flags(wide)), - #w{name = Name, id = wxButton:getId(B), wx = B} - end, - Buttons = lists:map(MakeButton, ButtonTemplates), - - _ = wxSizer:add(MainSz, Picker, zxw:flags(wide)), - _ = wxSizer:add(MainSz, ButtSz, zxw:flags(base)), - - ok = wxFrame:setSizer(Frame, MainSz), - ok = wxSizer:layout(MainSz), - - NewPrefs = - case maps:is_key(geometry, Prefs) of - true -> - Prefs; - false -> - Display = wxDisplay:new(), - {_, _, W, H} = wxDisplay:getGeometry(Display), - ok = wxDisplay:destroy(Display), - WW = 500, - WH = 350, - X = (W div 2) - (WW div 2), - Y = (H div 2) - (WH div 2), - Prefs#{geometry => {X, Y, WW, WH}} - end, - ok = gd_v:safe_size(Frame, NewPrefs), - - ok = wxFrame:connect(Frame, command_button_clicked), - ok = wxFrame:connect(Frame, close_window), - ok = wxListBox:connect(Picker, command_listbox_doubleclicked), - Count = length(Manifest) - ok = - if - Count =:= 0 -> - self() ! wiz, - ok; - Count =:= 1 -> - self() ! open_only, - ok; - Count > 1 -> - true = wxFrame:show(Frame), - ok - end, - State = #s{wx = Wx, frame = Frame, lang = Lang, j = J, prefs = Prefs, - wallets = Manifest, - picker = Picker, - buttons = Buttons}, - {Frame, State}. - - - -%%% wx_object - -handle_call(Unexpected, From, State) -> - ok = log(warning, "Unexpected call from ~tp: ~tp~n", [From, Unexpected]), - {noreply, State}. - - -handle_cast(to_front, State = #s{frame = Frame}) -> - ok = wxFrame:raise(Frame), - {noreply, State}; -handle_cast({show, Manifest}, State) -> - NewState = do_show(Manifest, State), - {noreply, NewState}; -handle_cast(Unexpected, State) -> - ok = log(warning, "Unexpected cast: ~tp~n", [Unexpected]), - {noreply, State}. - - -handle_info(wiz, State) -> - NewState = do_wiz(State), - {noreply, NewState}; -handle_info(open_only, State) -> - NewState = do_open_only(State), - {noreply, NewState}; -handle_info(Unexpected, State) -> - ok = log(warning, "Unexpected info: ~tp~n", [Unexpected]), - {noreply, State}. - - -handle_event(#wx{event = #wxCommand{type = command_button_clicked}, - id = ID}, - State = #s{buttons = Buttons}) -> - NewState = - case lists:keyfind(ID, #w.id, Buttons) of - #w{name = open} -> do_open(State); - #w{name = new} -> do_new(State); - #w{name = import} -> do_import(State); - #w{name = drop} -> do_drop(State); - #w{name = Name} -> handle_button(Name, State); - false -> State - end, - {noreply, NewState}; -handle_event(#wx{event = #wxCommand{type = command_listbox_doubleclicked, - commandInt = Selected}}, - State) -> - NewState = do_open2(Selected + 1, State), - {noreply, NewState}; -handle_event(#wx{event = #wxClose{}}, State) -> - ok = do_close(State), - {noreply, State}; -handle_event(Event, State) -> - ok = tell(info, "Unexpected event ~tp State: ~tp~n", [Event, State]), - {noreply, State}. - - -code_change(_, State, _) -> - {ok, State}. - - -terminate(wx_deleted, _) -> - wx:destroy(); -terminate(Reason, State) -> - ok = log(info, "Reason: ~tp, State: ~tp", [Reason, State]), - wx:destroy(). - - - -%%% doers - -do_show(Manifest, State = #s{picker = Picker}) -> - Names = [Name || #wr{name = Name} <- Manifest], - ok = wxListBox:set(Picker, Names), - State#s{wallets = Manifest}. - - -do_close(#s{frame = Frame, prefs = Prefs}) -> - Geometry = - case wxTopLevelWindow:isMaximized(Frame) of - true -> - max; - false -> - {X, Y} = wxWindow:getPosition(Frame), - {W, H} = wxWindow:getSize(Frame), - {X, Y, W, H} - end, - NewPrefs = maps:put(geometry, Geometry, Prefs), - ok = gd_con:save(?MODULE, NewPrefs), - ok = wxWindow:destroy(Frame). - - -handle_button(Name, State) -> - ok = tell("Button Click: ~p", [Name]), - State. - - -do_open(State = #s{wallets = []}) -> - State; -do_open(State = #s{picker = Picker}) -> - case wxListBox:getSelection(Picker) of - -1 -> State; - Selected -> do_open2(Selected + 1, State) - end. - -do_open2(Selected, State = #s{wallets = Wallets}) -> - case lists:nth(Selected, Wallets) of - #wr{pass = true, path = Path} -> - do_open3(Path, State); - #wr{pass = false, path = Path} -> - ok = gd_con:open_wallet(Path, none), - ok = do_close(State), - State - end. - -do_open3(Path, State = #s{frame = Frame, j = J}) -> - Label = J("Password"), - Dialog = wxDialog:new(Frame, ?wxID_ANY, Label), - Sizer = wxBoxSizer:new(?wxVERTICAL), - PassSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, Label}]), - PassTx = wxTextCtrl:new(Dialog, ?wxID_ANY, [{style, ?wxTE_PASSWORD}]), - _ = wxStaticBoxSizer:add(PassSz, PassTx, zxw:flags(wide)), - ButtSz = wxBoxSizer:new(?wxHORIZONTAL), - Affirm = wxButton:new(Dialog, ?wxID_OK), - _ = wxBoxSizer:add(ButtSz, Affirm, zxw:flags(wide)), - _ = wxBoxSizer:add(Sizer, PassSz, zxw:flags(base)), - _ = wxBoxSizer:add(Sizer, ButtSz, zxw:flags(wide)), - ok = wxDialog:setSizer(Dialog, Sizer), - ok = wxBoxSizer:layout(Sizer), - ok = wxDialog:setSize(Dialog, {500, 130}), - ok = wxFrame:center(Dialog), - ok = wxStyledTextCtrl:setFocus(PassTx), - case wxDialog:showModal(Dialog) of - ?wxID_OK -> - case wxTextCtrl:getValue(PassTx) of - "" -> - ok = wxDialog:destroy(Dialog), - State; - Phrase -> - ok = wxDialog:destroy(Dialog), - ok = gd_con:open_wallet(Path, Phrase), - ok = do_close(State), - State - end; - ?wxID_CANCEL -> - ok = wxDialog:destroy(Dialog), - State - end. - - -do_wiz(State = #s{wx = WX, lang = Lang, wiz = none}) -> - Trans = gd_jt:read_translations(?MODULE), - J = gd_jt:j(Lang, Trans), - - Frame = wxFrame:new(WX, ?wxID_ANY, J("Initializing Wallet")), - MainSz = wxBoxSizer:new(?wxVERTICAL), - - ButtonTemplates = - [{noob, J("Create a default wallet with a new account for me.")}, - {l33t, J("I know what I'm doing.\nJust show me the wallet manager.")}], - - MakeButton = - fun({Name, Label}) -> - B = wxButton:new(Frame, ?wxID_ANY, [{label, Label}]), - #w{name = Name, id = wxButton:getId(B), wx = B} - end, - - Buttons = lists:map(MakeButton, ButtonTemplates), - - Add = fun(#w{wx = Button}) -> wxBoxSizer:add(MainSz, Button, zxw:flags(wide)) end, - ok = lists:foreach(Add, Buttons), - - ok = wxFrame:setSizer(Frame, MainSz), - ok = wxSizer:layout(MainSz), - - ok = wxFrame:connect(Frame, command_button_clicked), - ok = wxFrame:connect(Frame, close_window), - ok = wxFrame:setSize(Frame, {300, 300}), - ok = wxFrame:center(Frame), - true = wxFrame:show(Frame), - State#s{wiz = {Frame, Buttons}}. - - -do_new(State = #s{frame = Frame, j = J, prefs = Prefs}) -> - DefaultDir = maps:get(dir, Prefs, zx_lib:path(var, "otpr", "gajudesk")), - Options = - [{message, J("Save Location")}, - {defaultDir, DefaultDir}, - {defaultFile, "default.gaju"}, - {wildCard, "*.gaju"}, - {sz, {300, 270}}, - {style, ?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT}], - Dialog = wxFileDialog:new(Frame, Options), - case wxFileDialog:showModal(Dialog) of - ?wxID_OK -> - Dir = wxFileDialog:getDirectory(Dialog), - File = wxFileDialog:getFilename(Dialog), - Path = filename:join(Dir, File), - ok = wxFileDialog:destroy(Dialog), - case do_new2(Path, J, Frame) of - ok -> - NewPrefs = maps:put(dir, Dir, Prefs), - do_close(State#s{prefs = NewPrefs}); - abort -> - State - end; - ?wxID_CANCEL -> - ok = wxFileDialog:destroy(Dialog), - State - end. - -do_new2(Path, J, Frame) -> - Dialog = wxDialog:new(Frame, ?wxID_ANY, J("New Wallet"), [{size, {400, 250}}]), - Sizer = wxBoxSizer:new(?wxVERTICAL), - - Network = wxRadioBox:new(Dialog, ?wxID_ANY, J("Network"), {0, 0}, {50, 50}, [J("Mainnet"), J("Testnet")]), - NameSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Name")}]), - NameTx = wxTextCtrl:new(Dialog, ?wxID_ANY), - _ = wxSizer:add(NameSz, NameTx, zxw:flags(wide)), - Label1 = J("Passphrase (optional)"), - PassSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, Label1}]), - PassTx = wxTextCtrl:new(Dialog, ?wxID_ANY, [{style, ?wxTE_PASSWORD}]), - _ = wxSizer:add(PassSz, PassTx, zxw:flags(wide)), - Label2= J("Passphrase Confirmation"), - PassConSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, Label2}]), - PassConTx = wxTextCtrl:new(Dialog, ?wxID_ANY, [{style, ?wxTE_PASSWORD}]), - _ = wxSizer:add(PassConSz, PassConTx, 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, Network, zxw:flags(base)), - _ = wxSizer:add(Sizer, NameSz, zxw:flags(base)), - _ = wxSizer:add(Sizer, PassSz, zxw:flags(base)), - _ = wxSizer:add(Sizer, PassConSz, zxw:flags(base)), - _ = wxSizer:add(Sizer, ButtSz, zxw:flags(wide)), - - ok = wxDialog:setSizer(Dialog, Sizer), - ok = wxDialog:setSize(Dialog, {300, 270}), - ok = wxBoxSizer:layout(Sizer), - ok = wxDialog:center(Dialog), - ok = wxStyledTextCtrl:setFocus(NameTx), - - Result = - case wxDialog:showModal(Dialog) of - ?wxID_OK -> - Net = - case wxRadioBox:getSelection(Network) of - 0 -> mainnet; - 1 -> testnet; - _ -> mainnet - end, - Name = - case wxTextCtrl:getValue(NameTx) of - "" -> Path; - N -> N - end, - Pass = - case {wxTextCtrl:getValue(PassTx), wxTextCtrl:getValue(PassConTx)} of - {"", ""} -> none; - {P, P} -> P; - {_, _} -> bad - end, - do_new3(Net, Name, Path, Pass); - ?wxID_CANCEL -> - abort - end, - ok = wxDialog:destroy(Dialog), - Result. - -do_new3(_, _, _, bad) -> - abort; -do_new3(Net, Name, Path, Pass) -> - gd_con:new_wallet(Net, Name, Path, Pass). - - -do_import(State = #s{frame = Frame, j = J, prefs = Prefs}) -> - DefaultDir = maps:get(dir, Prefs, zx_lib:path(var, "otpr", "gajudesk")), - Options = - [{message, J("Select Wallet File")}, - {defaultDir, DefaultDir}, - {wildCard, "*.gaju"}, - {style, ?wxFD_OPEN}], - Dialog = wxFileDialog:new(Frame, Options), - case wxFileDialog:showModal(Dialog) of - ?wxID_OK -> - Dir = wxFileDialog:getDirectory(Dialog), - File = wxFileDialog:getFilename(Dialog), - ok = wxFileDialog:destroy(Dialog), - case do_import2(Dir, File, J, Frame) of - ok -> - NewPrefs = maps:put(dir, Dir, Prefs), - do_close(State#s{prefs = NewPrefs}); - abort -> - State - end; - ?wxID_CANCEL -> - ok = wxFileDialog:destroy(Dialog), - State - end. - -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. - - -do_drop(State = #s{picker = Picker}) -> - case wxListBox:getSelection(Picker) of - -1 -> State; - Selected -> do_drop(Selected + 1, State) - end. - -do_drop(Selected, State = #s{j = J, frame = Frame, wallets = Wallets}) -> - #wr{name = Name, path = Path} = lists:nth(Selected, Wallets), - Dialog = wxDialog:new(Frame, ?wxID_ANY, J("Drop Wallet")), - Sizer = wxBoxSizer:new(?wxVERTICAL), - - MessageM = J("REALLY delete wallet?"), - Message = ["\r\n", MessageM, "\r\n\r\n\"", Name, "\""], - MessageT = wxStaticText:new(Dialog, ?wxID_ANY, Message, - [{style, ?wxALIGN_CENTRE_HORIZONTAL}]), - DeleteM = J("Delete file data?"), - DeleteCheck = wxCheckBox:new(Dialog, ?wxID_ANY, DeleteM), - - 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, MessageT, zxw:flags(base)), - _ = wxSizer:add(Sizer, DeleteCheck, 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 = - case wxDialog:showModal(Dialog) of - ?wxID_OK -> - Delete = wxCheckBox:getValue(DeleteCheck), - gd_con:drop_wallet(Path, Delete); - ?wxID_CANCEL -> - ok - end, - State. -- 2.30.2 From 45ab1a35a4bb007e020282af1b5ef7eb4f1256c8 Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Thu, 7 Aug 2025 12:10:18 +0900 Subject: [PATCH 4/9] Add first-run wallet creator --- ebin/gajudesk.app | 2 +- src/gajudesk.erl | 2 +- src/gd_con.erl | 29 ++++++++------------ src/gd_grids.erl | 2 +- src/gd_gui.erl | 2 +- src/gd_jt.erl | 2 +- src/gd_sophia_editor.erl | 2 +- src/gd_sup.erl | 2 +- src/gd_v.erl | 2 +- src/gd_v_devman.erl | 2 +- src/gd_v_netman.erl | 2 +- src/gd_v_wallman.erl | 57 +++++++++++++++++++++++++++++----------- zomp.meta | 2 +- 13 files changed, 64 insertions(+), 44 deletions(-) diff --git a/ebin/gajudesk.app b/ebin/gajudesk.app index 1f70874..676fc99 100644 --- a/ebin/gajudesk.app +++ b/ebin/gajudesk.app @@ -3,7 +3,7 @@ {registered,[]}, {included_applications,[]}, {applications,[stdlib,kernel,sasl,ssl]}, - {vsn,"0.6.6"}, + {vsn,"0.7.0"}, {modules,[gajudesk,gd_con,gd_grids,gd_gui,gd_jt, gd_sophia_editor,gd_sup,gd_v,gd_v_devman,gd_v_netman, gd_v_wallman]}, diff --git a/src/gajudesk.erl b/src/gajudesk.erl index 995acf3..6ae95a4 100644 --- a/src/gajudesk.erl +++ b/src/gajudesk.erl @@ -3,7 +3,7 @@ %%% @end -module(gajudesk). --vsn("0.6.6"). +-vsn("0.7.0"). -behavior(application). -author("Craig Everett "). -copyright("QPQ AG "). diff --git a/src/gd_con.erl b/src/gd_con.erl index c9e40cb..b68b319 100644 --- a/src/gd_con.erl +++ b/src/gd_con.erl @@ -3,7 +3,7 @@ %%% @end -module(gd_con). --vsn("0.6.6"). +-vsn("0.7.0"). -author("Craig Everett "). -copyright("QPQ AG "). -license("GPL-3.0-or-later"). @@ -66,12 +66,13 @@ show_ui(Name) -> gen_server:cast(?MODULE, {show_ui, Name}). --spec open_wallet(Path, Phrase) -> ok +-spec open_wallet(Path, Phrase) -> Result when Path :: file:filename(), - Phrase :: string(). + Phrase :: string(), + Result :: ok | {error, Reason :: term()}. open_wallet(Path, Phrase) -> - gen_server:cast(?MODULE, {open_wallet, Path, Phrase}). + gen_server:call(?MODULE, {open_wallet, Path, Phrase}). -spec close_wallet() -> ok. @@ -217,7 +218,7 @@ deploy(Build, Params, InitArgs) -> Size :: 256, Name :: string(), Seed :: string(), - Encoding :: utf8 | base64 | base58, + Encoding :: utf8 | base64 | base58 | none, Transform :: {Algo, Yugeness}, Algo :: sha3 | sha2 | x_or | pbkdf2, Yugeness :: rand | non_neg_integer(). @@ -365,6 +366,9 @@ handle_call(list_keys, _, State) -> handle_call({nonce, ID}, _, State) -> Response = do_nonce(ID), {reply, Response, State}; +handle_call({open_wallet, Path, Phrase}, _, State) -> + {Response, NewState} = do_open_wallet(Path, Phrase, State), + {reply, Response, NewState}; handle_call(network, _, State) -> Response = do_network(State), {reply, Response, State}; @@ -390,9 +394,6 @@ handle_call(Unexpected, From, State) -> handle_cast({show_ui, Name}, State) -> NewState = do_show_ui(Name, State), {noreply, NewState}; -handle_cast({open_wallet, Path, Phrase}, State) -> - NewState = do_open_wallet(Path, Phrase, State), - {noreply, NewState}; handle_cast(close_wallet, State) -> NextState = do_close_wallet(State), ok = gd_gui:show([]), @@ -489,7 +490,6 @@ handle_info(Unexpected, State) -> handle_down(Mon, PID, Info, State = #s{tasks = Tasks}) -> - tell("Handling down"), case lists:keytake(Mon, #ui.mon, Tasks) of {value, #ui{}, NewTasks} -> State#s{tasks = NewTasks}; @@ -1024,19 +1024,12 @@ do_open_wallet(Path, Phrase, State) -> {ok, ChainID} -> gd_gui:chain(ChainID, Node); Error -> gd_gui:trouble(Error) end, - State#s{pass = Pass, wallet = Recovered}; + {ok, State#s{pass = Pass, wallet = Recovered}}; Error -> - ok = gd_gui:trouble(Error), - % FIXME: This is chaotically stupid. - tell("Sending myself the show_ui message"), - self() ! {show_ui, gd_v_wallman}, - State + {Error, State} end. -default_wallet() -> - default_wallet(testnet). - default_wallet(mainnet) -> Node = #node{ip = "groot.mainnet.gajumaru.io"}, Groot = #chain{id = <<"groot.mainnet">>, diff --git a/src/gd_grids.erl b/src/gd_grids.erl index 7a5f3f0..e60b1aa 100644 --- a/src/gd_grids.erl +++ b/src/gd_grids.erl @@ -37,7 +37,7 @@ %%% @end -module(gd_grids). --vsn("0.6.6"). +-vsn("0.7.0"). -author("Craig Everett "). -copyright("QPQ AG "). -license("GPL-3.0-or-later"). diff --git a/src/gd_gui.erl b/src/gd_gui.erl index 0e91dae..b7650b4 100644 --- a/src/gd_gui.erl +++ b/src/gd_gui.erl @@ -3,7 +3,7 @@ %%% @end -module(gd_gui). --vsn("0.6.6"). +-vsn("0.7.0"). -author("Craig Everett "). -copyright("QPQ AG "). -license("GPL-3.0-or-later"). diff --git a/src/gd_jt.erl b/src/gd_jt.erl index d0e4858..6293b29 100644 --- a/src/gd_jt.erl +++ b/src/gd_jt.erl @@ -15,7 +15,7 @@ %%% translation library is retained). -module(gd_jt). --vsn("0.6.6"). +-vsn("0.7.0"). -export([read_translations/1, j/2, oneshot_j/2]). diff --git a/src/gd_sophia_editor.erl b/src/gd_sophia_editor.erl index 1529b60..448bb7c 100644 --- a/src/gd_sophia_editor.erl +++ b/src/gd_sophia_editor.erl @@ -1,5 +1,5 @@ -module(gd_sophia_editor). --vsn("0.6.6"). +-vsn("0.7.0"). -export([new/1, update/2, get_text/1, set_text/2]). diff --git a/src/gd_sup.erl b/src/gd_sup.erl index f3c9810..5f92335 100644 --- a/src/gd_sup.erl +++ b/src/gd_sup.erl @@ -12,7 +12,7 @@ %%% @end -module(gd_sup). --vsn("0.6.6"). +-vsn("0.7.0"). -behaviour(supervisor). -author("Craig Everett "). -copyright("QPQ AG "). diff --git a/src/gd_v.erl b/src/gd_v.erl index 5908de5..c93be9b 100644 --- a/src/gd_v.erl +++ b/src/gd_v.erl @@ -1,5 +1,5 @@ -module(gd_v). --vsn("0.6.6"). +-vsn("0.7.0"). -author("Craig Everett "). -copyright("QPQ AG "). -license("GPL-3.0-or-later"). diff --git a/src/gd_v_devman.erl b/src/gd_v_devman.erl index c2fa0a8..a5dad28 100644 --- a/src/gd_v_devman.erl +++ b/src/gd_v_devman.erl @@ -1,5 +1,5 @@ -module(gd_v_devman). --vsn("0.6.6"). +-vsn("0.7.0"). -author("Craig Everett "). -copyright("QPQ AG "). -license("GPL-3.0-or-later"). diff --git a/src/gd_v_netman.erl b/src/gd_v_netman.erl index 4de8e5e..000688a 100644 --- a/src/gd_v_netman.erl +++ b/src/gd_v_netman.erl @@ -1,5 +1,5 @@ -module(gd_v_netman). --vsn("0.6.6"). +-vsn("0.7.0"). -author("Craig Everett "). -copyright("QPQ AG "). -license("GPL-3.0-or-later"). diff --git a/src/gd_v_wallman.erl b/src/gd_v_wallman.erl index dd81ee1..e2e493c 100644 --- a/src/gd_v_wallman.erl +++ b/src/gd_v_wallman.erl @@ -1,5 +1,5 @@ -module(gd_v_wallman). --vsn("0.6.6"). +-vsn("0.7.0"). -author("Craig Everett "). -copyright("QPQ AG "). -license("GPL-3.0-or-later"). @@ -7,7 +7,7 @@ -behavior(wx_object). %-behavior(gd_v). -include_lib("wx/include/wx.hrl"). --export([to_front/1]). +-export([to_front/1, trouble/1]). -export([show/2]). -export([start_link/1]). -export([init/1, terminate/2, code_change/3, @@ -42,6 +42,13 @@ to_front(Win) -> wx_object:cast(Win, to_front). +-spec trouble(Info) -> ok + when Info :: term(). + +trouble(Info) -> + wx_object:cast(?MODULE, {trouble, Info}). + + -spec show(Win, Manifest) -> ok when Win :: wx:xw_object(), Manifest :: [#wr{}]. @@ -140,6 +147,9 @@ handle_cast(to_front, State = #s{frame = Frame}) -> ok = ensure_shown(Frame), ok = wxFrame:raise(Frame), {noreply, State}; +handle_cast({trouble, Info}, State) -> + ok = handle_troubling(State, Info), + {noreply, State}; handle_cast({show, Manifest}, State) -> NewState = do_show(Manifest, State), {noreply, NewState}; @@ -191,13 +201,17 @@ handle_event(#wx{event = #wxCommand{type = command_button_clicked}, end, {noreply, NewState}; handle_event(#wx{event = #wxClose{}}, State = #s{wiz = {_, _}}) -> - ok = close_wiz(State), - {noreply, State}; + NewState = close_wiz(State), + {noreply, NewState}; handle_event(Event, State) -> ok = tell(info, "Unexpected event ~tp State: ~tp~n", [Event, State]), {noreply, State}. +handle_troubling(#s{frame = Frame}, Info) -> + zxw:show_message(Frame, Info). + + code_change(_, State, _) -> {ok, State}. @@ -236,8 +250,7 @@ do_close(#s{frame = Frame, prefs = Prefs}) -> end, NewPrefs = maps:put(geometry, Geometry, Prefs), ok = gd_con:save(?MODULE, NewPrefs), - ok = wxWindow:destroy(Frame), - exit(normal). + ok = wxWindow:destroy(Frame). handle_button(Name, State) -> @@ -249,9 +262,15 @@ do_open(State = #s{wallets = []}) -> State; do_open(State = #s{wallets = [#wr{pass = true, path = Path}]}) -> do_open3(Path, State); -do_open(State = #s{wallets = [#wr{pass = false, path = Path}]}) -> - ok = gd_con:open_wallet(Path, none), - ok = do_close(State), +do_open(State = #s{wallets = [#wr{pass = false, path = Path}], frame = Frame}) -> + ok = + case gd_con:open_wallet(Path, none) of + ok -> + do_close(State); + Error -> + ok = ensure_shown(Frame), + trouble(Error) + end, State; do_open(State = #s{picker = Picker}) -> case wxListBox:getSelection(Picker) of @@ -295,8 +314,14 @@ do_open3(Path, State = #s{frame = Frame, j = J}) -> State; Phrase -> ok = wxDialog:destroy(Dialog), - ok = gd_con:open_wallet(Path, Phrase), - ok = do_close(State), + ok = + case gd_con:open_wallet(Path, Phrase) of + ok -> + do_close(State); + Error -> + ok = ensure_shown(Frame), + trouble(Error) + end, State end; ?wxID_CANCEL -> @@ -314,8 +339,8 @@ do_wiz(State = #s{wx = WX, lang = Lang, wiz = none}) -> MainSz = wxBoxSizer:new(?wxVERTICAL), ButtonTemplates = - [{noob, J("Create a default wallet with a new account for me.")}, - {l33t, J("I know what I'm doing.\nJust show me the wallet manager.")}], + [{noob, J("I'm new!\nCreate a new account for me.")}, + {l33t, J("Open the wallet manager.")}], MakeButton = fun({Name, Label}) -> @@ -347,6 +372,8 @@ wiz_noob_assist(State = #s{j = J, wiz = {Wiz, _}}) -> Path = filename:join(DefaultDir, Name ++ ".gaju"), case do_new2(Path, J, Wiz) of ok -> + Label = J("Account 1"), + ok = gd_con:make_key({eddsa, ed25519}, 256, Label, <<>>, none, {sha3, 256}), ok = do_close(State), State; abort -> @@ -387,8 +414,8 @@ do_new(State = #s{frame = Frame, j = J, prefs = Prefs}) -> do_new2(Path, J, Frame) -> Dialog = wxDialog:new(Frame, ?wxID_ANY, J("New Wallet"), [{size, {400, 250}}]), Sizer = wxBoxSizer:new(?wxVERTICAL), - - Network = wxRadioBox:new(Dialog, ?wxID_ANY, J("Network"), {0, 0}, {50, 50}, [J("Mainnet"), J("Testnet")]), + NetworkOptions = [J("Mainnet"), J("Testnet")], + Network = wxRadioBox:new(Dialog, ?wxID_ANY, J("Network"), {0, 0}, {50, 50}, NetworkOptions), NameSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Name")}]), NameTx = wxTextCtrl:new(Dialog, ?wxID_ANY), _ = wxSizer:add(NameSz, NameTx, zxw:flags(wide)), diff --git a/zomp.meta b/zomp.meta index 8f43620..7e49fef 100644 --- a/zomp.meta +++ b/zomp.meta @@ -4,7 +4,7 @@ {prefix,"gd"}. {author,"Craig Everett"}. {desc,"A desktop client for the Gajumaru network of blockchain networks"}. -{package_id,{"otpr","gajudesk",{0,6,6}}}. +{package_id,{"otpr","gajudesk",{0,7,0}}}. {deps,[{"otpr","hakuzaru",{0,6,1}}, {"otpr","eblake2",{1,0,1}}, {"otpr","base58",{0,1,1}}, -- 2.30.2 From 4c9e65b0863a0fe87416b54f9fdcfe3c148e4c5a Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Thu, 7 Aug 2025 13:52:49 +0900 Subject: [PATCH 5/9] Make show_ui synchronous to avoid disaster --- src/gd_con.erl | 15 ++++---- src/gd_gui.erl | 1 + src/gd_v_wallman.erl | 83 ++++++++++++++++++++++++-------------------- 3 files changed, 56 insertions(+), 43 deletions(-) diff --git a/src/gd_con.erl b/src/gd_con.erl index b68b319..6bb2e63 100644 --- a/src/gd_con.erl +++ b/src/gd_con.erl @@ -63,7 +63,7 @@ when Name :: ui_name(). show_ui(Name) -> - gen_server:cast(?MODULE, {show_ui, Name}). + gen_server:call(?MODULE, {show_ui, Name}). -spec open_wallet(Path, Phrase) -> Result @@ -329,6 +329,7 @@ init(none) -> 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, NewState}. @@ -378,6 +379,9 @@ handle_call({save, Module, Prefs}, _, State) -> handle_call({mnemonic, ID}, _, State) -> Response = do_mnemonic(ID, State), {reply, Response, State}; +handle_call({show_ui, Name}, _, State) -> + NewState = do_show_ui(Name, State), + {reply, ok, NewState}; handle_call(Unexpected, From, State) -> ok = log(warning, "Unexpected call from ~tp: ~tp~n", [From, Unexpected]), {noreply, State}. @@ -391,13 +395,11 @@ handle_call(Unexpected, From, State) -> %% The gen_server:handle_cast/2 callback. %% See: http://erlang.org/doc/man/gen_server.html#Module:handle_cast-2 -handle_cast({show_ui, Name}, State) -> - NewState = do_show_ui(Name, State), - {noreply, NewState}; handle_cast(close_wallet, State) -> NextState = do_close_wallet(State), ok = gd_gui:show([]), NewState = do_show_ui(gd_v_wallman, NextState), + ok = gd_v_wallman:to_front(), {noreply, NewState}; handle_cast({new_wallet, Net, Name, Path, Password}, State) -> NewState = do_new_wallet(Net, Name, Path, Password, State), @@ -535,7 +537,6 @@ do_show_ui(Name, State = #s{tasks = Tasks, prefs = Prefs}) -> Win = Name:start_link({TaskPrefs, TaskData}), PID = wx_object:get_pid(Win), Mon = monitor(process, PID), -% ok = Name:to_front(Win), UI = #ui{name = Name, pid = PID, wx = Win, mon = Mon}, State#s{tasks = [UI | Tasks]} end. @@ -867,7 +868,9 @@ do_make_key(Name, Seed, base58, Transform, State) -> do_make_key2(_, _, _, State = #s{wallet = none}) -> ok = gd_gui:trouble("No wallet selected!"), - do_show_ui(gd_v_wallman, State); + NewState = do_show_ui(gd_v_wallman, State), + ok = gd_v_wallman:to_front(), + NewState; do_make_key2(Name, Bin, Transform, State = #s{wallet = Current, wallets = Wallets, pass = Pass}) -> #wallet{name = WalletName, poas = POAs, keys = Keys} = Current, diff --git a/src/gd_gui.erl b/src/gd_gui.erl index b7650b4..3b583aa 100644 --- a/src/gd_gui.erl +++ b/src/gd_gui.erl @@ -361,6 +361,7 @@ refresh(State) -> wallman(State) -> ok = gd_con:show_ui(gd_v_wallman), + ok = gd_v_wallman:to_front(), State. diff --git a/src/gd_v_wallman.erl b/src/gd_v_wallman.erl index e2e493c..4f44db0 100644 --- a/src/gd_v_wallman.erl +++ b/src/gd_v_wallman.erl @@ -7,7 +7,7 @@ -behavior(wx_object). %-behavior(gd_v). -include_lib("wx/include/wx.hrl"). --export([to_front/1, trouble/1]). +-export([to_front/0, to_front/1, first_run/0, trouble/1]). -export([show/2]). -export([start_link/1]). -export([init/1, terminate/2, code_change/3, @@ -35,6 +35,12 @@ %%% Interface +-spec to_front() -> ok. + +to_front() -> + wx_object:cast(?MODULE, to_front). + + -spec to_front(Win) -> ok when Win :: wx:wx_object(). @@ -42,6 +48,12 @@ to_front(Win) -> wx_object:cast(Win, to_front). +-spec first_run() -> ok. + +first_run() -> + wx_object:cast(?MODULE, first_run). + + -spec trouble(Info) -> ok when Info :: term(). @@ -116,19 +128,6 @@ init({Prefs, Manifest}) -> ok = wxFrame:connect(Frame, command_button_clicked), ok = wxFrame:connect(Frame, close_window), ok = wxListBox:connect(Picker, command_listbox_doubleclicked), - Count = length(Manifest), - ok = - if - Count =:= 0 -> - self() ! wiz, - ok; - Count =:= 1 -> - self() ! open, - ok; - Count > 1 -> - true = wxFrame:show(Frame), - wxFrame:raise(Frame) - end, State = #s{wx = Wx, frame = Frame, lang = Lang, j = J, prefs = Prefs, wallets = Manifest, picker = Picker, @@ -137,6 +136,7 @@ init({Prefs, Manifest}) -> + %%% wx_object handle_call(Unexpected, From, State) -> @@ -147,6 +147,9 @@ handle_cast(to_front, State = #s{frame = Frame}) -> ok = ensure_shown(Frame), ok = wxFrame:raise(Frame), {noreply, State}; +handle_cast(first_run, State) -> + NewState = do_first_run(State), + {noreply, NewState}; handle_cast({trouble, Info}, State) -> ok = handle_troubling(State, Info), {noreply, State}; @@ -158,12 +161,6 @@ handle_cast(Unexpected, State) -> {noreply, State}. -handle_info(wiz, State) -> - NewState = do_wiz(State), - {noreply, NewState}; -handle_info(open, State) -> - NewState = do_open(State), - {noreply, NewState}; handle_info(Unexpected, State) -> ok = log(warning, "Unexpected info: ~tp~n", [Unexpected]), {noreply, State}. @@ -232,6 +229,20 @@ do_show(Manifest, State = #s{picker = Picker}) -> State#s{wallets = Manifest}. +do_first_run(State = #s{frame = Frame, wallets = Manifest}) -> + Count = length(Manifest), + if + Count =:= 0 -> + do_wiz(State); + Count =:= 1 -> + do_open(State); + Count > 1 -> + true = wxFrame:show(Frame), + wxFrame:raise(Frame), + State + end. + + close_wiz(State = #s{frame = Frame, wiz = {Wiz, _}}) -> ok = wxWindow:destroy(Wiz), true = wxFrame:show(Frame), @@ -274,8 +285,11 @@ do_open(State = #s{wallets = [#wr{pass = false, path = Path}], frame = Frame}) - State; do_open(State = #s{picker = Picker}) -> case wxListBox:getSelection(Picker) of - -1 -> State; - Selected -> do_open2(Selected + 1, State) + -1 -> + State; + Selected -> + ok = do_open2(Selected + 1, State), + State end. do_open2(Selected, State = #s{wallets = Wallets}) -> @@ -284,8 +298,7 @@ do_open2(Selected, State = #s{wallets = Wallets}) -> do_open3(Path, State); #wr{pass = false, path = Path} -> ok = gd_con:open_wallet(Path, none), - ok = do_close(State), - State + do_close(State) end. do_open3(Path, State = #s{frame = Frame, j = J}) -> @@ -310,24 +323,20 @@ do_open3(Path, State = #s{frame = Frame, j = J}) -> case wxTextCtrl:getValue(PassTx) of "" -> ok = wxDialog:destroy(Dialog), - ok = ensure_shown(Frame), - State; + ensure_shown(Frame); Phrase -> ok = wxDialog:destroy(Dialog), - ok = - case gd_con:open_wallet(Path, Phrase) of - ok -> - do_close(State); - Error -> - ok = ensure_shown(Frame), - trouble(Error) - end, - State + 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), - ok = ensure_shown(Frame), - State + ensure_shown(Frame) end. -- 2.30.2 From cbae5fba498e019c6e9da6798befc34096ee3025 Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Thu, 7 Aug 2025 13:57:04 +0900 Subject: [PATCH 6/9] Add notice about call pattern to gd_v_wallman --- src/gd_v_wallman.erl | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/gd_v_wallman.erl b/src/gd_v_wallman.erl index 4f44db0..7b388dc 100644 --- a/src/gd_v_wallman.erl +++ b/src/gd_v_wallman.erl @@ -1,3 +1,17 @@ +%%% @doc +%%% The GajuDesk Wallet Manager +%%% +%%% This is an interface for managing multiple wallets. +%%% A wallet is defined as a collection of accounts/keys. +%%% +%%% NOTE: +%%% Any call to `gd_con:show_ui(gd_v_wallman)' must be followed by a call to +%%% either `gd_v_wallman:to_front()' or `gd_v_wallman:first_run()' or else +%%% this UI process will just sit in the background (invisible) until told to +%%% do something. +%%% @end + + -module(gd_v_wallman). -vsn("0.7.0"). -author("Craig Everett "). -- 2.30.2 From f60b794996051277cec37c73e4b979de889f501e Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Thu, 7 Aug 2025 14:05:42 +0900 Subject: [PATCH 7/9] Make all new wallet names timestamps by default --- src/gd_v_wallman.erl | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/gd_v_wallman.erl b/src/gd_v_wallman.erl index 7b388dc..c5cf444 100644 --- a/src/gd_v_wallman.erl +++ b/src/gd_v_wallman.erl @@ -389,10 +389,8 @@ do_wiz(State = #s{wx = WX, lang = Lang, wiz = none}) -> wiz_noob_assist(State = #s{j = J, wiz = {Wiz, _}}) -> DefaultDir = zx_lib:path(var, "otpr", "gajudesk"), - {{YY, MM, DD}, {Hr, Mn, Sc}} = calendar:local_time(), - Form = "~4.10.0B-~2.10.0B-~2.10.0B_~2.10.0B-~2.10.0B-~2.10.0B", - Name = io_lib:format(Form, [YY, MM, DD, Hr, Mn, Sc]), - Path = filename:join(DefaultDir, Name ++ ".gaju"), + Name = default_name(), + Path = filename:join(DefaultDir, Name), case do_new2(Path, J, Wiz) of ok -> Label = J("Account 1"), @@ -403,13 +401,19 @@ wiz_noob_assist(State = #s{j = J, wiz = {Wiz, _}}) -> State end. +default_name() -> + {{YY, MM, DD}, {Hr, Mn, Sc}} = calendar:local_time(), + Form = "~4.10.0B-~2.10.0B-~2.10.0B_~2.10.0B-~2.10.0B-~2.10.0B", + Name = io_lib:format(Form, [YY, MM, DD, Hr, Mn, Sc]), + unicode:characters_to_list(Name ++ ".gaju"). + do_new(State = #s{frame = Frame, j = J, prefs = Prefs}) -> DefaultDir = maps:get(dir, Prefs, zx_lib:path(var, "otpr", "gajudesk")), Options = [{message, J("Save Location")}, {defaultDir, DefaultDir}, - {defaultFile, "default.gaju"}, + {defaultFile, default_name()}, {wildCard, "*.gaju"}, {sz, {300, 270}}, {style, ?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT}], -- 2.30.2 From 42b9e9e9db70b2bac4441c6303690d0b96a60cf7 Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Thu, 7 Aug 2025 14:14:14 +0900 Subject: [PATCH 8/9] Fix mistaken state record reassignment --- src/gd_v_wallman.erl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/gd_v_wallman.erl b/src/gd_v_wallman.erl index c5cf444..cdf8514 100644 --- a/src/gd_v_wallman.erl +++ b/src/gd_v_wallman.erl @@ -196,8 +196,8 @@ handle_event(#wx{event = #wxCommand{type = command_button_clicked}, handle_event(#wx{event = #wxCommand{type = command_listbox_doubleclicked, commandInt = Selected}}, State = #s{wiz = none}) -> - NewState = do_open2(Selected + 1, State), - {noreply, NewState}; + ok = do_open2(Selected + 1, State), + {noreply, State}; handle_event(#wx{event = #wxClose{}}, State = #s{wiz = none}) -> ok = do_close(State), {noreply, State}; @@ -286,7 +286,8 @@ handle_button(Name, State) -> do_open(State = #s{wallets = []}) -> State; do_open(State = #s{wallets = [#wr{pass = true, path = Path}]}) -> - do_open3(Path, State); + ok = do_open3(Path, State), + State; do_open(State = #s{wallets = [#wr{pass = false, path = Path}], frame = Frame}) -> ok = case gd_con:open_wallet(Path, none) of -- 2.30.2 From a174230f6efbd275a0394409f65a65738c124b30 Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Thu, 7 Aug 2025 14:30:16 +0900 Subject: [PATCH 9/9] Trim GRIDS URLs before parsing them --- src/gd_grids.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gd_grids.erl b/src/gd_grids.erl index e60b1aa..a639e30 100644 --- a/src/gd_grids.erl +++ b/src/gd_grids.erl @@ -59,7 +59,7 @@ Reason :: bad_url. parse(URL) -> - case uri_string:parse(URL) of + case uri_string:parse(string:trim(URL)) of #{path := "/1/s/" ++ R, host := H, query := Q, scheme := "grids"} -> spend(R, chain, list_to_binary(H), Q); #{path := "/1/s/" ++ R, host := H, query := Q, scheme := "grid"} -> -- 2.30.2