%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2009-2011. 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 : sud_board.erl
%%% Author : <[email protected]>
%%% Description : Manages the gui board
%%%
%%% Created : 9 Jan 2008 by <[email protected]>
%%%-------------------------------------------------------------------
-module(sudoku_board).
-export([new/1, setup_board/2, clear_board/1, left/1,
get_board_data/1,set_board_data/2,
set_butt/3, butt_correct/3,
draw/3,
%% Callbacks
init/1, handle_sync_event/3,
handle_event/2, handle_info/2, handle_call/3, handle_cast/2,
code_change/3, terminate/2]).
-include("sudoku.hrl").
-record(state, {win, parent, board=[], pen, fonts=[]}).
-record(sq, {key,val,correct=true,given=false}).
-define(BRD,10).
-define(ARC_R, 10).
-behaviour(wx_object).
%% API
new(ParentObj) ->
wx_object:start_link(?MODULE, [ParentObj, self()], []).
setup_board(Board, Init) ->
wx_object:call(Board, {setup_board, Init}).
clear_board(Board) ->
wx_object:call(Board, clear_board).
butt_correct(Board, Key, Correct) ->
wx_object:call(Board, {butt_correct, Key, Correct}).
set_butt(Board, Indx, Val) when is_integer(Indx) ->
{R,C,_} = sudoku_game:rcm(Indx),
set_butt(Board, {R,C}, Val);
set_butt(Board, Id, Val) ->
wx_object:call(Board, {set_butt, Id, Val}).
left(Board) ->
wx_object:call(Board, left).
get_board_data(Board) ->
wx_object:call(Board, get_board_data).
set_board_data(Board, List) ->
wx_object:call(Board, {set_board_data, List}).
draw(Board, DC, Size) ->
wx_object:call(Board, {draw, DC, Size}).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
init([ParentObj, ParentPid]) ->
Win = wxPanel:new(ParentObj, [{style, ?wxFULL_REPAINT_ON_RESIZE}]),
wxWindow:setFocus(Win), %% Get keyboard focus
wxWindow:setSizeHints(Win, {250,250}),
wxWindow:connect(Win, paint, [callback]),
wxWindow:connect(Win, size, []),
wxWindow:connect(Win, erase_background, []),
wxWindow:connect(Win, key_up, [{skip, true}]),
wxWindow:connect(Win, left_down, [{skip, true}]),
wxWindow:connect(Win, enter_window, [{skip, true}]),
%% Init pens and fonts
Pen = wxPen:new({0,0,0}, [{width, 3}]),
Fs0 = [{Sz,wxFont:new(Sz, ?wxSWISS, ?wxNORMAL, ?wxNORMAL,[])} ||
Sz <- [8,9,10,11,12,13,14,16,18,20,22,24,26,28,30,34,38,42,44,46]],
TestDC = wxClientDC:new(Win),
CW = fun({Sz,Font},Acc) ->
case wxFont:ok(Font) of
true ->
wxDC:setFont(TestDC, Font),
CH = wxDC:getCharHeight(TestDC),
[{CH,Sz,Font} | Acc];
false ->
Acc
end
end,
Fs = lists:foldl(CW, [], Fs0),
wxClientDC:destroy(TestDC),
{Win, #state{win=Win, board=[], pen=Pen, fonts=Fs, parent=ParentPid}}.
handle_sync_event(#wx{event=#wxPaint{}}, _Obj, State = #state{win=Win}) ->
%% io:format("EPaint~n",[]),
Size = wxWindow:getSize(Win),
DC = wxPaintDC:new(Win),
wxDC:destroyClippingRegion(DC),
redraw(DC,Size,State),
wxPaintDC:destroy(DC),
%%io:format("...EPaint~n",[]),
ok.
handle_event(#wx{event=#wxMouse{type=enter_window}}, State = #state{win=Win}) ->
wxWindow:setFocus(Win), %% Get keyboard focus
{noreply,State};
handle_event(#wx{event=#wxKey{keyCode=KeyC, x=X,y=Y}},
S = #state{parent=Pid, win=Win}) ->
Val = if KeyC > 47, KeyC < 58 -> KeyC - $0;
KeyC > 325, KeyC < 336 -> KeyC - 326; %% NUM LOCK
true -> 0
end,
case get_butt(X,Y,S) of
error -> %% Mac don't get correct coordinates.
Global = wx_misc:getMousePosition(),
{CX,CY} = wxWindow:screenToClient(Win, Global),
case get_butt(CX,CY,S) of
error -> ignore;
Id -> Pid ! {set_val,Id,Val}
end;
Id ->
Pid ! {set_val,Id,Val}
end,
{noreply, S};
handle_event(#wx{event=#wxMouse{type=left_down,x=X,y=Y}},
S = #state{parent=Gui, win=F}) ->
Id = get_butt(X,Y,S),
case Id of
error -> ignore;
_ -> create_popup_menu(Gui,Id,X,Y,F)
end,
{noreply, S};
handle_event(#wx{event=#wxSize{}}, State) ->
redraw(State),
{noreply,State};
handle_event(_Ev, State) ->
{noreply,State}.
%%%%%%%%%%%%%%%%%%%
handle_call({set_butt, Key, 0},_From,S0=#state{board=B0}) -> %% Reset
B = lists:keydelete(Key,2,B0),
S = S0#state{board=B},
redraw(S),
{reply, ok, S};
handle_call({set_butt, Key, Val},_From,S0=#state{board=B0}) ->
case lists:keysearch(Key,2,B0) of
{value, _} ->
B = lists:keyreplace(Key, 2, B0, #sq{key=Key,val=Val});
false ->
B = [#sq{key=Key, val=Val}|B0]
end,
S = S0#state{board=B},
redraw(S),
{reply, ok, S};
handle_call({butt_correct, Key, Correct},_From, S0=#state{board=B0}) ->
case lists:keysearch(Key,2,B0) of
{value, Butt} ->
B = lists:keyreplace(Key, 2, B0, Butt#sq{key=Key,correct=Correct});
false ->
B = B0
end,
S = S0#state{board=B},
redraw(S),
{reply, ok, S};
handle_call({setup_board, Init},_From, State) ->
B = [#sq{given=true, correct=true, key=Key, val=Val} || {Key,Val} <- Init],
S = State#state{board=B},
redraw(S),
{reply, ok, S};
handle_call(clear_board,_From, State = #state{board=B0}) ->
B = [Butt || Butt = #sq{given=true} <- B0],
S = State#state{board=B},
redraw(S),
Given = [{Key, Val} || #sq{key=Key,val=Val,given=true} <- B],
{reply, Given, S};
handle_call(get_board_data,_From, S=#state{board=B0}) ->
{reply, B0, S};
handle_call({set_board_data, B},_From, S0) ->
S = S0#state{board=B},
redraw(S),
G1 = [{Key, Val} || #sq{key=Key,val=Val,given=true} <- B],
G2 = [{Key, Val} || #sq{key=Key,val=Val,given=false,correct=true} <- B],
G3 = [{Key, Val} || #sq{key=Key,val=Val,given=false,correct=false} <- B],
{reply, G1 ++ G2 ++ G3, S};
handle_call(left,_From, S = #state{board=B}) ->
Res = 81 - length([ok || #sq{correct=C} <- B, C /= false]),
{reply, Res, S};
handle_call({draw, DC, Size},_From, S) ->
redraw(DC,Size,S),
{reply, ok, S}.
handle_cast(Msg, State) ->
io:format("Got cast ~p~n",[Msg]),
{noreply,State}.
code_change(_, _, State) ->
{stop, not_yet_implemented, State}.
handle_info(Msg, State) ->
{stop, {info, Msg}, State}.
terminate(_Reason, _State) ->
normal.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
get_butt(X, Y, #state{win=Win}) ->
{W0,H0} = wxWindow:getSize(Win),
BoxSz = getGeomSz(W0,H0),
%% io:format("~p ~p ~p ~p~n", [{X,Y}, {W0,H0}, BoxSz, calc_pos(X-?BRD,Y-?BRD, BoxSz)]),
case calc_pos(X-?BRD,Y-?BRD, BoxSz) of
Pos = {R,C} when 0 < R, R < 10, 0 < C, C < 10 -> Pos;
_ -> error
end.
calc_pos(X,Y, BoxSz) ->
{1+(Y*3 div BoxSz), 1+(X*3 div BoxSz)}.
redraw(S = #state{win=Win}) ->
DC0 = wxClientDC:new(Win),
DC = wxBufferedDC:new(DC0),
Size = wxWindow:getSize(Win),
redraw(DC, Size, S),
wxBufferedDC:destroy(DC),
wxClientDC:destroy(DC0),
ok.
redraw(DC, Size, S) ->
wx:batch(fun() ->
wxDC:setBackground(DC, ?wxWHITE_BRUSH),
wxDC:clear(DC),
BoxSz = draw_board(DC,Size,S),
F = sel_font(BoxSz div 3,S#state.fonts),
[draw_number(DC,F,BoxSz,Sq) || Sq <- S#state.board]
end).
sel_font(_BS,[{_H,_Sz,F}]) ->
%% io:format("Font sz ~p height ~p in BS ~p~n",[_Sz,_H, _BS]),
F;
sel_font(BS,[{H,_Sz,F}|_]) when BS > (H + 6) ->
%% io:format("Font sz ~p height ~p in BS ~p~n",[_Sz,H, BS]),
F;
sel_font(BS,[_|Fs]) ->
sel_font(BS,Fs).
draw_number(DC,F,Sz,#sq{key={R,C},val=Num,given=Bold,correct=Correct}) ->
{X,Y} = get_coords(Sz,R-1,C-1),
TBox = Sz div 3,
if Bold ->
wxFont:setWeight(F,?wxBOLD),
wxDC:setTextForeground(DC,{0,0,0});
Correct =:= false ->
wxFont:setWeight(F,?wxNORMAL),
wxDC:setTextForeground(DC,{255,40,40,255});
true ->
wxFont:setWeight(F,?wxNORMAL),
wxDC:setTextForeground(DC,{50,50,100,255})
end,
wxDC:setFont(DC,F),
CH = (TBox - wxDC:getCharHeight(DC)) div 2,
CW = (TBox - wxDC:getCharWidth(DC)) div 2,
wxDC:drawText(DC, integer_to_list(Num), {X+CW,Y+CH+1}),
ok.
get_coords(Sz,R,C) ->
TBox = Sz div 3,
R1 = R div 3,
R2 = R rem 3,
C1 = C div 3,
C2 = C rem 3,
{?BRD + C1*Sz + C2*TBox,
?BRD + R1*Sz + R2*TBox}.
draw_board(DC,{W0,H0},#state{pen=Pen}) ->
BoxSz = getGeomSz(W0,H0),
BS = ?BRD+3*BoxSz,
wxPen:setWidth(Pen, 3),
wxPen:setColour(Pen, {0,0,0}),
wxDC:setPen(DC,Pen),
wxDC:drawRoundedRectangle(DC, {?BRD,?BRD,3*BoxSz+1,3*BoxSz+1},
float(?ARC_R)),
%% Testing DrawLines
wxDC:drawLines(DC, [{?BRD+BoxSz, ?BRD}, {?BRD+BoxSz, BS}]),
wxDC:drawLine(DC, {?BRD+BoxSz*2, ?BRD}, {?BRD+BoxSz*2, BS}),
wxDC:drawLine(DC, {?BRD, ?BRD+BoxSz}, {BS, ?BRD+BoxSz}),
wxDC:drawLine(DC, {?BRD, ?BRD+BoxSz*2}, {BS, ?BRD+BoxSz*2}),
%% Draw inside lines
wxPen:setWidth(Pen, 1),
wxDC:setPen(DC,Pen),
TBox = BoxSz div 3,
wxDC:drawLine(DC, {?BRD+TBox, ?BRD}, {?BRD+TBox, BS}),
wxDC:drawLine(DC, {?BRD+TBox*2, ?BRD}, {?BRD+TBox*2, BS}),
wxDC:drawLine(DC, {?BRD+TBox+BoxSz, ?BRD}, {?BRD+TBox+BoxSz, BS}),
wxDC:drawLine(DC, {?BRD+TBox*2+BoxSz, ?BRD}, {?BRD+TBox*2+BoxSz, BS}),
wxDC:drawLine(DC, {?BRD+TBox+BoxSz*2, ?BRD}, {?BRD+TBox+BoxSz*2, BS}),
wxDC:drawLine(DC, {?BRD+TBox*2+BoxSz*2, ?BRD}, {?BRD+TBox*2+BoxSz*2, BS}),
%% Vert
wxDC:drawLine(DC, {?BRD, ?BRD+TBox}, {BS, ?BRD+TBox}),
wxDC:drawLine(DC, {?BRD, ?BRD+TBox*2}, {BS, ?BRD+TBox*2}),
wxDC:drawLine(DC, {?BRD, ?BRD+TBox+BoxSz}, {BS, ?BRD+TBox+BoxSz}),
wxDC:drawLine(DC, {?BRD, ?BRD+TBox*2+BoxSz}, {BS, ?BRD+TBox*2+BoxSz}),
wxDC:drawLine(DC, {?BRD, ?BRD+TBox+BoxSz*2}, {BS, ?BRD+TBox+BoxSz*2}),
wxDC:drawLine(DC, {?BRD, ?BRD+TBox*2+BoxSz*2}, {BS, ?BRD+TBox*2+BoxSz*2}),
BoxSz.
getGeomSz(W,H) ->
Small = if W < H -> W; true -> H end,
(Small - 2*?BRD) div 3.
%% popupmenu
create_popup_menu(GFX,Butt,X,Y,Frame) ->
Port = wx:get_env(),
spawn_link(fun() -> create_popup_menu1(GFX,Butt,Port,X,Y,Frame) end).
create_popup_menu1(GFX,Butt,Port,X,Y,Frame) ->
wx:set_env(Port),
PopupMenu = wxMenu:new(),
create_popup_menu2(1, PopupMenu),
wxEvtHandler:connect(PopupMenu, command_menu_selected),
wxWindow:popupMenu(Frame,PopupMenu,X,Y),
receive
#wx{event=#wxCommand{type=command_menu_selected},id=10} ->
GFX ! {set_val,Butt,0};
#wx{event=#wxCommand{type=command_menu_selected},id=What} ->
GFX ! {set_val,Butt,What}
end.
create_popup_menu2(N,PP) when N > 9 ->
wxMenu:append(PP, 10, "Clear");
create_popup_menu2(N,PP) ->
wxMenu:append(PP, N,integer_to_list(N)),
create_popup_menu2(N+1,PP).