diff --git a/src/gmc_con.erl b/src/gmc_con.erl index 26af293..1753774 100644 --- a/src/gmc_con.erl +++ b/src/gmc_con.erl @@ -1,5 +1,5 @@ %%% @doc -%%% Clutch Controller +%%% GajuDesk Controller %%% @end -module(gmc_con). diff --git a/src/gmc_gui.erl b/src/gmc_gui.erl index 8c92f86..7e2ba22 100644 --- a/src/gmc_gui.erl +++ b/src/gmc_gui.erl @@ -1,5 +1,5 @@ %%% @doc -%%% Clutch GUI +%%% GajuDesk GUI %%% @end -module(gmc_gui). @@ -58,8 +58,8 @@ chain(ChainID, Node) -> wx_object:cast(?MODULE, {chain, ChainID, Node}). -trouble(Message) -> - wx_object:cast(?MODULE, {trouble, Message}). +trouble(Info) -> + wx_object:cast(?MODULE, {trouble, Info}). ask_password() -> @@ -235,7 +235,7 @@ handle_cast({chain, ChainID, Node}, State) -> ok = do_chain(ChainID, Node, State), {noreply, State}; handle_cast({trouble, Info}, State) -> - ok = handle_trouble(Info, State), + ok = handle_troubling(State, Info), {noreply, State}; handle_cast(password, State) -> ok = do_ask_password(State), @@ -317,6 +317,10 @@ handle_event(Event, State) -> {noreply, State}. +handle_troubling(#s{frame = Frame}, Info) -> + zxw:show_message(Frame, Info). + + code_change(_, State, _) -> {ok, State}. @@ -922,10 +926,6 @@ do_chain(ChainID, #node{ip = IP}, #s{buttons = Buttons}) -> ok = wxButton:setLabel(NodeB, Address). -handle_trouble(Info, #s{frame = Frame}) -> - zxw:show_message(Frame, Info). - - do_ask_password(#s{frame = Frame, prefs = Prefs, j = J}) -> Dialog = wxDialog:new(Frame, ?wxID_ANY, J("Password")), Sizer = wxBoxSizer:new(?wxVERTICAL), diff --git a/src/gmc_v_devman.erl b/src/gmc_v_devman.erl index 38439fa..30ed377 100644 --- a/src/gmc_v_devman.erl +++ b/src/gmc_v_devman.erl @@ -1,6 +1,6 @@ -module(gmc_v_devman). -vsn("0.2.0"). --author("Craig Everett "). +-author("Craig Everett "). -copyright("QPQ AG "). -license("GPL-3.0-or-later"). @@ -8,35 +8,41 @@ %-behavior(gmc_v). -include_lib("wx/include/wx.hrl"). -export([to_front/1]). --export([set_manifest/1]). +-export([set_manifest/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:filename(), + {path = {file, ""} :: {file, file:filename()} | {hash, binary()}, + win = none :: none | wx:wx_object(), + code = none :: none | wxStyledTextCtrl:wxStyledTextCtrl()}). + +% Contract pages +-record(c, + {id = "" :: string(), win = none :: none | wx:wx_object(), code = none :: none | wxStyledTextCtrl:wxStyledTextCtrl(), cons = none :: none | wxStyledTextCtrl:wxStyledTextCtrl(), - side_sz = none :: none | wx:wx_object(), - instances = none :: none | wx:wx_object(), - funs = {#w{}, []} :: {#w{}, [#f{}]}, - builds = #{} :: #{InstanceID :: binary() := Build :: map()}}). + funs = {#w{}, []} :: {#w{}, [#f{}]}}). +% State -record(s, {wx = none :: none | wx:wx_object(), frame = none :: none | wx:wx_object(), @@ -44,7 +50,9 @@ j = none :: none | fun(), prefs = #{} :: map(), buttons = #{} :: #{WX_ID :: integer() := #w{}}, - book = {none, []} :: {Notebook :: none | wx:wx_object(), Pages :: [#p{}]}}). + 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()). @@ -67,6 +75,13 @@ set_manifest(Entries) -> end. +-spec trouble(Info) -> ok + when Info :: term(). + +trouble(Info) -> + wx_object:cast(?MODULE, {trouble, Info}). + + %%% Startup Functions @@ -83,133 +98,84 @@ init({Prefs, Manifest}) -> Frame = wxFrame:new(Wx, ?wxID_ANY, J("Contracts")), MainSz = wxBoxSizer:new(?wxVERTICAL), - ButtSz = wxBoxSizer:new(?wxHORIZONTAL), - - ButtonTemplates = - [{new, J("New")}, - {open, J("Open")}, - {save, J("Save")}, - {saven, J("Save (new name)")}, - {compile, J("Compile")}, - {close, J("Close")}], - - 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), - - AddButton = fun(#w{wx = B}) -> wxSizer:add(ButtSz, B, zxw:flags(wide)) end, - - Notebook = wxNotebook:new(Frame, ?wxID_ANY, [{style, ?wxBK_DEFAULT}]), - - ok = lists:foreach(AddButton, Buttons), - _ = wxSizer:add(MainSz, ButtSz, zxw:flags(base)), - _ = wxSizer:add(MainSz, Notebook, zxw:flags(wide)), + 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), - - MapButton = fun(B = #w{id = I}, M) -> maps:put(I, B, M) end, - ButtonMap = lists:foldl(MapButton, #{}, Buttons), - State = - #s{wx = Wx, frame = Frame, - j = J, prefs = Prefs, - buttons = ButtonMap, book = {Notebook, []}}, - NewState = add_pages(State, Manifest), + NewState = add_code_pages(State, Manifest), {Frame, NewState}. -add_pages(State, Files) -> - lists:foldl(fun add_page/2, State, Files). +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}. -add_page(State = #s{book = {Notebook, Pages}}, File) -> - case keyfind_index(File, #p.path, Pages) of - error -> - add_page2(State, File); - {ok, Index} -> - _ = wxNotebook:setSelection(Notebook, Index - 1), - State - end. +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_page2(State = #s{j = J}, File) -> - case file:read_file(File) of - {ok, Bin} -> - case unicode:characters_to_list(Bin) of - Code when is_list(Code) -> - add_page(State, File, Code); - Error -> - Message = io_lib:format(J("Opening ~p failed with: ~p"), [File, Error]), - ok = gmc_gui:trouble(Message), - State - end; - {error, Reason} -> - Message = io_lib:format(J("Opening ~p failed with: ~p"), [File, Reason]), - ok = gmc_gui:trouble(Message), - State - end. -add_page(State = #s{j = J, book = {Notebook, Pages}}, File, 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(Notebook, ?wxID_ANY), - PageSz = wxBoxSizer:new(?wxHORIZONTAL), - ProgSz = wxBoxSizer:new(?wxVERTICAL), - CodeSz = wxStaticBoxSizer:new(?wxVERTICAL, Window, [{label, J("Code")}]), - ConsSz = wxStaticBoxSizer:new(?wxVERTICAL, Window, [{label, J("Console")}]), - CodeTxStyle = {style, ?wxTE_MULTILINE bor ?wxTE_PROCESS_TAB bor ?wxTE_DONTWRAP}, - CodeTx = wxTextCtrl:new(Window, ?wxID_ANY, [CodeTxStyle]), - ConsTxStyle = {style, ?wxTE_MULTILINE bor ?wxTE_READONLY}, - ConsTx = wxTextCtrl:new(Window, ?wxID_ANY, [ConsTxStyle]), - 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, Code), - - _ = wxSizer:add(CodeSz, CodeTx, zxw:flags(wide)), - _ = wxSizer:add(ConsSz, ConsTx, zxw:flags(wide)), - _ = wxSizer:add(ProgSz, CodeSz, [{proportion, 5}, {flag, ?wxEXPAND}]), - _ = wxSizer:add(ProgSz, ConsSz, [{proportion, 1}, {flag, ?wxEXPAND}]), - - SideSz = wxBoxSizer:new(?wxVERTICAL), - - InstanceSz = wxStaticBoxSizer:new(?wxVERTICAL, Window, [{label, J("Instances")}]), - Instances = wxChoice:new(Window, ?wxID_ANY, [{choices, []}]), - _ = wxSizer:add(InstanceSz, Instances, zxw:flags(wide)), - - ScrollWin = wxScrolledWindow:new(Window), - FunSz = wxStaticBoxSizer:new(?wxVERTICAL, ScrollWin, [{label, J("Function Interfaces")}]), - ok = wxWindow:setSizer(ScrollWin, FunSz), - _ = wxSizer:add(SideSz, InstanceSz, zxw:flags(base)), - _ = wxSizer:add(SideSz, ScrollWin, zxw:flags(wide)), - - _ = wxSizer:add(PageSz, ProgSz, [{proportion, 2}, {flag, ?wxEXPAND}]), - _ = wxSizer:add(PageSz, SideSz, [{proportion, 1}, {flag, ?wxEXPAND}]), - - ok = wxWindow:setSizer(Window, PageSz), - ok = wxSizer:layout(PageSz), - FileName = filename:basename(File), - true = wxNotebook:addPage(Notebook, Window, FileName, [{bSelect, true}]), - Page = #p{path = File, win = Window, - code = CodeTx, cons = ConsTx, - side_sz = SideSz, instances = Instances, - funs = {ScrollWin, []}}, - NewPages = Pages ++ [Page], - State#s{book = {Notebook, NewPages}}. +add_code_pages(State, Files) -> + lists:foldl(fun add_code_page/2, State, Files). @@ -223,6 +189,9 @@ handle_call(Unexpected, From, State) -> handle_cast(to_front, State = #s{frame = Frame}) -> ok = wxFrame:raise(Frame), {noreply, State}; +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}. @@ -239,9 +208,14 @@ handle_event(E = #wx{event = #wxCommand{type = command_button_clicked}, NewState = case maps:get(ID, Buttons, undefined) of #w{name = new} -> new_file(State); - #w{name = open} -> open_file(State); - #w{name = close} -> close_file(State); - #w{name = compile} -> compile(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]), @@ -267,6 +241,10 @@ handle_event(Event, State) -> {noreply, State}. +handle_troubling(#s{frame = Frame}, Info) -> + zxw:show_message(Frame, Info). + + code_change(_, State, _) -> {ok, State}. @@ -279,44 +257,86 @@ terminate(Reason, State) -> %%% Doers -clicked(State = #s{book = {Notebook, Pages}}, Name, Button) -> - case wxNotebook:getSelection(Notebook) of +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 -> - Page = lists:nth(Index + 1, Pages), - clicked(State, Page, Name, Button) + Contract = lists:nth(Index + 1, Contracts), + clicked(State, Contract, Name, Button) end. -clicked(State, - #p{instances = Is, funs = {_, Funs}, builds = Builds}, - {<<"init">>, call}, - _Button) -> - Label = wxChoice:getStringSelection(Is), - Build = maps:get(Label, Builds), - #f{args = Args} = lists:keyfind(<<"init">>, #f.name, Funs), - InitArgs = lists:map(fun get_arg/1, Args), - ok = tell("Label: ~p~nArgs: ~p~nInitArgs: ~p", [Label, Args, InitArgs]), - case gmc_con:deploy(Build, InitArgs) of - {contract_id, ConID} -> - ok = tell("Got ConID: ~p", [ConID]), - State; - {tx_hash, TX_Hash} -> - ok = tell("Got TX_Hash: ~p", [TX_Hash]), - State; - {error, Reason} -> - ok = tell("Deploy failed with: ~p", [Reason]), - State - end; -clicked(State, Page, Name, Button) -> - ok = tell("Button: ~p ~p~nPage: ~p", [Button, Name, Page]), +clicked(State, Contract, Name, Button) -> + ok = tell("Button: ~p ~p~nContract: ~p", [Button, Name, Contract]), State. get_arg({_, TextCtrl, _}) -> wxTextCtrl:getValue(TextCtrl). +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{j = J, 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 @@ -351,7 +371,7 @@ new_file(State = #s{frame = Frame, j = J, prefs = Prefs}) -> Path = filename:join(Dir, File), NewPrefs = maps:put(dir, Dir, Prefs), NextState = State#s{prefs = NewPrefs}, - add_page(NextState, Path, "") + add_code_page(NextState, {file, Path}, "") end; ?wxID_CANCEL -> State @@ -360,44 +380,74 @@ new_file(State = #s{frame = Frame, j = J, prefs = Prefs}) -> NewState. -compile(State = #s{book = {Notebook, Pages}}) -> - case wxNotebook:getSelection(Notebook) of +deploy(State = #s{code = {Codebook, Pages}}) -> + case wxNotebook:getSelection(Codebook) of ?wxNOT_FOUND -> State; Index -> Page = #p{code = CodeTx} = lists:nth(Index + 1, Pages), Source = wxTextCtrl:getValue(CodeTx), - compile(State, Page, Source) + deploy2(State, Source) end. -compile(State = #s{book = {Notebook, Pages}, j = J, buttons = Buttons}, - Page = #p{path = Path, win = Win, cons = ConsTx, - side_sz = SideSz, instances = Instances, funs = Funs, builds = Builds}, - Source) -> +deploy2(State, Source) -> case aeso_compiler:from_string(Source, [{aci, json}]) of {ok, Build = #{aci := ACI}} -> - BuildName = build_name(Path), - I = wxChoice:append(Instances, BuildName), - ok = wxChoice:select(Instances, I), - NewBuilds = maps:put(BuildName, Build, Builds), - FunDefs = find_main(ACI), - UpdateInterfaces = fun() -> fun_interfaces(Win, Buttons, Funs, FunDefs, J) end, - {NewButtons, NewFuns} = wx:batch(UpdateInterfaces), - ScrollWin = element(1, NewFuns), - _ = wxSizer:add(SideSz, ScrollWin, zxw:flags(wide)), - ok = wxSizer:layout(SideSz), - NewPage = Page#p{funs = NewFuns, builds = NewBuilds}, - NewPages = lists:keystore(Path, #p.path, Pages, NewPage), - State#s{book = {Notebook, NewPages}, buttons = NewButtons}; + FunDefs = {#{functions := Funs}, _} = find_main(ACI), + Init = lom:find(name, <<"init">>, Funs), + ok = tell(info, "Compilation Succeeded!~n~tp~n~n~tp", [Build, FunDefs]), + deploy3(State, Init); Other -> - ok = wxTextCtrl:setValue(ConsTx, io_lib:format("~tp", [Other])), + ok = tell(info, "Compilation Failed!~n~tp", [Other]), State end. -build_name(Path) -> - File = filename:basename(Path, ".aes"), - TS = integer_to_list(os:system_time(millisecond)), - unicode:characters_to_list([File, "-", TS]). +deploy3(State = #s{frame = Frame, j = J}, #{arguments := As}) -> + 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), + ButtSz = wxDialog:createButtonSizer(Dialog, ?wxOK bor ?wxCANCEL), + 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, zxw:flags(wide)), + _ = wxSizer:add(Sizer, ButtSz), + ok = wxDialog:setSizer(Dialog, Sizer), + ok = wxBoxSizer:layout(Sizer), + ok = wxDialog:setSize(Dialog, {500, 300}), + ok = wxDialog:center(Dialog), + ok = + case wxDialog:showModal(Dialog) of + ?wxID_OK -> + tell(info, "DEPLOYING!"); + ?wxID_CANCEL -> + ok + end, + ok = wxDialog:destroy(Dialog), + State. find_main(ACI) -> find_main(ACI, none, []). @@ -495,6 +545,51 @@ map_iface_buttons(#f{call = C = #w{id = CID}, dryrun = none}, A) -> maps:put(CID, C, A). +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 @@ -528,7 +623,7 @@ open_file(State = #s{frame = Frame, j = J, prefs = Prefs}) -> Path = filename:join(Dir, File), NewPrefs = maps:put(dir, Dir, Prefs), NextState = State#s{prefs = NewPrefs}, - add_page(NextState, Path) + add_code_page(NextState, {file, Path}) end; ?wxID_CANCEL -> State @@ -537,17 +632,339 @@ open_file(State = #s{frame = Frame, j = J, prefs = Prefs}) -> NewState. -close_file(State = #s{book = {Notebook, Pages}}) -> - case wxNotebook:getSelection(Notebook) of +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 aeso_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(Notebook, Index), - State#s{book = {Notebook, NewPages}} + 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, Address, Source) -> + % TODO: Compile on load and verify the deployed hash for validity. + case aeso_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_instance_page(State, Address, Source); + Other -> + ok = tell(info, "Compilation Failed!~n~tp", [Other]), + State + end. + +add_instance_page(State = #s{cons = {Consbook, Pages}, 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}]), + + 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, + funs = {ScrollWin, []}}, + NewPages = Pages ++ [Page], + State#s{cons = {Consbook, NewPages}}. + + +edit(State) -> + ok = tell(info, "EDIT clicked"), + State. + + +close_instance(State) -> + ok = tell(info, "CLOSE_INSTANCE clicked"), + State. + + + +%% (Somewhat silly) Data operations + +store_nth(1, E, [_ | T]) -> [E | T]; +store_nth(N, E, [H | T]) -> [H | store_nth(N - 1, T, E)]. + + drop_nth(1, [_ | T]) -> T; drop_nth(N, [H | T]) -> [H | drop_nth(N - 1, T)]. diff --git a/src/gmc_v_wallman.erl b/src/gmc_v_wallman.erl index 0ad83e2..03dcee5 100644 --- a/src/gmc_v_wallman.erl +++ b/src/gmc_v_wallman.erl @@ -273,7 +273,7 @@ do_new(State = #s{frame = Frame, j = J, prefs = Prefs}) -> end. do_new2(Path, J, Frame) -> - Dialog = wxDialog:new(Frame, ?wxID_ANY, J("Set Node"), [{size, {400, 250}}]), + Dialog = wxDialog:new(Frame, ?wxID_ANY, J("New Wallet"), [{size, {400, 250}}]), Sizer = wxBoxSizer:new(?wxVERTICAL), NameSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Name")}]), @@ -360,7 +360,7 @@ do_import2(_, "", _, _) -> abort; do_import2(Dir, File, J, Frame) -> Path = filename:join(Dir, File), - Dialog = wxDialog:new(Frame, ?wxID_ANY, J("Set Node")), + Dialog = wxDialog:new(Frame, ?wxID_ANY, J("Import Wallet")), Sizer = wxBoxSizer:new(?wxVERTICAL), NameSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Name")}]), diff --git a/zomp.meta b/zomp.meta index 254046e..7a03bb0 100644 --- a/zomp.meta +++ b/zomp.meta @@ -2,10 +2,11 @@ {type,gui}. {modules,[]}. {prefix,"gmc"}. -{author,"Craig Everett"}. {desc,"A desktop client for the Gajumaru network of blockchain networks"}. +{author,"Craig Everett"}. {package_id,{"otpr","clutch",{0,2,0}}}. -{deps,[{"otpr","hakuzaru",{0,2,0}}, +{deps,[{"otpr","lom",{1,0,0}}, + {"otpr","hakuzaru",{0,2,0}}, {"otpr","aesophia",{8,0,1}}, {"otpr","aeserialization",{0,1,2}}, {"otpr","zj",{1,1,0}},