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