%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2009. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% %CopyrightEnd%
%%%-------------------------------------------------------------------
%%% File : sudoku_gui.erl
%%% Author : <[email protected]>
%%% Description : The Gui and it's event loop
%%%
%%% Created : 9 Jan 2008 by <[email protected]>
%%%-------------------------------------------------------------------
-module(sudoku_gui).
-export([init/1, handle_info/2, handle_call/3, handle_event/2,
terminate/2, code_change/3]).
-compile(export_all).
-behaviour(wx_object).
-include("sudoku.hrl").
-import(sudoku_game, [indx/1]).
%%%%%%%%%% Graphic engine %%%%%%%%%%%%%%
-record(gs,{board,show_err=true,level=hard,game,frame,orig=[], print_d, print_psdd}).
new(Game) ->
wx:new(),
wx_object:start_link(?MODULE, [Game], []).
%%%%%%%%%%%%%%%%%%%%% Server callbacks %%%%%%%%%%%%%
init([Game]) ->
{Frame, Board} = wx:batch(fun() -> create_window() end),
Game ! {gfx, self()},
{Frame, init_printer(#gs{board=Board,game=Game,frame=Frame})}.
create_window() ->
Frame = wxFrame:new(wx:null(), -1, "Sudoku", []),
wxFrame:createStatusBar(Frame,[]),
wxFrame:connect(Frame, close_window),
MenuBar = wxMenuBar:new(),
File = wxMenu:new([]),
Opt = wxMenu:new([]),
Help = wxMenu:new([]),
wxMenu:append(File, ?NEW, "&New Game"),
wxMenu:append(File, ?OPEN, "&Open Game"),
wxMenu:append(File, ?SAVE, "&Save Game"),
wxMenu:appendSeparator(File),
wxMenu:append(File, ?PRINT, "Print"),
wxMenu:append(File, ?PRINT_PAGE_SETUP, "Page Setup"),
wxMenu:append(File, ?PRINT_PRE, "Print Preview"),
wxMenu:appendSeparator(File),
wxMenu:append(File, ?QUIT, "&Quit Game"),
wxMenu:append(Help, ?RULES, "Rules"),
wxMenu:append(Help, ?ABOUT, "About"),
wxMenu:appendRadioItem(Opt, ?TRIVIAL, "Level: Trivial"),
wxMenu:appendRadioItem(Opt, ?EASY, "Level: Easy"),
LItem = wxMenu:appendRadioItem(Opt, ?NORMAL, "Level: Normal"),
wxMenu:appendRadioItem(Opt, ?HARD, "Level: Hard"),
wxMenu:appendRadioItem(Opt, ?HARDEST, "Level: Hardest"),
wxMenu:appendSeparator(Opt),
EItem = wxMenu:appendCheckItem(Opt, ?SHOW_ERROR, "Show errors"),
wxMenuBar:append(MenuBar, File, "&File"),
wxMenuBar:append(MenuBar, Opt, "O&ptions"),
wxMenuBar:append(MenuBar, Help, "&Help"),
wxFrame:setMenuBar(Frame, MenuBar),
wxFrame:connect(Frame, command_menu_selected),
MainSz = wxBoxSizer:new(?wxVERTICAL),
Top = wxBoxSizer:new(?wxHORIZONTAL),
Panel = wxPanel:new(Frame),
NewGame = wxButton:new(Panel, ?NEW, [{label,"New Game"}]),
wxButton:connect(NewGame, command_button_clicked),
Empty = wxButton:new(Panel, ?EMPTY, [{label,"Empty Board "}]),
wxButton:connect(Empty, command_button_clicked),
Clean = wxButton:new(Panel, ?CLEAR, [{label,"Clear"}]),
wxButton:connect(Clean, command_button_clicked),
Hint = wxButton:new(Panel, ?HINT, [{label, "Hint"}]),
wxButton:connect(Hint, command_button_clicked),
wxSizer:addSpacer(Top,2),
SF = wxSizerFlags:new(),
wxSizerFlags:proportion(SF,1),
wxSizer:add(Top, NewGame, wxSizerFlags:left(SF)),
wxSizer:addSpacer(Top,3),
wxSizer:add(Top, Empty, wxSizerFlags:center(SF)),
wxSizer:addSpacer(Top,3),
wxSizer:add(Top, Clean, wxSizerFlags:center(SF)),
wxSizer:addSpacer(Top,3),
wxSizer:add(Top, Hint, wxSizerFlags:right(SF)),
wxSizer:addSpacer(MainSz,5),
wxSizer:add(MainSz, Top, wxSizerFlags:center(wxSizerFlags:proportion(SF,0))),
wxSizer:addSpacer(MainSz,10),
Board = sudoku_board:new(Panel),
wxSizer:add(MainSz, Board, wxSizerFlags:proportion(wxSizerFlags:expand(SF),1)),
wxWindow:setSizer(Panel,MainSz),
wxSizer:fit(MainSz, Frame),
wxSizer:setSizeHints(MainSz,Frame),
wxWindow:show(Frame),
%% Check after append so it's initialized on all platforms
wxMenuItem:check(LItem),
wxMenuItem:check(EItem),
{Frame, Board}.
status(Win, F, A) ->
Str = lists:flatten(io_lib:format(F, A)),
wxFrame:setStatusText(Win, Str).
%%%%%%%%%%%%%%%% Info i.e. messages %%%%%%%%%%%%%%%%%%%%%
handle_info(quit, S=#gs{game=G,frame=F}) ->
wxWindow:close(F),
wx_core:quit(),
G ! quit,
{stop, shutdown, S};
handle_info({init, Init}, S = #gs{board=Board,frame=F}) ->
sudoku_board:setup_board(Board, Init),
status(F, "Given ~p Left ~p", [length(Init), sudoku_board:left(Board)]),
{noreply, S#gs{orig=[indx(Id)||{Id,_}<-Init]}};
handle_info({correct, ButtI}, S = #gs{board=Board, orig=Orig,frame=F}) ->
sudoku_board:butt_correct(Board, ButtI, true),
case sudoku_board:left(Board) of
0 ->
Str = "Congrats, now try a harder one",
MD = wxMessageDialog:new(F,Str,
[{style, ?wxOK bor ?wxICON_INFORMATION},
{caption, "Complete"}]),
wxDialog:showModal(MD),
wxDialog:destroy(MD),
status(F, "Given ~p Left ~p", [length(Orig), 0]);
L ->
status(F, "Given ~p Left ~p", [length(Orig), L])
end,
{noreply, S};
handle_info({wrong, ButtI}, S = #gs{board=Board}) ->
case S#gs.show_err of
true ->
sudoku_board:butt_correct(Board, ButtI, false);
false ->
ignore
end,
{noreply, S};
handle_info({set_val, ButtI, Val}, S = #gs{game=G,board=Board,orig=Orig}) ->
case lists:member(indx(ButtI), Orig) of
false -> set_val(ButtI, Val, Board, G);
true -> ignore
end,
{noreply, S};
handle_info({working, Done}, S = #gs{frame=F}) ->
status(F, "Thinking: ~p%", [Done]),
{noreply, S};
handle_info({busy, Mode},S) ->
case Mode of
start -> wx_misc:beginBusyCursor();
stop -> wx_misc:endBusyCursor()
end,
{noreply, S}.
%%%%%%%%%%%%%%%%% GUI-Events %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
handle_event(#wx{id=?HINT, event=#wxCommand{type=command_button_clicked}},
S = #gs{game=G}) ->
G ! {solve,false},
{noreply,S};
handle_event(#wx{event=#wxClose{}},
S = #gs{game=G,frame=F}) ->
catch wxWindow:'Destroy'(F),
G ! quit,
{stop, shutdown, S};
handle_event(#wx{id=?QUIT, event=#wxCommand{type=command_menu_selected}},
S = #gs{game=G,frame=F}) ->
wxWindow:close(F,[]),
G ! quit,
{stop, shutdown, S};
%% type=command_button_clicked,
handle_event(#wx{id=?NEW, event=#wxCommand{}},
S = #gs{game=G, board=Board}) ->
G ! {op,?NEW,S#gs.level},
sudoku_board:setup_board(Board,[]),
{noreply, S#gs{orig=[]}};
handle_event(#wx{id=?EMPTY, event=#wxCommand{}},
S = #gs{game=G, board=Board}) ->
G ! {op,?EMPTY},
sudoku_board:setup_board(Board,[]),
{noreply, S#gs{orig=[]}};
handle_event(#wx{id=?CLEAR, event=#wxCommand{}},
S = #gs{game=G,board=Board}) ->
Vals = sudoku_board:clear_board(Board),
G ! {loaded, Vals},
{noreply, S};
handle_event(#wx{id=ID, event=#wxCommand{}}, S) when ID > 125 ->
New = dialog(ID, S),
{noreply, New};
handle_event(Msg,S) ->
io:format("~p: Unhandled event ~p~n",[?MODULE, Msg]),
%%sudoku_board:event(Msg, Ids),
{noreply, S}.
handle_call(What, _From, State) ->
{stop, {call, What}, State}.
code_change(_, _, State) ->
{stop, not_yet_implemented, State}.
terminate(_Reason, _State) ->
normal.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
dialog(?SHOW_ERROR, S=#gs{show_err=Show}) ->
S#gs{show_err = not Show};
dialog(ID, S) when ID >= 210, ID =< 240 ->
Level = sudoku_game:level(ID-200),
S#gs{level = Level};
dialog(?SAVE, S=#gs{frame=Frame, board=Board}) ->
FD = wxFileDialog:new(Frame, [{style, ?wxFD_SAVE bor
?wxFD_OVERWRITE_PROMPT}]),
case wxFileDialog:showModal(FD) of
?wxID_OK ->
Path = wxFileDialog:getPath(FD),
{ok,Fd} = file:open(Path, [write]),
List = sudoku_board:get_board_data(Board),
io:format(Fd, "~w.~n", [List]),
file:close(Fd);
_ ->
ignore
end,
wxDialog:destroy(FD),
S;
dialog(?OPEN, S=#gs{game=Server, frame=Frame, board=Board}) ->
FD = wxFileDialog:new(Frame,[{style, ?wxFD_OPEN bor ?wxFD_FILE_MUST_EXIST}]),
case wxDialog:showModal(FD) of
?wxID_OK ->
Path = wxFileDialog:getPath(FD),
case file:consult(Path) of
{ok, [Game]} when is_list(Game) ->
Vals = sudoku_board:set_board_data(Board, Game),
Server ! {loaded, Vals};
_ ->
ignore
end;
_ ->
ignore
end,
wxFileDialog:destroy(FD),
S;
dialog(?ABOUT, S=#gs{frame=Frame}) ->
Str = "I'm just testing WxWidgets.\n"
"Testing various features and usages.\n/Dgud",
MD = wxMessageDialog:new(Frame,Str,
[{style, ?wxOK bor ?wxICON_INFORMATION},
{caption, "About box"}]),
wxDialog:showModal(MD),
wxDialog:destroy(MD),
S;
dialog(?RULES, S) ->
wx_misc:launchDefaultBrowser("http://www.sudoku.com"),
S;
dialog(?PRINT_PAGE_SETUP, S = #gs{frame=Frame, print_psdd=PsDD0, print_d=PD0}) ->
wxPageSetupDialogData:setPrintData(PsDD0, PD0),
PSD = wxPageSetupDialog:new(Frame, [{data,PsDD0}]),
wxPageSetupDialog:showModal(PSD),
PSDD1 = wxPageSetupDialog:getPageSetupData(PSD),
PD1 = wxPageSetupDialogData:getPrintData(PSDD1),
%% Create new objects using copy constr.
PD = wxPrintData:new(PD1),
PsDD = wxPageSetupDialogData:new(PSDD1),
wxPageSetupDialog:destroy(PSD),
wxPageSetupDialogData:destroy(PsDD0),
wxPrintData:destroy(PD0),
S#gs{print_psdd=PsDD, print_d=PD};
dialog(?PRINT_PRE, S = #gs{frame=Frame, print_d=PD}) ->
PDD = wxPrintDialogData:new(PD),
Printout1 = wxPrintout:new("Print", fun(This,Page) -> printout(This,Page,S) end,
[{getPageInfo, fun getPageInfo/1}]),
Printout2 = wxPrintout:new("Print", fun(This,Page) -> printout(This,Page,S) end,
[{getPageInfo, fun getPageInfo/1}]),
Preview = wxPrintPreview:new(Printout1, [{printoutForPrinting,Printout2},{data,PDD}]),
case wxPrintPreview:isOk(Preview) of
true ->
PF = wxPreviewFrame:new(Preview, Frame, [{title, "Print Preview"}]),
wxPreviewFrame:centre(PF, [{dir, ?wxBOTH}]),
wxPreviewFrame:initialize(PF),
wxPreviewFrame:centre(PF),
wxPreviewFrame:show(PF);
false ->
io:format("Could not create preview window.\n"
"Perhaps your current printer is not set correctly?~n", []),
wxPrintPreview:destroy(Preview)
end,
S;
dialog(?PRINT, S = #gs{frame=Frame, print_d=PD}) ->
PDD = wxPrintDialogData:new(PD),
Printer = wxPrinter:new([{data,PDD}]),
Printout = wxPrintout:new("Print", fun(This,Page) -> printout(This,Page,S) end,
[{getPageInfo, fun getPageInfo/1}]),
case wxPrinter:print(Printer, Frame, Printout, [{prompt,true}]) of
false ->
case wxPrinter:getLastError() of
?wxPRINTER_ERROR ->
io:format("There was a problem printing.\n"
"Perhaps your current printer is not set correctly?~n", []);
_ ->
io:format("You canceled printing~n", [])
end,
wxPrinter:destroy(Printer),
S;
true ->
PDD2 = wxPrinter:getPrintDialogData(Printer),
PD2 = wxPrintDialogData:getPrintData(PDD2),
%% Copy data PD2 will be deleted when Printer is destroyed
PD3 = wxPrintData:new(PD2),
wxPrintData:destroy(PD),
wxPrinter:destroy(Printer),
S#gs{print_d = PD3}
end;
dialog(Other, S) ->
io:format("other ~p~n",[Other]),
S.
init_printer(S) ->
PD = wxPrintData:new(),
%% You could set an initial paper size here
%% g_printData->SetPaperId(wxPAPER_LETTER); // for Americans
%% g_printData->SetPaperId(wxPAPER_A4); // for everyone else
PSDD = wxPageSetupDialogData:new(PD),
wxPageSetupDialogData:setMarginTopLeft(PSDD,{15,15}),
wxPageSetupDialogData:setMarginBottomRight(PSDD,{15,15}),
S#gs{print_d=PD, print_psdd=PSDD}.
getPageInfo(_This) ->
{1,1,1,1}.
printout(This, _Page, #gs{board=Board, print_psdd=PsDD}) ->
MX = MY = 500,
wxPrintout:fitThisSizeToPageMargins(This, {MX,MY}, PsDD),
_DBG = {_X,_Y,W,H} = wxPrintout:getLogicalPageMarginsRect(This, PsDD),
wxPrintout:offsetLogicalOrigin(This,(W-MX) div 2, (H-MY) div 2),
%% io:format("~p ->{~p,~p} ~n", [_DBG, (W-MX) div 2, (H-MY) div 2]),
DC = wxPrintout:getDC(This),
sudoku_board:draw(Board, DC, {500,500}),
true.
set_val(Id, Val, Board, G) ->
sudoku_board:set_butt(Board, Id,Val),
G ! {validate, Id, Val},
ok.