%% %% wings_wm.erl -- %% %% Window manager for Wings. %% %% Copyright (c) 2002-2005 Bjorn Gustavsson %% %% See the file "license.terms" for information on usage and redistribution %% of this file, and for a DISCLAIMER OF ALL WARRANTIES. %% %% $Id: wings_wm.erl,v 1.154 2005/08/16 09:10:16 dgud Exp $ %% -module(wings_wm). -export([toplevel/6,toplevel_title/1,toplevel_title/2,set_knob/3]). -export([init/0,enter_event_loop/0,dirty/0,dirty_mode/1,pdirty/0, reinit_opengl/0, new/4,delete/1,raise/1, link/2,hide/1,show/1,is_hidden/1, later/1,send/2,psend/2,send_after_redraw/2, set_timer/2,cancel_timer/1, this/0,offset/3,move/2,move/3,resize/2,pos/1,windows/0,is_window/1, window_below/2, update_window/2,clear_background/0, callback/1,current_state/1,get_current_state/0,notify/1, local2global/1,local2global/2,global2local/2,local_mouse_state/0, translation_change/0]). %% Window information. -export([top_size/0,viewport/0,viewport/1, win_size/0,win_ul/0,win_rect/0, win_size/1,win_ul/1,win_ur/1,win_ll/1,win_lr/1,win_z/1, win_rect/1]). %% Focus management. -export([grab_focus/0,grab_focus/1,release_focus/0, grabbed_focus_window/0,actual_focus_window/0]). %% Setting the information bar message. -export([message/1,message/2,message_right/1]). %% Menubar management. -export([menubar/2,get_menubar/1]). %% Drag & Drop support. -export([drag/3,drag/4,allow_drag/1]). %% Window property mangagement. -export([get_props/1,get_prop/1,get_prop/2,lookup_prop/1,lookup_prop/2, set_prop/2,set_prop/3,erase_prop/1,erase_prop/2, is_prop_defined/2]). %% Useful sizes -export([title_height/0,vscroller_width/0]). -define(NEED_OPENGL, 1). -define(NEED_ESDL, 1). -include("wings.hrl"). -import(lists, [map/2,foldl/3,last/1,sort/1,keysort/2,keysearch/3, reverse/1,foreach/2,member/2]). -define(Z_LOWEST_DYNAMIC, 10). -record(win, {z, %Z order. x,y, %Position. w,h, %Size. name, %Name of window. stk, %Event handler stack. props, %Window properties. links=[] %Windows linked to this one. }). -record(se, %Stack entry record. {h, %Handler (fun). msg=[], %Current message. msg_right=[], %Right-side message. menubar=[] %Menubar for this window. }). %%% %%% Process dictionary usage: %%% %%% wm_active Currently active window (handling current event). %%% wm_focus Actual focus window (implicitly or grabbed). %%% wm_focus_grab Window name of forced focus window or 'undefined'. %%% wm_windows All windows. %%% wm_dirty Exists if redraw is needed. %%% wm_dirty_mode front|back %%% wm_top_size Size of top window. %%% wm_viewport Current viewport. %%% wm_cursor Default cursor. %%% {wm_current_state,Displists} %%% init() -> put(wm_dirty_mode, front), put(wm_cursor, arrow), {W,H} = TopSize = wings_pref:get_value(window_size), put(wm_top_size, TopSize), translation_change(), put(wm_windows, gb_trees:empty()), new(desktop, {0,0,0}, {0,0}, {push,fun desktop_event/1}), new(message, {0,0,?Z_LOWEST_DYNAMIC-1}, {0,0}, {push,fun message_event/1}), init_opengl(), dirty_mode(back), resize_windows(W, H). desktop_event(got_focus) -> dirty(), keep; desktop_event(_) -> keep. message(Message) -> wings_io:putback_event({wm,{message,get(wm_active),Message}}). message_right(Right) -> wings_io:putback_event({wm,{message_right,get(wm_active),Right}}). message(Message, Right) -> message(Message), message_right(Right). menubar(Name, Menubar) -> wings_io:putback_event({wm,{menubar,Name,Menubar}}). get_menubar(Name) -> #win{stk=[#se{menubar=Bar}|_]} = get_window_data(Name), Bar. later(Ev) -> wings_io:putback_event({wm,{send_to,this(),Ev}}), keep. send(Name, Ev) -> wings_io:putback_event({wm,{send_to,Name,Ev}}), keep. %% Send from another erlang process than the main wings process psend(Name, Ev) -> wings ! {timeout,make_ref(),{event,{wm,{send_to,Name,Ev}}}}, keep. send_once(Name, Ev) -> wings_io:putback_event_once({wm,{send_to,Name,Ev}}), keep. send_after_redraw(Name, Ev) -> wings_io:putback_event({wm,{send_after_redraw,Name,Ev}}). current_state(St) -> DispLists = get_prop(this(), display_lists), case put({wm_current_state,DispLists}, St) of St -> ok; _ -> NewState = {current_state,St}, foreach(fun(#win{props=Props,name=Name}) -> case gb_trees:lookup(display_lists, Props) of none -> ok; {value,DispLists} -> send(Name, NewState); {value,_} -> send(Name, {current_state,DispLists,St}) end end, gb_trees:values(get(wm_windows))) end. get_current_state() -> DispLists = get_prop(this(), display_lists), get({wm_current_state,DispLists}). notify(Note) -> Msg = {note,Note}, foreach(fun(desktop) -> ok; (Name) -> send_once(Name, Msg) end, gb_trees:keys(get(wm_windows))). dirty_mode(front=Mode) -> put(wm_dirty_mode, Mode); dirty_mode(back=Mode) -> case get(wings_os_type) of {unix,darwin} -> ok; _ -> gl:clear(?GL_COLOR_BUFFER_BIT bor ?GL_DEPTH_BUFFER_BIT), put(wm_dirty_mode, Mode) end. dirty() -> put(wm_dirty, dirty), keep. %% Dirty marker from another erlang process than the main wings process pdirty() -> wings ! {timeout,make_ref(),{event,{wm,dirty}}}, keep. clean() -> erase(wm_dirty). callback(Cb) -> wings_io:putback_event({wm,{callback,Cb}}). new(Name, {X,Y}, Size, Op) -> new(Name, {X,Y,highest}, Size, Op); new(Name, {X,Y,Z0}, {W,H}, Op) when is_integer(X), is_integer(Y), is_integer(W), is_integer(H) -> Z = new_resolve_z(Z0), Stk = handle_response(Op, dummy_event, default_stack(Name)), Props = gb_trees:from_orddict([{font,wings_pref:get_value(new_system_font)}]), Win = #win{x=X,y=Y,z=Z,w=W,h=H,name=Name,stk=Stk,props=Props}, put(wm_windows, gb_trees:insert(Name, Win, get(wm_windows))), dirty(). new_resolve_z(highest) -> case highest_z() + 1 of Z when Z < ?Z_LOWEST_DYNAMIC -> ?Z_LOWEST_DYNAMIC; Z -> Z end; new_resolve_z(Z) when is_integer(Z), Z >= 0-> Z. delete(Name) -> Windows = delete_windows(Name, get(wm_windows)), put(wm_windows, Windows), case is_window(get(wm_focus_grab)) of false -> erase(wm_focus_grab); true -> ok end, case is_window(get(wm_focus)) of false -> update_focus(find_active(none)); true -> ok end, dirty(). delete_windows(Name, W0) -> case gb_trees:lookup(Name, W0) of none -> W0; {value,#win{links=Links}} -> This = this(), W = gb_trees:delete_any(Name, W0), foldl(fun(L, A) when L =/= This -> delete_windows(L, A); (_, A) -> A end, W, Links) end. raise(Name) -> case get_window_data(Name) of #win{z=Z} when Z < ?Z_LOWEST_DYNAMIC -> ok; #win{z=Z0} -> case highest_z() of Z when Z > Z0 -> update_window(Name, [{z,Z+1}]); _ -> ok end end. highest_z() -> highest_z_1(gb_trees:values(get(wm_windows)), 0). highest_z_1([#win{z=Z}|T], Max) when Z < Max -> highest_z_1(T, Max); highest_z_1([#win{z=Max}|T], _) -> highest_z_1(T, Max); highest_z_1([], Max) -> Max. link(From, To) -> #win{links=Links} = Win = get_window_data(From), case member(To, Links) of false -> put_window_data(From, Win#win{links=[To|Links]}); true -> ok end. hide(Name) -> case get_window_data(Name) of #win{z=Z} when Z < 0 -> ok; #win{z=Z}=Win -> put_window_data(Name, Win#win{z=-Z}) end. show(Name) -> case get_window_data(Name) of #win{z=Z} when Z > 0 -> ok; #win{z=Z}=Win -> put_window_data(Name, Win#win{z=-Z}) end. is_hidden(Name) -> case get_window_data(Name) of #win{z=Z} when Z < 0 -> true; _ -> false end. this() -> case get(wm_active) of undefined -> none; Active -> Active end. windows() -> gb_trees:keys(get(wm_windows)). is_window(Name) -> gb_trees:is_defined(Name, get(wm_windows)). offset(Name, Xoffs, Yoffs) -> update_window(Name, [{dx,Xoffs},{dy,Yoffs}]). move(Name, Pos) -> update_window(Name, [{pos,Pos}]). move(Name, Pos, {W,H}) -> update_window(Name, [{pos,Pos},{w,W},{h,H}]). resize(Name, {W,H}) -> update_window(Name, [{w,W},{h,H}]). update_window(Name, Updates) -> #win{z=Z0} = Data0 = get_window_data(Name), Data = update_window_1(Updates, Data0), put_window_data(Name, Data), send(Name, resized), #win{links=Links} = get_window_data(Name), Msg = {window_updated,Name}, case Data of #win{z=Z0} -> ok; #win{z=Z} -> update_linked_z(Links, Z-Z0) end, case Updates of [{z,_}] -> ok; _ -> foreach(fun(L) -> wings_wm:send(L, Msg) end, Links) end, dirty(). update_linked_z([N|Ns], Dz) -> case lookup_window_data(N) of none -> ok; #win{z=Hidden} when Hidden < 0 -> ok; #win{z=Z}=Data -> put_window_data(N, Data#win{z=Z+Dz}), update_linked_z(Ns, Dz) end; update_linked_z([], _) -> ok. update_window_1([{dx,Dx}|T], #win{x=X}=Win) -> update_window_1(T, Win#win{x=X+Dx}); update_window_1([{dy,Dy}|T], #win{y=Y}=Win) -> update_window_1(T, Win#win{y=Y+Dy}); update_window_1([{dw,Dw}|T], #win{w=W}=Win) -> update_window_1(T, Win#win{w=W+Dw}); update_window_1([{dh,Dh}|T], #win{h=H}=Win) -> update_window_1(T, Win#win{h=H+Dh}); update_window_1([{pos,{X,Y}}|T], Win) -> update_window_1(T, Win#win{x=X,y=Y}); update_window_1([{pos,{X,Y,Z}}|T], Win) -> update_window_1(T, Win#win{x=X,y=Y,z=Z}); update_window_1([{x,X}|T], Win) -> update_window_1(T, Win#win{x=X}); update_window_1([{y,Y}|T], Win) -> update_window_1(T, Win#win{y=Y}); update_window_1([{z,Z}|T], Win) -> update_window_1(T, Win#win{z=Z}); update_window_1([{w,W}|T], Win) -> update_window_1(T, Win#win{w=W}); update_window_1([{h,H}|T], Win) -> update_window_1(T, Win#win{h=H}); update_window_1([], Win) -> range_check(Win). range_check(#win{w=W}=Win) when W < 1 -> range_check(Win#win{w=1}); range_check(#win{h=H}=Win) when H < 1 -> range_check(Win#win{h=1}); range_check(Win) -> Win. pos(Name) -> #win{x=X,y=Y} = get_window_data(Name), {X,Y}. grab_focus() -> grab_focus(get(wm_active)). grab_focus(Name) -> case is_window(Name) of true -> put(wm_focus_grab, Name); false -> erase(wm_focus_grab) end. release_focus() -> erase(wm_focus_grab). grabbed_focus_window() -> get(wm_focus_grab). actual_focus_window() -> get(wm_focus). top_size() -> get(wm_top_size). set_timer(Time, Event) -> Active = get(wm_active), wings_io:set_timer(Time, {wm,{send_to,Active,Event}}). cancel_timer(Ref) -> wings_io:cancel_timer(Ref). viewport() -> get(wm_viewport). viewport(Name) -> #win{x=X,y=Y0,w=W,h=H} = get_window_data(Name), {_,TopH} = get(wm_top_size), Y = TopH-(Y0+H), {X,Y,W,H}. win_size() -> {_,_,W,H} = get(wm_viewport), {W,H}. win_ul() -> win_ul(this()). win_rect() -> win_rect(this()). win_size(Name) -> #win{w=W,h=H} = get_window_data(Name), {W,H}. win_ul(Name) -> #win{x=X,y=Y} = get_window_data(Name), {X,Y}. win_ur(Name) -> #win{x=X,y=Y,w=W} = get_window_data(Name), {X+W,Y}. win_ll(Name) -> #win{x=X,y=Y,h=H} = get_window_data(Name), {X,Y+H}. win_lr(Name) -> #win{x=X,y=Y,w=W,h=H} = get_window_data(Name), {X+W,Y+H}. win_z(Name) -> #win{z=Z} = get_window_data(Name), Z. win_rect(Name) -> #win{x=X,y=Y,w=W,h=H} = get_window_data(Name), {{X,Y},{W,H}}. local2global(#mousebutton{x=X0,y=Y0}=Ev) -> {X,Y} = local2global(X0, Y0), Ev#mousebutton{x=X,y=Y}; local2global(#mousemotion{x=X0,y=Y0}=Ev) -> {X,Y} = local2global(X0, Y0), Ev#mousemotion{x=X,y=Y}; local2global(Ev) -> Ev. local2global(X, Y) -> {_,TopH} = get(wm_top_size), {Xorig,Yorig,_,H} = viewport(), {Xorig+X,(TopH-Yorig-H)+Y}. global2local(X, Y) -> {_,TopH} = get(wm_top_size), {Xorig,Yorig,_,H} = viewport(), {X-Xorig,Y-(TopH-Yorig-H)}. local_mouse_state() -> {B,X0,Y0} = sdl_mouse:getMouseState(), {X,Y} = global2local(X0, Y0), {B,X,Y}. get_props(Win) -> #win{props=Props} = get_window_data(Win), gb_trees:to_list(Props). get_prop(Name) -> get_prop(this(), Name). get_prop(Win, Name) -> #win{props=Props} = get_window_data(Win), gb_trees:get(Name, Props). lookup_prop(Name) -> lookup_prop(this(), Name). lookup_prop(Win, Name) -> #win{props=Props} = get_window_data(Win), gb_trees:lookup(Name, Props). set_prop(Name, Value) -> set_prop(this(), Name, Value). set_prop(Win, Name, Value) -> #win{props=Props0} = Data = get_window_data(Win), Props = gb_trees:enter(Name, Value, Props0), put_window_data(Win, Data#win{props=Props}). erase_prop(Name) -> erase_prop(this(), Name). erase_prop(Win, Name) -> #win{props=Props0} = Data = get_window_data(Win), Props = gb_trees:delete_any(Name, Props0), put_window_data(Win, Data#win{props=Props}). is_prop_defined(Win, Name) -> #win{props=Props} = get_window_data(Win), gb_trees:is_defined(Name, Props). enter_event_loop() -> init_opengl(), event_loop(). event_loop() -> case get(wm_dirty) of undefined -> get_and_dispatch(); _ -> redraw_all() end. get_and_dispatch() -> Event = wings_io:get_event(), dispatch_event(Event), event_loop(). dispatch_matching(Filter) -> Evs = wings_io:get_matching_events(Filter), foreach(fun dispatch_event/1, Evs). dispatch_event(#resize{w=W,h=H}) -> ?CHECK_ERROR(), {SaveW,SaveH} = case sdl_video:wm_isMaximized() of false -> {W,H}; true -> {W-8,H-10} end, wings_pref:set_value(window_size, {SaveW,SaveH}), put(wm_top_size, {W,H}), init_opengl(), resize_windows(W, H), dirty(); dispatch_event(quit) -> foreach(fun(Name) -> send(Name, quit) end, gb_trees:keys(get(wm_windows))); dispatch_event({wm,WmEvent}) -> wm_event(WmEvent); dispatch_event(Ev = {external,_}) -> send(geom,Ev); dispatch_event(Event) -> case find_active(Event) of none -> % io:format("~p:~p: Dropped Event ~p~n",[?MODULE,?LINE,Event]), update_focus(none); Active -> update_focus(Active), do_dispatch(Active, Event) end. update_focus(none) -> case erase(wm_focus) of undefined -> ok; OldActive -> do_dispatch(OldActive, lost_focus) end; update_focus(Active) -> case put(wm_focus, Active) of undefined -> do_dispatch(Active, got_focus); Active -> ok; OldActive -> do_dispatch(OldActive, lost_focus), do_dispatch(Active, got_focus) end. resize_windows(W, H) -> Event = #resize{w=W,h=H}, MsgData0 = get_window_data(message), MsgH = ?CHAR_HEIGHT+7, MsgY = H-MsgH, MsgData1 = MsgData0#win{x=0,y=MsgY,w=W,h=MsgH}, put_window_data(message, MsgData1), MsgData = send_event(MsgData1, Event#resize{w=W}), put_window_data(message, MsgData), DesktopData0 = get_window_data(desktop), DesktopData = DesktopData0#win{x=0,y=0,w=W,h=H-MsgH}, put_window_data(desktop, DesktopData). do_dispatch(Active, Ev) -> case gb_trees:lookup(Active, get(wm_windows)) of none -> % io:format("~p:~p: Dropped Event ~p~n",[?MODULE,?LINE,Ev]), ok; {value,Win0} -> case send_event(Win0, Ev) of #win{name=Name,stk=delete} -> delete(Name); #win{stk=[]} -> ok; Win -> put_window_data(Active, Win) end end. redraw_all() -> %% Remove late buffers clear due to problems with ATI cards when AA. gl:clear(?GL_COLOR_BUFFER_BIT bor ?GL_DEPTH_BUFFER_BIT), Windows = keysort(2, gb_trees:to_list(get(wm_windows))), foreach(fun({Name,_}) -> dispatch_matching(fun({wm,{send_to,N,_}}) -> N =:= Name; (_) -> false end), do_dispatch(Name, redraw) end, Windows), gl:swapBuffers(), clean(), wings_io:set_cursor(get(wm_cursor)), event_loop(). clear_background() -> Name = this(), %% Optimization: Check if we really need to clear the area %% occupied by the window. Given a slow OpenGL implementation, %% it is a big win. case any_window_below(Name) of true -> clear_background_1(Name); false -> ok end. clear_background_1(Name) -> {X,Y,W,H} = viewport(Name), gl:scissor(X, Y, W, H), gl:enable(?GL_SCISSOR_TEST), gl:clear(?GL_COLOR_BUFFER_BIT bor ?GL_DEPTH_BUFFER_BIT), gl:disable(?GL_SCISSOR_TEST). any_window_below(Name) -> Windows = gb_trees:values(get(wm_windows)), #win{x=X,y=Y,z=Z,w=W,h=H} = get_window_data(Name), any_window_below_1(Windows, Name, Z, {X,Y,X+W,Y+H}). any_window_below_1([#win{name=Name}|T], Name, Z, Rect) -> any_window_below_1(T, Name, Z, Rect); any_window_below_1([#win{z=ThisZ}|T], Name, Z, Rect) when Z < ThisZ; ThisZ =< 0 -> any_window_below_1(T, Name, Z, Rect); any_window_below_1([W|T], Name, Z, Rect) -> case possible_intersection(W, Rect) of true -> true; false -> any_window_below_1(T, Name, Z, Rect) end; any_window_below_1([], _, _, _) -> false. possible_intersection(#win{x=X,y=Y,w=W,h=H}, {Left,Top,Right,Bot}) -> if Right =< X; Bot =< Y; X+W =< Left; Y+H =< Top -> false; true -> true end. reinit_opengl() -> wings_io:putback_event({wm,init_opengl}). init_opengl() -> {W,H} = get(wm_top_size), set_video_mode(W, H), gl:clear(?GL_COLOR_BUFFER_BIT bor ?GL_DEPTH_BUFFER_BIT), gl:pixelStorei(?GL_UNPACK_ALIGNMENT, 1), wings_io:resize(), wings_image:init_opengl(), {R,G,B} = wings_pref:get_value(background_color), gl:clearColor(R, G, B, 1), dirty(), foreach(fun(Name) -> do_dispatch(Name, init_opengl) end, gb_trees:keys(get(wm_windows))). send_event(Win, {expose}) -> dirty(), Win; send_event(#win{z=Z}=Win, redraw) when Z < 0 -> Win; send_event(#win{name=Name,x=X,y=Y0,w=W,h=H,stk=[Se|_]=Stk0}, Ev0) -> OldActive = put(wm_active, Name), Ev = translate_event(Ev0, X, Y0), {_,TopH} = get(wm_top_size), Y = TopH-(Y0+H), ViewPort = {X,Y,W,H}, case put(wm_viewport, ViewPort) of ViewPort -> ok; _ -> gl:viewport(X, Y, W, H) end, Stk = handle_event(Se, Ev, Stk0), case OldActive of undefined -> erase(wm_active); _ -> put(wm_active, OldActive) end, Win = get_window_data(Name), Win#win{stk=Stk}. translate_event(#mousemotion{state=Mask0,x=X,y=Y,mod=Mod0}=M, Ox, Oy) -> {Mask,Mod} = translate_bmask(Mask0, Mod0), M#mousemotion{state=Mask,x=X-Ox,y=Y-Oy,mod=Mod}; translate_event(#mousebutton{button=B0,x=X,y=Y,mod=Mod0,state=?SDL_PRESSED}=M, Ox, Oy) -> {B,Mod} = translate_button(B0, Mod0), M#mousebutton{button=B,x=X-Ox,y=Y-Oy,mod=Mod}; translate_event(#mousebutton{button=B0,x=X,y=Y,state=?SDL_RELEASED}=M, Ox, Oy) -> case erase({button_up,B0}) of undefined -> M#mousebutton{x=X-Ox,y=Y-Oy}; {B,Mod} -> M#mousebutton{button=B,x=X-Ox,y=Y-Oy,mod=Mod} end; translate_event({drop,{X,Y},DropData}, Ox, Oy) -> {drop,{X-Ox,Y-Oy},DropData}; translate_event(Ev, _, _) -> Ev. handle_event(State, Event, Stk) -> try case State of #se{h=Handler} -> Handler(Event); Fun when is_function(Fun) -> Fun() end of Res -> handle_response(Res, Event, Stk) catch throw:{command_error,Error} -> wings_u:message(Error), Stk; exit:normal -> exit(normal); exit:Exit -> [#se{h=CrashHandler}] = pop_all_but_one(Stk), handle_response(CrashHandler({crash,Exit}), Event, default_stack(this())); error:Reason -> [#se{h=CrashHandler}] = pop_all_but_one(Stk), handle_response(CrashHandler({crash,Reason}), Event, default_stack(this())) end. handle_response(Res, Event, Stk0) -> case Res of keep -> Stk0; next -> next_handler(Event, Stk0); pop -> pop(Stk0); delete -> delete; push -> [OldTop|_] = Stk0, [OldTop#se{h=dummy}|Stk0]; {push,Handler} -> [OldTop|_] = Stk0, [OldTop#se{h=Handler}|Stk0]; {seq,First,Then} -> Stk = handle_response(First, Event, Stk0), handle_response(Then, Event, Stk); {replace,Top} when is_function(Top) -> replace_handler(Top, Stk0); {replace,Top,Continue} when is_function(Top), is_function(Continue) -> Stk = replace_handler(Top, Stk0), handle_event(Continue, dummy_event, Stk); {pop_handler,_}=PopH -> replace_handler(PopH, Stk0); Top when is_function(Top) -> replace_handler(Top, Stk0) end. pop_all_but_one([_]=Stk) -> Stk; pop_all_but_one(Stk) -> pop_all_but_one(pop(Stk)). pop([_|Stk]) -> case Stk of [#se{h={pop_handler,Handler0}}|_] -> {replace,Handler} = Handler0(), replace_handler(Handler, Stk); _ -> Stk end. replace_handler(Handler, [Top|Stk]) -> [Top#se{h=Handler}|Stk]. next_handler(Event, [_|[Next|_]=Stk]) -> handle_event(Next, Event, Stk). default_stack(Name) -> Handler = fun({crash,Crash}) -> StkTrace = erlang:get_stacktrace(), io:format("Window ~p crashed:\n~P\n~P\n", [Name,Crash,20,StkTrace,40]), exit({window_crash,Name,Crash,StkTrace}); (Other) -> io:format("Window ~p's crash handler got:\n~p\n", [Name,Other]), exit({unexpected,Name,Other}) end, [#se{h=Handler}]. %%% %%% Handling Wm Events. %%% wm_event(dirty) -> dirty(); wm_event({message,Name,Msg}) -> case lookup_window_data(Name) of none -> ok; #win{stk=[#se{msg=Msg}|_]} -> ok; #win{stk=[Top0|Stk]}=Data0 -> Top = Top0#se{msg=Msg}, Data = Data0#win{stk=[Top|Stk]}, put_window_data(Name, Data), dirty() end; wm_event({message_right,Name,Right0}) -> Right = lists:flatten(Right0), case lookup_window_data(Name) of none -> ok; #win{stk=[#se{msg_right=Right}|_]} -> ok; #win{stk=[Top0|Stk]}=Data0 -> Top = Top0#se{msg_right=Right}, Data = Data0#win{stk=[Top|Stk]}, put_window_data(Name, Data), dirty() end; wm_event({menubar,Name,Menubar}) -> case lookup_window_data(Name) of none -> ok; #win{stk=[#se{menubar=Menubar}|_]} -> ok; #win{stk=[Top|Stk]}=Data0 -> Data = Data0#win{stk=[Top#se{menubar=Menubar}|Stk]}, put_window_data(Name, Data), dirty() end; wm_event({send_to,Name,Ev}) -> case gb_trees:is_defined(Name, get(wm_windows)) of false -> ok; true -> do_dispatch(Name, Ev) end; wm_event({send_after_redraw,Name,Ev}) -> case gb_trees:is_defined(Name, get(wm_windows)) of false -> ok; true -> do_dispatch(Name, Ev) end; wm_event({callback,Cb}) -> Cb(); wm_event(init_opengl) -> init_opengl(). %%% %%% Finding the active window. %%% find_active(Ev) -> case get(wm_focus_grab) of undefined -> find_active_0(Ev); Focus -> Focus end. find_active_0(Ev) -> case Ev of #mousebutton{x=X,y=Y} -> ok; #mousemotion{x=X,y=Y} -> ok; _ -> {_,X,Y} = sdl_mouse:getMouseState() end, window_below(X, Y). window_below(X, Y) -> All = reverse(sort(gb_trees:values(get(wm_windows)))), window_below_1(All, X, Y). window_below_1([#win{x=Wx,y=Wy,w=W,h=H,name=Name,z=Z}|T], X, Y) when Z >= 0 -> case {X-Wx,Y-Wy} of {Rx,Ry} when 0 =< Rx, Rx < W,0 =< Ry, Ry < H -> Name; _ -> window_below_1(T, X, Y) end; window_below_1(_, _, _) -> none. %%% %%% Drag and drop support. %%% -record(drag, {data, %Drop data. bstate, %State of mouse buttons. redraw, %Redraw function. over=none, %We are over this window. drop_ok=false %Drop is OK on this window. }). allow_drag(false) -> allow_drag_1(arrow); allow_drag(true) -> allow_drag_1(pointing_hand). allow_drag_1(Cursor) -> case get(wm_cursor) of Cursor -> ok; _ -> put(wm_cursor, Cursor), wings_io:set_cursor(Cursor) end. drag(Ev, Rect, DropData) -> Redraw = fun() -> gl:pushAttrib(?GL_POLYGON_BIT bor ?GL_LINE_BIT), gl:polygonMode(?GL_FRONT_AND_BACK, ?GL_LINE), gl:lineStipple(2, 2#0101010101010101), gl:enable(?GL_LINE_STIPPLE), gl:color3b(0, 0, 0), {W,H} = wings_wm:win_size(), gl:rectf(0.5, 0.5, W-1, H-1), gl:popAttrib() end, drag(Ev, Rect, Redraw, DropData). drag(#mousemotion{x=X,y=Y,state=State}, Rect, Redraw, DropData) -> drag_1(X, Y, State, Rect, Redraw, DropData); drag(#mousebutton{x=X,y=Y,button=B}, Rect, Redraw, DropData) -> State = 1 bsl (B-1), drag_1(X, Y, State, Rect, Redraw, DropData). drag_1(X0, Y0, State, {W,H}, Redraw, DropData) -> {X1,Y1} = wings_wm:local2global(X0, Y0), X = X1 - W div 2, Y = Y1 - H div 2, Drag = #drag{data=DropData,bstate=State,redraw=Redraw}, Op = {seq,push,get_drag_event(Drag)}, Name = dragger, new(Name, {X,Y,highest}, {W,H}, Op), grab_focus(Name), dirty(), keep. get_drag_event(Drag) -> {replace,fun(Ev) -> drag_event(Ev, Drag) end}. drag_event(redraw, #drag{redraw=Redraw}) -> wings_io:ortho_setup(), Redraw(), keep; drag_event(#mousemotion{x=X0,y=Y0}, #drag{over=Over0}=Drag0) -> {W,H} = wings_wm:win_size(), offset(dragger, X0 - W div 2, Y0 - H div 2), {X,Y} = local2global(X0, Y0), hide(dragger), Over = window_below(X, Y), show(dragger), case Over of Over0 -> keep; _ -> Drag = Drag0#drag{over=Over}, drag_filter(Drag) end; drag_event(#mousebutton{button=B,state=?SDL_RELEASED}, #drag{bstate=State,data=DropData,drop_ok=DropOK}) -> if ((1 bsl (B-1)) band State) =/= 0 -> case DropOK of false -> ok; true -> {{X,Y},{W,H}} = wings_wm:win_rect(dragger), Ev = {drop,{X + W div 2,Y + H div 2},DropData}, wings_io:putback_event(Ev) end, put(wm_cursor, arrow), delete; true -> keep end; drag_event(_, _) -> keep. drag_filter(#drag{over=none}=Drag) -> stop_cursor(Drag); drag_filter(#drag{over=Win,data=DropData}=Drag) -> case lookup_prop(Win, drag_filter) of {value,Fun} when is_function(Fun) -> case Fun(DropData) of yes -> message(""), put(wm_cursor, closed_hand), get_drag_event(Drag#drag{drop_ok=true}); {yes,Message} -> message(Message), put(wm_cursor, closed_hand), get_drag_event(Drag#drag{drop_ok=true}); no -> stop_cursor(Drag) end; none -> stop_cursor(Drag) end. stop_cursor(Drag) -> put(wm_cursor, stop), message(""), get_drag_event(Drag#drag{drop_ok=false}). %%% %%% Utility functions. %%% lookup_window_data(Name) -> case gb_trees:lookup(Name, get(wm_windows)) of none -> none; {value,Val} -> Val end. get_window_data(Name) -> gb_trees:get(Name, get(wm_windows)). put_window_data(Name, Data) -> put(wm_windows, gb_trees:update(Name, Data, get(wm_windows))). set_video_mode(W, H) -> {surfacep,_} = sdl_video:setVideoMode(W, H, 0, ?SDL_OPENGL bor ?SDL_RESIZABLE), ok. %%% %%% Button translation. %%% translation_change() -> case wings_pref:get_value(num_buttons) of 3 -> erase(mouse_translation); Buttons -> Mode = wings_pref:get_value(camera_mode), put(mouse_translation, {Mode,Buttons}) end. translate_bmask(0, Mod) -> {0, Mod}; translate_bmask(Mask, Mod) -> translate_bmask_1(1, Mask, Mod, 0). translate_bmask_1(4, _, Mod, Acc) -> {Acc,Mod}; translate_bmask_1(B0, Mask, Mod0, Acc) -> case 1 bsl (B0-1) of Bit when Bit band Mask =:= 0 -> translate_bmask_1(B0+1, Mask, Mod0, Acc); _ -> {B,Mod} = translate_button(B0, Mod0), translate_bmask_1(B0+1, Mask, Mod, Acc bor (1 bsl (B-1))) end. translate_button(B0, Mod0) -> Type = get(mouse_translation), case translate_button_1(B0, Type, Mod0) of {B0,_}=Res -> Res; {_,_}=Res -> put({button_up,B0}, Res), Res end. translate_button_1(1, {blender,2}, Mod) when Mod band ?ALT_BITS =/= 0 -> {2,Mod band (bnot ?ALT_BITS)}; translate_button_1(1, {nendo,1}, Mod) when Mod band ?CTRL_BITS =/= 0 -> {3,Mod band (bnot ?CTRL_BITS)}; translate_button_1(1, {nendo,1}, Mod) when Mod band ?ALT_BITS =/= 0 -> {2,Mod band (bnot ?ALT_BITS)}; translate_button_1(3, {nendo,2}, Mod) when Mod band ?CTRL_BITS =/= 0 -> {2,Mod band (bnot ?CTRL_BITS)}; translate_button_1(B, _, Mod) -> {B,Mod}. %% %% The message window. %% message_event(redraw) -> dispatch_matching(fun({wm,{message,_,_}}) -> true; ({wm,{message_right,_,_}}) -> true; (_) -> false end), case get(wm_focus) of undefined -> keep; Active -> #win{stk=[#se{msg=Msg,msg_right=Right}|_]} = get_window_data(Active), message_redraw(Msg, Right) end; message_event({action,_}=Action) -> send(geom, Action); message_event(got_focus) -> message(?__(1,"This is the information line")), dirty(); message_event(_) -> keep. message_redraw(Msg, Right) -> {W,_} = message_setup(), if Msg == [] -> ok; true -> wings_io:text_at(0, Msg) end, OsType = get(wings_os_type), RMarg0 = case OsType of {unix,darwin} -> 27; _ -> 0 end, case Right of [] -> ok; _ -> Cw = wings_text:width(), MsgW = wings_text:width(Msg), RightW = wings_text:width(Right), RMarg = RMarg0 + 2*Cw, if MsgW+RightW < W - RMarg -> Pos = W - RightW - RMarg, wings_io:set_color(wings_pref:get_value(menu_color)), gl:recti(Pos-Cw, 1-wings_text:height(), Pos+RightW+Cw, 3), gl:color3b(0, 0, 0), wings_io:text_at(Pos, Right); true -> ok end end, case OsType of {unix,darwin} -> wings_io:draw_icons(fun() -> wings_io:draw_icon(W-25, -8, resize) end); _ -> ok end, keep. message_setup() -> wings_io:ortho_setup(none), {W,H} = win_size(), wings_io:gradient_rect(0, 0, W, H, ?PANE_COLOR), gl:color3b(0, 0, 0), gl:translatef(10, H-5.375, 0), {W,H}. %%% %%% Toplevel: create a window and a controller window at the same time. %%% The controller adds a title bar (allowing the window to be moved) and %%% can optionally add other things such as a scroller. %%% toplevel(Name, Title, Pos, Size, Flags, Op) -> wings_wm_toplevel:toplevel(Name, Title, Pos, Size, Flags, Op). toplevel_title(Title) -> Win = this(), send({controller,Win}, {title,Title}), ok. toplevel_title(Win, Title) -> send({controller,Win}, {title,Title}), ok. set_knob(Name, Pos, Proportion) -> wings_wm_toplevel:set_knob(Name, Pos, Proportion). title_height() -> wings_wm_toplevel:title_height(). vscroller_width() -> wings_wm_toplevel:vscroller_width().