DoBetterWZoom.c

/*
    File:       DoBetterWZoom.c
 
    Contains:   The new functionality is really cool. I now pass in maximum bounds for the window and 
                make all sorts of adjustments to ensure that the window is zoomed to a good size at a 
                good place while minimizing unnecessary window motion. This implementation is slightly 
                better than the one used by the Finder in View by Icon.
 
    Written by:     
 
    Copyright:  Copyright © 1984-1999 by Apple Computer, Inc., All Rights Reserved.
 
                You may incorporate this Apple sample source code into your program(s) without
                restriction. This Apple sample source code has been provided "AS IS" and the
                responsibility for its operation is yours. You are not permitted to redistribute
                this Apple sample source code as "Apple sample source code" after having made
                changes. If you're going to re-distribute the source, we require that you make
                it clear in the source that the code was descended from Apple sample source
                code, but that you've made changes.
 
    Change History (most recent first):
                8/6/1999    Karl Groethe    Updated for Metrowerks Codewarror Pro 2.1
                
 
*/
void DoZoomWindow (WindowPtr theWindow, short zoomDir, short hMax, short vMax)
{
    extern EventRecord  gTheEvent;                      // from the main event loop
    extern Boolean      gHasColorQD;
 
    Rect                *windRect, *zoomRect;
    Rect                globalPortRect, theSect, dGDRect;
    GDHandle            nthDevice, dominantGDevice;
    long                sectArea, greatestArea;
 
    if (TrackBox(theWindow, gTheEvent.where, zoomDir)) {
        SetPort(theWindow);
        EraseRect(&theWindow->portRect);    // recommended for cosmetic reasons
 
        if (zoomDir == inZoomOut) {
 
            /*
             *  ZoomWindow() is a good basic tool, but it doesn't do everything necessary to
             *  implement a good human interface when zooming. In fact it's not even close for
             *  more high-end hardware configurations. We must help it along by calculating an
             *  appropriate window size and location any time a window zooms out.
             */
 
            windRect = &(**((WindowPeek) theWindow)->strucRgn).rgnBBox;
            dominantGDevice = nil;
            if (gHasColorQD) {
 
                /*
                 *  Color QuickDraw implies the possibility of multiple monitors. This is where
                 *  zooming becomes more interesting. One should zoom onto the monitor containing
                 *  the greatest portion of the window. This requires walking the gDevice list.
                 */
 
                nthDevice = GetDeviceList();
                greatestArea = 0;
                while (nthDevice != nil) {
                    if (TestDeviceAttribute(nthDevice, screenDevice)) {
                        if (TestDeviceAttribute(nthDevice, screenActive)) {
                            SectRect(windRect, &(**nthDevice).gdRect, &theSect);
                            sectArea = (long) rectWidth(theSect) * (long) rectHeight(theSect);
                            if (sectArea > greatestArea) {
                                greatestArea = sectArea;        // save the greatest intersection
                                dominantGDevice = nthDevice;    // and which device it belongs to
                            }
                        }
                    }
                    nthDevice = GetNextDevice(nthDevice);
                }
            }
 
            /*
             *  At this point, we know the dimensions of the window we're zooming, and we know
             *  what screen we're going to put it on. To be more specific, however, we need a
             *  rectangle which defines the maximum dimensions of the resized window's contents.
             *  This rectangle accounts for the thickness of the window frame, the menu bar, and
             *  one or two pixels around the edges for cosmetic compatibility with ZoomWindow().
             */
 
            if (dominantGDevice != nil) {
                dGDRect = (**dominantGDevice).gdRect;
                if (dominantGDevice == GetMainDevice())     // account for menu bar on main device
                    dGDRect.top += GetMBarHeight();
            }
            else {
                dGDRect = qd.screenBits.bounds;             // if no gDevice, use default monitor
                dGDRect.top += GetMBarHeight();
            }
 
            globalPortRect = theWindow->portRect;
            LocalToGlobal(&topLeft(globalPortRect));        // calculate the window's portRect
            LocalToGlobal(&botRight(globalPortRect));       // in global coordinates
 
            // account for the window frame and inset it a few pixels
            dGDRect.left    += 2 + globalPortRect.left - windRect->left;
            dGDRect.top     += 2 + globalPortRect.top - windRect->top;
            dGDRect.right   -= 1 + windRect->right - globalPortRect.right;
            dGDRect.bottom  -= 1 + windRect->bottom - globalPortRect.bottom;
 
            /*
             *  Now we know exactly what our limits are, and since there are input parameters
             *  specifying the dimensions we'd like to see, we can move and resize the zoom
             *  state rectangle for the best possible results. We have three goals in this:
             *  1. Display the window entirely visible on a single device.
             *  2. Resize the window to best represent the dimensions of the document itself.
             *  3. Move the window as short a distance as possible to achieve #1 and #2.
             */
 
            zoomRect = &(**(WStateDataHandle) ((WindowPeek) theWindow)->dataHandle).stdState;
 
            /*
             *  Initially set the zoom rectangle to the size requested by the input parameters,
             *  although not smaller than a minimum size. We do this without moving the origin.
             */
 
            zoomRect->right = (zoomRect->left = globalPortRect.left) +
                                    max(hMax, MinWindowWidth(theWindow));
            zoomRect->bottom = (zoomRect->top = globalPortRect.top) +
                                    max(vMax, MinWindowHeight(theWindow));
 
            // Shift the entire rectangle if necessary to bring its origin inside dGDRect.
            OffsetRect(zoomRect,
                        max(dGDRect.left - zoomRect->left, 0),
                        max(dGDRect.top - zoomRect->top, 0));
 
            /*
             *  Shift the rectangle up and/or to the left if necessary to accomodate the view,
             *  and if it is possible to do so. The rectangle may not be moved such that its
             *  origin would fall outside of dGDRect.
             */
 
            OffsetRect(zoomRect,
                        -pin(zoomRect->right - dGDRect.right, 0, zoomRect->left - dGDRect.left),
                        -pin(zoomRect->bottom - dGDRect.bottom, 0, zoomRect->top - dGDRect.top));
 
            // Clip expansion to dGDRect, in case view is larger than dGDRect.
            zoomRect->right = min(zoomRect->right, dGDRect.right);
            zoomRect->bottom = min(zoomRect->bottom, dGDRect.bottom);
        }
 
        ZoomWindow(theWindow, zoomDir, false);      // all it needed was a brain transplant
    }
}