Sprocket
Volume Number: | | 10
|
Issue Number: | | 10
|
Column Tag: | | Foundation Technology
|
|
Related Topics: Window Manager Process Manager
|
Sprocket: A Small 7.5-Adept Framework
Introducing the MacTech Magazine tiny application framework!
By Dave Falkenburg, Apple Computer, Inc.
Note: Source code files accompanying article are located on MacTech CD-ROM or source code disks.
This month we bring you yet another framework. This one, though, offers a little less than youre probably used to. Thats right, less. Its just about as small a framework as you can have and still have some interesting features. This one, written by Apples Dave Falkenburg, has support for many of the more recent Apple system features, yet comes in well under 33K for a 68K executable.
We plan to use Sprocket as the basis for many of the articles in the coming months. This will allow us to focus on whats being taught (the new code) rather than what youve seen before.
In addition, Dave plans to continue development of Sprocket, so we hope to see more articles detailing how hes making things work, as well as covering new framework features.
We dont have room in this issue to list all of the code, so were only listing what fits. The complete set of sources will be available on the source code disk and our online sites (see page 2 for info).
Let us hear what you think of this. Do you like the way Sprocket is implemented? Do you have a better way to do something? Is something unclear? Did you find a bug? Is there something youd like to see added? Let us know; were aiming to serve your needs - Ed stb
1994 Isnt 1984
With the advent of Apples System 7.5 release, many of the system extensions developed over the last few years will actually be delivered to the customer in a form they can understand: a single box.
AppleGuide, a faster disk cache, QuickTime 2.0, PC Exchange with Macintosh Easy Open, Macintosh Drag & Drop, and the Thread Manager are some of the enhancements which come along for free with 7.5. Unsuspecting customers also receive a copy of PowerTalk 1.1 and QuickDraw GX 1.01 which they can also install.
Its probably about time to think about adding support for these new features in your own applications. To help you along, heres Sprocket.
Sprocket sports a minimalist design, with just enough to crank out small applications which do their work inside windows. It has plenty of hooks to aid in supporting new Apple technologies, and in many cases has all the necessary support code. It isnt a full-blown class library. In fact, its only use of C++ objects is to do a very standard Macintosh thing: managing windows.
No! No! No! The Journey is the Journey -
The Reward is the Reward.
People not interested in the folklore of how Sprocket came to be can skip ahead to the next section.
Sprocket is the outgrowth of one programmer trying to make sense of all the interesting new things that have been added to Macintosh OS in the past few years. It leverages heavily on the ideas presented in recent develop magazine articles by Dean Yu, Steve Falkenburg, and Dave Hershey, as well as insights provided by SmartFriends all over.
After a dissatisfying experience with C++ and bulky application frameworks, I decided that there was something wrong with creating superfluous objects for things that wouldnt get reused - all it seemed to do was make code less readable to the average programmer, and make things tougher on the compiler.
Like many other folks, I had decided that my application skeleton couldnt be written in C++. Most of my motivation was slow compile times, and the bloated code which came out of CFront at the time. Still wanting flexibility, a decided to use ad hoc object-oriented C for my application skeleton. This meant making big structs full of function pointers that get jammed into the refCon of windows, etc. Lots of explicit initialization, and lots of opportunity to type in bugs.
Thankfully, Symantec finally shipped a real C++ compiler and I decided to bite the bullet. C++ was going to be the language I would use.
As it turns out, I just happened to be overhauling my Macintosh skeleton application about the time Dean Yu was working on his first develop article about making floating windows without defiling the Window Manager. As a result, I took an early version of his floating window code and methodized it. Similarly, Deans ideas about smartzooming on comp.sys.mac.programmer were incorporated into my skeleton. After a while, I shelved the project, because I was off to Apple to lend a hand on the PowerMac System Software project.
Maybe it was seeing developers porting their applications to PowerPC, or maybe it was the need to write new code instead of hacking other peoples code, but around the end of the 7.1.2 project I began hacking on my application at home after work. At the time (and until very recently) Icalled it App. Youll see references to it all through the code (and maybe even here and there in this article).
Around the same time, some folks around here were talking about some Canadian company with PowerPC development tools in their pocket. It turned out that the rumors were true, and that a little 12 person startup had decided to do what two Fortune 500 companies seemingly deemed impossible.
Metrowerks Code Warrior is an excellent, rapid turnaround environment for building both 68K and PowerPC applications for Macintosh - and its a hell of a lot cheaper than buying an RS/6000 and trying to weasel an unreleased C compiler from the local Big Blue rep. With new tools in hand, I started adding Mixed Mode Universal ProcPtrs everywhere I needed them, and got it to run on a friends 6100/60.
About the time I was getting ready to add support for PowerTalk mail and QuickDraw GX, Scott gave me a call and convinced me that this would be a cool thing to share with the rest of the world, especially as a framework for other articles to use as a default basis for what they're teaching.
Dealing with Macintosh Windows - A Few Techniques
Folks familiar with C++ Macintosh programming can skip ahead to the section about the design philosophy employed while writing Sprocket.
A very common technique used by Macintosh programmers is to use the refCon field of the WindowRecord to indicate which routines to call when handling activate, update, mouse clicks, and other events.
One popular technique is to use the windowKind field as an indicator of what routine to call for each possible thing you need to do for a window:
/* 1 */
void
MyHandleUpdateEvent(WindowPeek theWindowToUpdate)
{
GetPort(&oldPort);
SetPort(theWindowToUpdate);
BeginUpdate(theWindowToUpdate);
kindOfWindow = theWindowToUpdate->windowKind;
switch (kindOfWindow)
{
case kDocumentWindow:
MyDrawingWindowDrawProc();
break;
case kDrawingToolsPaletteWindow:
MyDrawingToolsDrawProc();
break;
//... even more explicit window types follow
}
EndUpdate(aWindow);
SetPort(oldPort);
}
One drawback is the need to maintain a ton of switch statements everywhere in your code. Another less obvious one is that a new type of window cannot be added to the application without recompiling the application. Wouldnt it be nice to let plug-in modules put up their own first class windows?
To avoid the problems here, clever programmers have begun to use the refCon field of the WindowRecord to store a table of function pointers. Programs that use this technique include NewsWatcher and CollaboDraw. In this way, all the switch statements can be removed, potentially saving code throughout the file:
/* 2 */
void
MyHandleUpdateEvent(WindowPtr theWindowToUpdate)
{
GrafPtroldPort;
WindowProcs*procTable =(WindowProcs*)GetWindowRefCon(theWindow);
GetPort(&oldPort);
SetPort(theWindowToUpdate);
BeginUpdate(theWindowToUpdate);
if (procTable->UpdateProc != NULL)// call the proc if it
(*(procTable->DrawProc)());// isnt null.
EndUpdate(aWindow);
SetPort(oldPort);
}
This is flexible, but leads to the need to initialize and maintain these procTables manually, which means a lot of initialization routines like:
/* 3 */
WindowPtr
MakeMyWindow()
{
WindowProcs *myProcTable = NewHandleClear(sizeof(WindowProcs));
myProcTable->DrawProc = MyDrawingWindowDrawProc;
// ... and many more things you need to type in.
}
Once youve seen the light, and switch over to using ProcPtrs like this, youve started down the road to object oriented programming in C - object weenies call this stuff polymorphism. Your event loop only needs to know how to deal with generic windows - no switch statements anywhere. Any new type of window with a different set of WindowProcs can be thought of as a subclass of the base window.
Why Do I Need To Type All This Stuff In?
I dont know about you, but I hate typing. Cut and paste can help here, but it also seems like thats where a lot of bugs get introduced in large programs. Just when you might say: Gee I can write a tool to automatically generate the initialization code for the window proc tables, youll notice that there is no need. A C++ compiler can do that for you.
Its simple to create a C++ object whose pointer can be stored in a windows refCon. Other than the function pointer magic that the compiler does for you, this method leads to very similar coding as used with the function pointer case:
/* 4 */
void HandleUpdate(EventRecord * anEvent)
{
GrafPtroldPort;
WindowPtraWindow = (WindowPtr) anEvent->message;
TWindow *wobj;
GetPort(&oldPort);
SetPort(aWindow);
BeginUpdate(aWindow);
if ((wobj = GetWindowObject(aWindow)) != nil)
wobj->Draw();
EndUpdate(aWindow);
SetPort(oldPort);
}
Besides the desire to dispatch events to windows in a fast and flexible manner, there are other things which good Macintosh programs should do. Smart zooming, and patchless floating windows are two of these things.
Both of these things involve overriding the normal behavior of Macintosh windows, and hence are natural for rolling into the the base window methods. If you look at the source, you can see that Dean Yus advice in develop 15 and 17 has been taken to heart. (Its a good thing he wrote those articles before starting to forget about all this Macintosh OS stuff)
Philosophy
The basic approaches used in developing Sprocket were:
Use C++ objects instead of initializing tables of function pointers. Let the tools do the work - I spent many days debugging stupid cut and paste errors in my older code. Now that stable C++ compilers exist for Macintosh, many nuisance reasons to avoid C++ are gone. In many environments C++ code compiles just in the same amount of time as C code. There are at least 5 compilers Ive used in the last year - MPW C++, Symantec C++, Metrowerks, PPCC, and xlC under AIX on an RS/6000.
Ignore System 6. If you have a friend with a Macintosh Classic, SE, LC, etc. get them to buy a PowerMac - theyll like it. It isnt that hard to add support back into Sprocket, but it probably isnt worth the added hassles.
Keep the RAM footprint small for the base functionality so that we dont have to worry about heap fragmentation in the future or possible VM thrashing.
Fold code snippets used by several subroutines into reusable chunks. Once again - reuse code where it makes sense to keep the code size down. This also tends to improve locality of reference for instruction cache accesses.
Keep use of C++ objects down to only areas where there will typically be a lot of shared code or reuse. MacApps Nothing application used to be 300K! We dont have a TApplication - theres usually only ONE application anyway, why carry around base-class code you might not use just because C++ insists on creating references to it in the __vtbls?
Sprocket is not a class library full of everything you ever had to do in CS101: If you want one of those, go buy it - itll have code written by genuine computer scientists that know more about algorithms than the average programmer. Lots of folks like the Booch class libraries.
Use the Thread Manager in lieu of lots of IdleProcs. We still have Idle handlers for windows, but they really arent needed if the application can rely on using the Thread Manager.
A Quick Look At The Sources
The source directory is broken down into three folders:
:Interfaces: Definitions for Sprocket-supplied objects and
utility functions.
:Lib: Implementation of Sprockets core routines and objects.
:AppSpecific: Example code for building your own application using Sprocket.
As a rule of thumb, you shouldnt need to change things inside :Interfaces: or :Lib:, while :AppSpecific: contains code designed for tweaking/replacement.
The code has been successfully built using Metrowerks CodeWarrior 4 and tested on a Macintosh IIsi. It is in the process of being run through even more compilers - I had some trouble using PPCC, so there could be something wrong with the code [we'll be grateful for any corrections you readers send in - Ed stb].
The coding conventions are based on MacApp and the unofficial C++ style guide published in an early develop article. I havent gone crazy with const parameters and the like, because like most of us, Im still learning things about Bjarnes creation. The indented braces are a habit of mine, since the Process Manager (and Finder) were typed this way.
Now on to the files themselves - first the core Sprocket sources:
:Interfaces:AppConditionals.h
Conditional compilation flags kept in a header. MPW and Unix folk tend to keep these things in Makefiles, but that really means that you have to use make. In this manner, we can potentially build Sprocket in both Unix/MPW and THINK/MW environments. If you do source code control, its also handy to keep conditionals in a source file.
:Interfaces:StandardMenus.h
Constants defining some standard MENU IDs. It would be nice to rip out menu IDs and item numbers from application code to abstract menu choices from the position in the menu bar. Just about every other framework does. We may do that eventually.
:Interfaces:AppLib.h
Function prototypes and declarations for all the utility functions and useful global variables provided in the less object oriented parts of Sprocket.
:Lib:AppLib.cp
main() lives in here. The heart of the Macintosh application. Routines include toolbox initialization, the main event loop, and cleanup routines. Points of interest include the use of dynamic run and sleep values inside the main event loop.
:Interfaces:AppleEventHandling.h
:Lib:AppleEventHandling.cp
Code for handling the required Apple event suite (kAEOpenApplication, kAEOpenDocuments, kAEPrintDocuments, and kAEQuitApplication). Support for opening AOCE letters is also supported. Once we add OSA scripting support, expect this file to grow by leaps and bounds.
:Interfaces:DialogUtils.h
:Lib:DialogUtils.cp
Useful utility functions for using modal dialogs and alerts in the System 7 world. Contains an auto-resized error alert mechanism and lots of FilterProcs needed to prevent update events from blocking background applications. For more information about why we do all these things see the Macintosh Technote: Pending Update Perils. Support for calling the Thread Manager YieldToAnyThread inside the filterProcs is also provided.
:Lib:Preferences.cp
Routines for dealing opening and/or creating a preferences file. [Am I the only one who thought that develop article on preferences files was a bit of overkill? - drf ]
:Interfaces:Window.h
:Lib:Window.cp
These are especially interesting, and are listed at the end of the article for your reading enjoyment.
Our favorite C++ object (so far). TWindow is the base class for defining a Macintosh window. Every window created directly by Sprocket is built using this object. When a Macintosh window is created, the pointer to a corresponding C++ object is stashed in the refCon.
Ive taken care to make creating windows from application code as simple as:
TWhizbangWindow * myWindow = new TWhizbangWindow(<WhizzyParameters>);
Creating new kinds of windows is as easy as overriding the MakeNewWindow method, and providing unique drawing & event handling methods.
Right now, both floating and normal windows are supported. Modal windows (e.g., windows that come above floaters and cause them to be deactivated) are next on the list for implementation.
Weird C++ ALERT: This isnt as easy as it looks because C++ has a habit of not letting you call virtual methods in an inherited class from within a constructor. From within the bottom-most derived window class, we call back to an inherited method, CreateWindow, to do common window creation, instead of creating the window from within TWindow::TWindow().
:Interfaces:DialogWindow.h
:Lib:DialogWindow.cp
A class called TDialogWindow which allows for the trivial creation of Dialog Manager-based windows. Its kinda skanky right now because it temporarily patches FrontWindow in order to fool IsDialogEvent and DialogSelect into working with Deans floater technique.
:Interfaces:SplashWindow.h
:Lib:SplashWindow.cp
Object-based implementation of the splash screen. Mostly gratuitous, but a fun example of subclassing TWindow.
Application Specific Stuff
:AppSpecific:App.cp
Recipe code for the application-specific things. It implements the following required functions, as well as an about box dialog:
OSErr SetupApplication(void);
OSErr TearDownApplication(void);
void HandleMenu(TWindow * topWindowObj,long menuCode);
void ConvertClipboard(void);
OSErr OpenNewDocument(void);
OSErr OpenDocument(LetterDescriptor *,void *);
OSErr PrintDocument(LetterDescriptor *,void *);
Boolean QuitApplication(void);
:AppSpecific:DocWindow.cp
:AppSpecific:DocWindow.h
A window which spins arrows using threads and draws a grow box.
:AppSpecific:PreferencesDialogWindow.cp
:AppSpecific:PreferencesDialogWindow.h
A totally bogus example showing how to override TDialogWindow. Preferences dialogs that look like this are less than nice to the novice user. Cool applications derive default preferences from the users use of the application, not from clever imitations of the System 6 Control Panel.
:AppSpecific:ToolWindow.cp
:AppSpecific:ToolWindow.h
An empty floating tool palette. Eventually itd be nice to define tool palette template resources, but for now we use the parameter as a WIND resource id.
Nothing against my brother or anything, but I just cant read the code in CollaboDraw without getting hives. Heres my first attempt at building some C++ window classes for dealing with AOCE:
:Unfinished AOCE Stuff:MailableWindow.h
:Unfinished AOCE Stuff:MailableWindow.cp
A base class for an AOCE mailable window. Intended to be placed within :Lib: once all the mailer commands and Undo support have been added to Sprocket.
:Unfinished AOCE Stuff:MailableDocWindow.cp
:Unfinished AOCE Stuff:MailableDocWindow.h
A simple subclass of TMailableWindow intended to be added to :AppSpecific: when it is more complete.
Have fun reading through the code. There should be a couple of even less obvious tips and tricks in there that Ive since forgotten about that might be useful. Like I said, Sprocket has picked up a lot of ideas from other sample code through the years.
Speaking of other code, remember that Sprocket is not the be-all, end-all Macintosh framework. Its really a minimalist shell that makes it easy to demonstrate cool things in the magazine or building little hacks.
Other Frameworks
There are a ton of other Macintosh specific and/or cross platform frameworks that can be of great help. Well be focusing on Sprocket for articles here in the magazine, but these other frameworks have a lot to offer for full-fledged applications. A number of them are advertised right here in the magazine. Here are a few more (in no particular order):
MacApp - The first framework for building applications supplied by Apple. It had its beginnings as Object Pascal framework, but has been converted C++ over the last few years, and can now be used to build Power Macintosh applications. MacApp 3.x started integrating System 7 functionality and began to embrace C++.
TCL - A THINK C Objects based framework. Since Symantec didnt have real C++ until a few years ago, the most popular compiler used by Mac developers was essentially locked out of MacApp. TCL is still a popular framework, and the 2.0 version has been ported to the PowerPC.
AppsToGo - Eric Soldan of Apple DTS has a C-based application framework. This framework is heavily built upon being able to dynamically instantiate user interface elements from templates. Because it sprang forth out of the DTS.Lib effort in the early 90s, lots of utility functions for doing useful things in MacOS are also included. Some things built using AppsToGo include ClickBook and Kibitz, as well as a bunch of other commercial applications. You can find it at ftp://ftp.apple.com/dts/mac/sc/apps.to.go
QuickApp - Another MacApp-flavored application framework. This is probably a lot like Sprocket, but is probably better tested, and leverages off more experiences. Apps can be as small as 50K. $149, e-mail emergent@aol.com.
MetroWerks PowerPlant - A C++ class library for building Macintosh Applications. There is a lot of very cool stuff inside PowerPlant, including support for floating windows, resource-based user interfaces, robust exception handling, AppleEvent Object model support, and lots of very useful classes for using the Thread Manager. Uses multiple inheritance to get more code reuse. Also has its own coding conventions similar to the MacApp-style, but also containing hints for whether or not the object pulls in lots of other stuff, or it's a lightweight, reusable Library class. Greg Dow, the original author of TCL, is the principal force behind PowerPlant.
Visual C++, MFC - Microsofts cross platform development strategy - dont write Mac software, just write Windows code. Probably a good choice for people porting vertical market applications from Windows to Mac on a very tight schedule.
OpenDoc - A whole new way of building applications. Stop thinking about applications - parts is parts. The idea here is that you essentially write a C++ object that can be embedded inside any document. It is important to note that OpenDoc isnt really a framework in the sense of providing default do the right thing methods for parts. Instead it provides the class hierarchy alone. A cross-platform parts framework (OPF, the OpenDoc Parts Framework, descended from BedRock) is also under development.
OLE - Microsofts component software strategy. Instead of eliminating the application altogether, this is kind of a super-plug in that lets Microsoft use your code within their Office Suite, and other OLE-enabled applications.
Taligent - Some really cool C++ stuff, but few details have yet been disclosed in a public forum.
What to expect in the future:
Making Sprocket more robust with a clean, ARM-inspired exception model.
More support for AppleScript, and hence more studly AppleGuide abilities.
Adding QuickDraw GX-aware Printing, and possibly GX window classes.
Changes to transparently adapt to new stuff coming from Apple.
And, of course, cool demos and new articles based on Sprocket.
And now to a few selected listings. Well show Window.h, Window.cp, and AppLib.h. Theyre especially interesting, and they filled up all of our available space for this month. To see the rest of the files, check out this months source disk or the online sites (see p. 2 for details).
Window.h
/* Contains: Definition of TWindow, a base class which provides a framework
for building way-cool windows which even John Sullivan would be happy
with. Floating windows and smart zooming algorithms are based on code
samples provided by Dean Yu. Written by: Dave Falkenburg, Dean Yu
Copyright:© 1993-94 by Dave Falkenburg, all rights reserved. */
/* 5 */
#ifndef _WINDOW_
#define _WINDOW_
#ifndef __TYPES__
#include<Types.h>
#endif
#ifndef __WINDOWS__
#include<Windows.h>
#endif
#ifndef __EVENTS__
#include<Events.h>
#endif
#ifndef __DRAG__
#include<Drag.h>
#endif
typedef short WindowTemplateID;
class TWindow
{
public:
enum WindowType
{
kNormalWindow = 0,
kFloatingWindow,
kModalWindow
};
TWindow();
virtual~TWindow();
//Event routing methods
virtualBoolean EventFilter(EventRecord * theEvent);
Methods you shouldnt need to override, but might need to
virtual void CreateWindow(WindowType typeOfWindowToCreate
= kNormalWindow);
virtualvoidSelect(void);
virtualvoidDrag(Point startPoint);
virtual void Nudge( short horizontalDistance,
short verticalDistance);
virtual void Grow(Point startPoint);
virtual void Zoom(short zoomState);
virtualvoidShowHide(Boolean showFlag);
Methods which MUST be overridden:
virtual WindowPtr MakeNewWindow(WindowPtr behindWindow) = 0;
Methods which probably should be overridden
virtualvoidAdjustCursor(EventRecord * anEvent);
virtual void Idle(EventRecord * anEvent);
virtual void Activate(Boolean activating);
virtual void Draw(void);
virtual void Click(EventRecord * anEvent);
virtualvoidKeyDown(EventRecord * anEvent);
virtual void GetPerfectWindowSize(Rect * perfectSize);
virtualvoidGetWindowSizeLimits(Rect * limits);
virtual void AdjustForNewWindowSize( Rect * oldRect,
Rect * newRect);
//Window property accessor methods
// watch for new ones when we add scripting support
virtualBoolean IsVisible(void);
virtualBoolean CanClose(void);
virtualBoolean Close(void);
virtualBoolean DeleteAfterClose(void);
virtualvoidDoEditMenu(short item);
//Methods for use with the Drag Manager
virtualOSErr HandleDrag(DragTrackingMessage dragMessage,
DragReference theDrag);
virtualOSErr DragEnterWindow(DragReference theDrag);
virtualOSErr DragInWindow(DragReference theDrag);
virtualOSErr DragLeaveWindow(DragReference theDrag);
virtualOSErr HandleDrop(DragReference theDragRef);
protected:
WindowPtrfWindow;
WindowType fWindowType;
BooleanfIsVisible;
};
Utility Functions:
// Dont you just wish you didnt have to do this?
pascal WindowPtr GetNewColorOrBlackAndWhiteWindow(
short windowID, void *wStorage, WindowPtr behind);
pascal WindowPtr NewColorOrBlackAndWhiteWindow(
void *wStorage, const Rect *boundsRect,
ConstStr255Param title, Boolean visible,
short theProc, WindowPtr behind,
Boolean goAwayFlag, long refCon);
TWindow * GetWindowObject(WindowPtr aWindow);
WindowPtr FrontModalWindow(void);
WindowPtr LastModalWindow(void);
WindowPtr FrontFloatingWindow(void);
WindowPtr LastFloatingWindow(void);
WindowPtr FrontNonFloatingWindow(void);
void HiliteAndActivateWindow( WindowPtr aWindow,
Boolean active);
void SuspendResumeWindows(Boolean resuming);
void HiliteWindowsForModalDialog(Boolean hiliting);
pascal OSErrCallWindowDragTrackingHandler(
DragTrackingMessage message,
WindowPtr theWindow,
void *handlerRefCon,
DragReference theDragRef);
pascal OSErrCallWindowDragReceiveHandler(
WindowPtr theWindow,
void *handlerRefCon,
DragReference theDragRef);
#endif
Window.cp
/* 6 */
/* Contains:Implementation of TWindow, a base class which provides a
framework for building way-cool windows which even John Sullivan would
be happy with. Floating windows and smart zooming algorithms are based
on code samples provided by Dean Yu. Tim Craycroft, the guy making the
window manager do all this work for you has also been a great help.
Written by: Dave Falkenburg
Copyright:© 1993-94 by Dave Falkenburg, all rights reserved.
To Do: Make sure invisible windows can be created & managed
Handle modal windows as another class of windows
Fix activate bugs when showing and hiding windows
Window positioning methods (getters and setters)
Display Manager support
Changes to support AEObject model
*/
#include <Types.h>
#include <Windows.h>
#include <Errors.h>
#include <Script.h>//for GetMBarHeight()
#include <LowMem.h>//for LMGetWindowList()
#include "AppLib.h"
#include "Window.h"
const short kFloatingWindowKind = 1000;
const short kNormalWindowKind = 1001;
const WindowPtr kNoFloatingWindows= (WindowPtr) -1;
const short kScreenEdgeSlop = 4;
const short kSpaceForFinderIcons = 64;
const short kMinimumTitleBarHeight = 21;
const short kMinimumWindowSize= 32;
static void HiliteShowHideFloatingWindows(
Boolean hiliting,Boolean hiding);
static void FindScreenRectWithLargestPartOfWindow(
WindowPtr aWindow, Rect *theBestScreenRect,
GDHandle * theBestDevice);
static pascal void CalculateWindowAreaOnDevice(
short depth, short deviceFlags, GDHandle targetDevice,
long userData);
struct CalcWindowAreaDeviceLoopUserData
{
GDHandle fScreenWithLargestPartOfWindow;
long fLargestArea;
Rect fWindowBounds;
};
TWindow::TWindow()
{
}
TWindow::~TWindow()
{
}
TWindow::CreateWindow
void TWindow::CreateWindow( WindowType typeOfWindowToCreate
/* = kNormalWindow */)
{
WindowPtrbehindWindow,oldFrontMostWindow;
if (typeOfWindowToCreate == kModalWindow)
{
DebugStr("\pModal windows arent supported yet");
fWindowType = kFloatingWindow;
return;
}
else if (typeOfWindowToCreate == kFloatingWindow)
{
behindWindow = (WindowPtr) -1;
oldFrontMostWindow = FrontWindow();
fWindowType = kFloatingWindow;
}
else if (typeOfWindowToCreate == kNormalWindow)
{
behindWindow = LastFloatingWindow();
fWindowType = kNormalWindow;
if (behindWindow == kNoFloatingWindows)
oldFrontMostWindow = nil;
else
oldFrontMostWindow = (WindowPtr)
((WindowPeek) behindWindow)->nextWindow;
}
fWindow = this->MakeNewWindow(behindWindow);
fIsVisible = ((WindowPeek) fWindow)->visible;
if (fWindow)
{
SetWRefCon(fWindow,(long) this);
if (typeOfWindowToCreate == kModalWindow)
{
DebugStr("\pCant create Modal windows yet");
}
else if (typeOfWindowToCreate == kFloatingWindow)
{
((WindowPeek) fWindow)->windowKind = kFloatingWindowKind;
//make sure the other window stays hilited
if (oldFrontMostWindow)
HiliteAndActivateWindow(oldFrontMostWindow,true);
}
else if (typeOfWindowToCreate == kNormalWindow)
{
((WindowPeek) fWindow)->windowKind = kNormalWindowKind;
//unhighlight the old front window
if (oldFrontMostWindow)
HiliteAndActivateWindow(oldFrontMostWindow,false);
//hilite the new window
HiliteAndActivateWindow(fWindow,true);
}
}
}
TWindow::AdjustCursor
void
TWindow::AdjustCursor(EventRecord * /* anEvent */)
{
}
TWindow::Idle
void
TWindow::Idle(EventRecord * /* anEvent */)
{
}
TWindow::Activate
void
TWindow::Activate(Boolean /* activating */)
{
}
TWindow::Draw
void
TWindow::Draw(void)
{
}
TWindow::Click
void
TWindow::Click(EventRecord * /* anEvent */)
{
}
TWindow::KeyDown
void
TWindow::KeyDown(EventRecord * /* anEvent */)
{
}
TWindow::Select
void
TWindow::Select(void)
{
WindowPtrcurrentFrontWindow;
if (fWindowType == kFloatingWindow)
currentFrontWindow = FrontWindow();
else if (fWindowType == kNormalWindow)
currentFrontWindow = FrontNonFloatingWindow();
else
{
}
if (currentFrontWindow != fWindow)
{
if (fWindowType == kFloatingWindow)
BringToFront(fWindow);
else
{
WindowPtrlastFloater = LastFloatingWindow();
//If there are no floating windows, just call SelectWindow like the
good ol days
if (lastFloater == kNoFloatingWindows)
SelectWindow(fWindow);
else
{
// Deactivate the window currently in front.
HiliteAndActivateWindow(currentFrontWindow,false);
// Bring it behind the last floating window and activate it. Note that
Inside Mac 1
// states that you need to call PaintOne() and CalcVis() on a window
if you are using
// SendBehind() to bring it closer to the front. With Sys 7, this is
no longer necessary.
SendBehind(fWindow,lastFloater);
HiliteAndActivateWindow(fWindow,true);
}
}
}
}
TWindow::Drag
void
TWindow::Drag(Point startPoint)
{
GrafPtrsavePort;
KeyMap theKeyMap;
BooleancommandKeyDown = false;
RgnHandledraggingRegion;
long dragResult;
WindowPeek windowAsWindowPeek = (WindowPeek) fWindow;
if (WaitMouseUp())//de-bounce?
{
// Set up the Window Manager port.
GetPort(&savePort);
SetPort(gWindowManagerPort);
SetClip(GetGrayRgn());
// Check to see if the command key is down.
GetKeys(theKeyMap);
commandKeyDown = ((theKeyMap[1] & 0x8000) != 0);
if (commandKeyDown)
{
//Were not going to change window ordering, so make sure that we dont
// drag in front of other windows which may be in front of ours.
ClipAbove(windowAsWindowPeek);
}
else if (fWindowType != kFloatingWindow)
{
//Were dragging a normal window, so make sure that we dont drag in
//front of any floating windows.
ClipAbove((WindowPeek) FrontNonFloatingWindow());
}
//Drag an outline of the window around the desktop.
//NOTE: DragGrayRgn destroys the region passed in, so make a copy
draggingRegion = NewRgn();
CopyRgn(windowAsWindowPeek->strucRgn,draggingRegion);
dragResult = DragGrayRgn(draggingRegion, startPoint,
&gDeskRectangle, &gDeskRectangle, noConstraint, nil);
DisposeRgn(draggingRegion);
SetPort(savePort);//Get back to old port
if ((dragResult != 0) && (dragResult != 0x80008000))
{
this->Nudge((dragResult & 0xFFFF),(dragResult >> 16));
}
}
if (!commandKeyDown)
Select();
}
TWindow::Nudge
void
TWindow::Nudge(short horizontalDistance, short verticalDistance)
{
WindowPeek windowAsWindowPeek = (WindowPeek) fWindow;
short newHorizontalPosition,newVerticalPosition;
newHorizontalPosition = (short) (**windowAsWindowPeek
->contRgn).rgnBBox.left + horizontalDistance;
newVerticalPosition = (short) (**windowAsWindowPeek
->contRgn).rgnBBox.top + verticalDistance;
MoveWindow(fWindow,newHorizontalPosition,
newVerticalPosition, false);
}
TWindow::Grow
void
TWindow::Grow(Point startPoint)
{
GrafPtroldPort;
long newSize;
Rect oldWindowRect,resizeLimits;
GetPort(&oldPort);
GetWindowSizeLimits(&resizeLimits);
newSize = GrowWindow(fWindow,startPoint,&resizeLimits);
if (newSize)
{
oldWindowRect = fWindow->portRect;
SizeWindow(fWindow,(short) newSize,
(short) (newSize >> 16),true);
SetPort(fWindow);
this->AdjustForNewWindowSize(&oldWindowRect,
&fWindow->portRect);
}
SetPort(oldPort);
}
TWindow::Zoom
void
TWindow::Zoom(short zoomState)
{
GrafPtroldPort;
FontInfo systemFontInfo;
short titleBarHeight;
Rect bestScreenRect,perfectWindowRect,scratchRect;
short amountOffscreen;
WindowPeek windowAsWindowPeek = (WindowPeek) fWindow;
GDHandle bestDevice;
GetPort(&oldPort);
//Figure out the height of the title bar so we can properly position
//a window. The algorithm is stolen from the System 7.x 'WDEF' (0)
//This probably isnt the best thing to do: A better way might be
//to diff the structure and content region rectangles?
SetPort(gWindowManagerPort);
GetFontInfo(&systemFontInfo);
titleBarHeight = (short) (systemFontInfo.ascent +
systemFontInfo.descent + 4);
if ((titleBarHeight % 2) == 1)
titleBarHeight--;
if (titleBarHeight < kMinimumTitleBarHeight)
titleBarHeight = kMinimumTitleBarHeight;
//Only do the voodoo magic if we are really zooming the window.
if (zoomState == inZoomOut)
{
FindScreenRectWithLargestPartOfWindow(
fWindow,&bestScreenRect,&bestDevice);
bestScreenRect.top += titleBarHeight;
this->GetPerfectWindowSize(&perfectWindowRect);
OffsetRect(&perfectWindowRect,-perfectWindowRect.left,
-perfectWindowRect.top);
//Take the zero-pined perfect window size and move it to
//the top left of the windows content region.
OffsetRect(&perfectWindowRect,
(**windowAsWindowPeek->contRgn).rgnBBox.left,
(**windowAsWindowPeek->contRgn).rgnBBox.top);
//Does perfectWindowRect fit completely on the best screen?
SectRect(&perfectWindowRect,&bestScreenRect,&scratchRect);
if (!EqualRect(&perfectWindowRect, &scratchRect))
{
//SectRect sez perfectWindowRect doesnt completely fit on the screen,
// so bump the window so that more of it fits.
//Make sure that the left edge of perfectWindowRect is forced onto the
best
// screen. This is in case we are bumping the window to the right.
amountOffscreen = bestScreenRect.left
- perfectWindowRect.left;
if (amountOffscreen > 0)
{
perfectWindowRect.left += amountOffscreen;
perfectWindowRect.right += amountOffscreen;
}
//Make sure that the left edge of perfectWindowRect is forced
//onto the best screen. This is in case we are bumping
//the window downward to a new screen.
amountOffscreen = bestScreenRect.top
- perfectWindowRect.top;
if (amountOffscreen > 0)
{
perfectWindowRect.top += amountOffscreen;
perfectWindowRect.bottom += amountOffscreen;
}
//If right edge of window falls off the screen,
//Move window to the left until the right edge IS on the screen
//OR the left edge is at bestScreenRect.left
amountOffscreen = perfectWindowRect.right
- bestScreenRect.right;
if (amountOffscreen > 0)
{
//Are we going to push the left edge offscreen? If so, change the
//offset so we move the window all the way over to the left.
if ((perfectWindowRect.left - amountOffscreen)
< bestScreenRect.left)
amountOffscreen = perfectWindowRect.left
- bestScreenRect.left;
perfectWindowRect.left -= amountOffscreen;
perfectWindowRect.right -= amountOffscreen;
}
//If bottom edge of window falls off the screen,
//Move window to up until the bottom edge IS on the screen
//OR the top edge is at bestScreenRect.top
amountOffscreen = perfectWindowRect.bottom
- bestScreenRect.bottom;
if (amountOffscreen > 0)
{
//Are we going to push the top edge offscreen? If so, change the
//offset so we move the window just to the top.
if ((perfectWindowRect.top - amountOffscreen)
< bestScreenRect.top)
amountOffscreen = perfectWindowRect.top
- bestScreenRect.top;
perfectWindowRect.top -= amountOffscreen;
perfectWindowRect.bottom -= amountOffscreen;
}
SectRect(&perfectWindowRect, &bestScreenRect,
&scratchRect);
if (!EqualRect(&perfectWindowRect, &scratchRect))
{
//The edges of the window still fall offscreen,
//so make the window smaller until it fits.
if (perfectWindowRect.bottom > bestScreenRect.bottom)
perfectWindowRect.bottom = bestScreenRect.bottom;
//If the right edge is still falling off,
//save space for Finders disk icons as well.
if (perfectWindowRect.right > bestScreenRect.right)
{
perfectWindowRect.right = bestScreenRect.right;
//If we were on the main screen, leave room for Finder icons, too.
if (bestDevice == GetMainDevice())
perfectWindowRect.right -= kSpaceForFinderIcons;
}
}
}
//Stash our new rectangle inside of the Windows dataHandle
//so that ZoomWindow does the right thing.
(**((WStateDataHandle)
(windowAsWindowPeek->dataHandle))).stdState
= perfectWindowRect;
}
//Dont forget to set the port to the window being zoomed
//Why, you ask? Because IM-IV-50 says to; otherwise you die
SetPort(fWindow);
Rect oldWindowRect = fWindow->portRect;
ZoomWindow(fWindow,zoomState,false);
this->AdjustForNewWindowSize(&oldWindowRect,
&fWindow->portRect);
SetPort(oldPort);
}
TWindow::ShowHide
void
TWindow::ShowHide(Boolean showFlag)
{
//Here we need the :: in front of ShowHide to indicate we are calling
//the global function, and not the method ShowHide. Unintended recursion
//can do bad things to the unsuspecting programmer.
//Some C++ programmers would always prepend the :: on function calls.
::ShowHide(fWindow,showFlag);
fIsVisible = showFlag;
}
TWindow::EventFilter
Boolean
TWindow::EventFilter(EventRecord * /* theEvent */)
{
return false;
}
TWindow::GetPerfectWindowSize
void
TWindow::GetPerfectWindowSize(Rect *perfectSize)
{
*perfectSize = qd.screenBits.bounds;
}
TWindow::GetWindowSizeLimits
void
TWindow::GetWindowSizeLimits(Rect *limits)
{
limits->top = limits->left = kMinimumWindowSize;
limits->right = gDeskRectangle.right - gDeskRectangle.left;
limits->bottom = gDeskRectangle.bottom
- gDeskRectangle.top;
}
TWindow::AdjustForNewWindowSize
void
TWindow::AdjustForNewWindowSize( Rect * /* oldRect */,
Rect * /* newSize */)
{
}
TWindow::IsVisible
Boolean
TWindow::IsVisible(void)
{
return fIsVisible;
}
TWindow::CanClose
Boolean
TWindow::CanClose(void)
{
return true;
}
TWindow::Close
Boolean
TWindow::Close(void)
{
WindowPtrnewFrontWindow = nil;
if (FrontNonFloatingWindow() == fWindow)
newFrontWindow = (WindowPtr) ((WindowPeek) fWindow)
->nextWindow;
DisposeWindow(fWindow);
if (newFrontWindow)
HiliteAndActivateWindow(newFrontWindow,true);
return true;
}
TWindow::DeleteAfterClose
Boolean
TWindow::DeleteAfterClose(void)
{
return true;
}
TWindow::DoEditMenu
void
TWindow::DoEditMenu(short /* menuCode */)
{
}
TWindow::HandleDrag
OSErr
TWindow::HandleDrag(DragTrackingMessage dragMessage,
DragReference theDrag)
{
OSErr result = dragNotAcceptedErr;
switch (dragMessage)
{
case dragTrackingEnterWindow:
result = this->DragEnterWindow(theDrag);
break;
case dragTrackingInWindow:
result = this->DragInWindow(theDrag);
break;
case dragTrackingLeaveWindow:
result = this->DragLeaveWindow(theDrag);
break;
default:
break;
}
return result;
}
TWindow::DragEnterWindow
OSErr
TWindow::DragEnterWindow(DragReference /* theDrag */)
{
return dragNotAcceptedErr;
}
TWindow::DragInWindow
OSErr
TWindow::DragInWindow(DragReference /* theDrag */)
{
return dragNotAcceptedErr;
}
TWindow::DragLeaveWindow
OSErr
TWindow::DragLeaveWindow(DragReference /* theDrag */)
{
return dragNotAcceptedErr;
}
TWindow::HandleDrop
OSErr
TWindow::HandleDrop(DragReference /* theDrag */)
{
return dragNotAcceptedErr;
}
Utility Functions used for floating windows
GetWindowObject
TWindow *
GetWindowObject(WindowPtr aWindow)
{
short wKind;
if (aWindow != nil)
{
wKind = ((WindowPeek) aWindow)->windowKind;
if (wKind >= userKind)
{
//All windowKinds >= userKind are based upon TWindow
return (TWindow *) GetWRefCon(aWindow);
}
}
return (TWindow *) nil;
}
Utility functions
GetNewColorOrBlackAndWhiteWindow
pascal WindowPtr
GetNewColorOrBlackAndWhiteWindow(short windowID,
void *wStorage, WindowPtr behind)
{
if (gHasColorQuickdraw)
return GetNewCWindow(windowID,wStorage,behind);
else
return GetNewWindow(windowID,wStorage,behind);
}
NewColorOrBlackAndWhiteWindow
pascal WindowPtr
NewColorOrBlackAndWhiteWindow( void *wStorage,
const Rect *boundsRect, ConstStr255Param title,
Boolean visible, short theProc, WindowPtr behind,
Boolean goAwayFlag, long refCon)
{
if (gHasColorQuickdraw)
return NewCWindow(wStorage, boundsRect, title, visible,
theProc, behind, goAwayFlag, refCon);
else
return NewWindow(wStorage, boundsRect, title, visible,
theProc, behind, goAwayFlag,refCon);
}
LastFloatingWindow
WindowPtr
LastFloatingWindow(void)
{
WindowPeek aWindow = (WindowPeek) FrontWindow();
WindowPtrlastFloater = (WindowPtr) kNoFloatingWindows;
while (aWindow && (aWindow->windowKind
== kFloatingWindowKind))
{
if (aWindow->visible)
lastFloater = (WindowPtr) aWindow;
aWindow = (WindowPeek) aWindow->nextWindow;
}
return(lastFloater);
}
FrontNonFloatingWindow
WindowPtr
FrontNonFloatingWindow(void)
{
WindowPeek aWindow = (WindowPeek) LMGetWindowList();
//Skip over floating windows
while (aWindow && (aWindow->windowKind
== kFloatingWindowKind))
aWindow = (WindowPeek) aWindow->nextWindow;
//Skip over invisible, but otherwise normal windows
while (aWindow && (aWindow->visible == 0))
aWindow = (WindowPeek) aWindow->nextWindow;
return (WindowPtr) aWindow;
}
HiliteAndActivateWindow
void
HiliteAndActivateWindow(WindowPtr aWindow,Boolean active)
{
GrafPtroldPort;
TWindow* wobj = GetWindowObject(aWindow);
if (aWindow)
{
HiliteWindow(aWindow,active);
if (wobj != nil)
{
GetPort(&oldPort);
SetPort(aWindow);
wobj->Activate(active);
SetPort(oldPort);
}
}
}
SuspendResumeWindows
void
SuspendResumeWindows(Boolean resuming)
{
//When we suspend/resume, hide/show all the visible floaters
HiliteShowHideFloatingWindows(resuming,true);
}
HiliteWindowsForModalDialog
void
HiliteWindowsForModalDialog(Boolean hiliting)
{
//When we display a modal dialog, we need to unhighlight
//all visible floaters. We also need to re-hilite them afterwards.
HiliteShowHideFloatingWindows(hiliting,false);
}
HiliteShowHideFloatingWindows
void
HiliteShowHideFloatingWindows(Boolean hiliting,Boolean dohiding)
{
WindowPeek aWindow;
TWindow *wobj;
HiliteAndActivateWindow(FrontNonFloatingWindow(),hiliting);
aWindow = LMGetWindowList();
while (aWindow && aWindow->windowKind
== kFloatingWindowKind)
{
wobj = GetWindowObject((WindowPtr) aWindow);
//If we are hiding or showing, only hide/show windows that were
// visible to begin with.
//NOTE:We use our copy of the visible flag so we can
//automatically show floaters on a resume event.
//NOTE:Since this isnt a method of TWindow, we dont really need the
::
// on ShowHide, but as long as were trying to avoid ambiguity
if (dohiding && (wobj != nil) && (wobj->IsVisible()))
::ShowHide((WindowPtr) aWindow,hiliting);
//All floaters are hilited if any floater is hilited
HiliteWindow((WindowPtr) aWindow,hiliting);
aWindow = (WindowPeek) aWindow->nextWindow;
}
}
Routines used for dealing with windows and multiple screens
CalculateWindowAreaOnDevice
pascal void
CalculateWindowAreaOnDevice(short /* depth */,
short /* deviceFlags */,GDHandle targetDevice,long userData)
{
CalcWindowAreaDeviceLoopUserData *deviceLoopDataPtr;
long windowAreaOnThisScreen;
Rect windowRectOnThisScreen;
deviceLoopDataPtr = (CalcWindowAreaDeviceLoopUserData *)
userData;
SectRect(&deviceLoopDataPtr->fWindowBounds,
&(**targetDevice).gdRect,&windowRectOnThisScreen);
OffsetRect(&windowRectOnThisScreen,
-windowRectOnThisScreen.left,
-windowRectOnThisScreen.top);
windowAreaOnThisScreen = windowRectOnThisScreen.right
* windowRectOnThisScreen.bottom;
if (windowAreaOnThisScreen > deviceLoopDataPtr->fLargestArea)
{
deviceLoopDataPtr->fLargestArea = windowAreaOnThisScreen;
deviceLoopDataPtr->fScreenWithLargestPartOfWindow
= targetDevice;
}
}
DeviceLoopDrawingUPP CallCalcWindowAreaOnDevice =
NewDeviceLoopDrawingProc(&CalculateWindowAreaOnDevice);
FindScreenRectWithLargestPartOfWindow
void
FindScreenRectWithLargestPartOfWindow(WindowPtr aWindow,
Rect *theBestScreenRect,GDHandle * theBestDevice)
{
RgnHandlecopyOfWindowStrucRgn;
CalcWindowAreaDeviceLoopUserData deviceLoopData;
//Use DeviceLoop to find out what GDevice contains the largest
//portion of the supplied window.
//NOTE:Assumes thePort == the Window Manager Port because we using
//the window strucRgn, not contRgn.
deviceLoopData.fScreenWithLargestPartOfWindow = nil;
deviceLoopData.fLargestArea = -1;
deviceLoopData.fWindowBounds = (**(((WindowPeek) aWindow)
->contRgn)).rgnBBox;
copyOfWindowStrucRgn = NewRgn();
CopyRgn(((WindowPeek) aWindow)->strucRgn,copyOfWindowStrucRgn);
DeviceLoop(copyOfWindowStrucRgn,
CallCalcWindowAreaOnDevice,
(long) &deviceLoopData,singleDevices);
DisposeRgn(copyOfWindowStrucRgn);
*theBestDevice =
deviceLoopData.fScreenWithLargestPartOfWindow;
*theBestScreenRect =
(**(deviceLoopData.fScreenWithLargestPartOfWindow)).gdRect;
//Leave some space around the edges of the screen so window look good,
AND
//if the best device is the main screen, leave space for the Menubar
InsetRect(theBestScreenRect,kScreenEdgeSlop,kScreenEdgeSlop);
if (GetMainDevice()
== deviceLoopData.fScreenWithLargestPartOfWindow)
theBestScreenRect->top += GetMBarHeight();
}
Drag Manager callback routines which dispatch to a windows method
CallWindowDragTrackingHandler
pascal OSErr
CallWindowDragTrackingHandler(DragTrackingMessage dragMessage,
WindowPtr theWindow,void * /* refCon */,DragReference theDrag)
{
TWindow *wobj = GetWindowObject(theWindow);
if (wobj)
return(wobj->HandleDrag(dragMessage,theDrag));
else
return dragNotAcceptedErr;
}
CallWindowDragReceiveHandler
pascal OSErr
CallWindowDragReceiveHandler(WindowPtr theWindow,
void * /* refCon */,DragReference theDrag)
{
TWindow *wobj = GetWindowObject(theWindow);
if (wobj)
return(wobj->HandleDrop(theDrag));
else
return dragNotAcceptedErr;
}
AppLib.h
/* 7 */
/* Contains:Prototypes for the guts of a Macintosh application.
Copyright:© 1993 by Dave Falkenburg, all rights reserved. */
#ifndef _APPLIB_
#define _APPLIB_
#include"AppConditionals.h"
#include"Preferences.h"
#include<Types.h>
#include<Windows.h>
#include<Dialogs.h>
#include<Menus.h>
#include<Files.h>
#include<AppleEvents.h>
#include<StandardFile.h>
#include<OCEStandardMail.h>
#include"Window.h"
#ifqUseQuickDrawGX
#include<FixMath.h>//make sure we dont use GX lame #define of fixed1
#include<graphics types.h>
#endif
useful macros
#ifqDebug
#define DebugMessage(x) DebugStr(x)
#else
#define DebugMessage(x)
#endif
Resource IDs
#define kErrorAlertID128
#define kStandardCloseAlertID 129
#define kStandardCloseWithNewPubsAlertID 130
#define kCoreErrorStrings 128
#define kUnsupportedSystemSoftware 1
#define kNeedsThreadManager 2
#define kStandardCloseStrings 129
#define kQuittingStr 1
#define kClosingStr2
#define kPreferencesFileStrings 130
#define kPreferencesFileName1
#define kSplashPictureID 128
#ifqUseQuickDrawGX
// When using GX, we want to create a 300K graphics heap
// NOTE: I have no idea what to use as a number here!
#define kGraphicsHeapSize (300 * 1024)
#endif
Useful functions provided by App:
void HandleEvent(EventRecord *anEvent);
void HandleClose(WindowPtr aWindow);
short StandardAlert(short alertID, short defaultItem=ok,
short cancelItem= 0,
ModalFilterUPP customFilterProc = nil);
void ErrorAlert(short stringList,short whichString);
void FatalErrorAlert(short stringList,short whichString);
extern ModalFilterUPPStandardDialogFilter;
extern ModalFilterYDUPP StandardDialogFilterYD;
extern voidPseudoClickInDialogItem(DialogPtr theDialog,
short itemToClick);
enum StandardCloseResult
{
kSaveDocument = 1,
kCancelSaveDocument = 2,
kDontSaveDocument = 3
};
StandardCloseResultStandardCloseDocument(
const StringPtr documentType,StringPtr documentName,
Boolean hasNewEditions, Boolean quitting);
OSErr CheckAppleEventForMissingParams(
AppleEvent *theAppleEvent);
short OpenPreferencesResFile(void);
// AOCE FrontWindow-equivalent routine for the Standard Mail package
extern FrontWindowUPPFrontWindowProcForAOCEUPP;
Globals
extern Boolean gDone;
extern Boolean gMenuBarNeedsUpdate;
extern Boolean gHasColorQuickdraw;
extern Boolean gHasThreadManager;
extern Boolean gHasDragManager;
extern Boolean gHasAOCE;
extern Boolean gHasDisplayManager;
#ifqInlineInputAware
extern Boolean gHasTextServices;
extern Boolean gHasTSMTE;
#endif
#ifqUseQuickDrawGX
extern Boolean gHasQuickDrawGX;
extern longgQuickDrawGXVersion;
extern longgQuickDrawGXPrintingVersion;
extern gxGraphicsClient gQuickDrawGXClient;
#endif
extern GrafPtr gWindowManagerPort;
extern RectgDeskRectangle;
extern RgnHandlegMouseRegion;
extern short gPreferencesRsrcRefNum;
Routines that the application MUST supply:
extern OSErr SetupApplication(void);
extern voidTearDownApplication(void);
extern voidHandleMenu(TWindow * topWindowObj,long menuCode);
extern voidConvertClipboard(void);
extern OSErr OpenNewDocument(void);
extern OSErr OpenDocument(LetterDescriptor *,void *);
extern OSErr PrintDocument(LetterDescriptor *,void *);
extern Boolean QuitApplication(void);
#endif
AppLib.cp
/* 8 */
/* Contains:The guts of a Macintosh application. Written by Dave and
many other SmartFriends Copyright: © 1993-94 by Dave Falkenburg, all
rights reserved. */
#ifdef SystemSevenOrLater
#undef SystemSevenOrLater
#endif
#define SystemSevenOrLater1
#include <limits.h>//For LONG_MAX
#include <Types.h>
#include <Quickdraw.h>
#include <Fonts.h>
#include <Menus.h>
#include <Windows.h>
#include <Dialogs.h>
#include <Desk.h>
#include <Events.h>
#include <AppleEvents.h>
#include <DiskInit.h>
#if qUseETO15Interfaces
#include <Gestalt.h>
#include <CodeFragments.h>
#include <Devices.h>
#else
#include <GestaltEqu.h>
#include <FragLoad.h>
#endif
#include <ToolUtils.h>
#include <Traps.h>
#include <LowMem.h>
#include <Threads.h>
#include <Drag.h>
#include <Editions.h>
#include <OCEStandardMail.h>
#ifqInlineInputAware
#include <TextServices.h>
#include <TSMTE.h>
#endif
#include "AppLib.h"
#include "StandardMenus.h"
#include "Window.h"
#include "SplashWindow.h"
#include "MailableWindow.h"
#include "AppleEventHandling.h"
#ifqUseQuickDrawGX
#include <graphics macintosh.h>
#include <graphics routines.h>
#include <PrintingManager.h>
#endif
Function Prototypes
void main(void);
void MainEventLoop(void);
void HandleMouseDown(TWindow * topWindowObj, EventRecord * anEvent);
void HandleUpdate(EventRecord * anEvent);
void HandleClose(WindowPtr aWindow);
Globals
Boolean gDone = false;
Boolean gMenuBarNeedsUpdate = true;
Boolean gHasColorQuickdraw = false;
Boolean gHasThreadManager = false;
Boolean gHasDragManager = false;
Boolean gHasAppleScript = false;
Boolean gHasAOCE = false;
Boolean gHasDisplayManager = false;
GrafPtr gWindowManagerPort;
Rect gDeskRectangle;
RgnHandle gMouseRegion = nil;
short gPreferencesRsrcRefNum;
#ifqInlineInputAware
Boolean gHasTextServices = false;
Boolean gHasTSMTE = false;
#endif
#ifqUseQuickDrawGX
Boolean gHasQuickDrawGX = false;
long gQuickDrawGXVersion = 0;
long gQuickDrawGXPrintingVersion = 0;
gxGraphicsClient gQuickDrawGXClient;
// PrintingEventOverride is our generic event handler for QuickDrawGX.
// It alows us to handle events while the QuickDrawGX movable modal
// printing dialogs are displayed.
// ¿ Really should move to a GX-specific place ?
PrintingEventOverrideForGX
OSErr
PrintingEventOverrideForGX(EventRecord *anEvent,
Boolean filterEvent)
{
if (!filterEvent)
switch (anEvent->what)
{
case mouseDown:
case keyDown:
case autoKey:
break;
default:
HandleEvent(anEvent);
}
return noErr;
}
#endif
// Values that can be adjusted by other application code to change
// the behavior of the MainEventLoop.
//
// Rules of thumb:
// Increase gXXXRunQuantum (and decrease gXXXSleepQuantum) when:
// The application has many threads running that need time
// Decrease gXXXRunQuantum when:
// Sending AppleEvents to other applications
// Launching other applications
// Running in the background
unsigned long gForegroundRunQuantum = 0;
unsigned long gForegroundSleepQuantum = GetCaretTime();
unsigned long gBackgroundRunQuantum = 0;
unsigned long gBackgroundSleepQuantum = LONG_MAX;
// Globals used to tune the performance of MainEventLoop
// (assume well be starting in the foreground)
static unsigned longgRunQuantum = gForegroundRunQuantum;
static unsigned longgSleepQuantum = gForegroundSleepQuantum;
#ifdef powerc
#ifndef __MWERKS__
QDGlobals qd;
#endif
#endif
main
void
main(void)
{
long feature;
MaxApplZone();
MoreMasters(); MoreMasters(); MoreMasters(); MoreMasters();
InitGraf(&qd.thePort);
InitFonts();
InitWindows();
InitMenus();
TEInit();
InitDialogs(nil);
if (GetToolTrapAddress(_Unimplemented)
== GetOSTrapAddress(_Gestalt))
FatalErrorAlert(kCoreErrorStrings,
kUnsupportedSystemSoftware);
if (Gestalt(gestaltQuickdrawFeatures,&feature) == noErr)
gHasColorQuickdraw = ((feature & (1 << gestaltHasColor)) != 0);
TSplashWindow * splashWindow = new TSplashWindow;
if ((Gestalt(gestaltAppleEventsAttr,&feature) == noErr)
&& (feature & (1 << gestaltAppleEventsPresent)))
{
//Figure out if we need to do AppleEvent recording
gHasAppleScript = (feature & (1 << gestaltScriptingSupport));
}
else
FatalErrorAlert(kCoreErrorStrings,
kUnsupportedSystemSoftware);
#ifqInlineInputAware
if ((Gestalt(gestaltTSMgrVersion,&feature) == noErr)
&& (feature >= 1))
{
gHasTextServices = true;
if (Gestalt(gestaltTSMTEAttr, &feature) == noErr)
gHasTSMTE = (feature & (1 << gestaltTSMTEPresent));
}
#endif
if (Gestalt(gestaltThreadMgrAttr,&feature) == noErr)
{
#ifdef powerc
//If running on a PowerPC, make sure that we not only have the 68K Thread
//Manager, but also the PowerPC shared library, too. Because of the
wonders
//of weak linking and out of memory errors we need to also check to
make
//sure that an entrypoint in the library is there, too.
if ((Ptr) NewThread != kUnresolvedSymbolAddress)
gHasThreadManager = ((feature
& ((1 << gestaltThreadMgrPresent)
| (1 << gestaltThreadsLibraryPresent))) != 0);
#else
gHasThreadManager = ((feature & (1 << gestaltThreadMgrPresent)) != 0);
#endif
}
//Check for and install Drag Manager callbacks
if (Gestalt(gestaltDragMgrAttr,&feature) == noErr)
{
#ifdef powerc
//If running on a PowerPC, make sure that we not only have the
//68K Drag Manager, but also the PowerPC shared library, too.
if ((Ptr) NewDrag != kUnresolvedSymbolAddress)
gHasDragManager = ((feature
& ((1 << gestaltDragMgrPresent)
| (1 << gestaltPPCDragLibPresent))) != 0);
#else
gHasDragManager = ((feature & (1<<gestaltDragMgrPresent))!=0);
#endif
if (gHasDragManager)
{
InstallTrackingHandler( NewDragTrackingHandlerProc
(CallWindowDragTrackingHandler),
(WindowPtr) nil,nil);
InstallReceiveHandler( NewDragReceiveHandlerProc
(CallWindowDragReceiveHandler),
(WindowPtr) nil,nil);
}
}
//Check for Display Manager
if (Gestalt(gestaltDisplayMgrAttr,&feature) == noErr)
gHasDisplayManager
= ((feature & (1 << gestaltDisplayMgrPresent)) != 0);
//Check for and initialize AOCE Standard Mail package if it exists
if ((Gestalt(gestaltSMPMailerVersion,&feature) == noErr)
&& (feature != 0))
{
#ifdef powerc
if ((Ptr) SMPInitMailer != kUnresolvedSymbolAddress)
gHasAOCE = (SMPInitMailer(kSMPVersion) == noErr);
#else
gHasAOCE = (SMPInitMailer(kSMPVersion) == noErr);
#endif
}
#ifqUseQuickDrawGX
//Check for and initialize QuickDrawGX
if (Gestalt(gestaltGXVersion, &gQuickDrawGXVersion)==noErr)
if (Gestalt(gestaltGXPrintingMgrVersion,
&gQuickDrawGXPrintingVersion) == noErr)
#ifdef powerc
if ((Ptr) GXEnterGraphics != kUnresolvedSymbolAddress)
gHasQuickDrawGX = true;
#else
gHasQuickDrawGX = true;
#endif
if (gHasQuickDrawGX)
{
// gQuickDrawGXClient = GXNewGraphicsClient(nil, kGraphicsHeapSize,
// (gxClientAttribute) 0);
GXEnterGraphics();
GXInitPrinting();
}
#endif
InstallAppleEventHandlers(); // Install our AppleEvent Handlers
//Setup desktop rectangle for dragging windows around
GetWMgrPort(&gWindowManagerPort);
gDeskRectangle = (**GetGrayRgn()).rgnBBox;
//Get the default menubar
SetMenuBar(GetNewMBar(rMenuBar));
AddResMenu(GetMHandle(mApple),'DRVR');
gPreferencesRsrcRefNum = OpenPreferencesResFile();
if (SetupApplication() == noErr)
{
delete splashWindow;// get rid of the splash screen
MainEventLoop();
TearDownApplication();
}
#ifqUseQuickDrawGX
if (gHasQuickDrawGX)// Tear down QuickDrawGX
{
GXExitPrinting();
// GXDisposeGraphicsClient(gQuickDrawGXClient); // Dies a horrible
death for me?
GXExitGraphics();
}
#endif
}
MainEventLoop
void
MainEventLoop(void)
{
EventRecordanEvent;
unsigned long nextTimeToCheckForEvents = 0;
while (!gDone)
{
if (gMenuBarNeedsUpdate)
{
gMenuBarNeedsUpdate = false;
DrawMenuBar();
}
if ((gRunQuantum == 0) ||
(TickCount() > nextTimeToCheckForEvents))
{
nextTimeToCheckForEvents = TickCount() + gRunQuantum;
(void) WaitNextEvent(everyEvent,&anEvent,
gSleepQuantum,gMouseRegion);
HandleEvent(&anEvent);
}
if (gHasThreadManager)
YieldToAnyThread();
}
}
HandleEvent
void
HandleEvent(EventRecord *anEvent)
{
TWindow* wobj;
if (anEvent->what != updateEvt)
wobj = GetWindowObject(FrontNonFloatingWindow());
else
wobj = GetWindowObject((WindowPtr) anEvent->message);
if (wobj != nil)
wobj->AdjustCursor(anEvent);
if ((wobj != nil) && wobj->EventFilter(anEvent))
return;
else switch (anEvent->what)
{
case nullEvent:
if (wobj != nil)
wobj->Idle(anEvent);
break;
case mouseDown:
HandleMouseDown(wobj,anEvent);
break;
case keyDown:
case autoKey:
if (anEvent->modifiers & cmdKey)
HandleMenu(wobj,MenuKey((short) anEvent->message
& charCodeMask));
else if (wobj != nil)
wobj->KeyDown(anEvent);
break;
case updateEvt:
HandleUpdate(anEvent);
break;
case diskEvt:
if (anEvent->message >> 16)
{
static Point where = {50,50};
(void) DIBadMount(where,anEvent->message);
}
break;
case osEvt:
switch ((anEvent->message & osEvtMessageMask) >> 24)
{
case mouseMovedMessage:
break;
case suspendResumeMessage:
if (anEvent->message & resumeFlag)
{
gRunQuantum = gForegroundRunQuantum;
gSleepQuantum = gForegroundSleepQuantum;
}
else
{
gRunQuantum = gBackgroundRunQuantum;
gSleepQuantum = gBackgroundSleepQuantum;
}
SuspendResumeWindows(
(anEvent->message & resumeFlag) != 0);
if (anEvent->message & convertClipboardFlag)
ConvertClipboard();
break;
}
break;
case kHighLevelEvent:
(void) AEProcessAppleEvent(anEvent);
break;
default:
break;
}
}
HandleMouseDown
void
HandleMouseDown(TWindow * topWindowObj,EventRecord *anEvent)
{
WindowPtraWindow;
short partCode;
TWindow*wobj;
partCode = FindWindow(anEvent->where,&aWindow);
wobj = GetWindowObject(aWindow);
switch(partCode)
{
case inMenuBar:
HandleMenu(topWindowObj,MenuSelect(anEvent->where));
break;
case inSysWindow:
SystemClick(anEvent,aWindow);
break;
case inContent:
if (wobj)
{
GrafPtroldPort;
GetPort(&oldPort);
SetPort(aWindow);
GlobalToLocal(&anEvent->where);
wobj->Click(anEvent);
SetPort(aWindow);
}
break;
case inDrag:
if (wobj)
wobj->Drag(anEvent->where);
break;
case inGrow:
if (wobj)
wobj->Grow(anEvent->where);
break;
case inGoAway:
if (TrackGoAway(aWindow,anEvent->where))
HandleClose(aWindow);
break;
case inZoomIn:
caseinZoomOut:
if (TrackBox(aWindow,anEvent->where,partCode) && (wobj))
wobj->Zoom(partCode);
break;
default:
break;
}
}
HandleUpdate
void HandleUpdate(EventRecord * anEvent)
{
GrafPtroldPort;
WindowPtraWindow = (WindowPtr) anEvent->message;
TWindow* wobj;
GetPort(&oldPort);
SetPort(aWindow);
BeginUpdate(aWindow);
if ((wobj = GetWindowObject(aWindow)) != nil)
wobj->Draw();
EndUpdate(aWindow);
SetPort(oldPort);
}
HandleClose
void
HandleClose(WindowPtr aWindow)
{
short windowKind;
TWindow*wobj;
if (aWindow)
{
windowKind = ((WindowPeek) aWindow)->windowKind;
if (windowKind < 0)
{
CloseDeskAcc(((WindowPeek)aWindow)->windowKind);
}
else if ( ((wobj = GetWindowObject(aWindow)) != nil) &&
wobj->CanClose() && wobj->Close()
&& wobj->DeleteAfterClose())
{
delete wobj;
}
}
}