ADVERTISEMENT
|
|
| Volume Number: | | 4
| | Issue Number: | | 12
| | Column Tag: | | C Workshop
|
Tool Window Wanager 
By Thomas Fruin, The Netherlands
The Tool Window Manager
Thomas Fruin is a graduate student of Computer Science at Leiden Universit, who also does study-related work for the two universities in Amsterdam, the University of Amsterdam and the Free University. He is the president and co-founder of VAMP (Vereniging Apple Macintosh Programmeurs), the main Dutch association for Macintosh programmers. Otherwise, he has done various odd Mac-related jobs to earn money, usually writing software (educational, communications, etc.).
The Macintosh user interface has been enhanced with some interesting new features lately: tear-off menus, pop-up menus, hierarchical menus, window layers, color, and even a new name: human interface. While most of these additions are in the new ROMs and the latest System file, not everything has been dealt with by Apple. Most notably is the lack of code to support tear-off menus, the most recent novelty and pioneered by HyperCard. They are something you are going to have to code all by yourself (well, with a bit of help from others). Darryl Lovato addressed the problem of tearing off a menu first, followed by an in depth article by Don Melton and Mike Ritter. In my contribution I will concentrate on keeping windows (torn-off menus or otherwise) floating above the others on your desktop, by presenting an alternative window manager - called the Tool Window Manager - that does all the work.
While tear-off menus are new to the Mac, floating windows are not. For several years now there have been applications putting their tools, patterns and other wares in these little windows, like FullPaint and PageMaker to name just two. Tool windows are a tricky business though, and it takes some careful planning to get everything right. Even commercial programs make mistakes at this (see the sidebar on the bugs in FullPaint and MacPaint).
Tool Windows Have a Special Appearance
First of all, since tool windows always lie on top, there is no distinction between an active or inactive tool window. This means that the tool should either have no title bar (like in FullPaint), or a title bar that never changes its appearance (like in HyperCard or Claris MacPaint). The latter case requires a special window definition function. This is not the subject of this article, however, since window definition functions have already been dealt with by MacTutor. Besides, you can cheat by experimenting with the WDEF for tool windows in the new MacPaint. Just copy WDEF 34 from your copy of MacPaint with ResEdit and paste it in your own application. Of course you cannot distribute it ... (but you knew that, didnt you?)
Don Melton and Mike Ritters comments on the user interface guidelines for tool windows, and especially their additions to the guidelines, are a good idea. Since the article came out, I have modified my manager to incorporate these additions: when a non-application window is brought to the front (usually a desk accessory), my manager will unhighlight all the tool windows. When that window disappears, all the tools are highlighted again. I also added this behavior when a modal dialog window is brought to the front, because it seems to make sense. Finally, the unhighlighting of the tool windows also takes place when another application is activated under MultiFinder. It is up to the application that uses the Tool Window Manager to hide the tool windows when the application is juggled out, since it may not always want to hide the tools (one of them may be some kind of status window, for example).
Tool Windows or GhostWindows?
Tool windows are also sometimes called ghost windows. This is probably due to the obscure GhostWindow low memory global that is mentioned in Inside Macintosh volume I, page 287. You had better forget about it. Im not only doing this as a public service (since Apple is discouraging people to use low memory globals), but also because it really doesnt help in our case. As in almost every part of Inside Macintosh, the information is there, but it takes some careful reading to understand what the paragraph is about. Besides, GhostWindow will only let you have one tool window at a time, and we want to create as many tool windows as will fit in memory (its ok with me if you only want two or three). So it seems the only way out is a front end for all the Window Manager routines that could cause a tool window to become obscured. This is what I have written in the Tool Window Manager, or TWindow Manager for short.
The Mad T Party
The TWindow Manager is a set of 10 functions that replace calls to functions in the Window and Event Managers in your source file. These new functions do some extra processing before they call the Window and Event Manager themselves. The names of these TWindow Manager routines are the same as their corresponding Window and Event Manager functions, except for a prefixed T, as in TSelectWindow and TGetNextEvent. Their parameters are almost the same as the original functions, with a few slight modifications. See the explanations below for details. There are six categories of window manipulations that need to be replaced by TWindow Manager functions to work correctly with tool windows: (1) Adding windows to the desktop: use TNewWindow, TGetNewWindow and TShowWindow. (2) Removing windows: use TCloseWindow, TDisposeWindow and THideWindow. (3) Bringing lower windows to the top with TSelectWindow. (4) Dragging with TDragWindow. (5) Utility functions you will need every now and then: TFrontWindow and the new TInitWindows and TGetWKind. (6) Event handling: TGetNextEvent. This last function is included, because activate and deactivate events are handled differently when there are tool windows about. Note that I did not mention resizing windows (yes, Im mentioning it now, thank you). Amazingly, GrowWindow is smart enough to draw its grow outline below any windows that are lying on top of it. Its always nice to have less work to do.
There are three source files that go with this article. TWindows.c is the TWindow Manager itself, written in MPW C, and can be compiled into a separate module for later linking with your own program. TWindows.h is a file for inclusion by your program. It holds common data types and the TWindow function types. Your program will need this file if it wants to call TWindow Manager functions and use constants such as toolKind and anyKind. Finally, TWindowTester.c (also in MPW C) is an example program that uses the TWindow Manager. It is a familiar vanilla Macintosh program that lets you create normal and tool windows to experiment with, as may as you like (see figure 1). You can select and drag windows, and open desk accessories as well. (Note that I got the tool window definition function from MacPaint 2.0, as I described above. In the source I am distributing, the windows look more like FullPaints tools, because MacPaints defproc is not included.) The menus called Windows and Tools let you make any document window or tool invisible and visible again, even if it isnt in front. I added these menus myself to test the robustness of the TWindow Manager. Note that you cant do anything useful with TWindowTester (although you might derive great pleasure from opening, say, 100 windows). For example, the scroll bars in the document windows dont work. I just put them there so you can see that windows get activated and deactivated properly.
Background Information, Utility Functions
The TWindow Manager keeps tool windows and document windows apart by storing a special value in the WindowRecords windowKind field. This value is defined as the toolKind constant (equal to 30000) in the source file for TWindows. If you like, you can modify it to a value more suitable to your application, but there should be little conflicts as is. If any of the TWindow Manager functions wants to know what kind of window it is dealing with, it calls the utility function TGetWKind. This function is precisely for determining the windowKind value of a window. Why write a function for such a simple task? Well, TGetWKind doesnt just return the windows windowKind, but does some extra processing as well. If the window is a desk accessory window (with a negative windowKind), TGetWKind will always return the constant systemKind (equal to -1). If the window is a dialog window, with windowKind equal to the toolbox constant dialogKind, TGetWKind only returns dialogKind if the window is a modal dialog window. If the dialog is modeless, TGetWKind returns the toolbox constant userKind (the toolbox constant for normal document windows). This is because modeless dialog windows behave almost exactly the same as document windows, i.e. they lie behind tool windows. TGetWKind figures out what kind of dialog it is by examining the windows variation code and definition procedure. Window definition procedures have an (optional) header embedded in their code that holds information like the WDEFs resource ID (see Technical Note 110 for more information about WDEFs - and make sure you have the real 110). The dialog is considered modeless if its window is a document window: this is when the WDEFs resource ID is zero and the variation code is equal to documentProc, noGrowDocProc or zoomDocProc. To play it safe, I let TGetWKind also check if the resource type in the header is really WDEF. If the WDEF doesnt have a header (like the MacPaint tool window for example), its extremely unlikely that there will be a WDEF string embedded at that particular position in the code.
The Window Manager in the Macintosh ROM has (as far as I can determine) only one global variable dealing with window positions, called WindowList - at location $9D6. It holds the very first window in the window list. By comparison, my TWindow Manager has three position globals of its own: frontToolWindow, backToolWindow and frontDocWindow. FrontToolWindow points to the frontmost visible tool window, backToolWindow to the backmost visible tool window, and frontDocWindow to the frontmost visible document window. Two other global variables I use mimic the CurActivate ($A64) and CurDeactive ($A68) low memory globals in the Macintosh: toBeActivated and toBeDeactivated. They hold the real windows that need activating and deactivating, and my TGetNextEvent takes them into account when it returns activate events. They are set by various TWindow Manager routines whenever activate events need to be posted. Finally I have two utility global variables, firstRgn and secondRgn. All these seven globals need to be initialized by the TInitWindows function, before you start using any functions in the TWindow Manager.

Figure 1: The TWindowTester Application
TFrontWindow is my replacement for FrontWindow. You can specify a window type in the windowKind parameter (note that FrontWindow has no parameters at all). This window type is one of the constants toolKind, userKind or anyKind. Usually you will want to call TFrontWindow with windowKind equal to userKind; it will then return the frontmost visible document window (the active window), ignoring any tool windows that may be lying above it (unless there is a desk accessory or modal dialog on top - it then returns the DAs or dialogs window). Calling TFrontWindow with windowKind equal to toolKind returns the frontmost visible tool window (again taking DAs and dialogs into account). When you call TFrontWindow with anyKind as the parameter, it behaves exactly like the original FrontWindow function - it simply returns the frontmost visible window.
Selecting Windows
Im saving the subjects of adding and removing windows for later, because they are the trickiest ones to handle. TSelectWindow, by comparison, is much easier. Here, the main problem is to bring a document window to the front while keeping it behind any tool windows that may be visible. Apple, in its infinite wisdom, already has a function that almost does this: its called SendBehind and its listed on page I-286 in Inside Macintosh. You use it to move a window closer to the front (ah, so thats why its called SendBehind), and specify the window behind which your window should be moved. In our case this will usually be the backmost tool window. Note what Inside Macintosh has to say about this: If youre moving theWindow closer to the front (that is, if its initially even further behind behindWindow), you must make the following calls after calling SendBehind:
wPeek = POINTER( theWindow );
PaintOne( wPeek, wPeek^.strucRgn );
CalcVis( wPeek );
This means that you have to do some low-level stuff, because SendBehind isnt really meant to bring windows forward. PaintOne (5 pages further) is a routine that whitewashes the newly exposed part of the window. CalcVis (on the next page in Inside Macintosh) recalculates the windows visRgn. Unfortunately, it seems Apple goofed here. CalcVis only recalculates the visRgn of one window, while we need to recalculate the visRgns of all the windows following our window. Apple makes up for this, though, by supplying us with the routine CalcVisBehind (on same page as CalcVis). This function does not only recalculate the visRgn of our window, but also of all the windows following it. If youre curious, try replacing CalcVisBehind by CalcVis and watch what happens on the screen (dont do this if you dislike making a mess of things).
Another thing: the above call to PaintOne paints the whole window white, and not only the newly exposed part. Therefore, Ive created the utility function BringForward, that calls SendBehind, PaintOne and CalcVisBehind, and only repaints those newly exposed parts. BringForward is called by TSelectWindow and many other routines in the TWindow Manager.
If you look at the source code for TSelectWindow, youll see it does a lot more than just calling BringForward. This is because we have to take desk accessories into account. An open desk accessory will always lie above any tool windows when it is active. If the user selects one of the document windows, it naturally comes to the front, but not until all of the tool windows have also been brought to the front (remember that they were lying behind the desk accessory). Ive written another utility function for this, obviously called BringToolsForward. This function makes repeated calls to BringForward for each visible tool window. These calls it makes to BringForward have a special flag to tell it not to call CalcVisBehind each time. This is because CalcVisBehind is a relatively time-consuming function. Once all the tool windows have been brought forward, BringToolsForward makes one final call to CalcVisBehind, recalculating all those changed visRgns at once. This speeds things up by about 30%, which is nice when you have a lot of tool windows (at one time during testing, I created about 50). The speed bottleneck, however, seems to be SendBehind. I wish Apple would publish interfaces to the Layer Manager in MultiFinder, because it has much more efficient ways of quickly moving windows to the front. Those arent standard Window Manager functions working there ...
Two other useful utility functions internal to the TWindow Manager are NextVisWindow and PrevVisWindow. They operate fairly straightforward, and do exactly what their names imply: NextVisWindow returns the next visible window, and PrevVisWindow returns the previous visible window in the window list, with respect to the window that gets passed to it. NextVisWindow and PrevVisWindow are used in various places in the TWindow Manager, including here in TSelectWindow.
Dragging Windows
TDragWindow replaces the standard toolbox function DragWindow. It lets you drag a document window, while ensuring that it doesnt obscure any tool windows that may be lying in front of it. Again, I am grateful to Apple for doing the dirty work: they wrote this useful low level function called DragGrayRgn (Inside Macintosh page I-294) that pulls a gray outline of a region around. DragWindow actually calls DragGrayRgn, but in the wrong way (for our purposes).
The first thing TDragWindow does is check if the user is holding down the Command key. It gets this information from the EventRecord that you pass it. (Note that TDragWindow wants you to pass an EventRecord as its second parameter, while DragWindow wants the point where the mouse was clicked.) If the Command key is not being held down, TDragWindow calls TSelectWindow to move the window to the front (while staying below any tool windows, remember?). Once that is done, we can call DragGrayRgn to move an outline of the window. This outline is the windows strucRgn, so we pass copy of the strucRgn to DragGrayRgn. DragGrayRgn needs some more parameters, like the point the mouse was originally clicked and the bounding rectangles for the drag, but they are pretty obvious from the documentation in Inside Macintosh.
We have one last problem: how do we get DragGrayRgn to draw its outline below the tool windows? Aha, well, theres yet another low-level Window Manager function for that (I hope Im not boring you). Its called ClipAbove, and Inside Macintosh says that ClipAbove sets the clipRgn of the Window Manager port to be the desktop intersected with the current clipRgn, minus the structure regions of all the windows in front of the given window. Great. So all we have to do is set the current port to the Window Manager port, set its clipRgn to the whole current desktop, and call ClipAbove. Then we go ahead and call DragGrayRgn. Youll see that the gray outline gets drawn in the Window Manager port, and stays neatly below the tool windows. After dragging, we check if the user really did move the window by examining DragGrayRgns result. If he did, we call MoveWindow to actually move the window to its new position. And of course we restore the Window Manager ports clipRgn (since weve been messing with it), and reset the current port.
Dealing With Activate and Deactivate Events
Normally, the Window Manager in the Macintosh ROM posts an deactivate event when the frontmost window gets moved back, and a activate event when a window gets moved to the front. This wont work in our case, because our document windows never get moved to the front. They always stay behind our tool windows. But we still want selected document windows to receive (de)activate events, and preferably in the standard way: as events returned by some kind of TGetNextEvent function. My solution was to copy the mechanism Apple uses: the Macintosh has two low memory globals, CurActivate and CurDeactive. GetNextEvent checks these two globals first, before it examines the event queue. If it finds that one of these globals is nonzero, it fills in the users EventRecord with an activate event for the window pointed to by one of these globals. CurActivate holds a pointer to the window that needs to be activated, and CurDeactive a pointer to the window that needs to be deactivated. Deactivate events have a higher priority than activate events.
My new TGetNextEvent does more or less the same, but instead it uses my globals toBeActivated and toBeDeactivated. (Please note that they are not in low memory, so dont worry about using them.) Whenever any routine in the TWindow Manager moves windows around, it updates these two globals. When TGetNextEvent gets called (as a replacement for GetNextEvent - writing TWaitNextEvent is left as an exercise for the reader), it checks these two globals and returns its own activate events back to the calling application.
TGetNextEvent handles three types of situations. It calls EventAvail to see what kind of event is currently pending. (1) The first situation arises when there is a true (de)activate event pending. In that case, it gets the event with GetNextEvent (the real one). Depending on the type of event (activate or deactivate), it compares the events window with either the toBeActivated or the toBeDeactivated global. If the window the toolbox wants to activate is equal to toBeActivated, or the window it wants to deactivate is equal to toBeDeactivated, the toolbox is doing fine and there is nothing left for us to rectify. If toBeActivated or toBeDeactivated are nil, it means we didnt anticipate the activate event. This is an important occurrence and happens when a desk accessory suddenly gets opened or closed. In this case we substitute our front document window (frontDocWindow) for the window that gets (de)activated; additionally, we call HiliteWindow to highlight or unhighlight the frontDocWindows title bar, since that hasnt been done yet, and we call HiliteTools to unhighlight or highlight all the tool windows. HiliteTools is a simple utility function I wrote that traverses all the tool windows, and calls HiliteWindow for each visible one. Oh yes, if there wasnt any document window open to pass the event to, we call TGetNextEvent again (recursively), thereby discard the current (de)activate event, and get a new event. After handling the event we reset toBeActivated or toBeDeactivated to nil and return.
(2) The second situation occurs when EventAvail does not return an (de)activate event and either toBeDeactivated or toBeActivated is not nil. This means some action of the TWindow Manager generated an (de)activate event without the toolbox knowing about it (like when you call TSelectWindow to activate a window without bringing it completely to the front). The thing to do is to fill the callers EventRecord with an activate event for the window in the toBeActivated or toBeDeactivated global. The call to WindowExists has to do with dialog windows: Ill get to that later. (3) The third and final situation occurs when EventAvail doesnt return an (de)activate event, and both toBeActivated and toBeDeactivated are nil. Theres nothing out of the ordinary to do here, so we simply call GetNextEvent and return the event.
While I was debugging this routine, I discovered something I had not anticipated since it had not been mentioned in Inside Macintosh: now and then, activate events were being returned for desk accessories! I was expecting GetNextEvent to take care of this by calling SystemEvent to pass the event on to the desk accessory directly (see page I-442). To remedy this I included the extra test in TGetNextEvent on the type of window belonging to an activate event. If the window isnt one of our (tool or document-) windows, we leave the event as it is. Another surprise were the deactivate events that were being posted for invisible windows. But if you think about it, it makes sense. When you hide the front window, the toolbox moves the window behind it to the front and activates it. Should you make your hidden window visible again, it will appear behind the current frontmost window, and should thus appear deactivated. Therefore the deactivate event. It makes you realize a lot is happening behind the scenes ...
Adding Windows
At first I thought it would be easy to write front ends for functions like NewWindow and GetNewWindow. All I would have to do was make sure that the calling application wasnt trying to create new document windows in front of existing tool windows. I thought I could check this with a couple of nested if-else statements. I managed to put together some stuff, but it didnt seem to cover everything. Whenever I thought about the code for a while, I would come up with a strange situation (usually in the bathroom, does that happen to you too?) that I hadnt anticipated yet. Things went really wrong when I tried to write TShowWindow! There were so many different combinations of windows, I had to take a completely different approach. The important thing to realize is that once the application program starts creating new windows, or making windows visible, these windows can appear absolutely anywhere in the window list. A document window could appear on top, with tools behind it. Or a desk accessory might turn up between a tool and a document window, which is also not allowed. When calling GetNewWindow or NewWindow, windows can turn up in the wrong place, because the caller can specify any position for the new window. When calling ShowWindow, things can go wrong, because you have very little control over what is happening with your invisible windows. OK, I admit I make things worse myself, because the BringToolsForward function, for example, only moves visible tool windows, and leaves invisible tools scattered all over the window list. But it is more efficient to keep only the visible windows in their correct planes. We just have to be very careful when we make windows visible again.
First of all, I changed my earlier TGetNewWindow and TNewWindow into very simple functions that do two things: (1) They create the window as specified by their parameters, but make it invisible. They then set the windows windowKind field according to an extra TNewWindow or TGetNewWindow parameter. This parameter is also called windowKind, and lets the application indicate if it wants a tool window or document window. (Remember that the TWindow Managers distinguishes tool windows by looking at the windowKind field in the WindowRecord.) But the important thing is that the window is always created invisible. (2) If the caller wants the window to be visible, I call the function TShowWindow afterwards. TShowWindow does the tedious work of making the window show up in an allowed position, and all the testing code is centralized in this one function. So how do I find out if the caller wants a visible or invisible window? Well, when the calling application uses TNewWindow its easy: I just examine the visible parameter. With TGetNewWindow I have do some extra work, because there is no visible parameter, but only a WIND resource. TGetNewWindow reads the WIND resource manually with a GetResource call, and gets the visible field from there. While the resource is in memory, TGetNewWindow also reads the other fields from it, such as the title, goAwayFlag etc. It then calls ReleaseResource to get rid of the WIND template, and creates the window with a normal NewWindow call.
Removing Windows
Before I get into TShowWindow, I want to point out that removing windows is very similar to adding windows. TCloseWindow and TDisposeWindow are very simple functions that call THideWindow to hide the window and make sure the rest of the windows are still in allowed positions. They then get rid of the window data structures through the usual calls to CloseWindow and DisposeWindow. There is one tricky situation though: suppose the window is the active one. THideWindow will then post a deactivate event for it in toBeDeactivated. If the window gets destroyed, that event will have to be removed. Thats why I check if the window is equal to toBeDeactivated, and if it is, clear toBeDeactivated. The toolbox does the same thing (hardly surprising, because thats where I got the idea in the first place): CloseWindow and DisposeWindow check CurDeactive and clear it if necessary.
So it all boils down to THideWindow and TShowWindow - on to the next section, class.
Hiding and Showing - Where Are We in the First Place?
If you start thinking about it, many strange permutations of windows can occur on the desktop: desk accessories, tool windows, document windows, modal dialogs and modeless dialogs. Somehow, we have to keep the tools in front of the documents and modeless dialogs, while letting DAs and modal dialogs be on top from time to time. And any category of windows might be absent - suppose the application doesnt have any tool windows visible?
There may be many window permutations, but you will be pleased to hear the number is not infinite. In fact, there are precisely ten distinct positions a window can be in with respect to the other windows on the screen. (There may be more or less, depending on your own way of classifying windows.) Once you know in what position a particular window is, its very clear what you have to do when that window is hidden or shown. The utility function TGetWPosition finds out what this position is: THideWindow or TShowWindow pass it a window, and it will tell them in which of the ten places the window is. It returns one of the ten possible constants describing this position. Note that TGetWPosition works for both visible and invisible windows. Since these ten positions are central to understanding how THideWindow and TShowWindow work, let me list them for you (remember that our window can be visible or invisible, and can be either a tool-, document- or dialog window). Youll want to glance at figure 2 along the way.
alone there are no other visible windows on the screen
beforeDialog our window is completely in front, and the window behind it is a modal dialog
beforeSystem our window is completely in front, and the window behind it is a desk accessory
beforeTool our window is completely in front, and the window behind it is a tool window
beforeDocument our window is completely in front, and the window behind it is a document window
behindFrontSystem there are one or more desk accessories in front, and our window is lying just behind them (but before any tool or document windows); it could be our window is the frontmost tool window
betweenTools our window is lying among the tool windows; if its visible, it could also be the last tool window
behindTools our window is lying behind all the tool windows, but in front of any document windows; if our window is a tool, it can only be invisible, otherwise it would be in the betweenTools position
behindSystem this is really an obscure arrangement, and only exists when our window is invisible: there are no document windows, there is a tool in front, and the tools are followed by one or more desk accessories; our invisible window is lying behind the desk accessories
betweenDocuments our window is lying among the document windows; if its a visible document window, it certainly isnt the frontmost one
TGetWPosition starts with a pointer to the frontmost visible window, and works its way through the window list until it finds our window. While its doing this, it keeps track of current window position. Once it hits our window, it returns this position. Notice that when our window is visible, TGetWPosition really looks for the window preceding it. That way the algorithm works for both visible and invisible windows. As a bonus you also get the windowKind of the frontmost window in one of the parameters. THideWindow and TShowWindow might want to look at this to see if there is a desk accessory in front.

Figure 2: Window Positions
Weve Found Our Place!
Now everything falls into place. TShowWindow and THideWindow simply call TGetWPosition, and let three big switch statements (the first if we are dealing with a tool window, the second for a document window/modeless dialog and a little one for a modal dialog) handle the nitty gritty details. We have a case for almost every window position. Some cases are the same (like hiding a tool in the positions alone and beforeSystem), while others are so obscure that they cannot possibly occur in each function (there is no case for hiding a window in the behindSystem position). If you look at the source, youll see that each case executes a small but different block of code. The code deals with things like setting the toBeActivated and toBeDeactivated globals, bringing tools forward with BringToolsForward, updating frontToolWindow, backToolWindow and frontDocWindow, and sometimes highlighting windows manually with HiliteWindow and HiliteTools. Most cases are easy to follow, although there are a few complex ones. The behindTools case for a hiding a document window does a lot of things, so it is a nice example to illustrate how all of this works.
An Example of Hiding a Window
When our document window is behindTools, it is obviously the frontmost document window (it is lying just behind the tools). This means we have to find the new document window (our window is going to be hidden). We call NextVisWindow with a windowKind parameter of userKind. This means NextVisWindow will return the next visible document window (or modeless dialog, because TGetWKind considers modeless dialogs the same as document windows). But suppose the next visible window is a desk accessory? Thats why there is the other call to NextVisWindow. This call determines what the real first window behind ours is, regardless of the type of the window, because we use the anyKind parameter. We then hide our window with ShowHide.
If there is no window behind ours, we only have to post a deactivate event for our hidden window. If there is, there are two possibilities: it is a desk accessory - if behindWindow differs from frontDocWindow - or it is a document window, in which case behindWindow should be equal to frontDocWindow. A desk accessory will have to be brought completely to the front - if there isnt already a DA in front - with SelectWindow. Then something important: a document window that could be lying much further back (behind the DA) has to be brought forward, just behind the tool windows. Tools always stick to at least one document window. This is what the BringForward call is for. Finally, if the window behind ours was not a DA, but a document window, we make it the active window by highlighting it and posting the necessary activate events. All of this only if there isnt a DA in front, of course. Phew! Rest assured that if you understood this, the rest of the cases are a piece of cake.
TNewDialog, TGetNewDialog, TCloseDialog in TDisposDialog?
Dont panic! Im not going to write front ends for every manager in the toolbox. You will have to do a few extra things for dialogs though (especially to get the window behind it to deactivate properly). When you want to put up a new dialog, do the following: first call NewDialog or GetNewDialog to create the dialog, but make sure it is still invisible. Then call TShowWindow to make it visible. TShowWindow will ensure that the document window behind it gets deactivated properly, and that all the tools are unhighlighted. Chances are that you are already doing this (creating an invisible dialog, and showing it afterwards) to install a user item to boldly go where no man ... oops, to boldly outline the OK button in the dialog. To remove a dialog you should do something similar: call THideWindow to hide it, and CloseDialog or DisposDialog afterwards.
You might remember that when a window gets destroyed, a possible pending deactivate event for it should be removed as well. Unfortunately, THideWindow posts a deactivate event in toBeDeactivated, but it never gets removed. Sure, CloseDialog and DisposDialog remove the toolboxs event in CurDeactive, but what about our own event? I suppose I could have written front ends for these Dialog Manager functions, but I think Ive gone far enough as is. So this is where the WindowExists function comes in (remember, I mentioned it back when I was discussing TGetNextEvent). As a last resort, WindowExists gets called to make sure the window that needs deactivating still exists. If it doesnt, we call TGetNextEvent again to get a new event instead.
The Point Of It All
The advantage of all this tedious, boring, dense and voluminous code is that you can forget it. Yes, go ahead and forget it. Once the TWindows.c module has been compiled, you can link it with your application and you wont have to worry about tool windows again. Just change your usual calls to the Window Manager into calls to the TWindow Manager. (See TWindowTester for examples). Whenever you create new document or tool windows, hide them, drag them around the screen, or bring them to the front, the TWindow Manager will do its utmost best to keep you happy by keeping those tools floating above your documents.
There is far more information in the source files than I have mentioned here. Especially the sources on the source code disks have lots more comments and explanations than what would fit in the magazine. Still, I might have overlooked something. In that case (or for any not necessarily useful reason - what I mean to say is: I dont mind getting mail), you can always reach me at one of the addresses below:
Thomas Fruin fruin@hlerul5.BITNET
Galgewater 38 thomas@uvabick.UUCP
2311 VZ LEIDEN dibs@well.UUCP
The Netherlands hol0066 on AppleLink
2:508/15 on FidoNet
| Continued in next frame
| |
| | Volume Number: | | 4
| | Issue Number: | | 12
| | Column Tag: | | C Workshop
|
Tool Window Wanager (code) 
{1}
Listing: TWindow.c
/*
* TWindows.c
*
* C source of an extended Window Manager that
* supports tool windows; these are windows
* that always float on top, for palettes and
* tools.
*
* Written in MPW C 2.0
*
* Copyright Thomas Fruin 1988
* All rights reserved.
*/
/*
* T O O L B O X I N C L U D E S
*/
#include <Types.h>
#include <Memory.h>
#include <Quickdraw.h>
#include <Events.h>
#include <Windows.h>
#include <ToolUtils.h>
#include <OSUtils.h>
#include <Resources.h>
/*
* T O O L B O X D E F I N I T I O N S
*/
#define GrayRgn (*(RgnHandle *)0x9EE)
#define WindowList (*(WindowPeek *)0x9D6)
/*
* D E F I N I T I O N S
*/
/* Defines LONGINT, INTEGER, the windowKind
constants and the externally callable
TWindow Manager functions. */
#include <TWindows.h>
/* Result codes describing window positions, as
returned by the function GetWPosition. */
#define alone 0
#define beforeDialog 1
#define beforeSystem 2
#define beforeTool 3
#define beforeDocument 4
#define behindFrontSystem 5
#define betweenTools 6
#define behindTools 7
#define behindSystem 8
#define betweenDocuments 9
/* Miscellaneous */
#define deactivFlag 0
#define noDrag -32768
#define invisible false
#define curVersion 1
#define postponeCalc true
#define normalCalc false
/*
* T Y P E S
*/
/* The WINDData structure matches the structure
of a WIND resource in a resource file. It
is used to access the various fields of the
WIND resource after it has been read in from
the file. */
struct WINDRecord
{
Rect boundsRect;
INTEGER procID;
Boolean visible;
char filler1;
Boolean goAwayFlag;
char filler2;
LONGINT refCon;
Str255 title;
};
typedef struct WINDRecord WINDData;
typedef struct WINDRecord *WINDPtr;
typedef struct WINDRecord **WINDHandle;
/* The WDEFHeader structure matches the
structure of the (optional) header of a
window definition procedure resource. */
struct WDEFRecord
{
INTEGER branch;
INTEGER flags;
LONGINT type;
INTEGER ID;
INTEGER version;
};
typedef struct WDEFRecord WDEFHeader;
typedef struct WDEFRecord *WDEFPtr;
typedef struct WDEFRecord **WDEFHandle;
/*
* G L O B A L S
*/
static WindowPeek toBeActivated,
toBeDeactivated,
frontToolWindow,
backToolWindow,
frontDocWindow;
static RgnHandle firstRgn,
secondRgn;
static SysEnvRec theWorld;
/*
* T I N I T W I N D O W S
*/
void
TInitWindows()
{
OSErr err;
frontToolWindow =
backToolWindow =
frontDocWindow =
toBeDeactivated =
toBeActivated = nil;
firstRgn = NewRgn();
secondRgn = NewRgn();
err = SysEnvirons( curVersion, &theWorld );
}
/*
* T G E T N E W W I N D O W
*/
WindowPtr
TGetNewWindow( windowKind, windowID,
wStorage, behind )
INTEGER windowKind;
INTEGER windowID;
Ptr wStorage;
WindowPtr behind;
{
Rect boundsRect;
Str255 title;
Boolean visible;
INTEGER procID;
Boolean goAwayFlag;
LONGINT refCon;
WindowPtr theWindow;
WINDHandle theWINDHandle;
WINDPtr theWINDPtr;
theWINDHandle =
( WINDHandle )GetResource( WIND,windowID );
if ( theWINDHandle != nil )
{
theWINDPtr = *theWINDHandle;
boundsRect = theWINDPtr->boundsRect;
visible = theWINDPtr->visible;
procID = theWINDPtr->procID;
goAwayFlag = theWINDPtr->goAwayFlag;
refCon = theWINDPtr->refCon;
if ( theWINDPtr->title.length == 0 )
title.length = 0;
else
BlockMove( &( theWINDPtr->title ), &title,
( Size )theWINDPtr->title.length + 1 );
p2cstr( &title );
ReleaseResource(( Handle )theWINDHandle );
theWindow =
NewWindow( wStorage, &boundsRect, &title,
invisible, procID, behind,
goAwayFlag, refCon );
if ( theWindow != nil )
{
(( WindowPeek )theWindow )->windowKind =
windowKind;
if ( visible != false )
TShowWindow( theWindow );
}
}
else
theWindow = nil;
return( theWindow );
}
/*
* T N E W W I N D O W
*/
WindowPtr
TNewWindow( windowKind, wStorage, boundsRect,
title, visible, procID, behind,
goAwayFlag, refCon )
INTEGER windowKind;
Ptr wStorage;
Rect *boundsRect;
char *title;
Boolean visible;
INTEGER procID;
WindowPtr behind;
Boolean goAwayFlag;
LONGINT refCon;
{
WindowPtr theWindow;
theWindow =
NewWindow( wStorage, boundsRect, title,
invisible, procID, behind,
goAwayFlag, refCon );
if ( theWindow != nil )
{
(( WindowPeek )theWindow )->windowKind =
windowKind;
if ( visible != false )
TShowWindow( theWindow );
}
return( theWindow );
}
/*
* T C L O S E W I N D O W
*/
void
TCloseWindow( theWindow )
WindowPtr theWindow;
{
THideWindow( theWindow );
CloseWindow( theWindow );
if ( theWindow == toBeDeactivated )
toBeDeactivated = nil;
}
/*
* T D I S P O S E W I N D O W
*/
void
TDisposeWindow( theWindow )
WindowPtr theWindow;
{
THideWindow( theWindow );
DisposeWindow( theWindow );
if ( theWindow == toBeDeactivated )
toBeDeactivated = nil;
}
/*
* T S E L E C T W I N D O W
*/
void
TSelectWindow( theWindow )
WindowPtr theWindow;
{
void BringForward();
WindowPeek thePWindow,
theFrontWindow,
NextVisWindow(),
PrevVisWindow();
INTEGER theWKind,
frontWKind;
Boolean toolsVisible,
BringToolsForward();
/* Initialize some variables. */
thePWindow = ( WindowPeek )theWindow;
theFrontWindow = ( WindowPeek )FrontWindow();
theWKind = TGetWKind( theWindow );
frontWKind =
TGetWKind(( WindowPtr )theFrontWindow );
if ( theWKind == toolKind )
{
if ( frontWKind == systemKind )
{
/* Window is a tool window, with a desk
accessory on top. */
SelectWindow( theWindow );
(void)BringToolsForward( thePWindow );
if ( frontDocWindow != nil )
{
frontDocWindow->hilited = true;
BringForward( frontDocWindow,
backToolWindow,
normalCalc );
toBeActivated = frontDocWindow;
}
frontToolWindow = thePWindow;
}
else
{
/* Window is a tool window, and no desk
accessory on top. */
if ( thePWindow != frontToolWindow )
{
if ( thePWindow == backToolWindow )
{
backToolWindow =
NextVisWindow( toolKind, thePWindow );
if ( backToolWindow == nil )
backToolWindow =
PrevVisWindow( thePWindow );
}
BringToFront( theWindow );
frontToolWindow = thePWindow;
}
}
}
else if ( theWKind == userKind )
{
if ( frontWKind == systemKind )
{
/* Window is a document window (or a
modeless dialog window), with a desk
accessory on top. */
toolsVisible =
BringToolsForward(( WindowPeek )inFront );
if ( toolsVisible == false )
SelectWindow( theWindow );
else
{
thePWindow->hilited = true;
BringForward( theWindow,
backToolWindow,
normalCalc );
}
toBeActivated =
frontDocWindow = thePWindow;
}
else
{
/* Window is a document window (or a
modeless dialog window), and no desk
accessory on top. */
if ( thePWindow != frontDocWindow )
{
if ( frontToolWindow == nil )
SelectWindow( theWindow );
else
{
HiliteWindow(( WindowPtr)frontDocWindow,
false );
thePWindow->hilited = true;
BringForward( thePWindow,
backToolWindow,
normalCalc );
}
toBeDeactivated = frontDocWindow;
toBeActivated =
frontDocWindow = thePWindow;
}
}
}
}
/*
* T H I D E W I N D O W
*/
void
THideWindow( theWindow )
WindowPtr theWindow;
{
WindowPeek thePWindow,
behindWindow;
INTEGER theWKind,
thePosition,
frontWKind,
GetWPosition();
void HiliteTools();
if ((( WindowPeek )theWindow )->visible
!= false )
{
thePWindow = ( WindowPeek )theWindow;
theWKind = TGetWKind( theWindow );
thePosition = GetWPosition( thePWindow,
&frontWKind );
/* Hide the window, and update the desktop
according to the windows position. */
if ( theWKind == toolKind )
{
/* The window is a tool window. */
switch( thePosition )
{
case alone:
case beforeSystem:
HideWindow( theWindow );
frontToolWindow =
backToolWindow = nil;
break;
case beforeTool:
ShowHide( theWindow, false );
frontToolWindow =
( WindowPeek )FrontWindow();
break;
case behindFrontSystem:
ShowHide( theWindow, false );
frontToolWindow =
NextVisWindow( toolKind,
thePWindow );
if ( frontToolWindow == nil )
backToolWindow = nil;
break;
case beforeDocument:
frontToolWindow =
backToolWindow = nil;
ShowHide( theWindow, false );
break;
/* NOTE: no behindTools possible! */
case betweenTools:
ShowHide( theWindow, false );
if ( thePWindow == backToolWindow )
backToolWindow =
PrevVisWindow( thePWindow );
break;
}
}
else if ( theWKind == userKind )
{
/* The window is a document window
(or a modeless dialog window). */
thePWindow->hilited = false;
switch( thePosition )
{
case alone:
case beforeSystem:
toBeDeactivated = thePWindow;
frontDocWindow = nil;
case betweenDocuments:
HideWindow( theWindow );
break;
case beforeDocument:
HideWindow( theWindow );
toBeDeactivated = thePWindow;
toBeActivated =
frontDocWindow =
( WindowPeek )FrontWindow();
break;
case behindFrontSystem:
ShowHide( theWindow, false );
frontDocWindow =
NextVisWindow( userKind,theWindow );
break;
case behindTools:
frontDocWindow =
NextVisWindow( userKind,theWindow);
behindWindow =
NextVisWindow( anyKind, theWindow );
ShowHide( theWindow, false );
if ( behindWindow == nil )
toBeDeactivated = thePWindow;
else
{
if ( behindWindow != frontDocWindow)
{
if ( frontWKind == toolKind )
{
HiliteTools( false );
SelectWindow(( WindowPtr )
behindWindow );
toBeDeactivated = thePWindow;
}
if ( frontDocWindow != nil )
BringForward( frontDocWindow,
backToolWindow,
normalCalc );
}
else
{
if ( frontWKind == toolKind )
{
HiliteWindow( frontDocWindow,
true );
toBeActivated = frontDocWindow;
toBeDeactivated = thePWindow;
}
}
}
break;
}
}
else
{
/* The window is a modal dialog window.*/
switch( thePosition )
{
case alone:
case beforeSystem:
case beforeDialog:
HideWindow( theWindow );
break;
case beforeTool:
case beforeDocument:
ShowHide( theWindow, false );
HiliteTools( true );
if ( frontDocWindow != nil )
{
toBeDeactivated = thePWindow;
toBeActivated = frontDocWindow;
HiliteWindow(( WindowPtr)
frontDocWindow, true );
}
break;
}
}
}
}
/*
* T S H O W W I N D O W
*/
void
TShowWindow( theWindow )
WindowPtr theWindow;
{
WindowPeek thePWindow,
theFrontWindow,
behind,
PrevVisWindow();
INTEGER theWKind,
frontWKind,
thePosition,
GetWPosition();
Boolean toolsExist,
BringToolsForward();
void BringForward(),
HiliteTools();
if ((( WindowPeek )theWindow )->visible
== false )
{
/* The window is still invisible. */
thePWindow = ( WindowPeek )theWindow;
theFrontWindow = ( WindowPeek )FrontWindow();
theWKind = TGetWKind( theWindow );
thePosition =
GetWPosition( thePWindow, &frontWKind );
if ( theWKind == toolKind )
{
/* The window is a tool window. Assume
it needs to appear highlighted
(correct this later). */
thePWindow->hilited = true;
switch( thePosition )
{
case beforeSystem:
ShowWindow( theWindow );
( void )BringToolsForward(thePWindow);
if ( frontDocWindow != nil )
{
frontDocWindow->hilited = true;
BringForward( frontDocWindow,
backToolWindow,
normalCalc );
toBeActivated = frontDocWindow;
}
frontToolWindow = thePWindow;
break;
case behindFrontSystem:
thePWindow->hilited = false;
case alone:
case beforeDocument:
if ( backToolWindow == nil )
backToolWindow = thePWindow;
case beforeTool:
ShowHide( theWindow, true );
frontToolWindow = thePWindow;
break;
case behindTools:
backToolWindow = thePWindow;
case betweenTools:
if ( frontWKind == systemKind )
thePWindow->hilited = false;
ShowHide( theWindow, true );
break;
case behindSystem:
SendBehind( theWindow,
( WindowPtr )backToolWindow );
if ( frontWKind == systemKind )
thePWindow->hilited = true;
ShowHide( theWindow, true );
backToolWindow = thePWindow;
break;
case betweenDocuments:
if ( backToolWindow != nil )
behind = backToolWindow;
else
{
behind =
PrevVisWindow( frontDocWindow );
frontToolWindow = thePWindow;
}
if ( behind == nil )
BringToFront( theWindow );
else
{
SendBehind( theWindow, behind );
if ( frontWKind == systemKind )
thePWindow->hilited = false;
}
ShowHide( theWindow, true );
backToolWindow = thePWindow;
break;
}
}
else if ( theWKind == userKind )
{
/* The window is a document window. */
switch( thePosition )
{
case beforeSystem:
toolsExist = BringToolsForward(
( WindowPeek )inFront );
if ( toolsExist != false )
thePWindow->hilited = true;
case alone:
ShowWindow( theWindow );
toBeActivated =
frontDocWindow = thePWindow;
break;
case beforeTool:
if ( frontDocWindow != nil )
{
HiliteWindow(( WindowPtr )
frontDocWindow, false );
toBeDeactivated = frontDocWindow;
}
SendBehind( theWindow,
backToolWindow );
thePWindow->hilited = true;
ShowHide( theWindow, true );
toBeActivated =
frontDocWindow = thePWindow;
break;
case beforeDocument:
/* The three steps HiliteWindow,
HiliteWindow and ShowHide could
be replaced with one call to
ShowWindow, but this gives a
better visual effect. (Try the
difference.) */
HiliteWindow( frontDocWindow, false );
HiliteWindow( theWindow, true );
ShowHide( theWindow, true );
toBeDeactivated = frontDocWindow;
toBeActivated =
frontDocWindow = thePWindow;
break;
case behindFrontSystem:
if ( backToolWindow != nil )
SendBehind( theWindow,
( WindowPtr )backToolWindow );
ShowHide( theWindow, true );
frontDocWindow = thePWindow;
break;
case betweenTools:
SendBehind( theWindow,
( WindowPtr )backToolWindow );
case behindTools:
if ( frontWKind == toolKind )
{
thePWindow->hilited = true;
toBeActivated = thePWindow;
if ( frontDocWindow != nil )
{
HiliteWindow(( WindowPtr )
frontDocWindow, false );
toBeDeactivated = frontDocWindow;
}
}
ShowHide( theWindow, true );
frontDocWindow = thePWindow;
break;
case behindSystem:
SendBehind( theWindow,
( WindowPtr )backToolWindow );
if ( frontWKind == toolKind )
{
thePWindow->hilited = true;
toBeActivated = thePWindow;
}
ShowHide( theWindow, true );
frontDocWindow = thePWindow;
break;
case betweenDocuments:
ShowHide( theWindow, true );
break;
}
}
else
{
/* The window is a modal dialog window.*/
switch( thePosition )
{
case alone:
case beforeSystem:
case beforeDialog:
ShowWindow( theWindow );
break;
case beforeTool:
case beforeDocument:
HiliteTools( false );
if ( frontDocWindow != nil )
{
toBeDeactivated = frontDocWindow;
HiliteWindow(( WindowPtr )
frontDocWindow, false );
}
ShowHide( theWindow, true );
break;
}
}
}
}
/*
* T F R O N T W I N D O W
*/
WindowPtr
TFrontWindow( wantedKind )
INTEGER wantedKind;
{
WindowPtr theWindow;
INTEGER theWKind;
theWindow = FrontWindow();
theWKind = TGetWKind( theWindow );
if ( theWKind == systemKind ||
theWKind == dialogKind )
return( theWindow );
else
switch ( wantedKind )
{
case toolKind:
return(( WindowPtr )frontToolWindow );
break;
case userKind:
return(( WindowPtr )frontDocWindow );
break;
case anyKind:
return( theWindow );
break;
}
}
/*
* T D R A G W I N D O W
*/
void
TDragWindow( theWindow, theEvent, boundsRect )
WindowPtr theWindow;
EventRecord *theEvent;
Rect *boundsRect;
{
GrafPtr savePort,
wPort;
Rect limitRect,
slopRect;
RgnHandle desktopRgn;
LONGINT result;
INTEGER hDrag,
vDrag;
Point thePoint;
if (( theEvent->modifiers & cmdKey ) == false )
{
TSelectWindow( theWindow );
if ( StillDown() == false )
return;
}
GetPort ( &savePort );
GetWMgrPort( &wPort );
SetPort ( wPort );
if ( theWorld.machineType == envMac ||
theWorld.machineType == envXL )
desktopRgn = GrayRgn;
else
desktopRgn = GetGrayRgn();
GetClip ( firstRgn );
SetClip ( desktopRgn );
ClipAbove(( WindowPeek )theWindow );
CopyRgn((( WindowPeek )theWindow )->strucRgn,
secondRgn );
SetPt ( &thePoint, theEvent->where.h,
theEvent->where.v );
GlobalToLocal( &thePoint );
result = DragGrayRgn( secondRgn, &thePoint,
boundsRect, boundsRect,
noConstraint, ( ProcPtr )nil );
SetClip( firstRgn );
vDrag = HiWord( result );
hDrag = LoWord( result );
if ( !( vDrag == noDrag && hDrag == noDrag ))
{
SetPort( theWindow );
SetPt ( &thePoint,
theWindow->portRect.left,
theWindow->portRect.top );
LocalToGlobal( &thePoint );
MoveWindow( theWindow, thePoint.h + hDrag,
thePoint.v + vDrag, false );
}
SetPort( savePort );
SetEmptyRgn( firstRgn );
SetEmptyRgn( secondRgn );
}
/*
* T G E T N E X T E V E N T
*/
Boolean
TGetNextEvent( eventMask, theEvent )
INTEGER eventMask;
EventRecord *theEvent;
{
Boolean result,
WindowExists();
INTEGER theWKind;
void HiliteTools();
if ( EventAvail( eventMask, theEvent )
!= false &&
theEvent->what == activateEvt )
{
result = GetNextEvent( eventMask, theEvent );
theWKind =
TGetWKind(( WindowPtr )theEvent->message );
if (( theEvent->modifiers & activeFlag )
== false )
{
if ( theEvent->message
!= ( LONGINT )toBeDeactivated )
{
if ( toBeDeactivated != nil )
theEvent->message =
( LONGINT )toBeDeactivated;
else
{
if ( theWKind == userKind ||
theWKind == toolKind )
{
if ( theWKind == toolKind )
HiliteTools( false );
if ( frontDocWindow != nil )
{
HiliteWindow(( WindowPtr )
frontDocWindow, false );
theEvent->message =
( LONGINT )frontDocWindow;
}
else
result = TGetNextEvent( eventMask,
theEvent );
}
}
}
toBeDeactivated = nil;
}
else
{
if ( theEvent->message
!= ( LONGINT )toBeActivated )
{
if ( toBeActivated != nil )
theEvent->message =
( LONGINT )toBeActivated;
else
{
if ( theWKind == userKind ||
theWKind == toolKind )
{
if ( theWKind == toolKind )
HiliteTools( true );
if ( frontDocWindow != nil )
{
HiliteWindow(( WindowPtr )
frontDocWindow, true );
theEvent->message =
( LONGINT )frontDocWindow;
}
else
result = TGetNextEvent( eventMask,
theEvent );
}
}
}
toBeActivated = nil;
}
}
else
{
if ( toBeDeactivated != nil &&
( eventMask & activMask ) != false )
{
if ( WindowExists( toBeDeactivated )
!= false )
{
theEvent->what = activateEvt;
theEvent->message =
( LONGINT )toBeDeactivated;
theEvent->modifiers = deactivFlag;
toBeDeactivated = nil;
result = true;
}
else
{
toBeDeactivated = nil;
result =
TGetNextEvent( eventMask, theEvent );
}
}
else if ( toBeActivated != nil &&
( eventMask & activMask) != false )
{
theEvent->what = activateEvt;
theEvent->message =
( LONGINT )toBeActivated;
theEvent->modifiers = activeFlag;
toBeActivated = nil;
result = true;
}
else
{
result = GetNextEvent( eventMask,theEvent);
}
}
return( result );
}
/*
* W I N D O W E X I S T S
*/
Boolean
WindowExists( thePWindow )
WindowPeek thePWindow;
{
WindowPeek loopWindow;
Boolean done;
for ( done = false,
loopWindow = WindowList;
done == false && loopWindow != nil;
loopWindow = loopWindow->nextWindow )
{
if ( loopWindow == thePWindow )
done = true;
}
return( done );
}
/*
* G E T W P O S I T I O N
*/
static INTEGER
GetWPosition( thePWindow, frontWKind )
WindowPeek thePWindow;
INTEGER *frontWKind;
{
WindowPeek theFrontWindow,
newFrontWindow,
loopWindow,
NextVisWindow();
Boolean done;
INTEGER result,
loopWKind;
theFrontWindow = ( WindowPeek )FrontWindow();
if ( thePWindow->visible != false &&
theFrontWindow == thePWindow )
newFrontWindow =
NextVisWindow( anyKind, theFrontWindow );
else
newFrontWindow = theFrontWindow;
if ( newFrontWindow == nil )
return( alone );
*frontWKind =
TGetWKind(( WindowPtr )newFrontWindow );
if ( thePWindow->visible != false &&
theFrontWindow == thePWindow ||
theFrontWindow ==
NextVisWindow( anyKind, thePWindow ))
{
/* Our window is visible and the frontmost,
or its invisible and lying in front of
the frontmost visible window. */
if ( *frontWKind == systemKind )
result = beforeSystem;
else if ( *frontWKind == dialogKind )
result = beforeDialog;
else if ( *frontWKind == toolKind )
result = beforeTool;
else
result = beforeDocument;
}
else
{
if (thePWindow->visible != false )
thePWindow = PrevVisWindow( thePWindow );
for ( done = false,
loopWindow = theFrontWindow;
done == false;
loopWindow = loopWindow->nextWindow )
{
loopWKind =
TGetWKind(( WindowPtr )loopWindow );
if ( loopWindow == theFrontWindow &&
loopWKind == systemKind )
result = behindFrontSystem;
else if ( loopWindow == frontToolWindow &&
backToolWindow != frontToolWindow )
result = betweenTools;
else if ( loopWindow == backToolWindow )
result = behindTools;
else if ( loopWKind == systemKind &&
loopWindow->visible != false &&
result == behindTools )
result = behindSystem;
else if ( loopWindow == frontDocWindow )
result = betweenDocuments;
if ( loopWindow == thePWindow )
done = true;
}
return( result );
}
}
/*
* T G E T W K I N D
*/
INTEGER
TGetWKind( theWindow )
WindowPtr theWindow;
{
INTEGER theWKind,
varCode,
windowID;
WDEFHandle defProc;
if ( theWindow != nil )
{
theWKind =
(( WindowPeek )theWindow )->windowKind;
if ( theWKind <= systemKind )
theWKind = systemKind;
else if ( theWKind == dialogKind )
{
defProc =
( WDEFHandle )
((WindowPeek )theWindow )->windowDefProc;
if ( theWorld.machineType == envMac ||
theWorld.machineType == envXL )
varCode =
( LONGINT )defProc >> 24 && 0x0000000F;
else
varCode = GetWVariant( theWindow );
if ( defProc != nil &&
*defProc != nil &&
( *defProc )->type == WDEF )
windowID =
( *defProc )->ID * 16 + varCode;
else
windowID = dBoxProc;
if ( windowID == documentProc ||
windowID == noGrowDocProc ||
windowID == zoomDocProc )
theWKind = userKind;
}
else if ( theWKind == toolKind )
/* nothing */ ;
else if ( theWKind >= userKind )
theWKind = userKind;
}
return( theWKind );
}
/*
* B R I N G T O O L S F O R W A R D
*/
static Boolean
BringToolsForward( thePWindow )
WindowPeek thePWindow;
{
WindowPeek NextVisWindow(),
toolWindow,
loopWindow;
void BringForward();
if ( thePWindow == ( WindowPeek )inFront )
{
toolWindow = NextVisWindow( toolKind,
( WindowPeek )FrontWindow() );
if ( toolWindow != nil )
{
loopWindow = toolWindow->nextWindow;
SelectWindow(( WindowPtr )toolWindow );
frontToolWindow =
backToolWindow = toolWindow;
}
else
{
return( false );
}
}
else
{
loopWindow = thePWindow->nextWindow;
backToolWindow = thePWindow;
}
SetEmptyRgn( secondRgn );
thePWindow = nil;
while ( loopWindow != nil )
{
if ( loopWindow->windowKind == toolKind &&
loopWindow->visible != false )
{
toolWindow = loopWindow;
toolWindow->hilited = true;
BringForward( toolWindow, backToolWindow,
postponeCalc );
UnionRgn( secondRgn, toolWindow->strucRgn,
secondRgn );
backToolWindow = toolWindow;
if ( thePWindow == nil )
thePWindow = toolWindow;
}
loopWindow = loopWindow->nextWindow;
}
CalcVisBehind( thePWindow, secondRgn );
SetEmptyRgn( secondRgn );
return( true );
}
/* End of BringToolsForward */
/*
* N E X T V I S W I N D O W
*/
static WindowPeek
NextVisWindow( wantedKind, thePWindow )
INTEGER wantedKind;
WindowPeek thePWindow;
{
WindowPeek loopWindow,
wantedWindow;
if ( thePWindow == ( WindowPeek )inFront )
{
thePWindow = ( WindowPeek )FrontWindow();
if ( thePWindow == nil )
return( thePWindow );
else if ( wantedKind == anyKind ||
wantedKind ==
TGetWKind(( WindowPtr)thePWindow ))
return( thePWindow );
}
for ( wantedWindow = nil,
loopWindow = thePWindow->nextWindow;
wantedWindow == nil &&
loopWindow != nil;
loopWindow = loopWindow->nextWindow )
{
if ( loopWindow->visible != false )
if ( wantedKind == anyKind ||
wantedKind ==
TGetWKind(( WindowPtr )loopWindow ))
wantedWindow = loopWindow;
}
return( wantedWindow );
}
/*
* P R E V V I S W I N D O W
*/
static WindowPeek
PrevVisWindow( thePWindow )
WindowPeek thePWindow;
{
WindowPeek loopWindow,
prevWindow,
theFrontWindow,
NextVisWindow();
Boolean done;
theFrontWindow = ( WindowPeek )FrontWindow();
if ( thePWindow == theFrontWindow ||
theFrontWindow ==
NextVisWindow( anyKind, thePWindow ))
return(( WindowPeek )nil );
prevWindow = theFrontWindow;
loopWindow = prevWindow->nextWindow;
done = false;
while ( done == false )
{
if ( loopWindow == thePWindow )
done = true;
else
if ( loopWindow->visible != false )
prevWindow = loopWindow;
loopWindow = loopWindow->nextWindow;
}
return( prevWindow );
}
/*
* B R I N G F O R W A R D
*/
static void
BringForward( thePWindow, behindWindow,
waitWithCalc )
WindowPeek thePWindow;
WindowPeek behindWindow;
Boolean waitWithCalc;
{
CopyRgn ( thePWindow->strucRgn, firstRgn );
OffsetRgn( thePWindow->port.visRgn,
- thePWindow->port.portBits.bounds.left,
- thePWindow->port.portBits.bounds.top );
DiffRgn ( firstRgn, thePWindow->port.visRgn,
firstRgn );
OffsetRgn( thePWindow->port.visRgn,
thePWindow->port.portBits.bounds.left,
thePWindow->port.portBits.bounds.top );
SendBehind(( WindowPtr )thePWindow,
( WindowPtr )behindWindow );
PaintOne( thePWindow, firstRgn );
if ( waitWithCalc == false )
CalcVisBehind( thePWindow,
thePWindow->strucRgn );
SetEmptyRgn( firstRgn );
}
/*
* H I L I T E T O O L S
*/
static void
HiliteTools( hilite )
Boolean hilite;
{
WindowPeek loopWindow;
Boolean done;
if ( frontToolWindow != nil )
{
for ( done = false,
loopWindow = frontToolWindow;
done == false && loopWindow != nil;
loopWindow = loopWindow->nextWindow )
{
if ( TGetWKind(( WindowPtr )loopWindow )
== toolKind &&
loopWindow->visible != false )
{
HiliteWindow(( WindowPtr )loopWindow,
hilite );
if ( loopWindow == backToolWindow )
done = true;
}
}
}
}
{2}
Listing: TWindow.h
/*
* TWindows.h
*
* Include file for usage with the TWindow
* Manager, an extended window manager that
* supports tool windows; these are windows
* that always float on top, for palettes and
* tools.
*
* Written in MPW C 2.0
*
* Copyright Thomas Fruin 1988
* All rights reserved.
*/
/*
* T Y P E S
*/
/* Define the standard toolbox types INTEGER
and LONGINT in terms of the equivalents for
the MPW C compiler. */
#define INTEGER short
#define LONGINT long
/* Constants for the kindes of windows */
/* dialogKind 2 */
/* userKind 8 */
#define systemKind -1
#define toolKind 30000
#define anyKind 30001
#define inFront ((WindowPtr)-1)
/* Callable functions in the TWindow Manager */
void TInitWindows();
WindowPtr TNewWindow();
WindowPtr TGetNewWindow();
void TCloseWindow();
void TDisposeWindow();
void TSelectWindow();
void THideWindow();
void TShowWindow();
WindowPtr TFrontWindow();
void TDragWindow();
Boolean TGetNextEvent();
INTEGER TGetWKind();
{3}
Listing: make TWindowTester
#
# Makefile for TWindowTester and TWindows
#
# TWindowTester is a simple program to demon-
# strate the usage of tool windows by using
# routines from TWindows, a complete manager
# for handling these windows that always float
# on top, ususally used for palettes and tools.
#
# Copyright Thomas Fruin 1988
# All rights reserved
#
Libs = {CLibraries}CInterface.o
{CLibraries}CRuntime.o
{CLibraries}glueenvirons.a.o
{CLibraries}StdCLib.o
TWindowTester TWindowTester.c.o TWindows.c.o
Link TWindowTester.c.o
TWindows.c.o
{Libs}
-o TWindowTester
-t APPL
-c TWIN
TWindowTester TWindowTester.r
rez -o TWindowTester
Types.r
TWindowTester.r
-a
TWindowTester.c.o TWindowTester.c TWindows.h
C TWindowTester.c
TWindows.c.o TWindows.c
C TWindows.c -d __ALLNU__
{4}
Listing: TWindowTester.c
/*
* TWindowTester
*
* C source of a program to demo usage of tool windows by
* using routines from the TWindow Manager. Tool windows are
* windows that always float on top, typically for palettes and
*tools.
* Thomas Fruin 1988
*
* fruin@hlerul5.BITNET University of Leiden
* thomas@uvabick.UUCP University of Amsterdam
* dibs@well.UUCP
* hol0066.AppleLink
* 2:508/15.FidoNetThe Netherlands
*
* TWindowTester is based on MiniEdit - Mini text
* editor,converted from
* the listing in Macintosh Revealed, vol II. As little as
* possible was
* modified in the original program, although most of the
* functionality is no longer there.
*/
#include <Types.h>
#include <QuickDraw.h>
#include <Fonts.h>
#include <Windows.h>
#include <Events.h>
#include <TextEdit.h>
#include <Controls.h>
#include <Dialogs.h>
#include <Menus.h>
#include <Memory.h>
#include <OSUtils.h>
#include <ToolUtils.h>
#include <Desk.h>
#include <TWindows.h>
#define MenuBarHeight20
#define TitleBarHeight 18
#define ScreenMargin 4
#define MinWidth 80
#define MinHeight80
#define SBarWidth16
#define AppleID 1
#define AboutItem 1
#define FileID 2
#define NewItem 1
#define NewToolItem 2
#define CloseItem 3
#define QuitItem 5
#define EditID 3
#define UndoItem 1
#define CutItem 3
#define CopyItem 4
#define PasteItem 5
#define ClearItem 7
#define WindowsID 4
#define ToolsID 5
#define AboutID 128
#define windowID 1000
#define scrollID 1000
#define toolID 1001
#define okButton 1
#define aboutBold 8
#define wHOffset 20
#define wVOffset 20
#define tHOffset 40
#define tVOffset 40
#define undoCmd 0
#define cutCmd 2
#define copyCmd 3
#define pasteCmd 4
#define clearCmd 5
/* z-ordered list of windows (nearest first) */
#define windowList (*(WindowPeek *)0x9D6)
#define nilproc ((ProcPtr)0)
struct WindowData
{
ControlHandle scBar;
};
typedef struct WindowData WindowData;
typedef struct WindowData *WDPtr;
typedef struct WindowData **WDHandle;
MenuHandleAppleMenu,
FileMenu,
EditMenu,
WindowsMenu,
ToolsMenu;
INTEGER toolCount,
windowCount,
lastToolsItem,
lastWindowsItem;
Boolean Finished,
ErrorFlag;
main()
{
Initialize();
do
{
if ( FrontWindow() == nil )
DisableItem( FileMenu, CloseItem );
SystemTask();
DoEvent();
}
while ( !Finished );
}
Initialize()
{
INTEGERtheMask;
InitGraf( &qd.thePort );
InitFonts();
InitWindows();
InitMenus();
TEInit();
InitDialogs( nilproc );
TInitWindows();
theMask = everyEvent - keyUpMask;
SetEventMask( theMask );
FlushEvents( everyEvent, 0 );
SetUpMenus();
InitCursor();
}
SetUpMenus()
{
AppleMenu = GetMenu( AppleID );
AddResMenu( AppleMenu, DRVR );
InsertMenu( AppleMenu, 0 );
FileMenu = GetMenu( FileID );
InsertMenu( FileMenu, 0 );
EditMenu = GetMenu( EditID );
InsertMenu( EditMenu, 0 );
WindowsMenu = GetMenu( WindowsID );
InsertMenu( WindowsMenu, 0 );
ToolsMenu = GetMenu( ToolsID );
InsertMenu( ToolsMenu, 0 );
DrawMenuBar();
lastToolsItem =
lastWindowsItem = 0;
}
DoEvent()
{
EventRecordtheEvent;
ErrorFlag = false;
if ( TGetNextEvent( everyEvent, &theEvent ))
{
switch ( theEvent.what )
{
case mouseDown:
DoMouseDown( &theEvent );
break;
case keyDown:
case autoKey:
DoKeyStroke( &theEvent );
break;
case updateEvt:
DoUpdate( &theEvent );
break;
case activateEvt:
DoActivate( &theEvent );
break;
default:
break;
}
}
}
DoMouseDown( theEvent )
EventRecord*theEvent;
{
WindowPtrwhichWindow;
INTEGERthePart;
thePart = FindWindow( &theEvent->where, &whichWindow );
switch ( thePart )
{
case inDesk:
break;
case inMenuBar:
DoMenuClick( theEvent );
break;
case inSysWindow:
SystemClick( theEvent, whichWindow );
break;
case inContent:
DoContent( theEvent, whichWindow );
break;
case inDrag:
DoDrag( theEvent, whichWindow );
break;
case inGrow:
DoGrow( theEvent, whichWindow );
break;
case inGoAway:
DoGoAway( theEvent, whichWindow );
break;
}
}
DoMenuClick( theEvent )
EventRecord*theEvent;
{
LONGINTmenuChoice;
menuChoice = MenuSelect( &theEvent->where );
DoMenuChoice( menuChoice );
}
DoMenuChoice( menuChoice )
LONGINTmenuChoice;
{
INTEGERtheMenu,
theItem;
if ( menuChoice )
{
theMenu = HiWord( menuChoice );
theItem = LoWord( menuChoice );
switch (theMenu)
{
case AppleID:
DoAppleChoice( theItem );
break;
case FileID:
DoFileChoice( theItem );
break;
case EditID:
DoEditChoice( theItem );
break;
case WindowsID:
DoWindowsChoice( WindowsMenu, theItem );
break;
case ToolsID:
DoWindowsChoice( ToolsMenu, theItem );
break;
}
HiliteMenu( 0 );
}
}
DoAppleChoice( theItem )
INTEGERtheItem;
{
char accName[255];
INTEGERaccNumber;
switch ( theItem )
{
case AboutItem:
DoAbout();
break;
default:
EnableItem( FileMenu, CloseItem );
EnableItem( EditMenu, UndoItem );
EnableItem( EditMenu, CutItem );
EnableItem( EditMenu, CopyItem );
EnableItem( EditMenu, PasteItem );
EnableItem( EditMenu, ClearItem );
GetItem( AppleMenu, theItem, accName );
accNumber = OpenDeskAcc( accName );
break;
}
}
DoAbout()
{
EventRecordtheEvent;
DialogPtraboutDialog;
INTEGERtheItem;
void SetBold();
aboutDialog = GetNewDialog( AboutID, nil, inFront );
SetBold( aboutDialog, aboutBold );
TShowWindow( aboutDialog );
if ( TGetNextEvent( activMask, &theEvent ))
DoActivate( &theEvent );
if ( TGetNextEvent( activMask, &theEvent ))
DoActivate( &theEvent );
ModalDialog( nilproc, &theItem );
THideWindow ( aboutDialog );
DisposDialog( aboutDialog );
}
DoFileChoice( theItem )
INTEGERtheItem;
{
switch (theItem)
{
case NewItem:
DoNew();
break;
case NewToolItem:
DoNewTool();
break;
case CloseItem:
DoClose();
break;
case QuitItem:
DoQuit();
break;
}
}
DoNew()
{
WindowPtrtheWindow;
WDHandle theData;
char title[ 255 ];
theWindow = TGetNewWindow( userKind, windowID, nil, inFront );
OffsetWindow( theWindow, wHOffset, wVOffset, &windowCount );
TShowWindow ( theWindow );
SetPort( theWindow );
theData = ( WDHandle )NewHandle(( Size )sizeof( WindowData ));
SetWRefCon( theWindow, ( LONGINT )theData );
HLock( theData );
( *theData )->scBar = GetNewControl( scrollID, theWindow );
HUnlock( theData );
EnableItem( FileMenu, CloseItem );
GetWTitle( theWindow, title );
InsMenuItem( WindowsMenu, title, lastWindowsItem++ );
CheckItem ( WindowsMenu, lastWindowsItem, true );
}
DoNewTool()
{
WindowPtrtoolWindow;
char title[ 255 ];
toolWindow = TGetNewWindow( toolKind, toolID, nil, inFront );
OffsetWindow( toolWindow, tHOffset, tVOffset, &toolCount );
TShowWindow( toolWindow );
SetPort( toolWindow );
EnableItem( FileMenu, CloseItem );
GetWTitle( toolWindow, title );
InsMenuItem( ToolsMenu, title, lastToolsItem++ );
CheckItem ( ToolsMenu, lastToolsItem, true );
}
OffsetWindow( whichWindow, hOffset, vOffset, count )
WindowPtrwhichWi |