1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2009-2017. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19
20%% This is example of the widgets and usage of wxErlang
21%% Hopefully it will contain all implemented widgets, it's event handling
22%% and some tutorials of how to use sizers and other stuff.
23
24-module(demo).
25
26-include_lib("wx/include/wx.hrl").
27
28-behaviour(wx_object).
29-export([start/0, start/1, start_link/0, start_link/1, format/3,
30	 init/1, terminate/2,  code_change/3,
31	 handle_info/2, handle_call/3, handle_cast/2, handle_event/2]).
32
33
34-record(state, {win, demo, example, selector, log, code}).
35
36start() ->
37    start([]).
38
39start(Debug) ->
40    wx_object:start(?MODULE, Debug, []).
41
42start_link() ->
43    start_link([]).
44
45start_link(Debug) ->
46    wx_object:start_link(?MODULE, Debug, []).
47
48format(#state{log=Log}, Str, Args) ->
49    wxTextCtrl:appendText(Log, io_lib:format(Str, Args)),
50    ok;
51format(Config,Str,Args) ->
52    Log = proplists:get_value(log, Config),
53    wxTextCtrl:appendText(Log, io_lib:format(Str, Args)),
54    ok.
55
56
57-define(DEBUG_NONE, 101).
58-define(DEBUG_VERBOSE, 102).
59-define(DEBUG_TRACE, 103).
60-define(DEBUG_DRIVER, 104).
61
62init(Options) ->
63    wx:new(Options),
64    process_flag(trap_exit, true),
65
66    Frame = wxFrame:new(wx:null(), ?wxID_ANY, "wxErlang widgets", [{size,{1400,800}}]),
67    MB = wxMenuBar:new(),
68    File    = wxMenu:new([]),
69    wxMenu:append(File, ?wxID_PRINT, "&Print code"),
70    wxMenu:appendSeparator(File),
71    wxMenu:append(File, ?wxID_EXIT, "&Quit"),
72    Debug    = wxMenu:new([]),
73    wxMenu:appendRadioItem(Debug, ?DEBUG_NONE, "None"),
74    wxMenu:appendRadioItem(Debug, ?DEBUG_VERBOSE, "Verbose"),
75    wxMenu:appendRadioItem(Debug, ?DEBUG_TRACE, "Trace"),
76    wxMenu:appendRadioItem(Debug, ?DEBUG_DRIVER, "Driver"),
77    Help    = wxMenu:new([]),
78    wxMenu:append(Help, ?wxID_HELP, "Help"),
79    wxMenu:append(Help, ?wxID_ABOUT, "About"),
80    wxMenuBar:append(MB, File, "&File"),
81    wxMenuBar:append(MB, Debug, "&Debug"),
82    wxMenuBar:append(MB, Help, "&Help"),
83    wxFrame:setMenuBar(Frame,MB),
84
85    wxFrame:connect(Frame, command_menu_selected),
86    wxFrame:connect(Frame, close_window),
87
88    _SB = wxFrame:createStatusBar(Frame,[]),
89
90    %% Setup on toplevel because stc seems to steal this on linux
91    wxFrame:dragAcceptFiles(Frame, true),
92    wxFrame:connect(Frame, drop_files),
93
94    %%   T        Uppersplitter
95    %%   O        Left   |    Right
96    %%   P  Widgets|Code |    Demo
97    %%   S  -------------------------------
98    %%   P          Log Window
99    TopSplitter   = wxSplitterWindow:new(Frame, [{style, ?wxSP_NOBORDER}]),
100    UpperSplitter = wxSplitterWindow:new(TopSplitter, [{style, ?wxSP_NOBORDER}]),
101    LeftSplitter  = wxSplitterWindow:new(UpperSplitter, [{style, ?wxSP_NOBORDER}]),
102    %% Setup so that sizers and initial sizes, resizes the windows correct
103    wxSplitterWindow:setSashGravity(TopSplitter,   0.5),
104    wxSplitterWindow:setSashGravity(UpperSplitter, 0.60),
105    wxSplitterWindow:setSashGravity(LeftSplitter,  0.20),
106
107    %% LeftSplitter:
108    Example = fun(Beam) ->
109		      "ex_" ++ F = filename:rootname(Beam),
110		      F
111	      end,
112    Mods = [Example(F) || F <- filelib:wildcard("ex_*.beam")],
113
114    CreateLB = fun(Parent) ->
115		       wxListBox:new(Parent, ?wxID_ANY,
116				     [{style, ?wxLB_SINGLE},
117				      {choices, Mods}])
118	       end,
119    {LBPanel, [LB],_} = create_subwindow(LeftSplitter, "Example", [CreateLB]),
120    wxListBox:setSelection(LB, 0),
121    wxListBox:connect(LB, command_listbox_selected),
122
123    CreateCode = fun(Parent) ->
124			 code_area(Parent)
125		 end,
126    {CodePanel, [Code],_} = create_subwindow(LeftSplitter, "Code", [CreateCode]),
127
128    wxSplitterWindow:splitVertically(LeftSplitter, LBPanel, CodePanel,
129				     [{sashPosition,150}]),
130
131    %% Demo:
132    {DemoPanel, [], DemoSz} = create_subwindow(UpperSplitter, "Demo", []),
133
134    %% UpperSplitter:
135    wxSplitterWindow:splitVertically(UpperSplitter, LeftSplitter, DemoPanel,
136				     [{sashPosition,600}]),
137
138    %% TopSplitter:
139    AddEvent = fun(Parent) ->
140		       EventText = wxTextCtrl:new(Parent,
141						  ?wxID_ANY,
142						  [{style, ?wxTE_DONTWRAP bor
143						    ?wxTE_MULTILINE bor ?wxTE_READONLY}
144						  ]),
145		       wxTextCtrl:appendText(EventText, "Welcome\n"),
146		       EventText
147	       end,
148
149    {EvPanel, [EvCtrl],_} = create_subwindow(TopSplitter, "Events", [AddEvent]),
150
151    wxSplitterWindow:splitHorizontally(TopSplitter, UpperSplitter, EvPanel,
152				       [{sashPosition,-100}]),
153
154    wxFrame:show(Frame),
155
156    State = #state{win=Frame, demo={DemoPanel,DemoSz}, selector=LB, log=EvCtrl, code=Code},
157    %% Load the first example:
158    Ex = wxListBox:getStringSelection(LB),
159    process_flag(trap_exit, true),
160    ExampleObj = load_example(Ex, State),
161    wxSizer:add(DemoSz, ExampleObj, [{proportion,1}, {flag, ?wxEXPAND}]),
162    wxSizer:layout(DemoSz),
163
164    %% The windows should be set up now, Reset Gravity so we get what we want
165    wxSplitterWindow:setSashGravity(TopSplitter,   1.0),
166    wxSplitterWindow:setSashGravity(UpperSplitter, 0.0),
167    wxSplitterWindow:setSashGravity(LeftSplitter,  0.0),
168    wxSplitterWindow:setMinimumPaneSize(TopSplitter, 1),
169    wxSplitterWindow:setMinimumPaneSize(UpperSplitter, 1),
170    wxSplitterWindow:setMinimumPaneSize(LeftSplitter, 1),
171
172    wxToolTip:enable(true),
173    wxToolTip:setDelay(500),
174
175    {Frame, State#state{example=ExampleObj}}.
176
177create_subwindow(Parent, BoxLabel, Funs) ->
178    Panel = wxPanel:new(Parent),
179    Sz    = wxStaticBoxSizer:new(?wxVERTICAL, Panel, [{label, BoxLabel}]),
180    wxPanel:setSizer(Panel, Sz),
181    Ctrls = [Fun(Panel) || Fun <- Funs],
182    [wxSizer:add(Sz, Ctrl, [{proportion, 1}, {flag, ?wxEXPAND}])
183     || Ctrl <- Ctrls],
184    {Panel, Ctrls, Sz}.
185
186%%%%%%%%%%%%
187%% Callbacks
188
189%% Handled as in normal gen_server callbacks
190handle_info({'EXIT',_, wx_deleted}, State) ->
191    {noreply,State};
192handle_info({'EXIT',_, shutdown}, State) ->
193    {noreply,State};
194handle_info({'EXIT',_, normal}, State) ->
195    {noreply,State};
196handle_info(Msg, State) ->
197    format(State, "Got Info ~p~n",[Msg]),
198    {noreply,State}.
199
200handle_call(Msg, _From, State) ->
201    format(State, "Got Call ~p~n",[Msg]),
202    {reply,ok,State}.
203
204handle_cast(Msg, State) ->
205    format(State, "Got cast ~p~n",[Msg]),
206    {noreply,State}.
207
208%% Async Events are handled in handle_event as in handle_info
209handle_event(#wx{event=#wxCommand{type=command_listbox_selected, cmdString=Ex}},
210	     State = #state{demo={_,DemoSz}, example=Example, code=Code}) ->
211    case Ex of
212	[] ->
213	    {noreply, State};
214	_  ->
215	    wxSizer:detach(DemoSz, Example),
216	    wx_object:call(Example, shutdown),
217	    unload_code(Code),
218	    NewExample = load_example(Ex, State),
219	    wxSizer:add(DemoSz, NewExample, [{proportion,1}, {flag, ?wxEXPAND}]),
220	    wxSizer:layout(DemoSz),
221	    {noreply, State#state{example=NewExample}}
222    end;
223handle_event(#wx{id = Id,
224		 event = #wxCommand{type = command_menu_selected}},
225	     State = #state{}) ->
226    case Id of
227	?wxID_PRINT ->
228	    %% If you are going to printout mainly text it is easier if
229	    %% you generate HTML code and use a wxHtmlEasyPrint
230	    %% instead of using DCs
231
232            %% Printpreview doesn't work in >2.9 without this
233            wxIdleEvent:setMode(?wxIDLE_PROCESS_ALL),
234	    Module = "ex_" ++ wxListBox:getStringSelection(State#state.selector) ++ ".erl",
235	    HEP = wxHtmlEasyPrinting:new([{name, "Print"},
236					  {parentWindow, State#state.win}]),
237	    Html = demo_html_tagger:erl2htmltext(Module),
238	    wxHtmlEasyPrinting:previewText(HEP, Html),
239	    {noreply, State};
240	?DEBUG_TRACE ->
241	    wx:debug(trace),
242	    {noreply, State};
243	?DEBUG_DRIVER ->
244	    wx:debug(driver),
245	    {noreply, State};
246	?DEBUG_VERBOSE ->
247	    wx:debug(verbose),
248	    {noreply, State};
249	?DEBUG_NONE ->
250	    wx:debug(none),
251	    {noreply, State};
252	?wxID_HELP ->
253	    wx_misc:launchDefaultBrowser("http://www.erlang.org/doc/apps/wx/part_frame.html"),
254	    {noreply, State};
255	?wxID_ABOUT ->
256	    WxWVer = io_lib:format("~p.~p.~p.~p",
257				   [?wxMAJOR_VERSION, ?wxMINOR_VERSION,
258				    ?wxRELEASE_NUMBER, ?wxSUBRELEASE_NUMBER]),
259	    application:load(wx),
260	    {ok, WxVsn} = application:get_key(wx,  vsn),
261	    AboutString =
262		"Demo of various widgets\n"
263		"Authors: Olle & Dan\n\n" ++
264		"Frontend: wx-" ++ WxVsn ++
265		"\nBackend: wxWidgets-" ++ lists:flatten(WxWVer),
266
267	    wxMessageDialog:showModal(wxMessageDialog:new(State#state.win, AboutString,
268							  [{style,
269							    ?wxOK bor
270							    ?wxICON_INFORMATION bor
271							    ?wxSTAY_ON_TOP},
272							   {caption, "About"}])),
273	    {noreply, State};
274	?wxID_EXIT ->
275	    wx_object:call(State#state.example, shutdown),
276	    {stop, normal, State};
277	_ ->
278	    {noreply, State}
279    end;
280handle_event(#wx{event=#wxClose{}}, State = #state{win=Frame}) ->
281    io:format("~p Closing window ~n",[self()]),
282    ok = wxFrame:setStatusText(Frame, "Closing...",[]),
283    {stop, normal, State};
284handle_event(Ev,State) ->
285    format(State, "~p Got event ~p ~n",[?MODULE, Ev]),
286    {noreply, State}.
287
288code_change(_, _, State) ->
289    {stop, not_yet_implemented, State}.
290
291terminate(_Reason, State = #state{win=Frame}) ->
292    catch wx_object:call(State#state.example, shutdown),
293    wxFrame:destroy(Frame),
294    wx:destroy().
295
296%%%%%%%%%%%%%%%%% Internals %%%%%%%%%%
297
298load_example(Ex, #state{demo={DemoPanel,DemoSz}, log=EvCtrl, code=Code}) ->
299    ModStr = "ex_" ++ Ex,
300    Mod = list_to_atom(ModStr),
301    ModFile = ModStr ++ ".erl",
302    load_code(Code, file:read_file(ModFile)),
303    find(Code),
304    Mod:start([{parent, DemoPanel}, {demo_sz, DemoSz}, {log, EvCtrl}]).
305
306-define(stc, wxStyledTextCtrl).
307
308code_area(Parent) ->
309    FixedFont = wxFont:new(10, ?wxFONTFAMILY_TELETYPE, ?wxNORMAL, ?wxNORMAL,[]),
310    Ed = wxStyledTextCtrl:new(Parent),
311
312    ?stc:styleClearAll(Ed),
313    ?stc:styleSetFont(Ed, ?wxSTC_STYLE_DEFAULT, FixedFont),
314    ?stc:setLexer(Ed, ?wxSTC_LEX_ERLANG),
315    ?stc:setMarginType(Ed, 0, ?wxSTC_MARGIN_NUMBER),
316    LW = ?stc:textWidth(Ed, ?wxSTC_STYLE_LINENUMBER, "9"),
317    ?stc:setMarginWidth(Ed, 0, LW),
318    ?stc:setMarginWidth(Ed, 1, 0),
319
320    ?stc:setSelectionMode(Ed, ?wxSTC_SEL_LINES),
321    %%?stc:hideSelection(Ed, true),
322
323    Styles =  [{?wxSTC_ERLANG_DEFAULT,  {0,0,0}},
324	       {?wxSTC_ERLANG_COMMENT,  {160,53,35}},
325	       {?wxSTC_ERLANG_VARIABLE, {150,100,40}},
326	       {?wxSTC_ERLANG_NUMBER,   {5,5,100}},
327	       {?wxSTC_ERLANG_KEYWORD,  {130,40,172}},
328	       {?wxSTC_ERLANG_STRING,   {170,45,132}},
329	       {?wxSTC_ERLANG_OPERATOR, {30,0,0}},
330	       {?wxSTC_ERLANG_ATOM,     {0,0,0}},
331	       {?wxSTC_ERLANG_FUNCTION_NAME, {64,102,244}},
332	       {?wxSTC_ERLANG_CHARACTER,{236,155,172}},
333	       {?wxSTC_ERLANG_MACRO,    {40,144,170}},
334	       {?wxSTC_ERLANG_RECORD,   {40,100,20}},
335	       {?wxSTC_ERLANG_NODE_NAME,{0,0,0}},
336	       %% Optional 2.9 stuff
337	       {?wxSTC_ERLANG_COMMENT_FUNCTION, {160,53,35}},
338	       {?wxSTC_ERLANG_COMMENT_MODULE, {160,53,35}},
339	       {?wxSTC_ERLANG_COMMENT_DOC, {160,53,35}},
340	       {?wxSTC_ERLANG_COMMENT_DOC_MACRO, {160,53,35}},
341	       {?wxSTC_ERLANG_ATOM_QUOTED, {0,0,0}},
342	       {?wxSTC_ERLANG_MACRO_QUOTED, {40,144,170}},
343	       {?wxSTC_ERLANG_RECORD_QUOTED, {40,100,20}},
344	       {?wxSTC_ERLANG_NODE_NAME_QUOTED, {0,0,0}},
345	       {?wxSTC_ERLANG_BIFS, {130,40,172}},
346	       {?wxSTC_ERLANG_MODULES, {64,102,244}},
347	       {?wxSTC_ERLANG_MODULES_ATT, {64,102,244}}
348	      ],
349    SetStyle = fun({Style, Color}) ->
350		       ?stc:styleSetFont(Ed, Style, FixedFont),
351		       ?stc:styleSetForeground(Ed, Style, Color)
352	       end,
353    [SetStyle(Style) || Style <- Styles],
354    ?stc:setKeyWords(Ed, 0, keyWords()),
355
356    %% Scrolling
357    Policy = ?wxSTC_CARET_SLOP bor ?wxSTC_CARET_JUMPS bor ?wxSTC_CARET_EVEN,
358    ?stc:setYCaretPolicy(Ed, Policy, 3),
359    ?stc:setVisiblePolicy(Ed, Policy, 3),
360
361    %% ?stc:connect(Ed, stc_doubleclick),
362    %% ?stc:connect(Ed, std_do_drop, fun(Ev, Obj) -> io:format("Ev ~p ~p~n",[Ev,Obj]) end),
363    ?stc:setReadOnly(Ed, true),
364    Ed.
365
366load_code(Ed, {ok, Code}) ->
367    ?stc:setReadOnly(Ed, false),
368    ?stc:setTextRaw(Ed, <<Code/binary, 0:8>>),
369    Lines = ?stc:getLineCount(Ed),
370    Sz = trunc(math:log10(Lines))+1,
371    LW = ?stc:textWidth(Ed, ?wxSTC_STYLE_LINENUMBER, lists:duplicate(Sz, $9)),
372    %%io:format("~p ~p ~p~n", [Lines, Sz, LW]),
373    ?stc:setMarginWidth(Ed, 0, LW+5),
374    ?stc:setReadOnly(Ed, true),
375    Ed.
376
377unload_code(Ed) ->
378    ?stc:setReadOnly(Ed, false),
379    ?stc:setTextRaw(Ed, <<0:8>>),
380    ?stc:setReadOnly(Ed, true),
381    Ed.
382
383find(Ed) ->
384    ?stc:searchAnchor(Ed),
385    Res = ?stc:searchNext(Ed, ?wxSTC_FIND_REGEXP, "^init"),
386    case Res >= 0 of
387	true ->
388	    %% io:format("Found ~p ~n",[Res]),
389	    ?stc:scrollToLine(Ed,?stc:lineFromPosition(Ed,Res) - 1),
390	    true;
391	false ->
392	    io:format("Not Found ~s ~n",["^init"]),
393	    false
394    end.
395
396keyWords() ->
397    L = ["after","begin","case","try","cond","catch","andalso","orelse",
398	 "end","fun","if","let","of","receive","when","bnot","not",
399	 "div","rem","band","and","bor","bxor","bsl","bsr","or","xor"],
400    lists:flatten([K ++ " " || K <- L] ++ [0]).
401
402