diff options
author | Erlang/OTP <[email protected]> | 2009-11-20 14:54:40 +0000 |
---|---|---|
committer | Erlang/OTP <[email protected]> | 2009-11-20 14:54:40 +0000 |
commit | 84adefa331c4159d432d22840663c38f155cd4c1 (patch) | |
tree | bff9a9c66adda4df2106dfd0e5c053ab182a12bd /lib/reltool/src/reltool_fgraph_win.erl | |
download | otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.gz otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.bz2 otp-84adefa331c4159d432d22840663c38f155cd4c1.zip |
The R13B03 release.OTP_R13B03
Diffstat (limited to 'lib/reltool/src/reltool_fgraph_win.erl')
-rw-r--r-- | lib/reltool/src/reltool_fgraph_win.erl | 726 |
1 files changed, 726 insertions, 0 deletions
diff --git a/lib/reltool/src/reltool_fgraph_win.erl b/lib/reltool/src/reltool_fgraph_win.erl new file mode 100644 index 0000000000..b063fb94ba --- /dev/null +++ b/lib/reltool/src/reltool_fgraph_win.erl @@ -0,0 +1,726 @@ +%% +%% %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% + +-module(reltool_fgraph_win). + +-export([ + new/2, + add_node/2, + add_node/3, + del_node/2, + change_node/3, + add_link/2, + del_link/2, + set_dbl_click/2, + stop/2 + ]). + +-include_lib("wx/include/wx.hrl"). +-include("reltool_fgraph.hrl"). + +-record(state, + { + parent_pid, + frame, + window, + width, + height, + q_slider, + l_slider, + k_slider, + mouse_act, + is_frozen, + ticker + }). + +-record(graph, + { + pen, + brush, + font, + select = none, + offset = {0,0}, + offset_state = false, + ke = 0, + vs = [], + es = [] + }). + +-define(BRD,10). +-define(ARC_R, 10). + +-define(reset, 80). +-define(lock, 81). +-define(unlock, 82). +-define(move, 83). +-define(select, 84). +-define(delete, 85). +-define(freeze, 86). + +-define(q_slider, 90). +-define(l_slider, 91). +-define(k_slider, 92). + +-define(default_q, 20). +-define(default_l, 20). +-define(default_k, 20). + +-define(color_bg, {45,50,95}). +-define(color_fg, {235,245,230}). +-define(color_default, {10,220,20}). +-define(color_default_bg, {20,230,30}). +-define(color_alternate, {220,10,20}). +-define(color_alternate_bg, {230,20,30}). + +add_node(Pid, Key) -> add_node(Pid, Key, default). +add_node(Pid, Key, Color) -> Pid ! {add_node, Key, Color}. +del_node(Pid, Key) -> Pid ! {del_node, Key}. +change_node(Pid, Key, Color) -> Pid ! {change_node, Key, Color}. + +add_link(Pid, {FromKey, ToKey}) -> Pid ! {add_link, {FromKey, ToKey}}. +del_link(Pid, {FromKey, ToKey}) -> Pid ! {del_link, {FromKey, ToKey}}. + +stop(Pid, Reason) -> + Ref = erlang:monitor(process, Pid), + Pid ! {stop, Reason}, + receive + {'DOWN', Ref, _, _, _} -> + ok + end. + +set_dbl_click(Pid, Fun) -> Pid ! {set_dbl_click, Fun}. + +new(Parent, Options) -> + Env = wx:get_env(), + Me = self(), + Pid = spawn_link(fun() -> init([Parent, Me, Env, Options]) end), + receive {Pid, {?MODULE, Panel}} -> {Pid,Panel} end. + +init([ParentWin, Pid, Env, Options]) -> + wx:set_env(Env), + + BReset = wxButton:new(ParentWin, ?reset, [{label,"Reset"}]), + BFreeze = wxButton:new(ParentWin, ?freeze, [{label,"Freeze"}]), + BLock = wxButton:new(ParentWin, ?lock, [{label,"Lock"}]), + BUnlock = wxButton:new(ParentWin, ?unlock, [{label,"Unlock"}]), + BDelete = wxButton:new(ParentWin, ?delete, [{label,"Delete"}]), + + SQ = wxSlider:new(ParentWin, ?q_slider, ?default_q, 1, 500, [{style, ?wxVERTICAL}]), + SL = wxSlider:new(ParentWin, ?l_slider, ?default_l, 1, 500, [{style, ?wxVERTICAL}]), + SK = wxSlider:new(ParentWin, ?k_slider, ?default_k, 1, 500, [{style, ?wxVERTICAL}]), + Win = wxWindow:new(ParentWin, ?wxID_ANY, Options), + + ButtonSizer = wxBoxSizer:new(?wxVERTICAL), + wxSizer:add(ButtonSizer, BReset), + wxSizer:add(ButtonSizer, BFreeze), + wxSizer:add(ButtonSizer, BLock), + wxSizer:add(ButtonSizer, BUnlock), + wxSizer:add(ButtonSizer, BDelete), + + SliderSizer = wxBoxSizer:new(?wxHORIZONTAL), + wxSizer:add(SliderSizer, SQ, [{flag, ?wxEXPAND}, {proportion, 1}]), + wxSizer:add(SliderSizer, SL, [{flag, ?wxEXPAND}, {proportion, 1}]), + wxSizer:add(SliderSizer, SK, [{flag, ?wxEXPAND}, {proportion, 1}]), + wxSizer:add(ButtonSizer, SliderSizer, [{flag, ?wxEXPAND}, {proportion, 1}]), + + WindowSizer = wxBoxSizer:new(?wxHORIZONTAL), + wxSizer:add(WindowSizer, ButtonSizer, [{flag, ?wxEXPAND}, {proportion, 0}]), + wxSizer:add(WindowSizer, Win, [{flag, ?wxEXPAND}, {proportion, 1}]), + + wxButton:setToolTip(BReset, "Remove selection and unlock all nodes."), + wxButton:setToolTip(BFreeze, "Start/stop redraw of screen."), + wxButton:setToolTip(BLock, "Lock all selected nodes."), + wxButton:setToolTip(BUnlock, "Unlock all selected nodes."), + wxButton:setToolTip(BDelete, "Delete all selected nodes."), + + wxButton:setToolTip(SQ, "Control repulsive force. This can also be controlled with the mouse wheel on the canvas."), + wxButton:setToolTip(SL, "Control link length."), + wxButton:setToolTip(SK, "Control attractive force. Use with care."), + wxButton:setToolTip(Win, + "Drag mouse while left mouse button is pressed to perform various operations. " + "Combine with control key to select. Combine with shift key to lock single node."), + + wxButton:connect(BReset, command_button_clicked), + wxButton:connect(BFreeze, command_button_clicked), + wxButton:connect(BLock, command_button_clicked), + wxButton:connect(BUnlock, command_button_clicked), + wxButton:connect(BDelete, command_button_clicked), + + wxWindow:connect(SQ, command_slider_updated), + wxWindow:connect(SL, command_slider_updated), + wxWindow:connect(SK, command_slider_updated), + + wxWindow:connect(Win, enter_window), + wxWindow:connect(Win, move), + wxWindow:connect(Win, motion), + wxWindow:connect(Win, mousewheel), + wxWindow:connect(Win, key_up), + wxWindow:connect(Win, left_down), + wxWindow:connect(Win, left_up), + wxWindow:connect(Win, right_down), + wxWindow:connect(Win, paint, [{skip, true}]), + + Pen = wxPen:new({0,0,0}, [{width, 3}]), + Font = wxFont:new(12, ?wxSWISS, ?wxNORMAL, ?wxNORMAL,[]), + Brush = wxBrush:new({0,0,0}), + + Pid ! {self(), {?MODULE, WindowSizer}}, + + wxWindow:setFocus(Win), %% Get keyboard focus + + Vs = reltool_fgraph:new(), + Es = reltool_fgraph:new(), + + Me = self(), + Ticker = spawn_link(fun() -> ticker_init(Me) end), + + loop( #state{ parent_pid = Pid, + q_slider = SQ, + l_slider = SL, + k_slider = SK, + mouse_act = ?move, + frame = ParentWin, + window = Win, + is_frozen = false, + ticker = Ticker}, + #graph{ vs = Vs, + es = Es, + pen = Pen, + font = Font, + brush = Brush}). + +graph_add_node_unsure(Key, State, G = #graph{ vs = Vs }) -> + case reltool_fgraph:is_defined(Key, Vs) of + true -> G; + false -> graph_add_node(Key, State, G) + end. + +graph_add_node(Key, Color, G = #graph{ vs = Vs}) -> + Q = 20.0, % repulsive force + M = 0.5, % mass + P = {float(450 + random:uniform(100)), + float(450 + random:uniform(100))}, + G#graph{ vs = reltool_fgraph:add(Key, #fg_v{ p = P, m = M, q = Q, color = Color}, Vs)}. + +graph_change_node(Key, Color, G) -> + case reltool_fgraph:get(Key, G#graph.vs) of + undefined -> + G; + V -> + G#graph{ vs = reltool_fgraph:set(Key, V#fg_v{ color = Color }, G#graph.vs)} + end. + +graph_del_node(Key, G = #graph{ vs = Vs0, es = Es0}) -> + Vs = reltool_fgraph:del(Key, Vs0), + Es = delete_edges(Es0, [Key]), + G#graph{ vs = Vs, es = Es }. + +graph_add_link(Key0, Key1, G = #graph{ es = Es}) -> + K = 60.0, % attractive force + L = 5.0, % spring length + G#graph{ es = reltool_fgraph:add({Key0, Key1}, #fg_e{ k = K, l = L}, Es) }. + +graph_del_link(Key0, Key1, G = #graph{ es = Es}) -> + G#graph{ es = reltool_fgraph:del({Key0, Key1}, Es) }. + +ticker_init(Pid) -> + ticker_loop(Pid, 50). +ticker_loop(Pid, Time) -> + receive after Time -> + Pid ! {self(), redraw}, + T0 = now(), + receive {Pid, ok} -> ok end, + T1 = now(), + D = timer:now_diff(T1, T0)/1000, + case round(40 - D) of + Ms when Ms < 0 -> + %io:format("ticker: wait is 0 ms [fg ~7s ms] [fps ~7s]~n", [s(D), s(1000/D)]), + ticker_loop(Pid, 0); + Ms -> + %io:format("ticker: wait is ~3s ms [fg ~7s ms] [fps ~7s]~n", [s(Ms), s(D), s(1000/40)]), + ticker_loop(Pid, Ms) + end + end. + +delete_edges(Es, []) -> + Es; +delete_edges(Es, [Key|Keys]) -> + Edges = reltool_fgraph:foldl(fun + ({{K1, K2}, _}, Out) when K1 =:= Key -> [{K1,K2}|Out]; + ({{K1, K2}, _}, Out) when K2 =:= Key -> [{K1,K2}|Out]; + (_, Out) -> Out + end, [], Es), + Es1 = lists:foldl(fun + (K, Esi) -> reltool_fgraph:del(K, Esi) + end, Es, Edges), + delete_edges(Es1, Keys). + + +set_charge(Q, Vs) -> % Repulsive force + F = fun({Key, Value}) -> {Key, Value#fg_v{ q = Q}} end, + reltool_fgraph:map(F, Vs). + +set_length(L, Es) -> % Spring length + F = fun({Ps, E}) -> {Ps, E#fg_e{ l = L}} end, + reltool_fgraph:map(F, Es). + +set_spring(K, Es) -> % Attractive force + F = fun({Ps, E}) -> {Ps, E#fg_e{ k = K}} end, + reltool_fgraph:map(F, Es). + +loop(S, G) -> + receive + #wx{id = ?reset, event = #wxCommand{type=command_button_clicked}} -> + %% Remove selection and unlock all nodes + Q = ?default_q, + L = ?default_l, + K = ?default_k, + wxSlider:setValue(S#state.q_slider, Q), + wxSlider:setValue(S#state.l_slider, L), + wxSlider:setValue(S#state.k_slider, K), + Es = set_length(L, G#graph.es), + Es2 = set_spring(K, Es), + + Vs2 = reltool_fgraph:map(fun({Key, V}) -> + {Key, V#fg_v{selected = false, type = dynamic, q = Q}} + end, + G#graph.vs), + + {Xs, Ys} = reltool_fgraph:foldl(fun({_Key, #fg_v{p = {X, Y}}}, {Xs, Ys}) -> + {[X| Xs], [Y | Ys]} + end, + {[], []}, + Vs2), + %% io:format("Before: ~p\n", [G#graph.offset]), + Offset = + case length(Xs) of + 0 -> + {0, 0}; + N -> + MeanX = (lists:sum(Xs) / N), + MeanY = (lists:sum(Ys) / N), + {SizeX, SizeY} = wxWindow:getSize(S#state.window), + %% io:format("Min: ~p\n", [{lists:min(Xs), lists:min(Ys)}]), + %% io:format("Mean: ~p\n", [{MeanX, MeanY}]), + %% io:format("Max: ~p\n", [{lists:max(Xs), lists:max(Ys)}]), + %% io:format("Size: ~p\n", [{SizeX, SizeY}]), + %% {XM - (XS / 2), YM - (YS / 2)} + %% {0 - lists:min(Xs) + 20, 0 - lists:min(Ys) + 20} + {0 - MeanX + (SizeX / 2), 0 - MeanY + (SizeY / 2)} + end, + %% io:format("After: ~p\n", [Offset]), + loop(S, G#graph{vs = Vs2, es = Es2, offset = Offset, offset_state = false}); + #wx{id = ?freeze, event = #wxCommand{type=command_button_clicked}} -> + %% Start/stop redraw of screen + IsFrozen = + case S#state.is_frozen of + true -> + S#state.ticker ! {self(), ok}, + false; + false -> + true + end, + loop(S#state{is_frozen = IsFrozen}, G); + #wx{id = ?lock, event = #wxCommand{type=command_button_clicked}} -> + %% Lock all selected nodes + Vs = reltool_fgraph:map(fun + ({Key, V = #fg_v{selected = true}}) -> + {Key, V#fg_v{ type = static }}; + (KV) -> KV + end, G#graph.vs), + loop(S, G#graph{ vs = Vs }); + #wx{id = ?unlock, event = #wxCommand{type=command_button_clicked}} -> + %% Unlock all selected nodes + Vs = reltool_fgraph:map(fun + ({Key, V = #fg_v{selected = true}}) -> + {Key, V#fg_v{ type = dynamic }}; + (KV) -> KV + end, G#graph.vs), + loop(S, G#graph{ vs = Vs }); + #wx{id = ?delete, event = #wxCommand{type=command_button_clicked}} -> + %% Delete all selected nodes + {Vs1, Keys} = reltool_fgraph:foldl(fun + ({Key, #fg_v{ selected = true}}, {Vs, Ks}) -> + {reltool_fgraph:del(Key,Vs), [Key|Ks]}; + (_, {Vs, Ks}) -> {Vs, Ks} + end, {G#graph.vs,[]}, G#graph.vs), + Es = delete_edges(G#graph.es, Keys), + loop(S, G#graph{ vs = Vs1, es = Es}); + + #wx{id = ?select, event = #wxCommand{type=command_button_clicked}} -> + loop(S#state{ mouse_act = ?select }, G); + + #wx{id = ?move, event = #wxCommand{type=command_button_clicked}} -> + loop(S#state{ mouse_act = ?move }, G); + + #wx{id = ?q_slider, event = #wxCommand{type=command_slider_updated, commandInt = Q}} -> + loop(S, G#graph{ vs = set_charge(Q, G#graph.vs)}); + #wx{id = ?l_slider, event = #wxCommand{type=command_slider_updated, commandInt = L}} -> + loop(S, G#graph{ es = set_length(L, G#graph.es)}); + #wx{id = ?k_slider, event = #wxCommand{type=command_slider_updated, commandInt = K}} -> + loop(S, G#graph{ es = set_spring(K, G#graph.es)}); + #wx{event=#wxKey{type=key_up, keyCode = 127}} -> % delete + {Vs1, Keys} = + reltool_fgraph:foldl(fun({Key, #fg_v{ selected = true}}, {Vs, Ks}) -> + {reltool_fgraph:del(Key,Vs), [Key|Ks]}; + (_, {Vs, Ks}) -> + {Vs, Ks} + end, + {G#graph.vs,[]}, G#graph.vs), + Es = delete_edges(G#graph.es, Keys), + loop(S, G#graph{ vs = Vs1, es = Es}); + #wx{event=#wxKey{type=key_up}} -> + loop(S, G); + #wx{event=#wxKey{type=key_down}} -> + loop(S, G); + + %% mouse + #wx{event=#wxMouse{type=left_down, shiftDown=Shift, controlDown=Ctrl, x=X, y=Y}} -> + if + Shift -> + loop(S, mouse_left_down_move(G, {X,Y})); + Ctrl -> + loop(S, mouse_left_down_select(G, {X,Y})); + S#state.mouse_act =:= ?move -> + loop(S, mouse_left_down_move(G, {X,Y})); + S#state.mouse_act =:= ?select -> + loop(S, mouse_left_down_select(G, {X,Y})) + end; + #wx{event=#wxMouse{type=motion, shiftDown=Shift, controlDown=Ctrl, x=X, y=Y}} -> + if + Shift -> + loop(S, mouse_motion_move(G, {X,Y})); + Ctrl -> + loop(S, mouse_motion_select(G, {X,Y})); + S#state.mouse_act =:= ?move -> + loop(S, mouse_motion_move(G, {X,Y})); + S#state.mouse_act =:= ?select -> + loop(S, mouse_motion_select(G, {X,Y})) + end; + #wx{event=#wxMouse{type=left_up, shiftDown=Shift, controlDown=Ctrl, x=X, y=Y}} -> + if + Shift -> + loop(S, mouse_left_up_move(G, {X,Y}, Shift)); + Ctrl -> + loop(S, mouse_left_up_select(G, {X,Y})); + S#state.mouse_act =:= ?move -> + loop(S, mouse_left_up_move(G, {X,Y}, Shift)); + S#state.mouse_act =:= ?select -> + loop(S, mouse_left_up_select(G, {X,Y})) + end; + + #wx{event=#wxMouse{type=right_down,x=_X,y=_Y}} -> + loop(S, G); + %% mouse wheel + #wx{event=#wxMouse{type=mousewheel, wheelRotation=Rotation}} -> + Q = wxSlider:getValue(S#state.q_slider), + if + Rotation > 0, Q > 5 -> + wxSlider:setValue(S#state.q_slider, Q - 4), + loop(S, G#graph{ vs = set_charge(Q - 4, G#graph.vs) }); + Rotation < 0 -> + wxSlider:setValue(S#state.q_slider, Q + 4), + loop(S, G#graph{ vs = set_charge(Q + 4, G#graph.vs) }); + true -> + loop(S, G) + end; + + %% #wx{event=#wxClose{}} -> + %% catch wxWindow:'Destroy'(S#state.frame); + %% #wx{id=?wxID_EXIT, event=#wxCommand{type=command_menu_selected}} -> + %% wxWindow:close(S#state.frame,[]); + #wx{obj=_Win,event=#wxPaint{}} -> + redraw(S, G), + loop(S, G); + #wx{obj=Win,event=#wxMouse{type=enter_window}} -> + wxWindow:setFocus(Win), + loop(S, G); + + %% Graph manipulation + {add_node, Key, State} -> + loop(S, graph_add_node_unsure(Key, State, G)); + {del_node, Key} -> + loop(S, graph_del_node(Key, G)); + {change_node, Key, Color} -> + loop(S, graph_change_node(Key, Color, G)); + {add_link, {K0,K1}} -> + loop(S, graph_add_link(K0, K1, G)); + {del_link, {K0,K1}} -> + loop(S, graph_del_link(K0, K1, G)); + + {Req, redraw} -> + {SizeX, SizeY} = wxWindow:getSize(S#state.window), + Vs = reltool_fgraph:step(G#graph.vs, G#graph.es, {SizeX/2.0 - 20.0, SizeY/2.0}), + case S#state.is_frozen of + false -> + Req ! {self(), ok}; + true -> + ignore + end, + redraw(S, G), + loop(S, G#graph{ vs = Vs} ); + + {stop, Reason} -> + unlink(S#state.parent_pid), + exit(Reason); + + Other -> + error_logger:format("~p~p got unexpected message:\n\t~p\n", + [?MODULE, self(), Other]), + loop(S, G) + end. + +mouse_left_down_select(G, {X0,Y0}) -> + G#graph{ select = {{X0,Y0}, {X0,Y0}} }. + +mouse_left_down_move(#graph{vs = Vs} = G, {X, Y}) -> + % point on node? + case coord_to_key(G, {X, Y}) of + false -> + G#graph{ offset_state = {X,Y}}; + {true, Key} -> + V = #fg_v{ type = Type} = reltool_fgraph:get(Key, Vs), + G#graph{ vs = reltool_fgraph:set(Key, V#fg_v{ type = moving}, Vs), select = {node, Key, Type, X, Y} } + end. + +coord_to_key(#graph{vs = Vs, offset = {Xo, Yo}}, {X, Y}) -> + Xr = X - Xo, + Yr = Y - Yo, + reltool_fgraph:foldl(fun({Key, #fg_v{ p = {Px, Py}}}, _) when abs(Px - Xr) < 10, + abs(Py - Yr) < 10 -> {true, Key}; + (_, Out) -> Out + end, false, Vs). + +mouse_left_up_select(G, {_X,_Y}) -> + case G#graph.select of + {{X0,Y0}, {X1, Y1}} -> + {Xo, Yo} = G#graph.offset, + Xmin = lists:min([X0,X1]) - Xo, + Xmax = lists:max([X1,X0]) - Xo, + Ymin = lists:min([Y0,Y1]) - Yo, + Ymax = lists:max([Y1,Y0]) - Yo, + Vs = reltool_fgraph:map(fun + ({Key, Value = #fg_v{ p = {Px, Py}}}) + when Px > Xmin, Px < Xmax, Py > Ymin, Py < Ymax -> + {Key, Value#fg_v{ selected = true }}; + ({Key, Value}) -> {Key, Value#fg_v{ selected = false }} + end, G#graph.vs), + G#graph{ select = none, vs = Vs}; + _ -> + G#graph{ select = none} + end. + +mouse_left_up_move(G = #graph{ select = Select, vs = Vs} = G, {X,Y}, Shift) -> + case Select of + {node, Key, _, X, Y} -> + io:format("click: ~p\n", [Key]), + G#graph{ select = none, offset_state = false }; + {node, Key, Type, _, _} -> + V = reltool_fgraph:get(Key, Vs), + Type2 = + case Shift of + true -> static; + false -> Type + end, + G#graph{ select = none, + vs = reltool_fgraph:set(Key, V#fg_v{ type = Type2}, Vs), + offset_state = false }; + _ -> + G#graph{ select = none, offset_state = false } + end. + +mouse_motion_select(G, {X,Y}) -> + case G#graph.select of + {P0, _P1} -> G#graph{ select = {P0, {X,Y}}}; + _ -> G + end. + +mouse_motion_move(G = #graph{ select = {node, Key, _, _, _}, vs = Vs}, {X,Y}) -> + {Xo, Yo} = G#graph.offset, + V = reltool_fgraph:get(Key, Vs), + V2 = V#fg_v{ p = {float(X - Xo), float(Y - Yo)}}, + G#graph{ vs = reltool_fgraph:set(Key, V2, Vs) }; +mouse_motion_move(G, {X,Y}) -> + case G#graph.offset_state of + {X1,Y1} -> + {X0, Y0} = G#graph.offset, + G#graph{ offset_state = {X,Y}, + offset = {X0 - (X1 - X), Y0 - (Y1 - Y)} }; + _ -> + G + end. + +redraw(#state{window=Win}, G) -> + DC0 = wxClientDC:new(Win), + DC = wxBufferedDC:new(DC0), + Size = wxWindow:getSize(Win), + redraw(DC, Size, G), + wxBufferedDC:destroy(DC), + wxClientDC:destroy(DC0), + ok. + +redraw(DC, _Size, G) -> + wx:batch(fun() -> + + Pen = G#graph.pen, + Font = G#graph.font, + Brush = G#graph.brush, + wxDC:setTextForeground(DC,?color_fg), + wxBrush:setColour(Brush, ?color_bg), + wxDC:setBrush(DC, Brush), + wxDC:setBackground(DC, Brush), + wxPen:setWidth(Pen, 1), + wxDC:clear(DC), + + % draw vertices and edges + wxPen:setColour(Pen, ?color_fg), + wxDC:setPen(DC,Pen), + + %draw_es(DC, G#graph.es_pts, G#graph.offset), + draw_es(DC, G#graph.vs, G#graph.es, G#graph.offset, Pen, Brush), + draw_vs(DC, G#graph.vs, G#graph.offset, Pen, Brush), + + % draw selection box + wxPen:setColour(Pen, ?color_fg), + wxDC:setPen(DC,Pen), + draw_select_box(DC, G#graph.select), + + % draw information text + wxFont:setWeight(Font,?wxNORMAL), + draw_text(DC, reltool_fgraph:size(G#graph.vs), reltool_fgraph:size(G#graph.es), G#graph.ke), + ok + end). + +draw_select_box(DC, {{X0,Y0}, {X1,Y1}}) -> + draw_line(DC, {X0,Y0}, {X1,Y0}, {0,0}), + draw_line(DC, {X1,Y1}, {X1,Y0}, {0,0}), + draw_line(DC, {X1,Y1}, {X0,Y1}, {0,0}), + draw_line(DC, {X0,Y0}, {X0,Y1}, {0,0}), + ok; +draw_select_box(_DC, _) -> + ok. + +draw_es(DC, Vs, Es, Po, Pen, Brush) -> + reltool_fgraph:foreach(fun + ({{K1, K2}, _}) -> + #fg_v{ p = P1} = reltool_fgraph:get(K1, Vs), + #fg_v{ p = P2} = reltool_fgraph:get(K2, Vs), + draw_arrow(DC, P1, P2, Po, Pen, Brush) + end, Es). + +draw_arrow(DC, {X0,Y0}, {X1, Y1}, {X, Y}, Pen, Brush) -> + Xdiff = (X0 - X1) / 4, + Ydiff = (Y0 - Y1) / 4, + X2 = X1 + Xdiff + X, + Y2 = Y1 + Ydiff + Y, + wxDC:setPen(DC, Pen), + wxDC:setBrush(DC, Brush), + + draw_line(DC, {X0,Y0}, {X1, Y1}, {X, Y}), + + %% Draw arrow head + Radians = calc_angle({X0, Y0}, {X1, Y1}), + Len = 10, + %% Angle = 30, + %% Degrees = radians_to_degrees(Radians), + %% Radians2 = degrees_to_radians(Degrees + Angle + 180), + %% Radians3 = degrees_to_radians(Degrees - Angle + 180), + Radians2 = Radians + 3.665191429188092, + Radians3 = Radians + 2.617993877991494, + {X3, Y3} = calc_point({X2, Y2}, Len, Radians2), + {X4, Y4} = calc_point({X2, Y2}, Len, Radians3), + Points = [{round(X2), round(Y2)}, + {round(X3), round(Y3)}, + {round(X4), round(Y4)}], + wxDC:drawPolygon(DC, Points, []). + +draw_line(DC, {X0,Y0}, {X1, Y1}, {X, Y}) -> + wxDC:drawLine(DC, {round(X0 + X), round(Y0 + Y)}, {round(X1 + X), round(Y1 + Y)}). + +draw_vs(DC, Vs, {Xo, Yo}, Pen, Brush) -> + reltool_fgraph:foreach(fun({Key, #fg_v{ p ={X, Y}, color = Color, selected = Sel}}) -> + String = s(Key), + case Sel of + true -> + wxPen:setColour(Pen, ?color_fg), + wxBrush:setColour(Brush, ?color_bg), + wxDC:setPen(DC,Pen), + wxDC:setBrush(DC, Brush), + SelProps = {round(X-12 + Xo), round(Y-12 + Yo), 24, 24}, + wxDC:drawRoundedRectangle(DC, SelProps, float(?ARC_R)), + ok; + false -> + ok + end, + case Color of + default -> + wxPen:setColour(Pen, ?color_default), + wxBrush:setColour(Brush, ?color_default_bg); + alternate -> + wxPen:setColour(Pen, ?color_alternate), + wxBrush:setColour(Brush, ?color_alternate_bg); + {FgColor, BgColor} -> + wxPen:setColour(Pen, FgColor), + wxBrush:setColour(Brush, BgColor); + Color -> + wxPen:setColour(Pen, Color), + wxBrush:setColour(Brush, Color) + end, + wxDC:setPen(DC,Pen), + wxDC:setBrush(DC, Brush), + NodeProps = {round(X-8 + Xo),round(Y-8 + Yo),17,17}, + wxDC:drawRoundedRectangle(DC, NodeProps, float(?ARC_R)), + wxDC:drawText(DC, String, {round(X + Xo), round(Y + Yo)}), + ok; + (_) -> + ok + end, + Vs). + +draw_text(DC, Nvs, Nes, _KE) -> + VsString = "#nodes: " ++ integer_to_list(Nvs), + EsString = "#links: " ++ integer_to_list(Nes), + %% KEString = " ke: " ++ s(KE), + wxDC:drawText(DC, VsString, {10,10}), + wxDC:drawText(DC, EsString, {10,25}), + %% wxDC:drawText(DC, KEString, {10,40}), + ok. + +s(Format, Terms) -> lists:flatten(io_lib:format(Format, Terms)). +s(Term) when is_float(Term) -> s("~.2f", [Term]); +s(Term) when is_integer(Term) -> integer_to_list(Term); +s(Term) when is_atom(Term) -> atom_to_list(Term); +s(Term) -> s("~p", [Term]). + +%% Calclulate angle in radians for a line between two points +calc_angle({X1, Y1}, {X2, Y2}) -> + math:atan2((Y2 - Y1), (X2 - X1)). + +%% Calc new point at a given distance and angle from another point +calc_point({X, Y}, Length, Radians) -> + X2 = round(X + Length * math:cos(Radians)), + Y2 = round(Y + Length * math:sin(Radians)), + {X2, Y2}. + +%% %% Convert from an angle in radians to degrees +%% radians_to_degrees(Radians) -> +%% Radians * 180 / math:pi(). +%% +%% %% Convert from an angle in degrees to radians +%% degrees_to_radians(Degrees) -> +%% Degrees * math:pi() / 180. |