diff --git a/ebin/gajudesk.app b/ebin/gajudesk.app index 293be9a..deaba41 100644 --- a/ebin/gajudesk.app +++ b/ebin/gajudesk.app @@ -6,5 +6,6 @@ {vsn,"0.9.0"}, {modules,[gajudesk,gd_con,gd_grids,gd_gui,gd_jt,gd_lib, gd_m_spend,gd_m_wallet_importer,gd_sophia_editor, - gd_sup,gd_v,gd_v_devman,gd_v_netman,gd_v_wallman]}, + gd_sup,gd_v,gd_v_call,gd_v_devman,gd_v_netman, + gd_v_wallman]}, {mod,{gajudesk,[]}}]}. diff --git a/src/gd_con.erl b/src/gd_con.erl index f655c57..1e1e878 100644 --- a/src/gd_con.erl +++ b/src/gd_con.erl @@ -54,7 +54,8 @@ -type state() :: #s{}. -type ui_name() :: gd_v_netman | gd_v_wallman - | gd_v_devman. + | gd_v_doomweaver + | gd_v_express. @@ -242,8 +243,8 @@ open_contract(ConID) -> when ConID :: string(), DevmanToFront :: boolean(). %% @doc -%% Ask the controller to tell the devman interface to open a deployed contract. -%% The controller will start the devman if it isn't already on. +%% Ask the controller to tell the doomweaver interface to open a deployed contract. +%% The controller will start the doomweaver if it isn't already on. open_contract(ConID, DevmanToFront) when is_binary(ConID) -> gen_server:cast(?MODULE, {open_contract, ConID, DevmanToFront}); @@ -266,9 +267,9 @@ show_call(ConID, Info) -> Info :: map(), DevmanToFront :: boolean(). %% @doc -%% Ask the controller to tell the devman interface to dislpay the result of a call +%% Ask the controller to tell the doomweaver interface to dislpay the result of a call %% to the indicated contract. Opens the contract in question if it isn't alread open. -%% Starts the devman if it isn't already running. +%% Starts the doomweaver if it isn't already running. show_call(ConID, Info, DevmanToFront) when is_binary(ConID) -> gen_server:cast(?MODULE, {show_call, ConID, Info, DevmanToFront}); @@ -653,13 +654,15 @@ do_show_ui(Name, ToFront, State = #s{tasks = Tasks, prefs = Prefs}) -> State#s{tasks = [UI | Tasks]} end. -task_data(gd_v_netman, #s{wallet = #wallet{nets = Nets}}) -> +task_data(gd_v_netman, #s{wallet = #wallet{nets = Nets}}) -> Nets; -task_data(gd_v_netman, #s{wallet = none}) -> +task_data(gd_v_netman, #s{wallet = none}) -> []; -task_data(gd_v_wallman, #s{wallets = Wallets}) -> +task_data(gd_v_wallman, #s{wallets = Wallets}) -> Wallets; -task_data(gd_v_devman, #s{}) -> +task_data(gd_v_doomweaver, #s{}) -> + []; +task_data(gd_v_express, #s{}) -> []. @@ -1063,15 +1066,15 @@ do_drop_key(ID, State = #s{wallet = W, wallets = Wallets, pass = Pass}) -> do_open_contract(ConID, ToFront, State) -> - NewState = do_show_ui(gd_v_devman, ToFront, State), - ok = gd_v_devman:open_contract(ConID), + NewState = do_show_ui(gd_v_doomweaver, ToFront, State), + ok = gd_v_doomweaver:open_contract(ConID), NewState. do_show_call(ConID, Info, ToFront, State) -> - NewState = do_show_ui(gd_v_devman, ToFront, State), - ok = gd_v_devman:open_contract(ConID), - ok = gd_v_devman:call_result(ConID, Info), + NewState = do_show_ui(gd_v_doomweaver, ToFront, State), + ok = gd_v_doomweaver:open_contract(ConID), + ok = gd_v_doomweaver:call_result(ConID, Info), NewState. diff --git a/src/gd_gui.erl b/src/gd_gui.erl index 82d909a..1f7d6ee 100644 --- a/src/gd_gui.erl +++ b/src/gd_gui.erl @@ -19,7 +19,6 @@ -include("gd.hrl"). -include("gdl.hrl"). - -record(h, {win = none :: none | wx:wx_object(), sz = none :: none | wx:wx_object()}). @@ -204,6 +203,7 @@ init(Prefs) -> ok = gd_v:safe_size(Frame, Prefs), ok = wxFrame:connect(Frame, command_button_clicked), + ok = wxFrame:connect(Frame, char_hook), ok = wxFrame:connect(Frame, close_window), true = wxFrame:show(Frame), ok = wxListBox:connect(Picker, command_listbox_selected), @@ -291,7 +291,7 @@ handle_event(#wx{event = #wxCommand{type = command_button_clicked}, case lists:keyfind(ID, #w.id, Buttons) of #w{name = wallet} -> wallman(State); #w{name = chain} -> netman(State); - #w{name = dev} -> devman(State); + #w{name = dev} -> doomweaver(State); #w{name = node} -> set_node(State); #w{name = make_key} -> make_key(State); #w{name = recover} -> recover_key(State); @@ -312,6 +312,16 @@ handle_event(#wx{event = #wxCommand{type = command_listbox_selected, State) -> NewState = do_selection(Selected, State), {noreply, NewState}; +handle_event(#wx{event = #wxKey{keyCode = Code}}, State) -> + NewState = + case Code of + 69 -> express(State); + 70 -> doomweaver(State); + _ -> + ok = io:format("Code: ~p", [Code]), + State + end, + {noreply, NewState}; handle_event(#wx{event = #wxClose{}}, State = #s{frame = Frame, prefs = Prefs}) -> Geometry = case wxTopLevelWindow:isMaximized(Frame) of @@ -367,8 +377,13 @@ netman(State) -> State. -devman(State) -> - ok = gd_con:show_ui(gd_v_devman), +doomweaver(State) -> + ok = gd_con:show_ui(gd_v_doomweaver), + State. + + +express(State) -> + ok = io:format("GajuExpress~n"), State. diff --git a/src/gd_v_devman.erl b/src/gd_v_doomweaver.erl similarity index 99% rename from src/gd_v_devman.erl rename to src/gd_v_doomweaver.erl index b7a7405..3906587 100644 --- a/src/gd_v_devman.erl +++ b/src/gd_v_doomweaver.erl @@ -1,4 +1,4 @@ --module(gd_v_devman). +-module(gd_v_doomweaver). -vsn("0.9.0"). -author("Craig Everett "). -copyright("QPQ AG "). diff --git a/src/gd_v_express.erl b/src/gd_v_express.erl new file mode 100644 index 0000000..349b5ac --- /dev/null +++ b/src/gd_v_express.erl @@ -0,0 +1,595 @@ +%%% @doc +%%% The GajuExpress +%%% @end + + +-module(gd_v_express). +-vsn("0.9.0"). +-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/0, to_front/1, trouble/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"). +-include("gdl.hrl"). + + +-record(s, + {wx = none :: none | wx:wx_object(), + frame = none :: none | wx:wx_object(), + lang = en :: en | jp, + j = none :: none | fun(), + prefs = #{} :: map(), + price = none :: none | {non_neg_integer(), wx:wx_object()}, + size = none :: none | {non_neg_integer(), wx:wx_object()}, + total = none :: none | {non_neg_integer(), wx:wx_object()}, + dest = none :: none | wx:wx_object(), + picker = none :: none | wx:wx_object(), + file = none :: none | wx:wx_object(), + sign_yn = none :: none | wx:wx_object(), + buttons = [] :: [#w{}]}). + +-record(mochila, + {tar = <<>> :: binary(), + sig = none :: none | {Key :: binary(), Sig :: binary()}}). + + +%%% Interface + +-spec to_front() -> ok. + +to_front() -> + wx_object:cast(?MODULE, to_front). + + +-spec to_front(Win) -> ok + when Win :: wx:wx_object(). + +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{}]. + +show(Win, Manifest) -> + wx_object:cast(Win, {show, Manifest}). + + +%%% Startup + +start_link(Args) -> + wx_object:start_link({local, ?MODULE}, ?MODULE, Args, []). + +% Sending... +% +% 0. Sender opens GajuExpress interface +% 1. GajuDesk asks GajuExpress for a price quote +% 2. GajuExpress responds with a price quote: pucks per byte per height +% 3. The user inputs the public key/ID of the party the data should be sent to +% 4. The user picks the file or directory to send +% 5. The user decides whether to sign the package or be anonymous +% 6. The user picks which key to sign with if not anonymous +% 7. The price is shown +% 8. The user agrees or aborts +% 9. If the user agrees, a payment window opens and they send a payment to GajuExpress +% 10. Once GajuExpress verifies the payment, the upload begins +% +% +% Receiving... +% +% 1. Sender opens GajuExpress interface +% 2. GajuDesk asks GajuExpress whether there are any packages waiting for KeyList +% 3. If yes, then the pending deliveries are shown in the delivery panel +% 4. The user selects + clicks open (or double clicks) a package +% 5. User selects where to unpack it in a file dialog +% 6. GajuDesk downloads the package from GajuExpress +% 7. Progress bar +% 8. File is decrypted and optionally signature verified +% 9. GajuExpress deletes their copy of the file + +init({Prefs, Manifest}) -> + Lang = maps:get(lang, Prefs, en), + Trans = gd_jt:read_translations(?MODULE), + J = gd_jt:j(Lang, Trans), + Wx = wx:new(), + Frame = wxFrame:new(Wx, ?wxID_ANY, J("GajuExpress")), + Panel = wxWindow:new(Frame, ?wxID_ANY), + TopSz = wxBoxSizer:new(?wxVERTICAL), + _ = wxBoxSizer:add(TopSz, Panel, zxw:flags(wide)), + MainSz = wxBoxSizer:new(?wxVERTICAL), + + Picker = wxListBox:new(Panel, ?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(Panel, ?wxID_ANY, [{label, Label}]), + _ = wxSizer:add(ButtSz, B, zxw:flags({wide, 5})), + #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(Panel, MainSz), + ok = wxFrame:setSizer(Frame, TopSz), + 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), + 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 = 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}; +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(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, wiz = none}) -> + 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 = #s{wiz = none}) -> + ok = do_open2(Selected + 1, State), + {noreply, 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 = {_, _}}) -> + 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}. + + +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_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), + State#s{wiz = none}. + + +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{wallets = [#wr{pass = true, path = Path}]}) -> + 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 + ok -> + do_close(State); + Error -> + ok = ensure_shown(Frame), + trouble(Error) + end, + State; +do_open(State = #s{picker = Picker}) -> + case wxListBox:getSelection(Picker) of + -1 -> + State; + Selected -> + ok = do_open2(Selected + 1, State), + 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), + do_close(State) + end. + +do_open3(Path, State = #s{frame = Frame, j = J}) -> + Title = J("Passphrase"), + case zxw_modal_text:show(Frame, Title, [{password, true}]) of + {ok, Phrase} -> + case gd_con:open_wallet(Path, Phrase) of + ok -> do_close(State); + Error -> trouble(Error) + end; + cancel -> + ok + end. + + +do_wiz(State = #s{wx = WX, lang = Lang, wiz = none}) -> + Trans = gd_jt:read_translations(?MODULE), + J = gd_jt:j(Lang, Trans), + + Wiz = wxFrame:new(WX, ?wxID_ANY, J("Initializing Wallet")), + MainSz = wxBoxSizer:new(?wxVERTICAL), + + ButtonTemplates = + [{noob, J("I'm new!\nCreate a new account for me.")}, + {l33t, J("Open the wallet manager.")}], + + MakeButton = + fun({Name, Label}) -> + B = wxButton:new(Wiz, ?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(Wiz, MainSz), + ok = wxSizer:layout(MainSz), + + 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"), + Name = default_name(), + Path = filename:join(DefaultDir, Name), + 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 -> + 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_name()}, + {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), + NewState = State#s{prefs = NewPrefs}, + ok = do_close(NewState), + NewState; + 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), + 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)), + 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), + NewState = State#s{prefs = NewPrefs}, + ok = do_close(NewState), + NewState; + 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), + 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}) -> + 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. + + +ensure_shown(Frame) -> + case wxWindow:isShown(Frame) of + true -> + ok; + false -> + true = wxFrame:show(Frame), + ok + end.