unit Floating; {A set of routines to handle floating windows. For use with THINK Pascal 4.0} {Written by F. Pottier, june 1993. E-mail : pottier@clipper.ens.fr} {Originally based on a set of C routines written by Patrick Doane (America OnLine : Patrick5) } {Thanks go to him, and also to Troy Gaul for placing his Infinity Windoid in the public domain} {This code is placed in the public domain. It may be used by anybody, for any purposes. It would be kind to give credits to me} {as well as to the aforementioned people, though.} {If you experience any problems, or have ideas for enhancements, tell me about it. I can't guarantee I'll spend much} {time on it, since I'm not a professional programmer, but I'll take a look at it.} { --- Some general comments ------------------------------------------------------------------------------------------ } {The Window Manager doesn't know about floating windows. To obtain floating windows in a program,} {one has to handle them 'by hand', which means calling low-level Window Manager routines to place the windows} {in their correct positions after each event.} {The frequently used, and now to be avoided, Window Manager routines are :} { SelectWindow } { CloseWindow } { HideWindow } { ShowWindow } { DisposeWindow } { DragWindow } {Those routines don't know about floating windows, so calling them would put the floating windows behind normal windows.} {You also have to be careful with GetNewWindow. You can't call GetNewWindow(ID, wStorage, Pointer(-1)) because that} {would put the new window in the front. Rather, we call GetNewWindow(ID, wStorage, nil) which places the window in the} {back, and then call our home-made routine, SelectTheWindow.} {At every point in the program, we maintain three global variables : topFloat, bottomFloat and topWindow.} {topFloat holds the top floating window, nil if there is none. Same thing for bottomFloat with the bottom floating window,} {and for topWindow with the top regular window. These three global variables, along with the Window Manager's window} {list, are enough to handle the behavior of our windows.} {Hidden windows are placed behind all others, so they don't count when determining those variables' values.} {In order to distinguish between floating windows and regular ones, we choose a different windowKind for the former.} {To make hiliting and unhiliting floating windows simpler, I chose to use a WDEF that always draws windoids in a hilited} {state (e.g. Infinity Windoid with the Always Hilite option). This way, I don't have to worry at all about it.} {A final remark about modeless dialogs : modeless dialogs and floating windows don't go well together. The Dialog Manager} {is confused by floating windows. IsDialogEvent and DialogSelect don't work because they use FrontWindow to find the dialog.} {It is easy to rewrite a modified version of isDialogEvent. It is already trickier for DialogSelect. Maybe it would be simpler} {to use windows with controls instead of modeless dialogs. Or maybe it's possible to have DialogSelect and isDialogEvent} {work by patching FrontWindow ? } {If somebody comes up with a good idea on this topic, please tell me about it.} { --- How to use the unit, in short -------------------------------------------------------------------------------------- } {- Call InitFloats once before opening any windows.} {- To create a new window, call GetNewWindow(id, storage, NIL). Then call SelectTheWindow if it's a regular window,} { otherwise call MakeFloat.} {- To determine whether a given window is a floating one, call isFloating} {- Instead of SelectWindow[and ShowWindow], HideWindow, DisposeWindow, DragWindow, use SelectTheWindow, HideTheWindow,} { DIsposeTheWindow and DragTheWindow.} {- Upon a receiving a suspend event, you may call HideFloats. Use ShowFloats when receiving a resume event.} {- Instead of calling FrontWindow, you can read the values of the variables topFloat and topWindow. They should be up-to-date} { at any point in the program.} { --- Unit interface --------------------------------------------------------------------------------------------------- } interface procedure InitFloats; {initializes the global variables} function IsFloating (whichWindow: WindowPtr): Boolean; {tells whether a window is floating} procedure MakeFloat (theWindow: WindowPtr); {turns a newly created window into a floating window} procedure HideTheWindow (whichWindow: WindowPtr); {equivalents for the old Toolbox calls} procedure DisposeTheWindow (whichWindow: WindowPtr); procedure SelectTheWindow (whichWindow: WindowPtr); procedure DragTheWindow (whichWindow: WindowPtr; event: EventRecord); procedure DisposeTheDialog (whichDialog: DialogPtr); procedure HideFloats; {hides all floating windows} procedure ShowFloats; {shows all floating windows} var bottomFloat, topFloat, topWindow: WindowPtr; hasColorQD: Boolean; {initialized by InitFloats} { -------------------------------------------------------------------------------------------------------------------- } implementation const kFloatingKind = 317; {arbitrary constant for floating windows' windowKind} {ActivateWindow hilites/unhilites the window and then generates an activate/deactivate event for it.} {In order to generate activate events, it mucks with low-mem globals. It would be cleaner to directly call the activate} {routine for the window. The advantage of this method is that it also works with modeless dialogs : the Dialog Manager} {doesn't know that a dialog is active unless it actually receives an activate event.} procedure ActivateWindow (window: WindowPtr; on: Boolean); const CurActivate = $0A64; {writing a WindowPtr in those globals generates} CurDeactive = $0A68; {an activate or deactivate event} var p: ^WindowPtr; begin if window <> nil then begin HiliteWindow(window, on); {hilite the window} if on then p := Pointer(CurActivate) {generate the appropriate event} else p := Pointer(CurDeactive); p^ := window; end; end; {InitFloats is to be called at the beginning of the program, before creating any windows. It sets our global variables to nil.} procedure InitFloats; var e: OSErr; response: longint; begin topFloat := nil; bottomFloat := nil; topWindow := nil; e := Gestalt(gestaltQuickdrawVersion, response); hasColorQD := (e = noErr) and (response >= gestalt8bitQD); end; {IsFloating and isVisible are little, foolprof routines that tell whether a window is floating or visible.} {Using them avoids errors such as examining WindowPeek(whichWindow)^.visible when whichWindow = nil} function IsFloating (whichWindow: WindowPtr): Boolean; begin if whichWindow = nil then IsFloating := false else IsFloating := WindowPeek(whichWindow)^.windowKind = kFloatingKind; end; function isVisible (whichWindow: WindowPtr): Boolean; begin if whichWindow = nil then isVisible := false else isVisible := WindowPeek(whichWindow)^.visible; end; {UpdateFloats updates the topFloat and bottomFloat variables by walking down the window list. It then determines} {topWindow by taking the first visible window after bottomFloat.} procedure UpdateVars; var theWindow: WindowPtr; begin theWindow := FrontWindow; {start from the frontmost window} if IsFloating(theWindow) then begin {if it's floating, then it's topFloat} topFloat := theWindow; while (theWindow <> nil) and isFloating(theWindow) do begin {walk down the list until we find a regular window} bottomFloat := theWindow; {or the end of the list} theWindow := WindowPtr(WindowPeek(theWindow)^.nextWindow); end; while (theWindow <> nil) and (not isVisible(theWindow)) do {find the next visible window after bottomFloat} theWindow := WindowPtr(WindowPeek(theWindow)^.nextWindow); topWindow := theWindow; end else begin {if the frontmost window is a regular one, then there} topFloat := nil; {are no floating windows and topWindow = FrontWindow} bottomFloat := nil; topWindow := FrontWindow; end; end; {MakeFloat turns a newly created window into a floating window and brings it to the front} {The window must have been created via a call to [Get]NewWindow(id, wStorage, nil).} procedure MakeFloat (theWindow: WindowPtr); begin BringToFront(theWindow); {Bring the window to the front without unhiliting other} ShowHide(theWindow, true); {windows. Make it visible in case it isn't} if topFloat = nil then bottomFloat := theWindow; {update bottomFloat et topFloat} topFloat := theWindow; WindowPeek(theWindow)^.windowKind := kFloatingKind; {remember that this window should float} end; {ReactToRemoval handles a few necessary steps after killing whichWindow. underWindow is the window that was} {under whichWindow (i.e. next to it in the window list).} procedure ReactToRemoval (underWindow: WindowPeek); begin while (underWindow <> nil) and (not isVisible(WindowPtr(underWindow))) do underWindow := underWindow^.nextWindow; {activate the next visible window under whichWindow} ActivateWindow(WindowPtr(underWindow), true); UpdateVars; {update the variables} end; {There was a bug in HideTheWindow, reported and fixed by Ralph ? (I stupidly lost his name and address). The problem was that} {HideWindow(whichWindow) moves the next visible window to the top of the list, so ReactToRemoval will end up activating the} {second visible window, and we will have two active windows. The fix is to call SendBehind to send whichWindow behind all} {other windows prior to hiding it. This way HideWindow has no side effects.} procedure HideTheWindow (whichWindow: WindowPtr); var underWindow: WindowPeek; {we must determine which window is under whichWindow} begin {before hiding it, because HideWindow sends it to the back} underWindow := WindowPeek(whichWindow)^.nextWindow; {thus changing the value of nextWindow} SendBehind(whichWindow, nil); HideWindow(whichWindow); ReactToRemoval(underWindow); end; procedure DisposeTheWindow (whichWindow: WindowPtr); var underWindow: WindowPeek; begin underWindow := WindowPeek(whichWindow)^.nextWindow; {same thing as HideTheWindow, with DisposeWindow instead} DisposeWindow(whichWindow); ReactToRemoval(underWindow); end; procedure DisposeTheDialog (whichDialog: DialogPtr); var underWindow: WindowPeek; begin underWindow := WindowPeek(whichDialog)^.nextWindow; {same thing as HideTheWindow, with DisposeDialog instead} DisposeDialog(whichDialog); ReactToRemoval(underWindow); end; {SelectTheWindow is the most important routine. Here are the steps to be taken :} {If whichWindow is not visible, show it.} {- If whichWindow is a floating window, just bring it to the front.} {- If it is a regular window and there are no floating windows, bring it to the front, activate it, deactivate the former front window} {- If it is a regular window and there are floating windows, send it behind bottomFloat, activate it, deactivate the old topWindow} {Finally, update the global variables.} procedure SelectTheWindow (whichWindow: WindowPtr); var wPeek: WindowPeek; begin wPeek := WindowPeek(whichWindow); if not wPeek^.visible then {make the window visible if necessary} ShowHide(whichWindow, true); if IsFloating(whichWindow) then {a floating window just needs to be brought to the front} BringToFront(whichWindow) else if whichWindow <> topWindow then begin {regular window. If it isn't topWindow we have to react} if bottomFloat <> nil then begin {if there are floating windows, then send whichWindow} SendBehind(whichWindow, bottomFloat); {behind bottomFloat} PaintOne(wPeek, wPeek^.strucRgn); {These two low-level calls are necessary to generate} CalcVis(wPeek); {update events and maintain the windows' visRgns} end else BringToFront(whichWindow); {no floating windows : just call BringToFront} ActivateWindow(whichWindow, true); {activate the new front window} ActivateWindow(topWindow, false); {and deactivate the old one} end; UpdateVars; {finally update our vars} end; {DragTheWindow is to be called instead of DragWindow, because DragWindow brings the window to the front unless the command} {key is down. We use DragGrayRgn to drag a gray outline of the window. We clip the dragging to a region consisting of the whole} {desktop, minus the windows that are in front of the window we're dragging.} {To emulate DragWindow's behavior, we send the window to the front, unless the command key is down.} procedure DragTheWindow (whichWindow: WindowPtr; event: EventRecord); const cancelDrag = $80008000; {returned by DragGrayRgn if the user aborts the drag} var savePort: GrafPtr; wPort: GrafPort; thePoint: point; newLoc: longint; theWindow: WindowPeek; dragRect: rect; dragRgn: RgnHandle; begin if BitAnd(event.modifiers, cmdKey) = 0 then {unless the command key is depressed, select the window} SelectTheWindow(whichWindow); {before doing the dragging} GetPort(savePort); {save the grafport} SetPort(whichWindow); thePoint := whichWindow^.portRect.topLeft; LocalToGlobal(thePoint); {remember the window's original position} dragRgn := NewRgn; {put the window's strucRgn in a new region} CopyRgn(WindowPeek(whichWindow)^.strucRgn, dragRgn); {we can't pass the strucRgn directly to DragGrayRgn} {because DragGrayRgn modifies the region} OpenPort(@wPort); {open a port to draw the gray outline} CopyRgn(GetGrayRgn, wPort.visRgn); {set its visRgn to the whole screen, minus the strucRgns} theWindow := WindowPeek(FrontWindow); {of all the windows in front of whichWindow} while theWindow <> WindowPeek(whichWindow) do begin DiffRgn(wPort.visRgn, theWindow^.strucRgn, wPort.visRgn); theWindow := theWindow^.nextWindow; end; dragRect := GetGrayRgn^^.rgnBBox; InsetRect(dragRect, 4, 4); newLoc := DragGrayRgn(dragRgn, event.where, dragRect, dragRect, noConstraint, nil); if (newLoc <> cancelDrag) and (newLoc <> 0) then MoveWindow(whichWindow, thePoint.h + LoWord(newLoc), thePoint.v + HiWord(newLoc), false); DisposeRgn(dragRgn); {dispose of the region and of the port} ClosePort(@wPort); SetPort(savePort); {then restore the old grafPort and exit} end; {HideFloats/ShowFloats are simple routines that hide/show all floating windows. They're useful to handle suspend/resume events.} procedure HideFloats; begin while isFloating(FrontWindow) do {calling FrontWindow twice is not very efficient, but} HideWindow(FrontWindow); {it's shorter!} topFloat := nil; bottomFloat := nil; end; procedure ShowFloats; const WindowList = $09D6; {this low-mem variable contains the address of the} var {first window in the Window Manager's list} theWindow, nextWindow: WindowPeek; p: ^WindowPeek; begin p := Pointer(WindowList); theWindow := WindowPeek(p^); {find the first window in the Window Manager's list} while (theWindow <> nil) do begin {walk down the list...} nextWindow := theWindow^.nextWindow; if isFloating(WindowPtr(theWindow)) then SelectTheWindow(WindowPtr(theWindow)); {..sending every floating window to the front} theWindow := nextWindow; end; end; end.