Implement JSON->ETerms conversion
... | ... | @@ -10,6 +10,7 @@ |
-license("MIT"). | ||
-behavior(gen_server). | ||
-export([read_file/1, save_file/2]). | ||
-export([start_link/0, stop/0]). | ||
-export([init/1, terminate/2, code_change/3, | ||
handle_call/3, handle_cast/2, handle_info/2]). | ||
... | ... | @@ -28,6 +29,15 @@ |
%% Interface | ||
|
||
read_file(Path) -> | ||
gen_server:call(?MODULE, {read_file, Path}). | ||
save_file(Path, Data) -> | ||
gen_server:call(?MODULE, {save_file, Path, Data}). | ||
-spec stop() -> ok. | ||
stop() -> | ||
... | ... | @@ -55,11 +65,9 @@ start_link() -> |
init(none) -> | ||
ok = log(info, "Starting"), | ||
Window = tg_gui:start_link("Hello, WX!"), | ||
Window = tg_gui:start_link("Termifier GUI"), | ||
ok = log(info, "Window: ~p", [Window]), | ||
State = #s{window = Window}, | ||
ArgV = zx_daemon:argv(), | ||
ok = tg_gui:show(ArgV), | ||
{ok, State}. | ||
... | ... | @@ -80,6 +88,12 @@ init(none) -> |
%% The gen_server:handle_call/3 callback. | ||
%% See: http://erlang.org/doc/man/gen_server.html#Module:handle_call-3 | ||
handle_call({read_file, Path}, _, State) -> | ||
Result = do_read_file(Path), | ||
{reply, Result, State}; | ||
handle_call({save_file, Path, Data}, _, State) -> | ||
Result = do_save_file(Path, Data), | ||
{reply, Result, State}; | ||
|
||
handle_call(Unexpected, From, State) -> | ||
ok = log(warning, "Unexpected call from ~tp: ~tp~n", [From, Unexpected]), | ||
{noreply, State}. | ||
... | ... | @@ -96,7 +110,8 @@ handle_call(Unexpected, From, State) -> |
handle_cast(stop, State) -> | ||
ok = log(info, "Received a 'stop' message."), | ||
% {noreply, State}; | ||
{stop, normal, State}; | ||
ok = zx:silent_stop(), | ||
{noreply, State}; | ||
|
||
handle_cast(Unexpected, State) -> | ||
ok = log(warning, "Unexpected cast: ~tp~n", [Unexpected]), | ||
{noreply, State}. | ||
... | ... | @@ -127,3 +142,11 @@ code_change(_, State, _) -> |
terminate(Reason, State) -> | ||
ok = log(info, "Reason: ~tp, State: ~tp", [Reason, State]), | ||
zx:stop(). | ||
do_read_file(Path) -> | ||
file:read_file(Path). | ||
do_save_file(Path, Data) -> | ||
file:write_file(Path, [Data, "\n"]). | ||
|
... | ... | @@ -13,7 +13,7 @@ |
-behavior(wx_object). | ||
-include_lib("wx/include/wx.hrl"). | ||
-export([show/1]). | ||
-export([json/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]). | ||
... | ... | @@ -21,18 +21,27 @@ |
-record(s, | ||
{frame = none :: none | wx:wx_object(), | ||
text = none :: none | wx:wx_object()}). | ||
{frame = none :: none | wx:wx_object(), | ||
json = none :: none | wx:wx_object(), | ||
eterms = none :: none | wx:wx_object()}). | ||
|
||
-type state() :: term(). | ||
%%% Labels | ||
-define(sysREAD, 10). | ||
-define(sysCONV, 11). | ||
-define(sysSAVE, 12). | ||
|
||
%%% Interface functions | ||
show(Terms) -> | ||
wx_object:cast(?MODULE, {show, Terms}). | ||
json(String) -> | ||
wx_object:cast(?MODULE, {json, String}). | ||
|
||
... | ... | @@ -46,17 +55,44 @@ init(Title) -> |
ok = log(info, "GUI starting..."), | ||
Wx = wx:new(), | ||
Frame = wxFrame:new(Wx, ?wxID_ANY, Title), | ||
MainSz = wxBoxSizer:new(?wxVERTICAL), | ||
TextC = wxTextCtrl:new(Frame, ?wxID_ANY, [{style, ?wxDEFAULT bor ?wxTE_MULTILINE}]), | ||
wxSizer:add(MainSz, TextC, [{flag, ?wxEXPAND}, {proportion, 1}]), | ||
wxFrame:setSizer(Frame, MainSz), | ||
wxSizer:layout(MainSz), | ||
State = build_interface(Frame), | ||
{Frame, State}. | ||
|
||
build_interface(Frame) -> | ||
MainSz = wxBoxSizer:new(?wxVERTICAL), | ||
MenuSz = wxBoxSizer:new(?wxHORIZONTAL), | ||
TextSz = wxBoxSizer:new(?wxHORIZONTAL), | ||
|
||
ReadBn = wxButton:new(Frame, ?sysREAD, [{label, "Read File"}]), | ||
ConvBn = wxButton:new(Frame, ?sysCONV, [{label, "Convert"}]), | ||
SaveBn = wxButton:new(Frame, ?sysSAVE, [{label, "Save File"}]), | ||
JSON = wxStyledTextCtrl:new(Frame), | ||
ETerms = wxStyledTextCtrl:new(Frame), | ||
Mono = wxFont:new(10, ?wxMODERN, ?wxNORMAL, ?wxNORMAL, | ||
[{encoding, ?wxFONTENCODING_UTF8}, {face, "Monospace"}]), | ||
ok = wxStyledTextCtrl:styleSetFont(JSON, ?wxSTC_STYLE_DEFAULT, Mono), | ||
ok = wxStyledTextCtrl:styleSetFont(ETerms, ?wxSTC_STYLE_DEFAULT, Mono), | ||
|
||
_ = wxBoxSizer:add(MenuSz, ReadBn, [{flag, ?wxEXPAND}, {proportion, 1}]), | ||
_ = wxBoxSizer:add(MenuSz, ConvBn, [{flag, ?wxEXPAND}, {proportion, 1}]), | ||
_ = wxBoxSizer:add(MenuSz, SaveBn, [{flag, ?wxEXPAND}, {proportion, 1}]), | ||
_ = wxBoxSizer:add(TextSz, JSON, [{flag, ?wxEXPAND}, {proportion, 1}]), | ||
_ = wxBoxSizer:add(TextSz, ETerms, [{flag, ?wxEXPAND}, {proportion, 1}]), | ||
_ = wxBoxSizer:add(MainSz, MenuSz, [{flag, ?wxEXPAND}, {proportion, 0}]), | ||
_ = wxBoxSizer:add(MainSz, TextSz, [{flag, ?wxEXPAND}, {proportion, 1}]), | ||
ok = wxFrame:setSizer(Frame, MainSz), | ||
ok = wxBoxSizer:layout(MainSz), | ||
|
||
Display = wxDisplay:new(), | ||
{X, Y, W, H} = wxDisplay:getClientArea(Display), | ||
ok = wxDisplay:destroy(Display), | ||
ok = wxFrame:connect(Frame, close_window), | ||
ok = wxFrame:connect(Frame, command_button_clicked), | ||
ok = wxFrame:setSize(Frame, {X, Y, (W div 4) * 3, (H div 4) * 3}), | ||
ok = wxFrame:center(Frame), | ||
true = wxFrame:show(Frame), | ||
State = #s{frame = Frame, text = TextC}, | ||
{Frame, State}. | ||
#s{frame = Frame, json = JSON, eterms = ETerms}. | ||
|
||
-spec handle_call(Message, From, State) -> Result | ||
... | ... | @@ -82,8 +118,8 @@ handle_call(Unexpected, From, State) -> |
%% The gen_server:handle_cast/2 callback. | ||
%% See: http://erlang.org/doc/man/gen_server.html#Module:handle_cast-2 | ||
handle_cast({show, Terms}, State) -> | ||
ok = do_show(Terms, State), | ||
handle_cast({json, String}, State) -> | ||
ok = do_json(String, State), | ||
{noreply, State}; | ||
handle_cast(Unexpected, State) -> | ||
ok = log(warning, "Unexpected cast: ~tp~n", [Unexpected]), | ||
... | ... | @@ -111,6 +147,18 @@ handle_info(Unexpected, State) -> |
%% The wx_object:handle_event/2 callback. | ||
%% See: http://erlang.org/doc/man/gen_server.html#Module:handle_info-2 | ||
handle_event(#wx{id = ID, event = #wxCommand{type = command_button_clicked}}, State) -> | ||
case ID of | ||
?sysREAD -> | ||
NewState = do_read(State), | ||
{noreply, NewState}; | ||
?sysCONV -> | ||
NewState = do_conv(State), | ||
{noreply, NewState}; | ||
?sysSAVE -> | ||
NewState = do_save(State), | ||
{noreply, NewState} | ||
end; | ||
|
||
handle_event(#wx{event = #wxClose{}}, State = #s{frame = Frame}) -> | ||
ok = tg_con:stop(), | ||
ok = wxWindow:destroy(Frame), | ||
... | ... | @@ -131,6 +179,55 @@ terminate(Reason, State) -> |
do_show(Terms, #s{text = TextC}) -> | ||
String = io_lib:format("Received args: ~tp", [Terms]), | ||
wxTextCtrl:changeValue(TextC, String). | ||
do_json(String, #s{json = JSON}) -> | ||
wxStyledTextCtrl:setText(JSON, String). | ||
do_read(State = #s{frame = Frame, json = JSON}) -> | ||
Dialog = wxFileDialog:new(Frame, [{style, ?wxFD_OPEN}]), | ||
_ = wxFileDialog:showModal(Dialog), | ||
Path = wxFileDialog:getPath(Dialog), | ||
ok = wxFileDialog:destroy(Dialog), | ||
ok = | ||
case tg_con:read_file(Path) of | ||
{ok, Bin} -> | ||
Text = unicode:characters_to_list(Bin), | ||
wxStyledTextCtrl:setText(JSON, Text); | ||
{error, eisdir} -> | ||
ok; | ||
{error, enoent} -> | ||
ok | ||
end, | ||
State. | ||
|
||
do_conv(State = #s{json = JSON, eterms = ETerms}) -> | ||
InText = wxStyledTextCtrl:getText(JSON), | ||
OutText = | ||
case zj:decode(InText) of | ||
{ok, Terms} -> format(Terms); | ||
Error -> io_lib:format("ERROR: ~tp", [Error]) | ||
end, | ||
ok = wxStyledTextCtrl:setText(ETerms, OutText), | ||
State. | ||
|
||
format(Terms) when is_list(Terms) -> | ||
Format = fun(Term) -> io_lib:format("~tp.~n", [Term]) end, | ||
lists:map(Format, Terms); | ||
format(Term) -> | ||
format([Term]). | ||
|
||
do_save(State = #s{frame = Frame, eterms = ETerms}) -> | ||
Dialog = wxFileDialog:new(Frame, [{style, ?wxFD_SAVE}]), | ||
_ = wxFileDialog:showModal(Dialog), | ||
ok = | ||
case wxFileDialog:getPath(Dialog) of | ||
[] -> | ||
ok; | ||
Path -> | ||
Data = wxStyledTextCtrl:getText(ETerms), | ||
tg_con:save_file(Path, Data) | ||
end, | ||
ok = wxFileDialog:destroy(Dialog), | ||
State. | ||
|
... | ... | @@ -2,8 +2,9 @@ |
{author,"Craig Everett"}. | ||
{c_email,"zxq9@zxq9.com"}. | ||
{copyright,"Craig Everett"}. | ||
{deps,[]}. | ||
{deps,[{"otpr","zj",{1,0,5}}]}. | ||
{desc,"Create, edit and convert JSON to Erlang terms."}. | ||
{file_exts,[]}. | ||
{key_name,none}. | ||
{license,"MIT"}. | ||
{modules,[]}. | ||
... | ... | @@ -13,4 +14,4 @@ |
{repo_url,[]}. | ||
{tags,[]}. | ||
{type,gui}. | ||
{ws_url,[]}. | ||
{ws_url,"https://gitlab.com/zxq9/termifierg"}. | ||
|