diff --git a/src/: b/src/: deleted file mode 100644 index 8a37adc..0000000 --- a/src/: +++ /dev/null @@ -1,1029 +0,0 @@ --module(gmc_v_devman). --vsn("0.3.0"). --author("Craig Everett "). --copyright("QPQ AG "). --license("GPL-3.0-or-later"). - --behavior(wx_object). -%-behavior(gmc_v). --include_lib("wx/include/wx.hrl"). --export([to_front/1]). --export([set_manifest/1, open_contract/1, trouble/1]). --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("gmc.hrl"). - -% Widgets --record(w, - {name = none :: atom() | {FunName :: binary(), call | dryr}, - id = 0 :: integer(), - wx = none :: none | wx:wx_object()}). - -% Contract functions in an ACI --record(f, - {name = <<"">> :: binary(), - call = #w{} :: #w{}, - dryrun = #w{} :: none | #w{}, - args = [] :: [{wx:wx_object(), wx:wx_object(), argt()}]}). - -% Code book pages --record(p, - {path = {file, ""} :: {file, file:filename()} | {hash, binary()}, - win = none :: none | wx:wx_object(), - code = none :: none | wxTextCtrl:wxTextCtrl()}). - -% Contract pages --record(c, - {id = "" :: string(), - win = none :: none | wx:wx_object(), - code = none :: none | wxTextCtrl:wxTextCtrl(), - cons = none :: none | wxTextCtrl:wxTextCtrl(), - build = none :: none | map(), - funs = {#w{}, []} :: {#w{}, [#f{}]}}). - -% State --record(s, - {wx = none :: none | wx:wx_object(), - frame = none :: none | wx:wx_object(), - lang = en :: en | jp, - j = none :: none | fun(), - prefs = #{} :: map(), - buttons = #{} :: #{WX_ID :: integer() := #w{}}, - tabs = none :: none | wx:wx_object(), - code = {none, []} :: {Codebook :: none | wx:wx_object(), Pages :: [#p{}]}, - cons = {none, []} :: {Consbook :: none | wx:wx_object(), Pages :: [#c{}]}}). - --type argt() :: int | string | address | list(argt()). - -%%% Interface - --spec to_front(Win) -> ok - when Win :: wx:wx_object(). - -to_front(Win) -> - wx_object:cast(Win, to_front). - - -% TODO: Probably kill this --spec set_manifest(Entries) -> ok - when Entries :: list(). - -set_manifest(Entries) -> - case is_pid(whereis(?MODULE)) of - true -> wx_object:cast(?MODULE, {set_manifest, Entries}); - false -> ok - end. - - --spec open_contract(Address) -> ok - when Address :: string(). - -open_contract(Address) -> - wx_object:cast(?MODULE, {open_contract, Address}). - - --spec trouble(Info) -> ok - when Info :: term(). - -trouble(Info) -> - wx_object:cast(?MODULE, {trouble, Info}). - - - - -%%% Startup Functions - -start_link(Args) -> - wx_object:start_link({local, ?MODULE}, ?MODULE, Args, []). - - -init({Prefs, Manifest}) -> - Lang = maps:get(lang, Prefs, en_us), - Trans = gmc_jt:read_translations(?MODULE), - J = gmc_jt:j(Lang, Trans), - Wx = wx:new(), - Frame = wxFrame:new(Wx, ?wxID_ANY, J("Contracts")), - - MainSz = wxBoxSizer:new(?wxVERTICAL), - TopBook = wxNotebook:new(Frame, ?wxID_ANY, [{style, ?wxBK_DEFAULT}]), - {LWin, LButtons, Codebook} = make_l_win(TopBook, J), - {RWin, RButtons, Consbook} = make_r_win(TopBook, J), - ButtonMap = maps:merge(LButtons, RButtons), - true = wxNotebook:addPage(TopBook, LWin, J("Contract Editor"), []), - true = wxNotebook:addPage(TopBook, RWin, J("Deployed Contracts"), []), - State = - #s{wx = Wx, frame = Frame, - j = J, prefs = Prefs, - buttons = ButtonMap, tabs = TopBook, - code = {Codebook, []}, cons = {Consbook, []}}, - _ = wxSizer:add(MainSz, TopBook, zxw:flags(wide)), - _ = wxFrame:setSizer(Frame, MainSz), - _ = wxSizer:layout(MainSz), - ok = gmc_v:safe_size(Frame, Prefs), - ok = wxFrame:connect(Frame, close_window), - ok = wxFrame:connect(Frame, command_button_clicked), - ok = wxFrame:center(Frame), - true = wxFrame:show(Frame), - NewState = add_code_pages(State, Manifest), - {Frame, NewState}. - - -make_l_win(TopBook, J) -> - Win = wxWindow:new(TopBook, ?wxID_ANY), - MainSz = wxBoxSizer:new(?wxVERTICAL), - ButtSz = wxBoxSizer:new(?wxHORIZONTAL), - ButtonTemplates = - [{new, J("New")}, - {open, J("Open")}, - {save, J("Save")}, - {rename, J("Save (rename)")}, - {deploy, J("Deploy")}, - {close_source, J("Close")}], - MakeButton = - fun({Name, Label}) -> - B = wxButton:new(Win, ?wxID_ANY, [{label, Label}]), - #w{name = Name, id = wxButton:getId(B), wx = B} - end, - Buttons = lists:map(MakeButton, ButtonTemplates), - AddButton = fun(#w{wx = B}) -> wxSizer:add(ButtSz, B, zxw:flags(wide)) end, - Codebook = wxNotebook:new(Win, ?wxID_ANY, [{style, ?wxBK_DEFAULT}]), - ok = lists:foreach(AddButton, Buttons), - _ = wxSizer:add(MainSz, ButtSz, zxw:flags(base)), - _ = wxSizer:add(MainSz, Codebook, zxw:flags(wide)), - _ = wxWindow:setSizer(Win, MainSz), - MapButton = fun(B = #w{id = I}, M) -> maps:put(I, B, M) end, - ButtonMap = lists:foldl(MapButton, #{}, Buttons), - {Win, ButtonMap, Codebook}. - -make_r_win(TopBook, J) -> - Win = wxWindow:new(TopBook, ?wxID_ANY), - MainSz = wxBoxSizer:new(?wxVERTICAL), - ButtSz = wxBoxSizer:new(?wxHORIZONTAL), - ButtonTemplates = - [{load, J("Load from Chain")}, - {edit, J("Copy to Editor")}, - {close_instance, J("Close")}], - MakeButton = - fun({Name, Label}) -> - B = wxButton:new(Win, ?wxID_ANY, [{label, Label}]), - #w{name = Name, id = wxButton:getId(B), wx = B} - end, - Buttons = lists:map(MakeButton, ButtonTemplates), - AddButton = fun(#w{wx = B}) -> wxSizer:add(ButtSz, B, zxw:flags(wide)) end, - Codebook = wxNotebook:new(Win, ?wxID_ANY, [{style, ?wxBK_DEFAULT}]), - ok = lists:foreach(AddButton, Buttons), - _ = wxSizer:add(MainSz, ButtSz, zxw:flags(base)), - _ = wxSizer:add(MainSz, Codebook, zxw:flags(wide)), - _ = wxWindow:setSizer(Win, MainSz), - MapButton = fun(B = #w{id = I}, M) -> maps:put(I, B, M) end, - ButtonMap = lists:foldl(MapButton, #{}, Buttons), - {Win, ButtonMap, Codebook}. - - - -add_code_pages(State, Files) -> - lists:foldl(fun add_code_page/2, State, Files). - - - -%%% OTP callbacks - -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({open_contract, Address}, State) -> - NewState = load2(State, Address), - {noreply, NewState}; -handle_cast({trouble, Info}, State) -> - ok = handle_troubling(State, Info), - {noreply, State}; -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(E = #wx{event = #wxCommand{type = command_button_clicked}, - id = ID}, - State = #s{buttons = Buttons}) -> - NewState = - case maps:get(ID, Buttons, undefined) of - #w{name = new} -> new_file(State); - #w{name = open} -> open(State); - #w{name = save} -> save(State); - #w{name = rename} -> rename(State); - #w{name = deploy} -> deploy(State); - #w{name = close_source} -> close_source(State); - #w{name = load} -> load(State); - #w{name = edit} -> edit(State); - #w{name = close_instance} -> close_instance(State); - #w{name = Name, wx = Button} -> clicked(State, Name, Button); - undefined -> - tell("Received message: ~w", [E]), - State - end, - {noreply, NewState}; -handle_event(#wx{event = #wxClose{}}, State = #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 = gmc_con:save(?MODULE, NewPrefs), - ok = wxWindow:destroy(Frame), - {noreply, State}; -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(Reason, State) -> - ok = log(info, "Reason: ~tp, State: ~tp", [Reason, State]), - wx:destroy(). - - - -%%% Doers - -clicked(State = #s{cons = {Consbook, Contracts}}, Name, Button) -> - case wxNotebook:getSelection(Consbook) of - ?wxNOT_FOUND -> - ok = tell(warning, "Inconcievable! No notebook page is selected!"), - State; - Index -> - Contract = lists:nth(Index + 1, Contracts), - clicked(State, Contract, Name, Button) - end. - -clicked(State, Contract, Name, Button) -> - ok = tell("Button: ~p ~p~nContract: ~p", [Button, Name, Contract]), - State. - - -add_code_page(State = #s{code = {Codebook, Pages}}, File) -> - case keyfind_index({file, File}, #p.path, Pages) of - error -> - add_code_page2(State, File); - {ok, Index} -> - _ = wxNotebook:setSelection(Codebook, Index - 1), - State - end. - -add_code_page2(State = #s{j = J}, {file, File}) -> - case file:read_file(File) of - {ok, Bin} -> - case unicode:characters_to_list(Bin) of - Code when is_list(Code) -> - add_code_page(State, {file, File}, Code); - Error -> - Message = io_lib:format(J("Opening ~p failed with: ~p"), [File, Error]), - ok = handle_troubling(Message, State), - State - end; - {error, Reason} -> - Message = io_lib:format(J("Opening ~p failed with: ~p"), [File, Reason]), - ok = handle_troubling(Message, State), - State - end; -add_code_page2(State, {hash, Address}) -> - open_hash2(State, Address). - -add_code_page(State = #s{tabs = TopBook, code = {Codebook, Pages}}, Location, Code) -> -% FIXME: One of these days we need to define the text area as a wxStyledTextCtrl and will -% have to contend with system theme issues (light/dark themese, namely) -% Leaving this little thing here to remind myself how any of that works later. -% The call below returns a wx_color4() type (not that we need alpha...). -% Color = wxSystemSettings:getColour(?wxSYS_COLOUR_WINDOW), -% tell("Color: ~p", [Color]), - Window = wxWindow:new(Codebook, ?wxID_ANY), - PageSz = wxBoxSizer:new(?wxHORIZONTAL), - - CodeTxStyle = {style, ?wxTE_MULTILINE bor ?wxTE_PROCESS_TAB bor ?wxTE_DONTWRAP}, - CodeTx = wxTextCtrl:new(Window, ?wxID_ANY, [CodeTxStyle]), - TextAt = wxTextAttr:new(), - Mono = wxFont:new(10, ?wxMODERN, ?wxNORMAL, ?wxNORMAL, [{face, "Monospace"}]), - ok = wxTextAttr:setFont(TextAt, Mono), - true = wxTextCtrl:setDefaultStyle(CodeTx, TextAt), - ok = wxTextCtrl:setValue(CodeTx, Code), - - _ = wxSizer:add(PageSz, CodeTx, zxw:flags(wide)), - - ok = wxWindow:setSizer(Window, PageSz), - ok = wxSizer:layout(PageSz), - _ = wxNotebook:changeSelection(TopBook, 0), - FileName = - case Location of - {file, Path} -> filename:basename(Path); - {hash, Addr} -> Addr - end, - true = wxNotebook:addPage(Codebook, Window, FileName, [{bSelect, true}]), - Page = #p{path = Location, win = Window, code = CodeTx}, - NewPages = Pages ++ [Page], - State#s{code = {Codebook, NewPages}}. - - -new_file(State = #s{frame = Frame, j = J, prefs = Prefs}) -> - DefaultDir = - case maps:find(dir, Prefs) of - {ok, PrefDir} -> - PrefDir; - error -> - case os:getenv("ZOMP_DIR") of - "" -> file:get_pwd(); - D -> filename:basename(D) - end - end, - Options = - [{message, J("Save Location")}, - {defaultDir, DefaultDir}, - {defaultFile, "my_contract.aes"}, - {wildCard, "*.aes"}, - {style, ?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT}], - Dialog = wxFileDialog:new(Frame, Options), - NewState = - case wxFileDialog:showModal(Dialog) of - ?wxID_OK -> - Dir = wxFileDialog:getDirectory(Dialog), - case wxFileDialog:getFilename(Dialog) of - "" -> - State; - Name -> - File = - case filename:extension(Name) of - ".aes" -> Name; - _ -> Name ++ ".aes" - end, - Path = filename:join(Dir, File), - NewPrefs = maps:put(dir, Dir, Prefs), - NextState = State#s{prefs = NewPrefs}, - add_code_page(NextState, {file, Path}, "") - end; - ?wxID_CANCEL -> - State - end, - ok = wxFileDialog:destroy(Dialog), - NewState. - - -deploy(State = #s{code = {Codebook, Pages}}) -> - case wxNotebook:getSelection(Codebook) of - ?wxNOT_FOUND -> - State; - Index -> - #p{code = CodeTx} = lists:nth(Index + 1, Pages), - Source = wxTextCtrl:getValue(CodeTx), - deploy2(State, Source) - end. - -deploy2(State, Source) -> - case so_compiler:from_string(Source, [{aci, json}]) of - {ok, Build} -> - deploy3(State, Build); - Other -> - ok = tell(info, "Compilation Failed!~n~tp", [Other]), - State - end. - -deploy3(State, Build) -> - case gmc_con:list_keys() of - {ok, 0, []} -> - handle_troubling(State, "No keys exist in the current wallet."); - {ok, Selected, Keys} -> - deploy4(State, Build, Selected, Keys); - error -> - handle_troubling(State, "No wallet is selected!") - end. - -deploy4(State = #s{frame = Frame, j = J}, Build = #{aci := ACI}, Selected, Keys) -> - {#{functions := Funs}, _} = find_main(ACI), - #{arguments := As} = lom:find(name, <<"init">>, Funs), - Dialog = wxDialog:new(Frame, ?wxID_ANY, J("Deploy Contract")), - Sizer = wxBoxSizer:new(?wxVERTICAL), - ScrollWin = wxScrolledWindow:new(Dialog), - FunName = unicode:characters_to_list(["init/", integer_to_list(length(As))]), - FunSizer = wxStaticBoxSizer:new(?wxVERTICAL, ScrollWin, [{label, FunName}]), - ok = wxScrolledWindow:setSizerAndFit(ScrollWin, FunSizer), - ok = wxScrolledWindow:setScrollRate(ScrollWin, 5, 5), - KeySz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Signature Key")}]), - KeyPicker = wxChoice:new(Dialog, ?wxID_ANY, [{choices, Keys}]), - _ = wxStaticBoxSizer:add(KeySz, KeyPicker, zxw:flags(wide)), - ok = wxChoice:setSelection(KeyPicker, Selected - 1), - 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)), - GridSz = wxFlexGridSizer:new(2, 4, 4), - ok = wxFlexGridSizer:setFlexibleDirection(GridSz, ?wxHORIZONTAL), - ok = wxFlexGridSizer:addGrowableCol(GridSz, 1), - MakeArgField = - fun(#{name := AN, type := T}) -> - Type = - case T of - <<"address">> -> address; - <<"int">> -> integer; - <<"bool">> -> boolean; - L when is_list(L) -> list; % FIXME -% I when is_binary(I) -> iface % FIXME - I when is_binary(I) -> address % FIXME - end, - ANT = wxStaticText:new(ScrollWin, ?wxID_ANY, AN), - TCT = wxTextCtrl:new(ScrollWin, ?wxID_ANY), - _ = wxFlexGridSizer:add(GridSz, ANT, zxw:flags(base)), - _ = wxFlexGridSizer:add(GridSz, TCT, zxw:flags(wide)), - {ANT, TCT, Type} - end, - ArgFields = lists:map(MakeArgField, As), - _ = wxStaticBoxSizer:add(FunSizer, GridSz, zxw:flags(wide)), - _ = wxSizer:add(Sizer, ScrollWin, [{proportion, 5}, {flag, ?wxEXPAND}]), - _ = wxSizer:add(Sizer, KeySz, [{proportion, 0}, {flag, ?wxEXPAND}]), - _ = wxSizer:add(Sizer, ButtSz, [{proportion, 1}, {flag, ?wxEXPAND}]), - ok = wxDialog:setSizer(Dialog, Sizer), - ok = wxBoxSizer:layout(Sizer), - ok = wxDialog:setSize(Dialog, {500, 300}), - ok = wxDialog:center(Dialog), - Outcome = - case wxDialog:showModal(Dialog) of - ?wxID_OK -> - ID = wxChoice:getString(KeyPicker, wxChoice:getSelection(KeyPicker)), - BinID = unicode:characters_to_binary(ID), - Inputs = lists:map(fun get_arg/1, ArgFields), - {ok, BinID, Inputs}; - ?wxID_CANCEL -> - cancel - end, - ok = wxDialog:destroy(Dialog), - case Outcome of - {ok, SigID, Args} -> deploy5(State, SigID, Build, Args); - cancel -> State - end. - -deploy5(State, SigID, Build, Args) -> - tell(info, "Build: ~p", [Build]), - ok = gmc_con:deploy(SigID, Build, Args), - State. - - -open(State = #s{frame = Frame, j = J}) -> - Dialog = wxDialog:new(Frame, ?wxID_ANY, J("Open Contract Source")), - Sizer = wxBoxSizer:new(?wxVERTICAL), - Choices = wxRadioBox:new(Dialog, - ?wxID_ANY, - J("Select Origin"), - ?wxDefaultPosition, - ?wxDefaultSize, - [J("From File"), J("From Hash")], - [{majorDim, 1}]), - 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, Choices, zxw:flags(wide)), - _ = wxSizer:add(Sizer, ButtSz, zxw:flags(wide)), - ok = wxDialog:setSizer(Dialog, Sizer), - ok = wxDialog:setSize(Dialog, {250, 170}), - ok = wxBoxSizer:layout(Sizer), - Choice = - case wxDialog:showModal(Dialog) of - ?wxID_OK -> - case wxRadioBox:getSelection(Choices) of - 0 -> file; - 1 -> hash; - ?wxNOT_FOUND -> none - end; - ?wxID_CANCEL -> cancel - end, - ok = wxDialog:destroy(Dialog), - case Choice of - file -> - open_file(State); - hash -> - open_hash(State); - none -> - ok = tell(info, "No selection."), - State; - cancel -> - ok = tell(info, "Cancelled."), - State - end. - - -open_file(State = #s{frame = Frame, j = J, prefs = Prefs}) -> - DefaultDir = - case maps:find(dir, Prefs) of - {ok, PrefDir} -> - PrefDir; - error -> - case os:getenv("ZOMP_DIR") of - "" -> file:get_pwd(); - D -> filename:basename(D) - end - end, - Options = - [{message, J("Load Contract Source")}, - {defaultDir, DefaultDir}, - {wildCard, "*.aes"}, - {style, ?wxFD_OPEN}], - Dialog = wxFileDialog:new(Frame, Options), - NewState = - case wxFileDialog:showModal(Dialog) of - ?wxID_OK -> - Dir = wxFileDialog:getDirectory(Dialog), - case wxFileDialog:getFilename(Dialog) of - "" -> - State; - Name -> - File = - case filename:extension(Name) of - ".aes" -> Name; - _ -> Name ++ ".aes" - end, - Path = filename:join(Dir, File), - NewPrefs = maps:put(dir, Dir, Prefs), - NextState = State#s{prefs = NewPrefs}, - add_code_page(NextState, {file, Path}) - end; - ?wxID_CANCEL -> - State - end, - ok = wxFileDialog:destroy(Dialog), - NewState. - - -open_hash(State = #s{frame = Frame, j = J}) -> - Dialog = wxDialog:new(Frame, ?wxID_ANY, J("Retrieve Contract Source")), - Sizer = wxBoxSizer:new(?wxVERTICAL), - AddressSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Address Hash")}]), - AddressTx = wxTextCtrl:new(Dialog, ?wxID_ANY), - _ = wxSizer:add(AddressSz, AddressTx, zxw:flags(wide)), - ButtSz = wxBoxSizer:new(?wxHORIZONTAL), - Affirm = wxButton:new(Dialog, ?wxID_OK), - Cancel = wxButton:new(Dialog, ?wxID_CANCEL), - _ = wxBoxSizer:add(ButtSz, Affirm, zxw:flags(wide)), - _ = wxBoxSizer:add(ButtSz, Cancel, zxw:flags(wide)), - _ = wxSizer:add(Sizer, AddressSz, zxw:flags(wide)), - _ = wxSizer:add(Sizer, ButtSz, zxw:flags(wide)), - ok = wxDialog:setSizer(Dialog, Sizer), - ok = wxBoxSizer:layout(Sizer), - ok = wxDialog:setSize(Dialog, {500, 200}), - ok = wxDialog:center(Dialog), - ok = wxStyledTextCtrl:setFocus(AddressTx), - Choice = - case wxDialog:showModal(Dialog) of - ?wxID_OK -> - case wxTextCtrl:getValue(AddressTx) of - "" -> cancel; - A -> {ok, A} - end; - ?wxID_CANCEL -> - cancel - end, - ok = wxDialog:destroy(Dialog), - case Choice of - {ok, Address} -> open_hash2(State, Address); - cancel -> State - end. - -open_hash2(State, Address) -> - case hz:contract_source(Address) of - {ok, Source} -> - open_hash3(State, Address, Source); - Error -> - ok = handle_troubling(Error, State), - State - end. - -open_hash3(State, Address, Source) -> - % TODO: Compile on load and verify the deployed hash for validity. - case so_compiler:from_string(Source, [{aci, json}]) of - {ok, Build = #{aci := ACI}} -> - {Defs = #{functions := Funs}, ConIfaces} = find_main(ACI), - Callable = lom:delete(name, <<"init">>, Funs), - FunDefs = {maps:put(functions, Callable, Defs), ConIfaces}, - ok = tell(info, "Compilation Succeeded!~n~tp~n~n~tp", [Build, FunDefs]), - add_code_page(State, {hash, Address}, Source); - Other -> - ok = tell(info, "Compilation Failed!~n~tp", [Other]), - State - end. - - -% TODO: Break this down -- tons of things in here recur. -save(State = #s{frame = Frame, j = J, prefs = Prefs, code = {Codebook, Pages}}) -> - case wxNotebook:getSelection(Codebook) of - ?wxNOT_FOUND -> - State; - Index -> - case lists:nth(Index + 1, Pages) of - #p{path = {file, Path}, code = Widget} -> - Source = wxStyledTextCtrl:getText(Widget), - case filelib:ensure_dir(Path) of - ok -> - case file:write_file(Path, Source) of - ok -> - State; - Error -> - ok = handle_troubling(State, Error), - State - end; - Error -> - ok = handle_troubling(State, Error), - State - end; - Page = #p{path = {hash, Hash}, code = Widget} -> - DefaultDir = - case maps:find(dir, Prefs) of - {ok, PrefDir} -> - PrefDir; - error -> - case os:getenv("ZOMP_DIR") of - "" -> file:get_pwd(); - D -> filename:basename(D) - end - end, - Options = - [{message, J("Save Location")}, - {defaultDir, DefaultDir}, - {defaultFile, unicode:characters_to_list([Hash, ".aes"])}, - {wildCard, "*.aes"}, - {style, ?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT}], - Dialog = wxFileDialog:new(Frame, Options), - NewState = - case wxFileDialog:showModal(Dialog) of - ?wxID_OK -> - Dir = wxFileDialog:getDirectory(Dialog), - case wxFileDialog:getFilename(Dialog) of - "" -> - State; - Name -> - File = - case filename:extension(Name) of - ".aes" -> Name; - _ -> Name ++ ".aes" - end, - Path = filename:join(Dir, File), - Source = wxTextCtrl:getValue(Widget), - case filelib:ensure_dir(Path) of - ok -> - case file:write_file(Path, Source) of - ok -> - true = wxNotebook:setPageText(Codebook, Index, File), - NewPrefs = maps:put(dir, Dir, Prefs), - NewPage = Page#p{path = {file, Path}}, - NewPages = store_nth(Index + 1, NewPage, Pages), - NewCode = {Codebook, NewPages}, - State#s{prefs = NewPrefs, code = NewCode}; - Error -> - ok = handle_troubling(State, Error), - State - end; - Error -> - ok = handle_troubling(State, Error), - State - end - end; - ?wxID_CANCEL -> - State - end, - ok = wxFileDialog:destroy(Dialog), - NewState - end - end. - -% TODO: Break this down -- tons of things in here recur. -rename(State = #s{frame = Frame, j = J, prefs = Prefs, code = {Codebook, Pages}}) -> - case wxNotebook:getSelection(Codebook) of - ?wxNOT_FOUND -> - State; - Index -> - case lists:nth(Index + 1, Pages) of - Page = #p{path = {file, Path}, code = Widget} -> - DefaultDir = filename:dirname(Path), - Options = - [{message, J("Save Location")}, - {defaultDir, DefaultDir}, - {defaultFile, filename:basename(Path)}, - {wildCard, "*.aes"}, - {style, ?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT}], - Dialog = wxFileDialog:new(Frame, Options), - NewState = - case wxFileDialog:showModal(Dialog) of - ?wxID_OK -> - Dir = wxFileDialog:getDirectory(Dialog), - case wxFileDialog:getFilename(Dialog) of - "" -> - State; - Name -> - File = - case filename:extension(Name) of - ".aes" -> Name; - _ -> Name ++ ".aes" - end, - NewPath = filename:join(Dir, File), - Source = wxTextCtrl:getValue(Widget), - case filelib:ensure_dir(NewPath) of - ok -> - case file:write_file(NewPath, Source) of - ok -> - true = wxNotebook:setPageText(Codebook, Index, File), - NewPrefs = maps:put(dir, Dir, Prefs), - NewPage = Page#p{path = {file, NewPath}}, - NewPages = store_nth(Index + 1, NewPage, Pages), - NewCode = {Codebook, NewPages}, - State#s{prefs = NewPrefs, code = NewCode}; - Error -> - ok = handle_troubling(State, Error), - State - end; - Error -> - ok = handle_troubling(State, Error), - State - end - end; - ?wxID_CANCEL -> - State - end, - ok = wxFileDialog:destroy(Dialog), - NewState; - #p{path = {hash, _}} -> - save(State) - end - end. - - -close_source(State = #s{code = {Codebook, Pages}}) -> - case wxNotebook:getSelection(Codebook) of - ?wxNOT_FOUND -> - State; - Index -> - NewPages = drop_nth(Index + 1, Pages), - true = wxNotebook:deletePage(Codebook, Index), - State#s{code = {Codebook, NewPages}} - end. - - -load(State = #s{frame = Frame, j = J}) -> - % TODO: Extract the exact compiler version, load it, and use only that or fail if - % the specific version is unavailable. - % TODO: Compile on load and verify the deployed hash for validity. - Dialog = wxDialog:new(Frame, ?wxID_ANY, J("Retrieve Contract Source")), - Sizer = wxBoxSizer:new(?wxVERTICAL), - AddressSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Address Hash")}]), - AddressTx = wxTextCtrl:new(Dialog, ?wxID_ANY), - _ = wxSizer:add(AddressSz, AddressTx, zxw:flags(wide)), - ButtSz = wxBoxSizer:new(?wxHORIZONTAL), - Affirm = wxButton:new(Dialog, ?wxID_OK), - Cancel = wxButton:new(Dialog, ?wxID_CANCEL), - _ = wxBoxSizer:add(ButtSz, Affirm, zxw:flags(wide)), - _ = wxBoxSizer:add(ButtSz, Cancel, zxw:flags(wide)), - _ = wxSizer:add(Sizer, AddressSz, zxw:flags(wide)), - _ = wxSizer:add(Sizer, ButtSz, zxw:flags(wide)), - ok = wxDialog:setSizer(Dialog, Sizer), - ok = wxBoxSizer:layout(Sizer), - ok = wxDialog:setSize(Dialog, {500, 200}), - ok = wxDialog:center(Dialog), - ok = wxStyledTextCtrl:setFocus(AddressTx), - Choice = - case wxDialog:showModal(Dialog) of - ?wxID_OK -> - case wxTextCtrl:getValue(AddressTx) of - "" -> cancel; - A -> {ok, A} - end; - ?wxID_CANCEL -> - cancel - end, - ok = wxDialog:destroy(Dialog), - case Choice of - {ok, Address} -> load2(State, Address); - cancel -> State - end. - -load2(State, Address) -> - case hz:contract_source(Address) of - {ok, Source} -> - load3(State, Address, Source); - Error -> - ok = handle_troubling(Error, State), - State - end. - -load3(State = #s{tabs = TopBook, cons = {Consbook, Pages}, buttons = Buttons, j = J}, - Address, - Source) -> - Window = wxWindow:new(Consbook, ?wxID_ANY), - PageSz = wxBoxSizer:new(?wxVERTICAL), - ProgSz = wxBoxSizer:new(?wxHORIZONTAL), - CodeSz = wxStaticBoxSizer:new(?wxVERTICAL, Window, [{label, J("Contract Source")}]), - CodeTxStyle = {style, ?wxTE_MULTILINE - bor ?wxTE_PROCESS_TAB - bor ?wxTE_DONTWRAP - bor ?wxTE_READONLY}, - CodeTx = wxTextCtrl:new(Window, ?wxID_ANY, [CodeTxStyle]), - Mono = wxFont:new(10, ?wxMODERN, ?wxNORMAL, ?wxNORMAL, [{face, "Monospace"}]), - TextAt = wxTextAttr:new(), - ok = wxTextAttr:setFont(TextAt, Mono), - true = wxTextCtrl:setDefaultStyle(CodeTx, TextAt), - ok = wxTextCtrl:setValue(CodeTx, Source), - _ = wxSizer:add(CodeSz, CodeTx, zxw:flags(wide)), - ScrollWin = wxScrolledWindow:new(Window), - FunSz = wxStaticBoxSizer:new(?wxVERTICAL, ScrollWin, [{label, J("Function Interfaces")}]), - ok = wxWindow:setSizer(ScrollWin, FunSz), - ConsSz = wxStaticBoxSizer:new(?wxVERTICAL, Window, [{label, J("Console")}]), - ConsTxStyle = {style, ?wxTE_MULTILINE bor ?wxTE_READONLY}, - ConsTx = wxTextCtrl:new(Window, ?wxID_ANY, [ConsTxStyle]), - _ = wxSizer:add(ConsSz, ConsTx, zxw:flags(wide)), - _ = wxSizer:add(ProgSz, CodeSz, [{proportion, 3}, {flag, ?wxEXPAND}]), - _ = wxSizer:add(ProgSz, ScrollWin, [{proportion, 1}, {flag, ?wxEXPAND}]), - _ = wxSizer:add(PageSz, ProgSz, [{proportion, 3}, {flag, ?wxEXPAND}]), - _ = wxSizer:add(PageSz, ConsSz, [{proportion, 1}, {flag, ?wxEXPAND}]), - {Out, IFaces, Build, NewButtons} = - case so_compiler:from_string(Source, [{aci, json}]) of - {ok, B = #{aci := ACI}} -> - {#{functions := Fs}, _} = find_main(ACI), - Callable = lom:delete(name, <<"init">>, Fs), - {NB, IFs} = fun_interfaces(ScrollWin, FunSz, Buttons, Callable, J), - O = io_lib:format("Compilation Succeeded!~n~tp~n~nDone!~n", [B]), - {O, IFs, B, NB}; - Other -> - O = io_llib:format("Compilation Failed!~n~tp~n", [Other]), - {O, [], none, Buttons} - end, - ok = wxWindow:setSizer(Window, PageSz), - ok = wxSizer:layout(PageSz), - true = wxNotebook:addPage(Consbook, Window, Address, [{bSelect, true}]), - Page = #c{id = Address, win = Window, - code = CodeTx, cons = ConsTx, - build = Build, funs = {ScrollWin, IFaces}}, - NewPages = Pages ++ [Page], - ok = wxTextCtrl:appendText(ConsTx, Out), - _ = wxNotebook:changeSelection(TopBook, 1), - % TODO: Verify the deployed hash for validity. - State#s{cons = {Consbook, NewPages}, buttons = NewButtons}. - - -get_arg({_, TextCtrl, _}) -> - wxTextCtrl:getValue(TextCtrl). - -find_main(ACI) -> - find_main(ACI, none, []). - -find_main([#{contract := I = #{kind := contract_interface}} | T], M, Is) -> - find_main(T, M, [I | Is]); -find_main([#{contract := M = #{kind := contract_main}} | T], _, Is) -> - find_main(T, M, Is); -find_main([#{namespace := _} | T], M, Is) -> - find_main(T, M, Is); -find_main([C | T], M, Is) -> - ok = tell("Surprising ACI element: ~p", [C]), - find_main(T, M, Is); -find_main([], M, Is) -> - {M, Is}. - -fun_interfaces(ScrollWin, FunSz, Buttons, Funs, J) -> - MakeIface = - fun(#{name := N, arguments := As}) -> - FunName = unicode:characters_to_list([N, "/", integer_to_list(length(As))]), - FN = wxStaticBoxSizer:new(?wxVERTICAL, ScrollWin, [{label, FunName}]), - GridSz = wxFlexGridSizer:new(2, 4, 4), - ok = wxFlexGridSizer:setFlexibleDirection(GridSz, ?wxHORIZONTAL), - ok = wxFlexGridSizer:addGrowableCol(GridSz, 1), - MakeArgField = - fun(#{name := AN, type := T}) -> - Type = - case T of - <<"address">> -> address; - <<"int">> -> integer; - <<"bool">> -> boolean; - L when is_list(L) -> list; % FIXME -% I when is_binary(I) -> iface % FIXME - I when is_binary(I) -> address % FIXME - end, - ANT = wxStaticText:new(ScrollWin, ?wxID_ANY, AN), - TCT = wxTextCtrl:new(ScrollWin, ?wxID_ANY), - _ = wxFlexGridSizer:add(GridSz, ANT, zxw:flags(base)), - _ = wxFlexGridSizer:add(GridSz, TCT, zxw:flags(wide)), - {ANT, TCT, Type} - end, - ArgFields = lists:map(MakeArgField, As), - ButtSz = wxBoxSizer:new(?wxHORIZONTAL), - {CallButton, DryRunButton} = - case N =:= <<"init">> of - false -> - CallBn = wxButton:new(ScrollWin, ?wxID_ANY, [{label, J("Call")}]), - DryRBn = wxButton:new(ScrollWin, ?wxID_ANY, [{label, J("Dry Run")}]), - _ = wxBoxSizer:add(ButtSz, CallBn, zxw:flags(wide)), - _ = wxBoxSizer:add(ButtSz, DryRBn, zxw:flags(wide)), - {#w{name = {N, call}, id = wxButton:getId(CallBn), wx = CallBn}, - #w{name = {N, dryr}, id = wxButton:getId(DryRBn), wx = DryRBn}}; - true -> - Deploy = wxButton:new(ScrollWin, ?wxID_ANY, [{label, J("Deploy")}]), - _ = wxBoxSizer:add(ButtSz, Deploy, zxw:flags(wide)), - {#w{name = {N, call}, id = wxButton:getId(Deploy), wx = Deploy}, - none} - end, - _ = wxStaticBoxSizer:add(FN, GridSz, zxw:flags(wide)), - _ = wxStaticBoxSizer:add(FN, ButtSz, zxw:flags(base)), - _ = wxSizer:add(FunSz, FN, zxw:flags(base)), - #f{name = N, call = CallButton, dryrun = DryRunButton, args = ArgFields} - end, - IFaces = lists:map(MakeIface, Funs), - NewButtons = lists:foldl(fun map_iface_buttons/2, Buttons, IFaces), - {NewButtons, IFaces}. - -button_key_list([#f{call = #w{id = C}, dryrun = #w{id = D}} | T]) -> - [C, D | button_key_list(T)]; -button_key_list([#f{call = #w{id = C}, dryrun = none} | T]) -> - [C | button_key_list(T)]; -button_key_list([]) -> - []. - -map_iface_buttons(#f{call = C = #w{id = CID}, dryrun = D = #w{id = DID}}, A) -> - maps:put(DID, D, maps:put(CID, C, A)); -map_iface_buttons(#f{call = C = #w{id = CID}, dryrun = none}, A) -> - maps:put(CID, C, A). - - -edit(State = #s{cons = {Consbook, Pages}}) -> - case wxNotebook:getSelection(Consbook) of - ?wxNOT_FOUND -> - State; - Index -> - #c{code = CodeTx} = lists:nth(Index + 1, Pages), - Address = wxNotebook:getPageText(Consbook, Index), - Source = wxTextCtrl:getValue(CodeTx), - add_code_page(State, {hash, Address}, Source) - end. - - -close_instance(State = #s{cons = {Consbook, Pages}, buttons = Buttons}) -> - case wxNotebook:getSelection(Consbook) of - ?wxNOT_FOUND -> - State; - Index -> - {#c{funs = {_, IFaces}}, NewPages} = take_nth(Index + 1, Pages), - IDs = list_iface_buttons(IFaces), - NewButtons = maps:without(IDs, Buttons), - true = wxNotebook:deletePage(Consbook, Index), - State#s{cons = {Consbook, NewPages}, buttons = NewButtons} - end. - -list_iface_buttons(IFaces) -> - lists:foldl(fun list_iface_buttons/2, [], IFaces). - -list_iface_buttons(#f{call = #w{id = CID}, dryrun = #w{id = DID}}, A) -> - [CID, DID | A]. - - - -%% (Somewhat silly) Data operations - -store_nth(1, E, [_ | T]) -> [E | T]; -store_nth(N, E, [H | T]) -> [H | store_nth(N - 1, E, T)]. - - -drop_nth(1, [_ | T]) -> T; -drop_nth(N, [H | T]) -> [H | drop_nth(N - 1, T)]. - -take_nth(N, L) -> - take_nth(N, L, []). - -take_nth(1, [E | T], A) -> {E, lists:reverse(A) ++ T}; -take_nth(N, [H | T], A) -> take_nth(N - 1, T, [H | A]). - - -keyfind_index(K, E, L) -> - keyfind_index(K, E, 1, L). - -keyfind_index(K, E, I, [H | T]) -> - case element(E, H) =:= K of - false -> keyfind_index(K, E, I + 1, T); - true -> {ok, I} - end; -keyfind_index(_, _, _, []) -> - error.