TweetFollow Us on Twitter

Window Management
Volume Number:10
Issue Number:12
Column Tag:Improving the Framework

Related Info: Window Manager

Better Window Management in MacApp

Plugging some holes in MacApp windows

By Richard Gillam, GE Information Services

Note: Source code files accompanying article are located on MacTech CD-ROM or source code disks.

The little things count. This article doesn’t aim to present any Higher Truths of Object-Oriented Computing, or to show you how to solve third-order differential equations using the THINK Class Library, or anything like that. Instead, I want to share with you a little class I created a while back to plug up a few holes in MacApp 3.0.1’s window-management code.

What’s Wrong With
MacApp’s Window Stuff?

Some things don’t quite live up to their press. As with printing in MacApp (“It’s only a couple lines of code” only if you’re doing really basic, no-frills printing), so it is with window management in MacApp, only not quite as bad. I found two significant deficiencies in MacApp’s window-management code and one feature I wanted to add.

The first feature was MacApp’s window-staggering code. If you want the initial positions of your document windows to march down the screen and to the right as you successively open windows, rather than opening directly on top of each other, you can set the window’s fMustStagger flag in the view template resource. But if you do that without implementing a staggering routine of your own, you’ll run into two big problems. The first is a truly brain-dead positioning algorithm. MacApp keeps a global variable that contains a count of how many staggered windows it has opened. To figure out where to open the next one, it just multiplies the standard staggering offset by the number of windows that have already been opened. That’s it. This means that if you’ve opened ten windows and then closed all of them, the next window you open will still be halfway down the screen. Ugly.

Worse, there’s a bug in their algorithm. The algorithm is smart enough to sense when you’ve marched off the edge of the monitor and move you back to the upper left-hand corner, but they didn’t do it quite right. If you have two monitors and the auxiliary monitor is placed to the right of the main monitor (the one with the menu bar), you can run into a situation where new windows open with their initial position straddling monitors, a definite human-interface no-no. This is either because they don’t take multiple monitors into account or because they only check to see if the window has gone off the bottom of the screen and not if it’s gone off the right-hand side. [To see what I’m talking about, comment out the line that begins “RegisterStdType(“TBetterWindow” )” in the demo program and compile and run it. On a two-monitor machine, if you open enough windows, several of them will hang off on the right-hand monitor. They won’t move back to the upper left-hand corner until they march off the bottom.]

The other feature I wanted to improve was the window-zooming code. Here, MacApp does take multiple monitors into account, but doesn’t take the contents of the window itself into account. The human-interface guidelines say that clicking the zoom box of a window should make it just large enough to hold its contents (if that’ll fit on the screen), but no larger. MacApp’s default window-zooming algorithm always zooms the window to full-screen size.

Finally, I needed to add the ability to save and restore window positions.

The TBetterWindow Class

You’ll find source for a class designed to solve that above problems and a demo program called BetterWindow in the usual source code locations. The program is basically the Nothing application beefed up with the addition of the TBetterWindow class. I made the default view wider so that you could see the multiple-monitor bug, and I added the ability to make the view itself larger and smaller. You may want to follow along in the demo program as I go through these examples.

It would be nice if we could improve the positioning and zooming algorithms by adding a behavior to a regular TWindow. Unfortunately, we can’t, because we have to override too many routines which are specific to TWindow and not available to override from TBehavior. So I had to subclass TWindow and create a class called TBetterWindow. Fortunately, for certain classes, MacApp provides a way to use a subclass instead of standard class. In your application’s initialization code (probably in your application class’s I method), call RegisterStdType(). My call to RegisterStdType() looks like this:

RegisterStdType("TBetterWindow", kStdWindow);

kStdWindow tells MacApp you’re providing a class name to use when opening a standard window, and the string parameter is the class name to use instead of TWindow. Now anywhere you use TWindow, TBetterWindow will be substituted.

You can probably safely use TBetterWindow all the time, even in cases (such as dialog boxes) where you don’t need its features. But if you want more control over when specifically you use TBetterWindow, you need to specify it in all of the view hierarchy resources where you intend to use it.

Smart Window Positioning

To control how a window is staggered, you override TWindow::SimpleStagger(). TWindow::Open() calls this routine any time the window being opened has its fMustStagger flag set. This routine positions and resizes the window as appropriate. It takes two parameters: the amount to offset each window from the previous one, and a count of the staggered windows opened so far. We don’t use the count parameter.

The basic idea behind this routine is that every new window opens offset by the specified amount from the window right behind it. But we’ve added two important refinements: 1) If that position will run partially off the screen or straddle two monitors, we don’t use it, but instead move back toward the upper left-hand corner of the monitor containing the largest part of the previous active window. 2) If the position where we want to open the window is already occupied by another window (i.e., another window has its upper left-hand corner where we were going to put this window’s upper left-hand corner), then move down and to the right until we find an unoccupied position.


/* 1 */
pascal void TBetterWindow :: SimpleStagger(
 CPoint delta,
 short& /*count*/)
 {
 CPoint ulhc(7, 20);
 CPoint newPos;
 WindowPtrfrontWindow;
 CRect  temp;
 CRect  monitorRect;
 CPoint localUlhc;
 VRect  frame;
 
 // take into account the menu bar when determining
 // where to put the first window
 ulhc.v += *(short*)MBarHeight;
 
 // find the front window and take note of its monitor
 // rectangle
 frontWindow = MAGetActiveWindow();
 GetMonitorInfo(frontWindow, monitorRect);
 
 // if there is no front window, the new window will go
 // in the upper left-hand corner of the main monitor,
 // defined above
 if (frontWindow == NULL) {
 newPos = ulhc;
 localUlhc = ulhc;
 }
 
 // if there is a front window, the new one will be
 // offset from it by the value of "delta" and the upper
 // left-hand corner we use for positioning will be that
 // of the monitor containing the most of the current
 // front window (notice we have to adjust for the menu
 // bar size)
 else {
 temp = (**((WindowPeek)frontWindow)->contRgn)
 .rgnBBox;
 newPos = temp[topLeft];
 newPos += delta;
 localUlhc = ulhc;
 if (monitorRect[topLeft] != gZeroPt) {
 localUlhc += monitorRect[topLeft];
 localUlhc.v -= *(short*)MBarHeight;
 }
 }
 this->MakePositionUnique(newPos, localUlhc, delta,
 monitorRect);
 this->Locate(VPoint(newPos), false);
 }

Note the extra code that is required to take the menu bar and multiple monitors into account. Most of this code is fairly self-explanatory, but it is worth noting that “ulhc” is initialized to (7, 20) to give adequate clearance around the window. The position the Mac OS uses is actually the upper left-hand corner of the window’s content area, so we have to leave room for the title bar (hence the 20), and it looks a little better if we also leave some room on the left-hand side (hence the 7).

There are two refinements that can be added to the above algorithm. First, for performance reasons, it’s a good idea to take advantage of the window’s fStaggered flag:


/* 2 */
// if fStaggered is true, then we've already done all this
if (this->fStaggered)
 return;
 
// otherwise, go ahead and set the flag now so we won't go
// through all this the next time
this->fStaggered = true;

Second, if the window is a text-editing window, it’s nice to take advantage of all available screen real estate by elongating the window after you’ve positioned it:


/* 3 */
 if ( /*I'm a text-editing window*/ ) {
 this->GetFrame(frame);
 frame.bottom = monitorRect.bottom - 3;
 this->Resize(frame.GetSize(), false);
 }

If you elongate the window afterwards, the window can take up all available vertical space, no matter how much there is. It also means you can define the window’s size in your view template as the minimum size of the window. The positioning algorithm will move the window back to the upper left-hand corner when you’re too far down to show this minimum height.

Of course, much of the real work of the above routine is being done in subroutines. GetMonitorInfo() is used several different places to retrieve the bounding rectangle of the monitor containing the largest part of the specified window.


/* 4 */
pascal void TBetterWindow :: GetMonitorInfo(
 WindowPtrfrontWindow,
 CRect& monitorRect)
 {
 GDHandle monitor;
 GrafPtrwMgrPort;
 TWindow* frontTWindow;
 
// if we don't have Color QuickDraw, we can't have
// multiple monitors, so by definition our entire
// universe is defined by the Window Manager port's
// portRect
 if (!gConfiguration.hasColorQD) {
 GetWMgrPort(wMgrPort);
 monitorRect = wMgrPort->portRect;
 return;
 }
 
// if there are no open windows, use the main monitor
 if (frontWindow == NULL) {
 monitor = GetMainDevice();
 monitorRect = (**monitor).gdRect;
 }
 
// otherwise, use a method in TWindow to find out which
// monitor contains more of the frontmost window and
// then get info for it
 else {
 frontTWindow = WMgrToWindow(frontWindow);
 if (frontTWindow == NULL) {
 monitor = GetMainDevice();
 monitorRect = (**monitor).gdRect;
 }
 else {
 monitor = frontTWindow->
 GetMaxIntersectedDevice(monitorRect);
 monitorRect.top -= *(short*)MBarHeight;
 }
 }
 }

In most cases, we can take advantage of a MacApp routine called TWindow::GetMaxIntersectedDevice() to find out which monitor a window is on. GetMonitorInfo() helps us by taking care of the situation where we don’t have Color QuickDraw (in which case we can use the Window Manager port’s portRect, since there can only be one monitor), and the situation where there isn’t actually an open window to use (in which case we get the rectangle of the main monitor).

The job of find an unoccupied position for the new window is done by TBetterWindow::MakePositionUnique().


/* 5 */
pascal void TBetterWindow :: MakePositionUnique(
 CPoint&newPos,
 CPoint ulhc,
 CPoint delta,
 const CRect&  monitorRect)
 {
 CPoint workPos(newPos);
 CPoint tempUlhc;
 
// for as long as the proposed position is on the
// screen and another window occupies this position,
// keep advancing down and to the right until we
// either march off the screen or find an unoccupied
// position
 while (monitorRect.Contains(workPos) &&
 !IsPositionUnique(workPos))
 workPos += delta;
 
// if we've marched off the screen or if the unoccupied
// position won't allow the whole window to be on the
// screen, try again starting at the monitor's upper
// left-hand corner
 if (!monitorRect.Contains(workPos) ||
 !WillWindowFit(workPos, monitorRect)) {
 if (newPos != ulhc) {
 workPos = ulhc;
 if (!IsPositionUnique(workPos)) {
 MakePositionUnique(workPos, ulhc, delta,
 monitorRect);
 
// if we can't find an unoccupied position that
// will hold the window after starting at the
// upper left-hand corner of the monitor, try
// again, but start halfway between the first
// two window positions on the monitor (the test
// here causes recursive calls to fall out if
// they don't find an acceptable position)
 if (workPos == ulhc) {
 workPos += CPoint(delta.h / 2, delta.v / 2);
 
 if (!IsPositionUnique(workPos)) {
 tempUlhc = workPos;
 MakePositionUnique(workPos, tempUlhc, delta, monitorRect);
 
// if that doesn't work either, give up and
// just put the window in the upper left-
// hand corner of the monitor
 if (workPos == tempUlhc)
 workPos = ulhc;
 }
 }
 }
 }
 else
 workPos = newPos;
 }
 
// return the new position
 newPos = workPos;
 }

The basic idea here is that we check the current position of the window. If it’s occupied or part of the window dangles off the monitor, move down and to the right by the offset amount and try again. If the upper left-hand corner of the window marches off the screen, start over again (calling ourselves recursively), but start at the upper left-hand corner of the monitor. If that doesn’t work either, try again (with another recursive call), with the first spot being half the offset value from the upper left-hand corner of the screen. If that doesn’t work either (at which point some twenty-odd positions will have been tried), we just give up and put the window in the upper left-hand corner of the screen.

I’ll grant that the giving-up part isn’t real cool, but it should only happen under extreme conditions, and there is no easy way to modify this algorithm to reuse used positions (we’d have to count the windows in each position, which is painful). We could go hog-wild with the staggering (after all, there are some 15 valid positions between two windows offset from each other by the standard offset amount). I didn’t do this either, but it would be a good way of handling it if you often find yourself in a state where there are too many windows open for this algorithm to support.

MakePositionUnique() relies on another routine, IsPositionUnique(), to determine when it’s found a usable position. IsPositionUnique() in turn relies on a routine called WillWindowFit() to determine whether the whole window can fit at the specified position without hanging off the screen or straddling monitors:


/* 6 */
pascal Boolean TBetterWindow :: IsPositionUnique(
 CPoint pos)
 {
 CWMgrIterator   iter;
 WindowPtraWindowPtr;
 CRect  windowRect;
 
 for (aWindowPtr = iter.FirstWMgrWindow(); 
 iter.More();
 aWindowPtr = iter.NextWMgrWindow())
 if ((((WindowPeek)aWindowPtr)->visible) 
 &&   IsDocumentWindow(aWindowPtr)) {
 windowRect = (**((WindowPeek)aWindowPtr)
 ->contRgn).rgnBBox;
 if (windowRect[topLeft] == pos)
 return false;
 }
 return true;
 }

pascal Boolean TBetterWindow :: WillWindowFit(
 CPoint pos,
 const CRect&  monitorRect)
 {
 CRect  frame;
 
 frame[topLeft] = pos;
 frame[botRight] = pos + this->fSize.ToPoint();
 
// the insetting here takes into account the frame and
// drop shadow (we don't care about the title bar,
// since we're always going down and to the right
// and we know we started in a position where the title
// bar will work)
 frame.Inset(CPoint(-2, -2));
 
 return monitorRect.Contains(frame);
 }

WillWindowFit() is pretty self-explanatory. IsPositionUnique() takes advantage of an internal MacApp class, the CWMgrIterator, to walk the Window Manager’s window list to find out whether any other windows already on the screen occupy our proposed position. It’s a lot easier to walk the Window Manager’s list than to walk MacApp’s list.

Smart Window Zooming

To fix the zooming problem, the primary place to tap in is TWindow::GetStandardStateFrame(). There are two TWindow routines: GetStandardStateFrame() and GetUserStateFrame(). The standard state is the system-supplied position and size for the window, a canonical “most natural size” for the window (which is why it’s called the “standard state”). The user state is the size the window was before zooming, a state the window probably got into because the user resized or moved it (which is why it’s called the “user state”). Resizing the window by dragging the size box always puts the window into the user state. Clicking the window’s zoom box toggles between the user and standard states.

We don’t need to override GetUserStateFrame() because we’re not doing anything to the user state. But the standard state supplied by MacApp is always the full size of the monitor, which is often way too big in at least one dimension for what’s actually in the window, so

 
/* 7 */
pascal void TBetterWindow :: GetStandardStateFrame(
 const VRect&  /*boundingRect*/,
 VRect& stdFrame)
 {
 TView* targetView;
 TScroller* targetScroller;
 VRect  targetFrame;
 VRect  windowFrame;
 VPoint difference;
 GDHandle monitor;
 CRect  tempMonitorRect;
 VRect  monitorRect;
 VPoint ulhc(7, 20);
 
// get the window target view and figure out the
// difference between its current size and the window's
// current size (if the window target view is
// enclosed in a scroller, we're actually interested in
// the size of the scroller here instead [this probably
// won't work right if the window target view isn't the
// only thing in the scroller])
 targetView = (TView*)this->GetWindowTarget();
 targetScroller = targetView->GetScroller(false);
 if (targetScroller == NULL)
 targetView->GetFrame(targetFrame);
 else
 targetScroller->GetFrame(targetFrame);
 this->GetFrame(windowFrame);
 difference = windowFrame.GetSize() -
 targetFrame.GetSize();
 
// calculate the target view's minimum frame size, and
// calculate a new frame rectangle for the window by
// adding the difference between the window
// size and the old frame size to the new frame size
// and making the window frame that new size
 targetView->CalcMinFrame(targetFrame);
 windowFrame[botRight] = windowFrame[topLeft] +
 targetFrame.GetSize() + difference;
 if (windowFrame.GetLength(hSel) < 
 fResizeLimits.left)
 windowFrame.right = windowFrame.left +
 fResizeLimits.left;
 if (windowFrame.GetLength(vSel) < 
 fResizeLimits.top)
 windowFrame.bottom = windowFrame.top +
 fResizeLimits.top;
 
// get the rectangle of the monitor containing the
// largest part of the window (inset it a little to
// leave some slop on the sides and to leave
// room for the menu bar (if we have one) and the
// window's title bar)
// [NOTE: GetMaxIntersectedDevice() will usually take
// the menu bar into account. If it does, the else
// clause below will automatically adjust
// ulhc to take the menu bar into account too.]
 monitor = this->GetMaxIntersectedDevice
 (tempMonitorRect);
 monitorRect = VRect(tempMonitorRect);
 if (monitorRect[topLeft] == gZeroVPt)
 ulhc.v += *(short*)MBarHeight;
 else
 ulhc += monitorRect[topLeft];
 monitorRect[topLeft] = ulhc;
 monitorRect[botRight] -= VPoint(3, 3);
 
// does the new window frame fit on that monitor? If
// not, move the window to the upper left-hand corner
// of the monitor
 if (!monitorRect.Contains(windowFrame)) {
 windowFrame += ulhc - windowFrame[topLeft];
 
// does it fit now? If not, resize it so that as
// much of it as possible does
 if (!monitorRect.Contains(windowFrame)) {
 windowFrame.bottom = Min(windowFrame.bottom,
 monitorRect.bottom);
 windowFrame.right = Min(windowFrame.right,
 monitorRect.right);
 }
 }
 
// and return the result
 stdFrame = windowFrame;
 }

The algorithm I’m using here isn’t completely general, but it should cover most of the common cases. It works on the theory that when you resize a window by dragging the resize box, the part of the window that actually changes size is the window target view (or more accurately, the scroller containing the window target view). Everything else (scroll bars, other panes, etc.) derives its new size or position from the new size of the window target view’s scroller. Obviously, if you have a window for which this isn’t true, you’ll need to modify this algorithm, but it ought to work for most cases.

At any rate, the routine figures out the optimum size for the window by getting the current size of the window target view’s scroller and taking note of the difference between this size and the size of the window’s whole content area. It then adds this difference to the size returned by the window target view’s CalcMinFrame() routine, which is by definition the smallest frame size that will hold the view’s contents.

Actually, that’s not always true. We’ve probably all had a programming situation where a view was smaller than its scroller (or window, or containing view, or whatever), but where we wanted to take mouse hits or draw selections and mouse-tracking feedback in the area outside the view. Often, the best way to do this is to enlarge to view so it is always at least as large as its containing view. This, of course, messes up the zooming algorithm.

My solution to this is have CalcMinFrame() always return the smallest possible view size and do the adjustment in ComputeFrame() instead.


/* 8 */
pascal void TBetterView :: ComputeFrame(
 VRect& newFrame)
 {
 VPoint superViewSize;
 SizeDeterminer  saveHSizeDet, saveVSizeDet;
 BooleantryAgain = false;
 
 inherited::ComputeFrame(newFrame);
 
// the inherited is sufficient if we're printing or if
// by some weird twist of fate we don't have a
// superview
 if (!fSuperView || gPrinting)
 return;
 
// otherwise, save off our size determiners (we're
// going to change them)
 saveHSizeDet = fSizeDeterminer[hSel];
 saveVSizeDet = fSizeDeterminer[vSel];
 superViewSize = fSuperView->fSize;
 
// if this view is smaller than its superview in the x
// direction, change the size determiner to
// sizeSuperView so we make ourselves the same size
 if (fSizeDeterminer[hSel] == sizeVariable &&
 superViewSize[hSel] > newFrame.
 GetLength(hSel)) {
 fSizeDeterminer[hSel] = sizeSuperView;
 tryAgain = true;
 }
 
// if this view is smaller than its superview in the y
// direction, change the size determiner to 
// sizeSuperView so we make ourselves the same size
 if (fSizeDeterminer[vSel] == sizeVariable &&
 superViewSize[vSel] > newFrame.
 GetLength(vSel)) {
 fSizeDeterminer[vSel] = sizeSuperView;
 tryAgain = true;
 }
 
// if we changed either size determiner, call inherited
// again to make this view at least as big as its
// superview in each direction, and restore the
// real size determiners
 if (tryAgain) {
 inherited :: ComputeFrame(newFrame);
 fSizeDeterminer[hSel] = saveHSizeDet;
 fSizeDeterminer[vSel] = saveVSizeDet;
 }
 }

Of course, you can’t override ComputeFrame() from a behavior either, so it’s probably best to put this override of ComputeFrame() (possibly controlled by a flag so you don’t always have to use it) in a subclass of TView that all your view classes descend from.

Up to now, GetStandardStateFrame() has just figured the optimum size for the window. If the size will fit on the monitor without moving the window, we’re done and we return. If not, we move the window to the upper left-hand corner of the monitor. If it’ll fit there, we’re done. Otherwise, we shrink the window in each direction until it fits. This way, the window will fill the whole monitor only when all that space is actually filled with information.

We’re not done yet. There’s one more wrinkle that must be taken into account. Now that the size of the window’s standard state is based on the size of the information in the window, the size of the standard state will change when the size of the window’s content changes. Since the size of the window can’t change when the size of the standard state does, the window must be considered to have transitioned to the user state when its content changes size, just as it does when the user manually resizes the window.

To see what I’m talking about, run the demo program. Open a window. The window is in its user state. Now click on its zoom box. The window is now in its standard state. Now change the size of the window’s content. I have fixed it so that hitting U and D make the window’s content larger and smaller, respectively (creative, huh?) so you can see this. Make the content bigger by hitting U a couple times. Now if you click on the zoom box again, you don’t want to go back to the last user state (the window’s original frame size). You want to go to the new standard state size (the current size of the content). If you click the zoom box, you’ll see that this is what happens. Now hit D four times to make the view smaller than it originally was. Again, clicking the zoom box should make the window shrink to the current size of the view, not go back to the size it was before you last clicked the zoom box. Again, if you click it, you’ll see this is what is does.

To accomplish this, I had to override TWindow::Zoom() as well as TWindow:: GetStandardStateFrame(). This method checks anytime we’re in the standard state to make sure the standard state size is still valid:


/* 9 */
pascal void TBetterWindow :: Zoom(shortpartCode)
 {
 VRect  curFrame;
 VRect  newStdStateFrame;
 
 if (partCode == inZoomOut)
 inherited :: Zoom(partCode);
 else {
 this->GetFrame(curFrame);
 this->GetStandardStateFrame(curFrame,
 newStdStateFrame);
// ("curFrame" will be ignored)
 if (curFrame == newStdStateFrame)
 inherited :: Zoom(partCode);
 else {
 if (fProcID & zoomDocProc)
 (*((WStateDataHandle)(((WindowPeek)fWMgrWindow)
 ->dataHandle)))-> userState =
 curFrame.ToRect();
 inherited :: Zoom(inZoomOut);
 }
 }
 }

If the window is in the user state (partCode == inZoomOut), we can just go ahead and zoom. If the window is in the standard state, we check (by calling GetStandardStateFrame()) to see if the standard state has changed size (because the window target view has changed size and the change affects the window size [going from two feet high to three feet high won’t resize the window because two feet is already bigger than the monitor]). If it hasn’t, we can call the inherited routine with no further ado. Otherwise, we have to manually transition ourselves to the user state (saving off the window’s current size in the WindowRecord’s auxiliary data block) and then call the inherited routine as if we had been in the user state all along.

Saving Window Positions

I didn’t actually include any window-position-saving code in this article. This is because the act of saving a window position is so application-specific. The way we did it in the application I’ve been working on doesn’t really apply to anybody else’s application, and uses proprietary code. But TBetterWindow does include hooks for it.

The basic idea here is that we add two flags to TBetterWindow: fInDefaultPosition and fSavePosition. fInDefaultPosition starts out true when a window is first opened (if its position hasn’t been restored from disk). You override MoveByUser(), ResizeByUser(), and ZoomByUser() to set it false so that anytime the user manually moves or resizes the window, you know you have to resize it. fSavePosition starts out false, and is set to true by some outside routine (through the MarkPositionAsDirty() routine) anytime something else has happened that would cause you to save the window position (in case you’re saving other stuff, such as sort order or selection position, with the window position).

Now you have a routine called SavePosition(), which is called by an override of TWindow::Close(). It checks these flags and if fInDefaultPosition is false or fSavePosition is true, it saves the window’s position. SimpleStagger() now calls a routine called PositionIsSaved() before doing its normal staggering. If PositionIsSaved() returns true, the window’s position has been previously saved, and we want to use the saved position rather than deriving a new position algorithmically. So we call RestorePosition(), which restores the window’s position, and return without doing anything. The expression of all this in C++ code is included in the source code to the demo program.

#include stddisclaimers.h

As I mentioned at the top of this article, TBetterWindow aims to fix deficiencies in the MacApp 3.0.1 window-handling code. I haven’t yet made the switch to MacApp 3.1, so I can’t say whether these problems are still there. I suspect they are. I hope, if anyone on the MacApp team is listening, that you adopt something like these solutions in MacApp 3.5.

Meanwhile, I hope someone out there finds these techniques useful. They made a big difference in our application. As always, I welcome your questions, comments, and potshots.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Hopper Disassembler 4.2.5- - Binary disa...
Hopper Disassembler is a binary disassembler, decompiler, and debugger for 32-bit and 64-bit executables. It will let you disassemble any binary you want, and provide you all the information about... Read more
BetterTouchTool 2.261 - Customize Multi-...
BetterTouchTool adds many new, fully customizable gestures to the Magic Mouse, Multi-Touch MacBook trackpad, and Magic Trackpad. These gestures are customizable: Magic Mouse: Pinch in / out (zoom... Read more
Sketch 44.1 - Design app for UX/UI for i...
Sketch is an innovative and fresh look at vector drawing. Its intentionally minimalist design is based upon a drawing space of unlimited size and layers, free of palettes, panels, menus, windows, and... Read more
BetterTouchTool 2.260 - Customize Multi-...
BetterTouchTool adds many new, fully customizable gestures to the Magic Mouse, Multi-Touch MacBook trackpad, and Magic Trackpad. These gestures are customizable: Magic Mouse: Pinch in / out (zoom... Read more
Chromium 59.0.3071.115 - Fast and stable...
Chromium is an open-source browser project that aims to build a safer, faster, and more stable way for all Internet users to experience the web. Version 59.0.3071.115: This update has no Flash plug... Read more
SyncTwoFolders 2.2.3 - Syncs two user-sp...
SyncTwoFolders simply synchronizes two folders. It supports synchronization across mounted network drives and it is a possibility to run a simulation showing in a log what will be done. Please visit... Read more
Myriad 4.1 - $79.00
Myriad is, simply put, one of the best audio batch processors. Totally redesigned, it looks beautiful and delivers incredible performance. Let Myriad do the heavy lifting while you get back to doing... Read more
Suitcase Fusion 7 18.2.4 - Font manageme...
Suitcase Fusion 7 is the creative professional's font manager. Every professional font manager should deliver the basics: spectacular previews, powerful search tools, and efficient font organization... Read more
SoftRAID 5.6.1 - High-quality RAID manag...
SoftRAID allows you to create and manage disk arrays to increase performance and reliability. SoftRAID allows the user to create and manage RAID 4 and 5 volumes, RAID 1+0, and RAID 1 (Mirror) and... Read more
BetterTouchTool 2.25 - Customize Multi-T...
BetterTouchTool adds many new, fully customizable gestures to the Magic Mouse, Multi-Touch MacBook trackpad, and Magic Trackpad. These gestures are customizable: Magic Mouse: Pinch in / out (zoom... Read more

Latest Forum Discussions

See All

Ravenscroft 275 Piano (Music)
Ravenscroft 275 Piano 1.0.0 Device: iOS Universal Category: Music Price: $35.99, Version: 1.0.0 (iTunes) Description: Experience the splendor of a Ravenscroft Grand with the most realistic sounding piano ever created for iOS. Launch... | Read more »
This War of Mine gets a new ending and m...
This War of Mine just got a big new update, featuring free DLC that adds a new ending to the game, among other exciting changes. The update is celebrating the game's two-year release anniversary. Apart from the new ending, which will be quite... | Read more »
Summon eight new heroes in Fire Emblem H...
Nintendo keeps coming at us with Fire Emblem Heroes updates, and it doesn't look like that trend is stopping anytime soon. The folks behind the game have just announced the new War of the Clerics Voting Gauntlet, expected to start next Tuesday. [... | Read more »
The best deals on the App Store this wee...
iOS publishers are pulling out all the stops this week -- there's a huge number of seriously great games at discounted prices this week. Let's not waste any time and get right down to business. [Read more] | Read more »
The House of da Vinci (Games)
The House of da Vinci 1.0.0 Device: iOS Universal Category: Games Price: $4.99, Version: 1.0.0 (iTunes) Description: Enter The House of Da Vinci, a new must-try 3D puzzle adventure game. Solve mechanical puzzles, discover hidden... | Read more »
Solve the disappearance of history’s gre...
Blue Brain Games invites you to indulge in an immersive hands-on 3D puzzle adventure in similar vein to The Room series, with its debut release The House of Da Vinci. Set during the historic period of the Italian Renaissance (when Leonardo himself... | Read more »
Age of Rivals (Games)
Age of Rivals 3.3 Device: iOS Universal Category: Games Price: $.99, Version: 3.3 (iTunes) Description: Deep civilization-building strategy in a fast-paced card game! | Read more »
Panthera Frontier (Games)
Panthera Frontier 1.0 Device: iOS Universal Category: Games Price: $4.99, Version: 1.0 (iTunes) Description: | Read more »
Angry Birds Evolution beginner's gu...
Angry Birds changes things up a fair bit in its latest iteration, Angry Birds Evolution. The familiar sling-shot physics mechanics are still there, but the game now features team-based gameplay, RPG elements, and a new top-down view. With all of... | Read more »
Sega Forever is for the retro game fans
Sega is launching a new retro games service titled Sega Forever, in a move that's sure to delight games enthusiasts with a bit of nostalgia. Sega's releasing five classic games for free. The titles include Sonic the Hedgehog, Phantasy Star II,... | Read more »

Price Scanner via MacPrices.net

Apple Releases iOS 11 Public Beta; How To Get...
The official release of Apple’s latest mobile operating system iOS 11 is vaguely slated for the fall, but as of June 26, ordinary users can download an iOS 11 public beta. To download the iOS 11... Read more
Extend Life of MacBook Pro Retina 2.0TB With...
MacSales.com/Other World Computing has announced availability of the new OWC 2.0TB Aura Pro Solid State Drive for mid-2012 to early 2013 Apple MacBook Pro with Retina display. One of the fastest... Read more
BBEdit SummerFest 2017 Discount Ends Friday,...
You can get 20% off BBEdit for a limited time in Bare Bones Software’s http://www.eastgate.com/Tinderbox/Specials/SummerFest.html?mc_cid=f2101ca260&mc_eid=[UNIQID]SummerFest 2017 sale and... Read more
Use Apple’s Education discount to save up to...
Purchase a new Mac using Apple’s Education discount, and take up to $300 off MSRP. All teachers, students, and staff of any educational institution qualify for the discount. Shipping is free: - 15″ 2... Read more
Clearance 27-inch 3.3GHz 5K iMac available fo...
Amazon clearance 27″ 3.3GHz 5K iMacs (MK482LL/A) available for $1799.90 including free shipping. Their price is $500 off original MSRP, and it’s the lowest price available for this model from any... Read more
13-inch 1.8GHz/256GB MacBook Air on sale for...
B&H Photo has the updated 2017 13″ 1.8GHz/256GB MacBook Air (MQD42LL/A) in stock and on sale for $1129 including free shipping plus NY & NJ tax only. Their price is $70 off MSRP. Read more
27-inch 3.4GHz iMac on sale for $1699, save $...
B&H Photo has the new 2017 27″ 3.4GHz iMac (MNE92LL/A) in stock and on sale for $1699 including free shipping plus NY & NJ sales tax only. Their price is $100 off MSRP. Read more
21-inch 2.3GHz iMac on sale for $1049, save $...
B&H Photo has the new 2017 21″ 2.3GHz iMac (MMQA2LL/A) in stock and on sale for $1049 including free shipping plus NY & NJ tax only. Their price is $50 off MSRP. Read more
ABBYY TextGrabber 6 for iOS Implements Instan...
ABBYY has announced the release of TextGrabber 6.0.0, an important feature update to the company’s productivity app developed for iOS and Android devices. TextGrabber 6.0 now offers Real-Time... Read more
vPhone, First Smartphone That Can’t Be Lost,...
Austin, Texas based Hypori has introduced the vPhone, a virtual smartphone that affords every business user the benefits of separate work and personal phones, conveniently delivered on a single... Read more

Jobs Board

*Apple* News Product Marketing Mgr., Publish...
…organizational consensus on strategy and vision for publisher tools, authoring, and Apple News Format.Carries this strategy and vision across the organization to Read more
*Apple* Retail - Multiple Positions - Apple,...
Job Description: Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
Security Data Analyst - *Apple* Information...
…data sources need to be collected to allow Information Security to better protect Apple employees and customers from a wide range of threats.Act as the subject matter Read more
Lead *Apple* Solutions Consultant - Apple I...
…integrity, and trust.Success Metrics/Key Performance Indicators:Quantitative* Year over Year growth in Apple Product and Beyond the Box sales in the assigned Point of Read more
*Apple* Solutions Consultant till v%u00E5r...
…ethics, integrity, and trust.Success Metrics/Key Performance Indicators:QuantitativeYear over Year growth in Apple Product and Beyond the Box sales in the assigned Point Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.