From bd047a6a46732e87c388b1f225600b6eca7d8d27 Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Fri, 7 Nov 2025 13:13:27 +0900 Subject: [PATCH] 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}) ->