%%
%% %CopyrightBegin%
%%
%% SPDX-License-Identifier: Apache-2.0
%%
%% Copyright Ericsson AB 2009-2025. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%

%% This is example of the widgets and usage of Wx.
%% Hopefully it will contain all implemented widgets, it's event handling
%% and some tutorials of how to use sizers and other stuff.

-module(demo).

-include_lib("wx/include/wx.hrl").

-behaviour(wx_object).
-export([start/0, start/1, start_link/0, start_link/1, format/3, 
	 init/1, terminate/2,  code_change/3,
	 handle_info/2, handle_call/3, handle_cast/2, handle_event/2]).


-record(state, {win, demo, example, selector, log, code}).

start() ->
    start([]).

start(Debug) ->
    wx_object:start(?MODULE, Debug, []).

start_link() ->
    start_link([]).

start_link(Debug) ->
    wx_object:start_link(?MODULE, Debug, []).

format(#state{log=Log}, Str, Args) ->
    wxTextCtrl:appendText(Log, io_lib:format(Str, Args)),
    ok;
format(Config,Str,Args) ->
    Log = proplists:get_value(log, Config),
    wxTextCtrl:appendText(Log, io_lib:format(Str, Args)),
    ok.


-define(DEBUG_NONE, 101).
-define(DEBUG_VERBOSE, 102).
-define(DEBUG_TRACE, 103).
-define(DEBUG_DRIVER, 104).
    
init(Options) ->
    wx:new(Options),
    process_flag(trap_exit, true),

    Frame = wxFrame:new(wx:null(), ?wxID_ANY, "Wx widgets", [{size,{1400,800}}]),
    MB = wxMenuBar:new(),
    File    = wxMenu:new([]),
    wxMenu:append(File, ?wxID_PRINT, "&Print code"),
    wxMenu:appendSeparator(File),
    wxMenu:append(File, ?wxID_EXIT, "&Quit"),
    Debug    = wxMenu:new([]),
    wxMenu:appendRadioItem(Debug, ?DEBUG_NONE, "None"), 
    wxMenu:appendRadioItem(Debug, ?DEBUG_VERBOSE, "Verbose"), 
    wxMenu:appendRadioItem(Debug, ?DEBUG_TRACE, "Trace"), 
    wxMenu:appendRadioItem(Debug, ?DEBUG_DRIVER, "Driver"), 
    Help    = wxMenu:new([]),
    wxMenu:append(Help, ?wxID_HELP, "Help"), 
    wxMenu:append(Help, ?wxID_ABOUT, "About"), 
    wxMenuBar:append(MB, File, "&File"),
    wxMenuBar:append(MB, Debug, "&Debug"),
    wxMenuBar:append(MB, Help, "&Help"),
    wxFrame:setMenuBar(Frame,MB),

    wxFrame:connect(Frame, command_menu_selected, [{skip, true}]),
    wxFrame:connect(Frame, close_window),

    _SB = wxFrame:createStatusBar(Frame,[]),

    %% Setup on toplevel because stc seems to steal this on linux
    wxFrame:dragAcceptFiles(Frame, true),
    wxFrame:connect(Frame, drop_files),

    %%   T        Uppersplitter
    %%   O        Left   |    Right
    %%   P  Widgets|Code |    Demo
    %%   S  -------------------------------
    %%   P          Log Window
    TopSplitter   = wxSplitterWindow:new(Frame, [{style, ?wxSP_NOBORDER}]),    
    UpperSplitter = wxSplitterWindow:new(TopSplitter, [{style, ?wxSP_NOBORDER}]),    
    LeftSplitter  = wxSplitterWindow:new(UpperSplitter, [{style, ?wxSP_NOBORDER}]),
    %% Setup so that sizers and initial sizes, resizes the windows correct
    wxSplitterWindow:setSashGravity(TopSplitter,   0.5),
    wxSplitterWindow:setSashGravity(UpperSplitter, 0.60),
    wxSplitterWindow:setSashGravity(LeftSplitter,  0.20),

    %% LeftSplitter:
    Example = fun(Beam) ->
		      "ex_" ++ F = filename:rootname(Beam),
		      F
	      end,
    Mods = [Example(F) || F <- filelib:wildcard("ex_*.beam")],

    CreateLB = fun(Parent) ->
		       wxListBox:new(Parent, ?wxID_ANY, 
				     [{style, ?wxLB_SINGLE},
				      {choices, Mods}])
	       end,
    {LBPanel, [LB],_} = create_subwindow(LeftSplitter, "Example", [CreateLB]),
    wxListBox:setSelection(LB, 0),
    wxListBox:connect(LB, command_listbox_selected),

    CreateCode = fun(Parent) -> 
			 code_area(Parent)
		 end,
    {CodePanel, [Code],_} = create_subwindow(LeftSplitter, "Code", [CreateCode]),

    wxSplitterWindow:splitVertically(LeftSplitter, LBPanel, CodePanel,
				     [{sashPosition,150}]),

    %% Demo: 
    {DemoPanel, [], DemoSz} = create_subwindow(UpperSplitter, "Demo", []),
    
    %% UpperSplitter:
    wxSplitterWindow:splitVertically(UpperSplitter, LeftSplitter, DemoPanel,
				     [{sashPosition,600}]),
    
    %% TopSplitter: 
    AddEvent = fun(Parent) ->
		       EventText = wxTextCtrl:new(Parent, 
						  ?wxID_ANY, 
						  [{style, ?wxTE_DONTWRAP bor 
						    ?wxTE_MULTILINE bor ?wxTE_READONLY}
						  ]),
		       wxTextCtrl:appendText(EventText, "Welcome\n"),
		       EventText
	       end,

    {EvPanel, [EvCtrl],_} = create_subwindow(TopSplitter, "Events", [AddEvent]),
    
    wxSplitterWindow:splitHorizontally(TopSplitter, UpperSplitter, EvPanel, 
				       [{sashPosition,-100}]),
    
    wxFrame:show(Frame),
    
    State = #state{win=Frame, demo={DemoPanel,DemoSz}, selector=LB, log=EvCtrl, code=Code},
    %% Load the first example:
    Ex = wxListBox:getStringSelection(LB),        
    process_flag(trap_exit, true),
    ExampleObj = load_example(Ex, State), 
    wxSizer:add(DemoSz, ExampleObj, [{proportion,1}, {flag, ?wxEXPAND}]),
    wxSizer:layout(DemoSz),
    
    %% The windows should be set up now, Reset Gravity so we get what we want
    wxSplitterWindow:setSashGravity(TopSplitter,   1.0),
    wxSplitterWindow:setSashGravity(UpperSplitter, 0.0),
    wxSplitterWindow:setSashGravity(LeftSplitter,  0.0),
    wxSplitterWindow:setMinimumPaneSize(TopSplitter, 1),
    wxSplitterWindow:setMinimumPaneSize(UpperSplitter, 1),
    wxSplitterWindow:setMinimumPaneSize(LeftSplitter, 1),

    wxToolTip:enable(true),
    wxToolTip:setDelay(500),

    {Frame, State#state{example=ExampleObj}}.

create_subwindow(Parent, BoxLabel, Funs) ->
    Panel = wxPanel:new(Parent),
    Sz    = wxStaticBoxSizer:new(?wxVERTICAL, Panel, [{label, BoxLabel}]),
    wxPanel:setSizer(Panel, Sz),
    Ctrls = [Fun(Panel) || Fun <- Funs],
    [wxSizer:add(Sz, Ctrl, [{proportion, 1}, {flag, ?wxEXPAND}]) 
     || Ctrl <- Ctrls],
    {Panel, Ctrls, Sz}.

%%%%%%%%%%%%
%% Callbacks

%% Handled as in normal gen_server callbacks
handle_info({'EXIT',_, wx_deleted}, State) ->
    {noreply,State};
handle_info({'EXIT',_, shutdown}, State) ->
    {noreply,State};
handle_info({'EXIT',_, normal}, State) ->
    {noreply,State};
handle_info(Msg, State) ->
    format(State, "Got Info ~p~n",[Msg]),
    {noreply,State}.

handle_call(Msg, _From, State) ->
    format(State, "Got Call ~p~n",[Msg]),
    {reply,ok,State}.

handle_cast(Msg, State) ->
    format(State, "Got cast ~p~n",[Msg]),
    {noreply,State}.

%% Async Events are handled in handle_event as in handle_info
handle_event(#wx{event=#wxCommand{type=command_listbox_selected, cmdString=Ex}}, 
	     State = #state{demo={_,DemoSz}, example=Example, code=Code}) ->
    case Ex of
	[] ->
	    {noreply, State};
	_  ->
	    wxSizer:detach(DemoSz, Example),
	    wx_object:call(Example, shutdown),
	    unload_code(Code),
	    NewExample = load_example(Ex, State),
	    wxSizer:add(DemoSz, NewExample, [{proportion,1}, {flag, ?wxEXPAND}]),
	    wxSizer:layout(DemoSz),
	    {noreply, State#state{example=NewExample}}
    end;
handle_event(#wx{id = Id,
		 event = #wxCommand{type = command_menu_selected}},
	     State = #state{}) ->
    case Id of
	?wxID_PRINT ->
	    %% If you are going to printout mainly text it is easier if
	    %% you generate HTML code and use a wxHtmlEasyPrint
	    %% instead of using DCs

            %% Printpreview doesn't work in >2.9 without this
            wxIdleEvent:setMode(?wxIDLE_PROCESS_ALL),
	    Module = "ex_" ++ wxListBox:getStringSelection(State#state.selector) ++ ".erl",
	    HEP = wxHtmlEasyPrinting:new([{name, "Print"},
					  {parentWindow, State#state.win}]),
	    Html = demo_html_tagger:erl2htmltext(Module),
	    wxHtmlEasyPrinting:previewText(HEP, Html),
	    {noreply, State};
	?DEBUG_TRACE ->
	    wx:debug(trace),
	    {noreply, State};
	?DEBUG_DRIVER ->
	    wx:debug(driver),
	    {noreply, State};
	?DEBUG_VERBOSE ->
	    wx:debug(verbose),
	    {noreply, State};
	?DEBUG_NONE ->
	    wx:debug(none),
	    {noreply, State};
	?wxID_HELP ->
	    wx_misc:launchDefaultBrowser("http://www.erlang.org/doc/apps/wx/part_frame.html"),
	    {noreply, State};
	?wxID_ABOUT ->
	    WxWVer = io_lib:format("~p.~p.~p.~p",
				   [?wxMAJOR_VERSION, ?wxMINOR_VERSION,
				    ?wxRELEASE_NUMBER, ?wxSUBRELEASE_NUMBER]),
	    application:load(wx),
	    {ok, WxVsn} = application:get_key(wx,  vsn),
	    AboutString =
		"Demo of various widgets\n"
		"Authors: Olle & Dan\n\n" ++
		"Frontend: wx-" ++ WxVsn ++
		"\nBackend: wxWidgets-" ++ lists:flatten(WxWVer),

	    wxMessageDialog:showModal(wxMessageDialog:new(State#state.win, AboutString,
							  [{style,
							    ?wxOK bor
							    ?wxICON_INFORMATION bor
							    ?wxSTAY_ON_TOP},
							   {caption, "About"}])),
	    {noreply, State};
	?wxID_EXIT ->
	    wx_object:call(State#state.example, shutdown),
	    {stop, normal, State};
	_ ->
	    {noreply, State}
    end;
handle_event(#wx{event=#wxClose{}}, State = #state{win=Frame}) ->
    io:format("~p Closing window ~n",[self()]),
    ok = wxFrame:setStatusText(Frame, "Closing...",[]),
    {stop, normal, State};
handle_event(Ev,State) ->
    format(State, "~p Got event ~p ~n",[?MODULE, Ev]),
    {noreply, State}.

code_change(_, _, State) ->
    {stop, not_yet_implemented, State}.

terminate(_Reason, State = #state{win=Frame}) ->
    catch wx_object:call(State#state.example, shutdown),
    wxFrame:destroy(Frame),
    wx:destroy().

%%%%%%%%%%%%%%%%% Internals %%%%%%%%%%

load_example(Ex, #state{demo={DemoPanel,DemoSz}, log=EvCtrl, code=Code}) ->
    ModStr = "ex_" ++ Ex,
    Mod = list_to_atom(ModStr),
    ModFile = ModStr ++ ".erl",
    load_code(Code, file:read_file(ModFile)),
    find(Code),
    Mod:start([{parent, DemoPanel}, {demo_sz, DemoSz}, {log, EvCtrl}]).

-define(stc, wxStyledTextCtrl).

code_area(Parent) ->
    FixedFont = wxFont:new(10, ?wxFONTFAMILY_TELETYPE, ?wxNORMAL, ?wxNORMAL,[]),
    Ed = wxStyledTextCtrl:new(Parent),

    ?stc:styleClearAll(Ed),
    ?stc:styleSetFont(Ed, ?wxSTC_STYLE_DEFAULT, FixedFont),
    ?stc:setLexer(Ed, ?wxSTC_LEX_ERLANG),
    ?stc:setMarginType(Ed, 0, ?wxSTC_MARGIN_NUMBER),
    LW = ?stc:textWidth(Ed, ?wxSTC_STYLE_LINENUMBER, "9"),
    ?stc:setMarginWidth(Ed, 0, LW),
    ?stc:setMarginWidth(Ed, 1, 0),

    ?stc:setSelectionMode(Ed, ?wxSTC_SEL_LINES),
    %%?stc:hideSelection(Ed, true),

    Styles =  [{?wxSTC_ERLANG_DEFAULT,  {0,0,0}},
	       {?wxSTC_ERLANG_COMMENT,  {160,53,35}},
	       {?wxSTC_ERLANG_VARIABLE, {150,100,40}},
	       {?wxSTC_ERLANG_NUMBER,   {5,5,100}},
	       {?wxSTC_ERLANG_KEYWORD,  {130,40,172}},
	       {?wxSTC_ERLANG_STRING,   {170,45,132}},
	       {?wxSTC_ERLANG_OPERATOR, {30,0,0}},
	       {?wxSTC_ERLANG_ATOM,     {0,0,0}},
	       {?wxSTC_ERLANG_FUNCTION_NAME, {64,102,244}},
	       {?wxSTC_ERLANG_CHARACTER,{236,155,172}},
	       {?wxSTC_ERLANG_MACRO,    {40,144,170}},
	       {?wxSTC_ERLANG_RECORD,   {40,100,20}},
	       {?wxSTC_ERLANG_NODE_NAME,{0,0,0}},
	       %% Optional 2.9 stuff
	       {?wxSTC_ERLANG_COMMENT_FUNCTION, {160,53,35}},
	       {?wxSTC_ERLANG_COMMENT_MODULE, {160,53,35}},
	       {?wxSTC_ERLANG_COMMENT_DOC, {160,53,35}},
	       {?wxSTC_ERLANG_COMMENT_DOC_MACRO, {160,53,35}},
	       {?wxSTC_ERLANG_ATOM_QUOTED, {0,0,0}},
	       {?wxSTC_ERLANG_MACRO_QUOTED, {40,144,170}},
	       {?wxSTC_ERLANG_RECORD_QUOTED, {40,100,20}},
	       {?wxSTC_ERLANG_NODE_NAME_QUOTED, {0,0,0}},
	       {?wxSTC_ERLANG_BIFS, {130,40,172}},
	       {?wxSTC_ERLANG_MODULES, {64,102,244}},
	       {?wxSTC_ERLANG_MODULES_ATT, {64,102,244}}
	      ],
    SetStyle = fun({Style, Color}) ->
		       ?stc:styleSetFont(Ed, Style, FixedFont),
		       ?stc:styleSetForeground(Ed, Style, Color)
	       end,
    [SetStyle(Style) || Style <- Styles],
    ?stc:setKeyWords(Ed, 0, keyWords()),
    
    %% Scrolling
    Policy = ?wxSTC_CARET_SLOP bor ?wxSTC_CARET_JUMPS bor ?wxSTC_CARET_EVEN, 
    ?stc:setYCaretPolicy(Ed, Policy, 3),
    ?stc:setVisiblePolicy(Ed, Policy, 3),

    %% ?stc:connect(Ed, stc_doubleclick),
    %% ?stc:connect(Ed, std_do_drop, fun(Ev, Obj) -> io:format("Ev ~p ~p~n",[Ev,Obj]) end),
    ?stc:setReadOnly(Ed, true),
    Ed.

load_code(Ed, {ok, Code}) ->
    ?stc:setReadOnly(Ed, false),
    ?stc:setTextRaw(Ed, <<Code/binary, 0:8>>),
    Lines = ?stc:getLineCount(Ed),
    Sz = trunc(math:log10(Lines))+1,
    LW = ?stc:textWidth(Ed, ?wxSTC_STYLE_LINENUMBER, lists:duplicate(Sz, $9)),
    %%io:format("~p ~p ~p~n", [Lines, Sz, LW]),
    ?stc:setMarginWidth(Ed, 0, LW+5),
    ?stc:setReadOnly(Ed, true),
    Ed.

unload_code(Ed) ->
    ?stc:setReadOnly(Ed, false),
    ?stc:setTextRaw(Ed, <<0:8>>),
    ?stc:setReadOnly(Ed, true),
    Ed.

find(Ed) ->
    ?stc:searchAnchor(Ed),
    Res = ?stc:searchNext(Ed, ?wxSTC_FIND_REGEXP, "^init"),
    case Res >= 0 of
	true -> 
	    %% io:format("Found ~p ~n",[Res]),
	    ?stc:scrollToLine(Ed,?stc:lineFromPosition(Ed,Res) - 1),
	    true;
	false ->
	    io:format("Not Found ~s ~n",["^init"]),
	    false
    end.

keyWords() ->
    L = ["after","begin","case","try","cond","catch","andalso","orelse",
	 "end","fun","if","let","of","receive","when","bnot","not",
	 "div","rem","band","and","bor","bxor","bsl","bsr","or","xor"],
    lists:flatten([K ++ " " || K <- L] ++ [0]).

