1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2009-2018. 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-module(reltool_fgraph_win).
21
22-export([
23         new/2,
24         add_node/2,
25         add_node/3,
26         del_node/2,
27         change_node/3,
28         add_link/2,
29         del_link/2,
30         set_dbl_click/2,
31         stop/2
32        ]).
33
34-include_lib("wx/include/wx.hrl").
35-include("reltool_fgraph.hrl").
36
37-record(state,
38        {
39          parent_pid,
40          frame,
41          window,
42          width,
43          height,
44          q_slider,
45          l_slider,
46          k_slider,
47          mouse_act,
48          is_frozen,
49          ticker
50         }).
51
52-record(graph,
53        {
54          pen,
55          brush,
56          font,
57          select = none,
58          offset = {0,0},
59          offset_state = false,
60          ke = 0,
61          vs = [],
62          es = []
63         }).
64
65-define(BRD,10).
66-define(ARC_R, 10).
67
68-define(reset, 80).
69-define(lock, 81).
70-define(unlock, 82).
71-define(move, 83).
72-define(select, 84).
73-define(delete, 85).
74-define(freeze, 86).
75
76-define(q_slider, 90).
77-define(l_slider, 91).
78-define(k_slider, 92).
79
80-define(default_q, 20).
81-define(default_l, 20).
82-define(default_k, 20).
83
84-define(color_bg, {45,50,95}).
85-define(color_fg, {235,245,230}).
86-define(color_default, {10,220,20}).
87-define(color_default_bg, {20,230,30}).
88-define(color_alternate, {220,10,20}).
89-define(color_alternate_bg, {230,20,30}).
90
91add_node(Pid, Key) -> add_node(Pid, Key, default).
92add_node(Pid, Key, Color) -> Pid ! {add_node, Key, Color}.
93del_node(Pid, Key) -> Pid ! {del_node, Key}.
94change_node(Pid, Key, Color) ->  Pid ! {change_node, Key, Color}.
95
96add_link(Pid, {FromKey, ToKey}) -> Pid ! {add_link, {FromKey, ToKey}}.
97del_link(Pid, {FromKey, ToKey}) -> Pid ! {del_link, {FromKey, ToKey}}.
98
99stop(Pid, Reason) ->
100    Ref = erlang:monitor(process, Pid),
101    Pid ! {stop, Reason},
102    receive
103        {'DOWN', Ref, _, _, _} ->
104            ok
105    end.
106
107set_dbl_click(Pid, Fun) -> Pid ! {set_dbl_click, Fun}.
108
109new(Parent, Options) ->
110    Env = wx:get_env(),
111    Me  = self(),
112    Pid = spawn_link(fun() -> init([Parent, Me, Env, Options]) end),
113    receive {Pid, {?MODULE, Panel}} -> {Pid,Panel} end.
114
115init([ParentWin, Pid, Env, Options]) ->
116    wx:set_env(Env),
117
118    BReset  = wxButton:new(ParentWin, ?reset,  [{label,"Reset"}]),
119    BFreeze = wxButton:new(ParentWin, ?freeze, [{label,"Freeze"}]),
120    BLock   = wxButton:new(ParentWin, ?lock,   [{label,"Lock"}]),
121    BUnlock = wxButton:new(ParentWin, ?unlock, [{label,"Unlock"}]),
122    BDelete = wxButton:new(ParentWin, ?delete, [{label,"Delete"}]),
123
124    SQ  = wxSlider:new(ParentWin, ?q_slider, ?default_q, 1, 500,
125		       [{style, ?wxVERTICAL}]),
126    SL  = wxSlider:new(ParentWin, ?l_slider, ?default_l, 1, 500,
127		       [{style, ?wxVERTICAL}]),
128    SK  = wxSlider:new(ParentWin, ?k_slider, ?default_k, 1, 500,
129		       [{style, ?wxVERTICAL}]),
130    Win = wxWindow:new(ParentWin, ?wxID_ANY, Options),
131
132    ButtonSizer = wxBoxSizer:new(?wxVERTICAL),
133    wxSizer:add(ButtonSizer, BReset),
134    wxSizer:add(ButtonSizer, BFreeze),
135    wxSizer:add(ButtonSizer, BLock),
136    wxSizer:add(ButtonSizer, BUnlock),
137    wxSizer:add(ButtonSizer, BDelete),
138
139    SliderSizer = wxBoxSizer:new(?wxHORIZONTAL),
140    wxSizer:add(SliderSizer, SQ, [{flag, ?wxEXPAND}, {proportion, 1}]),
141    wxSizer:add(SliderSizer, SL, [{flag, ?wxEXPAND}, {proportion, 1}]),
142    wxSizer:add(SliderSizer, SK, [{flag, ?wxEXPAND}, {proportion, 1}]),
143    wxSizer:add(ButtonSizer, SliderSizer, [{flag, ?wxEXPAND}, {proportion, 1}]),
144
145    WindowSizer = wxBoxSizer:new(?wxHORIZONTAL),
146    wxSizer:add(WindowSizer, ButtonSizer, [{flag, ?wxEXPAND}, {proportion, 0}]),
147    wxSizer:add(WindowSizer, Win, [{flag, ?wxEXPAND}, {proportion, 1}]),
148
149    wxButton:setToolTip(BReset, "Remove selection and unlock all nodes."),
150    wxButton:setToolTip(BFreeze, "Start/stop redraw of screen."),
151    wxButton:setToolTip(BLock, "Lock all selected nodes."),
152    wxButton:setToolTip(BUnlock, "Unlock all selected nodes."),
153    wxButton:setToolTip(BDelete, "Delete all selected nodes."),
154
155    wxButton:setToolTip(SQ, "Control repulsive force. This can also be"
156			" controlled with the mouse wheel on the canvas."),
157    wxButton:setToolTip(SL, "Control link length."),
158    wxButton:setToolTip(SK, "Control attractive force. Use with care."),
159    wxButton:setToolTip(Win,
160			"Drag mouse while left mouse button is pressed "
161			"to perform various operations. "
162			"Combine with control key to select. Combine "
163			"with shift key to lock single node."),
164
165    wxButton:connect(BReset,  command_button_clicked),
166    wxButton:connect(BFreeze, command_button_clicked),
167    wxButton:connect(BLock,   command_button_clicked),
168    wxButton:connect(BUnlock, command_button_clicked),
169    wxButton:connect(BDelete, command_button_clicked),
170
171    wxWindow:connect(SQ, command_slider_updated),
172    wxWindow:connect(SL, command_slider_updated),
173    wxWindow:connect(SK, command_slider_updated),
174
175    wxWindow:connect(Win, enter_window),
176    wxWindow:connect(Win, move),
177    wxWindow:connect(Win, motion),
178    wxWindow:connect(Win, mousewheel),
179    wxWindow:connect(Win, key_up),
180    wxWindow:connect(Win, left_down),
181    wxWindow:connect(Win, left_up),
182    wxWindow:connect(Win, right_down),
183    wxWindow:connect(Win, paint,  [{skip, true}]),
184
185    Pen   = wxPen:new({0,0,0}, [{width, 3}]),
186    Font  = wxFont:new(12, ?wxSWISS, ?wxNORMAL, ?wxNORMAL,[]),
187    Brush = wxBrush:new({0,0,0}),
188
189    Pid ! {self(), {?MODULE, WindowSizer}},
190
191    wxWindow:setFocus(Win), %% Get keyboard focus
192
193    Vs = reltool_fgraph:new(),
194    Es = reltool_fgraph:new(),
195
196    Me = self(),
197    Ticker = spawn_link(fun() -> ticker_init(Me) end),
198
199    loop( #state{ parent_pid = Pid,
200                  q_slider = SQ,
201		  l_slider = SL,
202		  k_slider = SK,
203                  mouse_act = ?move,
204		  frame = ParentWin,
205		  window = Win,
206                  is_frozen = false,
207		  ticker = Ticker},
208          #graph{ vs = Vs,
209		  es = Es,
210		  pen = Pen,
211		  font = Font,
212		  brush = Brush}).
213
214graph_add_node_unsure(Key, State, G = #graph{ vs = Vs }) ->
215    case reltool_fgraph:is_defined(Key, Vs) of
216        true  -> G;
217        false -> graph_add_node(Key, State, G)
218    end.
219
220graph_add_node(Key, Color, G = #graph{ vs = Vs}) ->
221    Q  = 20.0,   % repulsive force
222    M  = 0.5,    % mass
223    P  = {float(450 + rand:uniform(100)),
224	  float(450 + rand:uniform(100))},
225    G#graph{ vs = reltool_fgraph:add(Key,
226				     #fg_v{ p = P, m = M, q = Q, color = Color},
227				     Vs)}.
228
229graph_change_node(Key, Color, G) ->
230    case reltool_fgraph:get(Key, G#graph.vs) of
231	undefined ->
232	    G;
233	V ->
234	    G#graph{ vs = reltool_fgraph:set(Key, V#fg_v{ color = Color },
235					     G#graph.vs)}
236    end.
237
238graph_del_node(Key, G = #graph{ vs = Vs0, es = Es0}) ->
239    Vs = reltool_fgraph:del(Key, Vs0),
240    Es = delete_edges(Es0, [Key]),
241    G#graph{ vs = Vs, es = Es }.
242
243graph_add_link(Key0, Key1, G = #graph{ es = Es}) ->
244    K  = 60.0,   % attractive force
245    L  =  5.0,   % spring length
246    G#graph{ es = reltool_fgraph:add({Key0, Key1}, #fg_e{ k = K, l = L}, Es) }.
247
248graph_del_link(Key0, Key1, G = #graph{ es = Es}) ->
249    G#graph{ es = reltool_fgraph:del({Key0, Key1}, Es) }.
250
251ticker_init(Pid) ->
252    ticker_loop(Pid, 50).
253ticker_loop(Pid, Time) ->
254    receive after Time ->
255        Pid ! {self(), redraw},
256        T0 = erlang:monotonic_time(),
257        receive {Pid, ok} -> ok end,
258        T1 = erlang:monotonic_time(),
259        D = erlang:convert_time_unit(T1-T0, native, milli_seconds),
260        case round(40 - D) of
261            Ms when Ms < 0 ->
262                %io:format("ticker: wait is   0 ms [fg ~7s ms] [fps ~7s]~n",
263		%          [s(D), s(1000/D)]),
264                ticker_loop(Pid, 0);
265            Ms ->
266                %io:format("ticker: wait is ~3s ms [fg ~7s ms] [fps ~7s]~n",
267		%          [s(Ms), s(D), s(1000/40)]),
268                ticker_loop(Pid, Ms)
269        end
270    end.
271
272delete_edges(Es, []) ->
273    Es;
274delete_edges(Es, [Key|Keys]) ->
275    Edges = reltool_fgraph:foldl(fun
276        ({{K1, K2}, _}, Out) when K1 =:= Key -> [{K1,K2}|Out];
277        ({{K1, K2}, _}, Out) when K2 =:= Key -> [{K1,K2}|Out];
278        (_, Out) -> Out
279    end, [], Es),
280    Es1 = lists:foldl(fun
281        (K, Esi) -> reltool_fgraph:del(K, Esi)
282    end, Es, Edges),
283    delete_edges(Es1, Keys).
284
285
286set_charge(Q, Vs) -> % Repulsive force
287    F = fun({Key, Value}) -> {Key, Value#fg_v{ q = Q}} end,
288    reltool_fgraph:map(F, Vs).
289
290set_length(L, Es) -> % Spring length
291    F  = fun({Ps, E}) -> {Ps, E#fg_e{ l = L}} end,
292    reltool_fgraph:map(F, Es).
293
294set_spring(K, Es) -> % Attractive force
295    F  = fun({Ps, E}) -> {Ps, E#fg_e{ k = K}} end,
296    reltool_fgraph:map(F, Es).
297
298loop(S, G) ->
299    receive
300        #wx{id = ?reset, event = #wxCommand{type=command_button_clicked}} ->
301	    %% Remove selection and unlock all nodes
302            Q = ?default_q,
303            L = ?default_l,
304            K = ?default_k,
305            wxSlider:setValue(S#state.q_slider, Q),
306            wxSlider:setValue(S#state.l_slider, L),
307            wxSlider:setValue(S#state.k_slider, K),
308            Es = set_length(L, G#graph.es),
309            Es2 = set_spring(K, Es),
310
311            Vs2 =
312		reltool_fgraph:map(fun({Key, V}) ->
313					   {Key, V#fg_v{selected = false,
314							type = dynamic,
315							q = Q}}
316				   end,
317				   G#graph.vs),
318
319            {Xs, Ys} =
320		reltool_fgraph:foldl(fun({_Key,
321					  #fg_v{p = {X, Y}}}, {Xs, Ys}) ->
322					     {[X| Xs], [Y | Ys]}
323				     end,
324				     {[], []},
325				     Vs2),
326	   %% io:format("Before: ~p\n", [G#graph.offset]),
327	    Offset =
328                case length(Xs) of
329                    0 ->
330                        {0, 0};
331                    N ->
332			MeanX = (lists:sum(Xs) / N),
333			MeanY = (lists:sum(Ys) / N),
334			{SizeX, SizeY} = wxWindow:getSize(S#state.window),
335			%% io:format("Min: ~p\n",
336			%%           [{lists:min(Xs), lists:min(Ys)}]),
337			%% io:format("Mean: ~p\n",
338			%%           [{MeanX, MeanY}]),
339			%% io:format("Max: ~p\n",
340			%%           [{lists:max(Xs), lists:max(Ys)}]),
341			%% io:format("Size: ~p\n", [{SizeX, SizeY}]),
342			%% {XM - (XS / 2), YM - (YS / 2)}
343			%% {0 - lists:min(Xs) + 20, 0 - lists:min(Ys) + 20}
344			{0 - MeanX + (SizeX / 2), 0 - MeanY + (SizeY / 2)}
345                end,
346	    %% io:format("After: ~p\n", [Offset]),
347	    loop(S, G#graph{vs = Vs2,
348			    es = Es2,
349			    offset = Offset,
350			    offset_state = false});
351        #wx{id = ?freeze, event = #wxCommand{type=command_button_clicked}} ->
352	    %% Start/stop redraw of screen
353            IsFrozen =
354                case S#state.is_frozen of
355                    true ->
356                        S#state.ticker ! {self(), ok},
357                        false;
358                    false ->
359                        true
360                end,
361            loop(S#state{is_frozen = IsFrozen}, G);
362        #wx{id = ?lock, event = #wxCommand{type=command_button_clicked}} ->
363	    %% Lock all selected nodes
364            Vs = reltool_fgraph:map(fun
365                            ({Key, V = #fg_v{selected = true}}) ->
366				   {Key, V#fg_v{ type = static  }};
367                            (KV) -> KV
368                           end, G#graph.vs),
369            loop(S, G#graph{ vs = Vs });
370        #wx{id = ?unlock, event = #wxCommand{type=command_button_clicked}} ->
371	    %% Unlock all selected nodes
372            Vs = reltool_fgraph:map(fun
373                            ({Key, V = #fg_v{selected = true}}) ->
374				   {Key, V#fg_v{ type = dynamic }};
375                            (KV) -> KV
376                           end, G#graph.vs),
377            loop(S, G#graph{ vs = Vs });
378        #wx{id = ?delete, event = #wxCommand{type=command_button_clicked}} ->
379	    %% Delete all selected nodes
380            {Vs1, Keys} =
381		reltool_fgraph:foldl(fun
382					 ({Key,
383					   #fg_v{ selected = true}},
384					  {Vs, Ks}) ->
385					     {reltool_fgraph:del(Key,Vs),
386					      [Key|Ks]};
387					 (_, {Vs, Ks}) ->
388					     {Vs, Ks}
389                                      end, {G#graph.vs,[]}, G#graph.vs),
390            Es = delete_edges(G#graph.es, Keys),
391            loop(S, G#graph{ vs = Vs1, es = Es});
392
393        #wx{id = ?select, event = #wxCommand{type=command_button_clicked}} ->
394            loop(S#state{ mouse_act = ?select }, G);
395
396        #wx{id = ?move, event = #wxCommand{type=command_button_clicked}} ->
397            loop(S#state{ mouse_act = ?move }, G);
398
399        #wx{id = ?q_slider, event = #wxCommand{type=command_slider_updated,
400					       commandInt = Q}} ->
401            loop(S, G#graph{ vs = set_charge(Q, G#graph.vs)});
402        #wx{id = ?l_slider, event = #wxCommand{type=command_slider_updated,
403					       commandInt = L}} ->
404            loop(S, G#graph{ es = set_length(L, G#graph.es)});
405        #wx{id = ?k_slider, event = #wxCommand{type=command_slider_updated,
406					       commandInt = K}} ->
407            loop(S, G#graph{ es = set_spring(K, G#graph.es)});
408        #wx{event=#wxKey{type=key_up, keyCode = 127}} -> % delete
409            {Vs1, Keys} =
410		reltool_fgraph:foldl(fun({Key,
411					  #fg_v{ selected = true}},
412					 {Vs, Ks}) ->
413					     {reltool_fgraph:del(Key,Vs),
414					      [Key|Ks]};
415					(_, {Vs, Ks}) ->
416					     {Vs, Ks}
417				     end,
418				     {G#graph.vs,[]}, G#graph.vs),
419            Es = delete_edges(G#graph.es, Keys),
420            loop(S, G#graph{ vs = Vs1, es = Es});
421        #wx{event=#wxKey{type=key_up}} ->
422            loop(S, G);
423        #wx{event=#wxKey{type=key_down}} ->
424            loop(S, G);
425
426        %% mouse
427        #wx{event=#wxMouse{type=left_down,
428			   shiftDown=Shift,
429			   controlDown=Ctrl,
430			   x=X,
431			   y=Y}} ->
432            if
433                Shift ->
434                    loop(S, mouse_left_down_move(G, {X,Y}));
435                Ctrl ->
436                    loop(S, mouse_left_down_select(G, {X,Y}));
437                S#state.mouse_act =:= ?move ->
438                    loop(S, mouse_left_down_move(G, {X,Y}));
439                S#state.mouse_act =:= ?select ->
440                    loop(S, mouse_left_down_select(G, {X,Y}))
441            end;
442        #wx{event=#wxMouse{type=motion,
443			   shiftDown=Shift,
444			   controlDown=Ctrl,
445			   x=X,
446			   y=Y}} ->
447            if
448                Shift ->
449                    loop(S, mouse_motion_move(G, {X,Y}));
450                Ctrl ->
451                    loop(S, mouse_motion_select(G, {X,Y}));
452                S#state.mouse_act =:= ?move ->
453                    loop(S, mouse_motion_move(G, {X,Y}));
454                S#state.mouse_act =:= ?select ->
455                    loop(S, mouse_motion_select(G, {X,Y}))
456            end;
457        #wx{event=#wxMouse{type=left_up,
458			   shiftDown=Shift,
459			   controlDown=Ctrl, x=X, y=Y}} ->
460            if
461                Shift ->
462                    loop(S, mouse_left_up_move(G, {X,Y}, Shift));
463                Ctrl ->
464                    loop(S, mouse_left_up_select(G, {X,Y}));
465                S#state.mouse_act =:= ?move ->
466                    loop(S, mouse_left_up_move(G, {X,Y}, Shift));
467                S#state.mouse_act =:= ?select ->
468                    loop(S, mouse_left_up_select(G, {X,Y}))
469            end;
470
471        #wx{event=#wxMouse{type=right_down,x=_X,y=_Y}} ->
472            loop(S, G);
473        %% mouse wheel
474        #wx{event=#wxMouse{type=mousewheel, wheelRotation=Rotation}} ->
475            Q = wxSlider:getValue(S#state.q_slider),
476            if
477		Rotation > 0, Q > 5 ->
478                    wxSlider:setValue(S#state.q_slider, Q - 4),
479                    loop(S, G#graph{ vs = set_charge(Q - 4, G#graph.vs) });
480		Rotation < 0 ->
481                    wxSlider:setValue(S#state.q_slider, Q + 4),
482                    loop(S, G#graph{ vs = set_charge(Q + 4, G#graph.vs) });
483                true ->
484                    loop(S, G)
485            end;
486
487        %% #wx{event=#wxClose{}} ->
488        %%     catch wxWindow:'Destroy'(S#state.frame);
489        %% #wx{id=?wxID_EXIT, event=#wxCommand{type=command_menu_selected}} ->
490        %%     wxWindow:close(S#state.frame,[]);
491        #wx{obj=_Win,event=#wxPaint{}} ->
492            redraw(S, G),
493            loop(S, G);
494        #wx{obj=Win,event=#wxMouse{type=enter_window}} ->
495            wxWindow:setFocus(Win),
496            loop(S, G);
497
498        %% Graph manipulation
499        {add_node, Key, State} ->
500	    loop(S, graph_add_node_unsure(Key, State, G));
501        {del_node, Key} ->
502	    loop(S, graph_del_node(Key, G));
503        {change_node, Key, Color} ->
504	    loop(S, graph_change_node(Key, Color, G));
505        {add_link, {K0,K1}} ->
506	    loop(S, graph_add_link(K0, K1, G));
507        {del_link, {K0,K1}} ->
508	    loop(S, graph_del_link(K0, K1, G));
509
510        {Req, redraw} ->
511	    {SizeX, SizeY} = wxWindow:getSize(S#state.window),
512            Vs = reltool_fgraph:step(G#graph.vs,
513				     G#graph.es,
514				     {SizeX/2.0 - 20.0, SizeY/2.0}),
515            case S#state.is_frozen of
516                false ->
517                    Req ! {self(), ok};
518                true ->
519                    ignore
520            end,
521            redraw(S, G),
522            loop(S, G#graph{ vs = Vs} );
523
524        {stop, Reason} ->
525	    unlink(S#state.parent_pid),
526	    exit(Reason);
527
528        Other ->
529            error_logger:format("~w~w got unexpected message:\n\t~tp\n",
530                                [?MODULE, self(), Other]),
531            loop(S, G)
532    end.
533
534mouse_left_down_select(G, {X0,Y0}) ->
535    G#graph{ select = {{X0,Y0}, {X0,Y0}} }.
536
537mouse_left_down_move(#graph{vs = Vs} = G, {X, Y}) ->
538    % point on node?
539    case coord_to_key(G, {X, Y}) of
540        false ->
541            G#graph{ offset_state = {X,Y}};
542        {true, Key} ->
543            V = #fg_v{ type = Type} = reltool_fgraph:get(Key, Vs),
544            G#graph{ vs = reltool_fgraph:set(Key,
545					     V#fg_v{ type = moving}, Vs),
546		     select = {node, Key, Type, X, Y} }
547    end.
548
549coord_to_key(#graph{vs = Vs, offset = {Xo, Yo}}, {X, Y}) ->
550    Xr = X - Xo,
551    Yr = Y - Yo,
552    reltool_fgraph:foldl(fun({Key, #fg_v{ p = {Px, Py}}}, _)
553			       when abs(Px - Xr) < 10,
554				    abs(Py - Yr) < 10 ->
555				 {true, Key};
556			    (_, Out) ->
557				 Out
558			 end, false, Vs).
559
560mouse_left_up_select(G, {_X,_Y}) ->
561    case G#graph.select of
562        {{X0,Y0}, {X1, Y1}} ->
563            {Xo, Yo} = G#graph.offset,
564            Xmin = lists:min([X0,X1]) - Xo,
565            Xmax = lists:max([X1,X0]) - Xo,
566            Ymin = lists:min([Y0,Y1]) - Yo,
567            Ymax = lists:max([Y1,Y0]) - Yo,
568            Vs = reltool_fgraph:map(fun
569                ({Key, Value = #fg_v{ p = {Px, Py}}})
570   		   when Px > Xmin, Px < Xmax, Py > Ymin, Py < Ymax ->
571                    {Key, Value#fg_v{ selected = true }};
572                ({Key, Value}) -> {Key, Value#fg_v{ selected = false }}
573            end, G#graph.vs),
574            G#graph{ select = none, vs = Vs};
575        _ ->
576            G#graph{ select = none}
577    end.
578
579mouse_left_up_move(G = #graph{ select = Select, vs = Vs} = G, {X,Y}, Shift) ->
580    case Select of
581        {node, Key, _, X, Y} ->
582            io:format("click: ~p\n", [Key]),
583            G#graph{ select = none, offset_state = false };
584        {node, Key, Type, _, _} ->
585            V = reltool_fgraph:get(Key, Vs),
586            Type2 =
587                case Shift of
588                    true -> static;
589                    false -> Type
590                end,
591            G#graph{ select = none,
592		     vs = reltool_fgraph:set(Key, V#fg_v{ type = Type2}, Vs),
593		     offset_state = false };
594        _ ->
595            G#graph{ select = none, offset_state = false }
596    end.
597
598mouse_motion_select(G, {X,Y}) ->
599    case G#graph.select of
600        {P0, _P1} -> G#graph{ select = {P0, {X,Y}}};
601        _        -> G
602    end.
603
604mouse_motion_move(G = #graph{ select = {node, Key, _, _, _}, vs = Vs}, {X,Y}) ->
605    {Xo, Yo} = G#graph.offset,
606    V = reltool_fgraph:get(Key, Vs),
607    V2 = V#fg_v{ p = {float(X - Xo), float(Y - Yo)}},
608    G#graph{ vs = reltool_fgraph:set(Key, V2, Vs) };
609mouse_motion_move(G, {X,Y}) ->
610    case G#graph.offset_state of
611        {X1,Y1} ->
612            {X0, Y0} = G#graph.offset,
613            G#graph{ offset_state = {X,Y},
614		     offset = {X0 - (X1 - X), Y0 - (Y1 - Y)} };
615            _ ->
616                G
617    end.
618
619redraw(#state{window=Win}, G) ->
620    DC0  = wxClientDC:new(Win),
621    DC   = wxBufferedDC:new(DC0),
622    Size = wxWindow:getSize(Win),
623    redraw(DC, Size, G),
624    wxBufferedDC:destroy(DC),
625    wxClientDC:destroy(DC0),
626    ok.
627
628redraw(DC, _Size, G) ->
629    wx:batch(fun() ->
630
631        Pen   = G#graph.pen,
632        Font  = G#graph.font,
633        Brush = G#graph.brush,
634        wxDC:setTextForeground(DC,?color_fg),
635        wxBrush:setColour(Brush, ?color_bg),
636        wxDC:setBrush(DC, Brush),
637        wxDC:setBackground(DC, Brush),
638        wxPen:setWidth(Pen, 1),
639        wxDC:clear(DC),
640
641        % draw vertices and edges
642        wxPen:setColour(Pen, ?color_fg),
643        wxDC:setPen(DC,Pen),
644
645        %draw_es(DC, G#graph.es_pts, G#graph.offset),
646        draw_es(DC, G#graph.vs, G#graph.es, G#graph.offset, Pen, Brush),
647        draw_vs(DC, G#graph.vs, G#graph.offset, Pen, Brush),
648
649        % draw selection box
650        wxPen:setColour(Pen, ?color_fg),
651        wxDC:setPen(DC,Pen),
652        draw_select_box(DC, G#graph.select),
653
654        % draw information text
655        wxFont:setWeight(Font,?wxNORMAL),
656        draw_text(DC,
657		  reltool_fgraph:'size'(G#graph.vs),
658		  reltool_fgraph:'size'(G#graph.es), G#graph.ke),
659        ok
660    end).
661
662draw_select_box(DC, {{X0,Y0}, {X1,Y1}}) ->
663    draw_line(DC, {X0,Y0}, {X1,Y0}, {0,0}),
664    draw_line(DC, {X1,Y1}, {X1,Y0}, {0,0}),
665    draw_line(DC, {X1,Y1}, {X0,Y1}, {0,0}),
666    draw_line(DC, {X0,Y0}, {X0,Y1}, {0,0}),
667    ok;
668draw_select_box(_DC, _) ->
669    ok.
670
671draw_es(DC, Vs, Es, Po, Pen, Brush) ->
672    reltool_fgraph:foreach(fun
673        ({{K1, K2}, _}) ->
674            #fg_v{ p = P1} = reltool_fgraph:'get'(K1, Vs),
675            #fg_v{ p = P2} = reltool_fgraph:'get'(K2, Vs),
676            draw_arrow(DC, P1, P2, Po, Pen, Brush)
677        end, Es).
678
679draw_arrow(DC, {X0,Y0}, {X1, Y1}, {X, Y}, Pen, Brush) ->
680    Xdiff = (X0 - X1) / 4,
681    Ydiff = (Y0 - Y1) / 4,
682    X2 = X1 + Xdiff + X,
683    Y2 = Y1 + Ydiff + Y,
684    wxDC:setPen(DC, Pen),
685    wxDC:setBrush(DC, Brush),
686
687    draw_line(DC, {X0,Y0}, {X1, Y1}, {X, Y}),
688
689    %% Draw arrow head
690    Radians = calc_angle({X0, Y0}, {X1, Y1}),
691    Len = 10,
692    %% Angle = 30,
693    %% Degrees = radians_to_degrees(Radians),
694    %% Radians2 = degrees_to_radians(Degrees + Angle + 180),
695    %% Radians3 = degrees_to_radians(Degrees - Angle + 180),
696    Radians2 = Radians + 3.665191429188092,
697    Radians3 = Radians + 2.617993877991494,
698    {X3, Y3} = calc_point({X2, Y2}, Len, Radians2),
699    {X4, Y4} = calc_point({X2, Y2}, Len, Radians3),
700    Points = [{round(X2), round(Y2)},
701	      {round(X3), round(Y3)},
702	      {round(X4), round(Y4)}],
703    wxDC:drawPolygon(DC, Points, []).
704
705draw_line(DC, {X0,Y0}, {X1, Y1}, {X, Y}) ->
706    wxDC:drawLine(DC,
707		  {round(X0 + X), round(Y0 + Y)},
708		  {round(X1 + X), round(Y1 + Y)}).
709
710draw_vs(DC, Vs, {Xo, Yo}, Pen, Brush) ->
711    reltool_fgraph:foreach(fun({Key,
712				#fg_v{p ={X, Y},
713				      color = Color,
714				      selected = Sel}}) ->
715				   String = s(Key),
716				   case Sel of
717				       true ->
718					   wxPen:setColour(Pen, ?color_fg),
719					   wxBrush:setColour(Brush, ?color_bg),
720					   wxDC:setPen(DC,Pen),
721					   wxDC:setBrush(DC, Brush),
722					   SelProps = {round(X-12 + Xo),
723						       round(Y-12 + Yo),
724						       24,
725						       24},
726					   wxDC:drawRoundedRectangle(DC,
727								     SelProps,
728								     float(?ARC_R)),
729					   ok;
730				       false ->
731					   ok
732				   end,
733				   case Color of
734				       default ->
735					   wxPen:setColour(Pen, ?color_default),
736					   wxBrush:setColour(Brush,
737							     ?color_default_bg);
738				       alternate ->
739					   wxPen:setColour(Pen,
740							   ?color_alternate),
741					   wxBrush:setColour(Brush,
742							     ?color_alternate_bg);
743				       {FgColor, BgColor} ->
744					   wxPen:setColour(Pen, FgColor),
745					   wxBrush:setColour(Brush, BgColor);
746				       Color ->
747					   wxPen:setColour(Pen, Color),
748					   wxBrush:setColour(Brush, Color)
749				   end,
750				   wxDC:setPen(DC,Pen),
751				   wxDC:setBrush(DC, Brush),
752				   NodeProps = {round(X-8 + Xo),
753						round(Y-8 + Yo),17,17},
754				   wxDC:drawRoundedRectangle(DC,
755							     NodeProps,
756							     float(?ARC_R)),
757				   wxDC:drawText(DC,
758						 String,
759						 {round(X + Xo),
760						  round(Y + Yo)}),
761				   ok;
762			      (_) ->
763				   ok
764			   end,
765			   Vs).
766
767draw_text(DC, Nvs, Nes, _KE) ->
768    VsString = "#nodes: " ++ integer_to_list(Nvs),
769    EsString = "#links: " ++ integer_to_list(Nes),
770    %% KEString = " ke: " ++ s(KE),
771    wxDC:drawText(DC, VsString, {10,10}),
772    wxDC:drawText(DC, EsString, {10,25}),
773    %% wxDC:drawText(DC, KEString, {10,40}),
774    ok.
775
776s(Format, Terms) -> lists:flatten(io_lib:format(Format, Terms)).
777s(Term) when is_float(Term) -> s("~.2f", [Term]);
778s(Term) when is_integer(Term) -> integer_to_list(Term);
779s(Term) when is_atom(Term) -> atom_to_list(Term);
780s(Term) -> s("~p", [Term]).
781
782%% Calclulate angle in radians for a line between two points
783calc_angle({X1, Y1}, {X2, Y2}) ->
784    math:atan2((Y2 - Y1), (X2 - X1)).
785
786%% Calc new point at a given distance and angle from another point
787calc_point({X, Y}, Length, Radians) ->
788    X2 = round(X + Length * math:cos(Radians)),
789    Y2 = round(Y + Length * math:sin(Radians)),
790    {X2, Y2}.
791
792%% %% Convert from an angle in radians to degrees
793%% radians_to_degrees(Radians) ->
794%%     Radians * 180 / math:pi().
795%%
796%% %% Convert from an angle in degrees to radians
797%% degrees_to_radians(Degrees) ->
798%%     Degrees * math:pi() / 180.
799