TweetFollow Us on Twitter

MDEFS, Part 2
Volume Number:10
Issue Number:7
Column Tag:Getting Started

Related Info: Menu Manager Custom Menus

MDEFS, Part 2

Custom menus give you more control over behavior and appearance

By Dave Mark, MacTech Magazine Regular Contributing Author

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

Last month’s column presented two separate programs. The first implemented a menu definition procedure (a.k.a. MDEF), and the second was an application whose sole purpose was to take the MDEF for a test drive. In this month’s column, we’ll start off with a little bit of background on menu definition procedures, followed by a walk-through of both programs.

The Menu Definition Procedure

A normal Macintosh application features a menu bar made up of individual menus. At initialization time you’ll set up the menu bar (perhaps basing it on an MBAR resource). If a command-key keyDown or autoKey occurs, you’ll pass the character code to the Menu Manager function MenuKey(). MenuKey() maps the character code into its corresponding menu and item, returning the two-byte menu and item codes packed into a four-byte long.

When you detect a mouseDown in the menu bar, you’ll call the Menu Manager function MenuSelect(). MenuSelect() tracks the movement of the mouse until the mouse button is released. As the mouse passes over each menu title in the menu bar, MenuSelect() calls the menu definition procedure specified by that menu.

A menu definition procedure (more commonly known by its resource name, MDEF) is a chunk of code that brings a menu to life. An MDEF is designed to respond to different requests from the Menu Manager. The first request asks the MDEF to calculate the bounding rectangle for the menu. This is usually based on the width of the menu’s largest item and the number of items in the menu. Once it has the menu’s bounding rectangle, the Menu Manager can figure out exactly what pixels will be covered by the menu once it is drawn. Once it knows this, the Menu Manager copies those pixels to another location in memory so it can blast them back in as soon as the menu disappears.

Once it knows the menu’s bounding rectangle, the Menu Manager makes a second request, asking the MDEF to draw the menu inside the bounding rectangle. The MDEF mechanism gives your application incredible flexibility. If you like, you can make your menu look exactly like the standard Macintosh menu, drawing each item using 12-point Chicago and including the appropriate appointments, like command-key equivalents, check boxes and the like, and even an icon or two.

On the other hand, you can construct a menu that goes where no menu has gone before. Your menu can be made up of pictures, icons, text, anything you like. You can draw in black and white, gray-scale, or even living color. If you can draw it in a window, you can draw it in your menu.

Once the menu is drawn, the Menu Manager makes a third request, passing a mouse location to the MDEF. The MDEF uses the mouse location to determine which item should be highlighted. The MDEF unhighlights any previously highlighted item, highlights the new item, and returns the number of the newly highlighted item.

The flexibility of the MDEF lets you create all kinds of interesting menus. For example, you might create a menu with items in multiple columns. Figure 1 shows a menu that supports color and keeps its items in multiple rows and columns.

Figure 1 This MDEF lets you select from a
two-dimensional palette of colors.

Writing an MDEF

Writing a menu definition procedure is not quite the same as writing an application. First off, you’ll need to tell the compiler that you want to create a code resource. With THINK C, you’ll do this in the Set Project Type... dialog (Figure 2). Clicking the Code Resource radio button tells THINK C you want to create a code resource instead of an application. Since you are creating a resource and not an application file, you can ask THINK C to save the resource in a resource file. Setting the File Type to rsrc and the Creator to RSED will launch ResEdit when you double-click on the output file. Use a File Type of RSRC and a Creator of Doug if you want to launch Resorcerer when you double-click on the output file.

Figure 2 The Set Project Type... dialog,
showing settings for an MDEF.

The Type, Name, and ID fields tell THINK C what resource type to create, and what name and ID to give the new resource. In our case, we want to create an ‘MDEF’ resource with a name of “PICT” and an ID of 128. When you select Build Code Resource... from THINK C’s Project menu, once your code compiles you’ll be prompted for the name of a resource file in which to save your MDEF. This dialog box will feature a Merge checkbox, that let’s you add the resource to an existing resource file instead of creating a new one. To do this, type the name of an existing file in the file name field.

Now that your project is set up, let’s talk about the source code that actually implements the MDEF. Your source code will have a single entry point that follows this function prototype:


/* 1 */
pascal void MenuDef( short message, MenuHandle menu,
 Rect menuRectPtr, Point hitPt, short itemPtr );

The pascal keyword tells you that this code will be called by a Toolbox routine. The first parameter tells you what the Menu Manager wants you to do during this call. The four possible messages are:

• mSizeMsg - Calculate the menu’s width and height.

• mDrawMsg - Draw the menu.

• mChooseMsg - Figure out which item was selected, deselect any highlighted items if necessary, and highlight the appropriate item.

• mPopUpMsg - Calculate the bounding rectangle in the case where the MDEF is used to implement a popup menu.

The second parameter is a MenuHandle, a handle to the menu which specified this MDEF in the first place. When message is either mDrawMsg or mChooseMsg, the third parameter specifies the menu’s bounding rectangle in global coordinates. It’s your job to make sure that the menu fits on the screen. If their are too many items for the menu to fit, you should create a scrolling menu with up (s) and down (t) scroll indicators as needed.

When the message is mPopUpMsg, the Menu Manager is asking the MDEF to calculate the rectangle, in global coordinates, in which the menu should appear. Typically, you’ll use the fourth parameter, hitPt, to determine the position of the menu. If appropriate, place the menu so that the default (or last selected) item is at hitPt, so that the item is selected as soon as the menu appears. Of course, if this puts part of the menu off the screen, you’ll have to make allowances.

By the way, code resources (such as the MDEF, LDEF, and WDEF) can’t have global variables. Fortunately, we can get by without them.

When the message is mSizeMsg, the Menu Manager is asking the MDEF to calculate the menu’s height and width and place those values in the MenuRecord’s menuHeight and menuWidth fields.

As already mentioned, the fourth parameter contains the mouse position, in global coordinates, for use when the message is either mChooseMsg or mPopUpMsg. On mChooseMsg, you’ll use hitPt to figure out which item should be selected. If the mouse was in a scroll indicator, you should scroll the menu one item in the appropriate direction (you’ll probably want to take advantage of ScrollRect()). On mPopUpMsg, hitPt contains the upper-left corner of the popup label.

The last parameter, itemPtr, points to a short containing the last item selected, or 0 if no item was selected yet. You’ll use this information when message is mChooseMsg to determine whether an item is currently highlighted and, if the highlighted item isn’t the same as the newly selected item, to unhighlight the old item.

Creating an MDEF for the PowerPC requires a little bit of extra work (and goes beyond the scope of this article). To learn how to deal with executable resources on a PowerPC, you’ll need to read Inside Macintosh: PowerPC System Software. You’ll want to focus on the Mixed Mode Manager and the Code Fragment Manager.

Walking Through MDEF.c

OK, here’s the MDEF source code. As you’ve already seen, this MDEF creates a menu built completely out of PICT resources (Figure 3). It doesn’t handle scrolling and it doesn’t handle the mPopUpMsg message. Your mission, should you choose to accept it, is to add them yourself.

kTopMargin is the number of pixels of blank space placed on top and below each PICT in the menu. kLeftMargin is the number of pixels placed to the left and right of each PICT.


/* 2 */
#define kTopMargin 1
#define kLeftMargin2

Here’s the function prototypes:


/* 3 */
void  DoSizeMessage( MenuHandle menu, Rect *menuRectPtr );
void  DoDrawMessage( MenuHandle menu, Rect *menuRectPtr );
void  DoChooseMessage( MenuHandle menu, Rect *menuRectPtr,
 Point hitPt, short *whichItemPtr );
void  InvertItem( short itemNumber, short itemHeight, Rect *menuRectPtr 
);
void  DrawCenteredPict( PicHandle pic, Rect *rectPtr );
void  CalcitemHeightAndWidth( short basePICTid, short numPICTs,
 short *widthPtr, short *heightPtr );
void  GetNumPICTs( MenuHandle menu, short *baseIDPtr,
 short *numPICTsPtr );

main() is declared according to the function prototype described at the beginning of the column.


/* 4 */
main
pascal void main( short message,
 MenuHandle menu,
 Rect *menuRectPtr,
 Point hitPt,
 short *whichItemPtr )
{

Each of the messages (remember, we don’t handle mPopUpMsg) is handled by its own routine.


/* 5 */
 switch( message )
 {
 case mDrawMsg:
 DoDrawMessage( menu, menuRectPtr );
 break;
 case mChooseMsg:
 DoChooseMessage( menu, menuRectPtr, hitPt, whichItemPtr );
 break;
 case mSizeMsg:
 DoSizeMessage( menu, menuRectPtr );
 break;
 }
}

DoSizeMessage() starts with a call to GetNumPICTs(). GetNumPICTs() looks for a ‘long’ resource with the same resource ID as the current menu. The ‘long’ resource is not a standard Mac resource. It consists of 2 sets of 2 bytes. The first 2 bytes of the resource specify the resource ID of the first PICT to display in this menu. The second 2 bytes tell you how many PICT’s to display. If the base resource ID is 128 and the number of PICT’s is 3, the menu will display PICT resources 128, 129, and 130.


/* 6 */
DoSizeMessage
void  DoSizeMessage( MenuHandle menu,
 Rect *menuRectPtr )
{
 short  basePICTid, numPICTs, maxPICTWidth, maxPICTHeight;
 
 GetNumPICTs( menu, &basePICTid, &numPICTs );

We could have designed a menu where each item was a different height, but that wouldn’t have looked that great. Instead, this MDEF uses the width of the widest picture as the width of each item and the height of the tallest picture as the height of each item, with the kLeftMargin and kTopMargin factored in.


/* 7 */
 CalcitemHeightAndWidth( basePICTid, numPICTs, &maxPICTWidth, &maxPICTHeight 
);

The item width and height are then used to calculate the menu’s height and width, and those values are placed in the menuWidth and menuHeight fields of the menu.


/* 8 */
 (**menu).menuWidth = maxPICTWidth + 2 * kLeftMargin;
 (**menu).menuHeight = (maxPICTHeight+kTopMargin*2)*numPICTs;
}

DoDrawMessage() also starts with calls to GetNumPICTs() and CalcitemHeightAndWidth().


/* 9 */
DoDrawMessage
void  DoDrawMessage( MenuHandle menu,
 Rect *menuRectPtr )
{
 short  basePICTid, numPICTs, 
           maxPICTWidth, maxPICTHeight, 
           itemHeight, i;
 Rect   r, tempRect;
 PicHandlepic;
 
 GetNumPICTs( menu, &basePICTid, &numPICTs );
 CalcitemHeightAndWidth( basePICTid, numPICTs, 
                          &maxPICTWidth, &maxPICTHeight );

We’ll use maxPICTHeight to calculate the height in pixels of a single item. We’ll use this value in our call to OffsetRect() at the bottom of the routine.


/* 10 */
 itemHeight = maxPICTHeight + kTopMargin * 2;

The Rect r is set to border the first item, inset by the margins specified by kTopMargin and kLeftMargin.


/* 11 */
 r.top = menuRectPtr->top + kTopMargin;
 r.left = menuRectPtr->left + kLeftMargin;
 r.bottom = r.top + maxPICTHeight;
 r.right = r.left + maxPICTWidth;

In the loop, we’ll draw one picture, then move the Rect r down the height of one picture so we’ll be ready to draw the next one. We should probably check the value returned by GetPicture(), just in case we don’t have enough memory to load the PICT. We might want to draw a big X if GetPicture() failed. Hey, it’s better than crashing, right?


/* 12 */
 for ( i=0; i<numPICTs; i++ )
 {
 pic = GetPicture( basePICTid + i );
 
 DrawCenteredPict( pic, &r );
 
 OffsetRect( &r, 0, itemHeight );
 }
}

DoChooseMessage() is the most complex of all the message dispatching routines. As usual, it starts with calls to GetNumPICTs() and CalcitemHeightAndWidth().


/* 13 */
DoChooseMessage
void  DoChooseMessage( MenuHandle menu,
 Rect *menuRectPtr,
 Point hitPt,
 short *whichItemPtr )
{
 short  basePICTid, selectedItem, numPICTs, 
         maxPICTWidth, maxPICTHeight, itemHeight;
 Rect r;

 GetNumPICTs( menu, &basePICTid, &numPICTs );
 CalcitemHeightAndWidth( basePICTid, numPICTs, 
                          &maxPICTWidth, &maxPICTHeight );

Next, the height of a single item is calculated, based on the height of the tallest picture plus a top and bottom margin.


/* 14 */
 itemHeight = (2 * kTopMargin) + maxPICTHeight;

If the mouse is currently inside the menu we’ll first figure out which item we’re in.


/* 15 */
 if ( PtInRect( hitPt, menuRectPtr ) )
 {
 selectedItem = ((hitPt.v - menuRectPtr->top) / itemHeight) + 1;

If an item was already selected and its not the item we’re pointing at we have to deselect the old item.


/* 16 */
 if ( ( *whichItemPtr > 0 ) 
         && ( *whichItemPtr != selectedItem ) )
 {
 InvertItem( *whichItemPtr, itemHeight, menuRectPtr );
 }

If the last item selected is not the item we’re currently pointing at, we’ll tell the Menu Manager which item is selected, then select this item.


/* 17 */
 if ( *whichItemPtr != selectedItem )
 {
 *whichItemPtr = selectedItem;
 InvertItem( *whichItemPtr, itemHeight, menuRectPtr );
 }
 }

If the mouse was not in the menu’s rectangle, and if an item is currently selected, we’ll deselect the item and tell the Menu Manager that there is no longer an item selected.


/* 18 */
 else if ( *whichItemPtr > 0 )
 {
 InvertItem( *whichItemPtr, itemHeight, menuRectPtr );
 *whichItemPtr = 0;
 }
}

InvertItem() creates a Rect surrounding the item in question, then calls InvertRect() to invert the item. If your items will be drawn in color, you might want to use a different strategy for highlighting an item. You can use the highlight color (see the Color Quickdraw routine HiliteColor()) or perhaps a 2 pixel or more border around the currently selected item.


/* 19 */
InvertItem
void  InvertItem( short itemNumber,
 short itemHeight,
 Rect *menuRectPtr )
{
 Rect r;
 
 r = *menuRectPtr;
 
 r.top += ( (itemNumber-1) * itemHeight );
 r.bottom = r.top + itemHeight;
 
 InvertRect( &r );
}

DrawCenteredPict() centers and draws the specified picture in the Rect pointed to by rectPtr.


/* 20 */
DrawCenteredPict
void  DrawCenteredPict( PicHandle pic,
 Rect *rectPtr )
{
 Rect pictRect;
 
 pictRect = (**pic).picFrame;
 
 OffsetRect( &pictRect, rectPtr->left - pictRect.left,
    rectPtr->top  - pictRect.top);
 OffsetRect( &pictRect,(rectPtr->right - pictRect.right)/2,
   (rectPtr->bottom - pictRect.bottom)/2);
 
 DrawPicture( pic, &pictRect );
}

CalcitemHeightAndWidth() steps through each of the menu’s pictures, tracking values for the widest and tallest pictures.


/* 21 */
CalcitemHeightAndWidth
void  CalcitemHeightAndWidth( short basePICTid,
 short numPICTs,
 short *widthPtr,
 short *heightPtr )
{
 short  i;
 Rect   r;
 PicHandlepic;
 
 *widthPtr = 0;
 *heightPtr = 0;

 for ( i=0; i<numPICTs; i++ )
 {
 pic = GetPicture( basePICTid + i );
 r = (**pic).picFrame;
 
 if ( r.bottom - r.top > *heightPtr )
 *heightPtr = r.bottom - r.top;
 
 if ( r.right - r.left > *widthPtr )
 *widthPtr = r.right - r.left;
 }
}

GetNumPICTs() starts by retrieving the menu ID from the current menu. This ID is used as a resource ID to retrieve the appropriate ‘long’ resource.


/* 22 */
GetNumPICTs
void  GetNumPICTs( MenuHandle menu,
 short *baseIDPtr,
 short *numPICTsPtr )
{
 Handle longHandle;
 long retrievedLong;
 short  menuID;
 
 menuID = (**menu).menuID;
 
 longHandle = GetResource( 'long', menuID );

This 4 byte value is then broken into two shorts, one holding the base 
PICT resource ID, and the other the number of PICT’s in the menu.

 retrievedLong = (*((long *)(*longHandle)));
 
 *baseIDPtr = HiWord( retrievedLong );
 *numPICTsPtr = LoWord( retrievedLong );
}

Walking Through Tester.c

There’s nothing magical about Tester. It implements a standard menu bar with three menus. What is unusual is that one of the menus uses the MDEF created by MDEF.c instead of the standard Apple MDEF. The standard Apple MDEF has a resource ID of 0. Our MDEF has a resource ID of 128. When you create a menu that uses a custom MDEF, be sure to change the MDEF id in whatever resource editor you use.


/* 23 */
#define kWindowResID 128
#define kMBARResID 128
#define kNULLStorage 0L
#define kMoveToFront (WindowPtr)-1L
#define kSleep   60L

#define mApple   128
#define iAbout   1

#define mFile    129
#define iQuit    1

#define mPICT    131

Tester uses two globals. gDone does its usual job and gCurPICTid keeps track of the current PICT.


/* 24 */
Globals 

Boolean gDone;
short   gCurPICTid;

Here are all the function prototypes...


/* 25 */
 Functions 
void  ToolboxInit( void );
void  MenuBarInit( void );
void  WindowInit( void );
void  EventLoop( void );
void  DoEvent( EventRecord *eventPtr );
void  HandleMouseDown( EventRecord *eventPtr );
void  HandleMenuChoice( long menuChoice );
void  HandleAppleChoice( short item );
void  HandleFileChoice( short item );
void  HandlePICTChoice( short item );
void  DoUpdate( WindowPtr window );
void  DrawPictInWindow( PicHandle pic, WindowPtr window );
short GetBasePICTid( short menuID );

main() initializes the Toolbox, sets up the menu bar, then creates an empty window. Next, gCurPICTid is set to the first one in the sequence. The picture will be drawn in the window when an updateEvt is processed for the window. Finally, the main event loop is entered.


/* 26 */
main
void  main( void )
{
 ToolboxInit();
 MenuBarInit();
 WindowInit();
 
 gCurPICTid = GetBasePICTid( mPICT );
 
 EventLoop();
}

ToolboxInit() and MenuBarInit() are the same as they ever were.


/* 27 */
ToolboxInit
void  ToolboxInit( void )
{
 InitGraf( &thePort );
 InitFonts();
 InitWindows();
 InitMenus();
 TEInit();
 InitDialogs( NULL );
 InitCursor();
}


MenuBarInit
void  MenuBarInit( void )
{
 Handle menuBar;
 MenuHandle menu;
 
 menuBar = GetNewMBar( kMBARResID );
 SetMenuBar( menuBar );

 menu = GetMHandle( mApple );
 AddResMenu( menu, 'DRVR' );
 
 DrawMenuBar();
}

WindowInit() retrieves a ‘WIND’ resource. If there’s a problem, we’ll just beep and exit.


/* 28 */
WindowInit
void  WindowInit( void )
{
 WindowPtrwindow;
 
 window = GetNewWindow( kWindowResID, kNULLStorage,
  kMoveToFront );
 
 if ( window == NULL )
 {
 SysBeep( 20 );  /* Couldn't load WIND */
 ExitToShell();
 }
 
 SetPort( window );
 ShowWindow( window );
}

Nothing unusual about EventLoop(), DoEvent(), or HandleMouseDown().


/* 29 */
EventLoop
void  EventLoop( void )
{
 EventRecordevent;
 
 gDone = false;
 while ( gDone == false )
 {
 if ( WaitNextEvent( everyEvent, &event, kSleep, nil ) )
 DoEvent( &event );
 }
}



DoEvent
void  DoEvent( EventRecord *eventPtr )
{
 char theChar;
 
 switch ( eventPtr->what )
 {
 case mouseDown: 
 HandleMouseDown( eventPtr );
 break;
 case keyDown:
 case autoKey:
 theChar = eventPtr->message & charCodeMask;
 if ( (eventPtr->modifiers & cmdKey) != 0 ) 
 HandleMenuChoice( MenuKey( theChar ) );
 break;
 case updateEvt:
 DoUpdate( (WindowPtr)eventPtr->message );
 break;
 }
}


HandleMouseDown

void  HandleMouseDown( EventRecord *eventPtr )
{
 WindowPtrwindow;
 short  thePart;
 long   menuChoice;
 
 thePart = FindWindow( eventPtr->where, &window );
 
 switch ( thePart )
 {
 case inMenuBar:
 menuChoice = MenuSelect( eventPtr->where );
 HandleMenuChoice( menuChoice );
 break;
 case inSysWindow: 
 SystemClick( eventPtr, window );
 break;
 case inDrag : 
 DragWindow( window, eventPtr->where,
  &(screenBits.bounds) );
 break;
 }
}

As usual, HandleMenuChoice() dispatches a menu selection. Notice that a selection from the Picture menu is dispatched just like any other menu selection. You can make changes to your MDEF without affecting the Tester code.


/* 30 */
HandleMenuChoice
void  HandleMenuChoice( long menuChoice )
{
 short  menu;
 short  item;
 
 if ( menuChoice != 0 )
 {
 menu = HiWord( menuChoice );
 item = LoWord( menuChoice );
 
 switch ( menu )
 {
 case mApple:
 HandleAppleChoice( item );
 break;
 case mFile:
 HandleFileChoice( item );
 break;
 case mPICT:
 HandlePICTChoice( item );
 break;
 }
 HiliteMenu( 0 );
 }
}

HandleAppleChoice() and HandleFileChoice() are remarkable in their lack of excitement.


/* 31 */
HandleAppleChoice
void  HandleAppleChoice( short item )
{
 MenuHandle appleMenu;
 Str255 accName;
 short  accNumber;
 
 switch ( item )
 {
 case iAbout:
 SysBeep( 20 );
 break;
 default:
 appleMenu = GetMHandle( mApple );
 GetItem( appleMenu, item, accName );
 accNumber = OpenDeskAcc( accName );
 break;
 }
}


HandleFileChoice
void  HandleFileChoice( short item )
{
 switch ( item )
 {
 case iQuit :
 gDone = true;
 break;
 }
}

HandlePICTChoice() erases the window and then calls InvalRect() to force an updateEvt. Finally, gCurPICTid is updated, based on the item selected.


/* 32 */
HandlePICTChoice
void  HandlePICTChoice( short item )
{
 WindowPtrwindow;
 
 window = FrontWindow();
 
 EraseRect( &window->portRect );
 InvalRect( &window->portRect );
 
 gCurPICTid = GetBasePICTid( mPICT ) + item - 1;
}

DoUpdate() starts the update process by calling BeginUpdate(), loads the current PICT, then calls DrawPictInWindow() before calling EndUpdate().


/* 33 */
DoUpdate
void  DoUpdate( WindowPtr window )
{
 PicHandlepic;
 
 BeginUpdate( window );
 
 pic = GetPicture( gCurPICTid );

 if ( pic == NULL )
 {
 SysBeep( 20 );  /* Couldn't load PICT */
 ExitToShell();
 }
 
 DrawPictInWindow( pic, FrontWindow() );
 
 EndUpdate( window );
}

DrawPictInWindow() centers the picture in the window, then draws it.


/* 34 */
DrawPictInWindow
void  DrawPictInWindow( PicHandle pic, WindowPtr window )
{
 Rect   pictRect, windRect;
 
 pictRect = (**pic).picFrame;
 
 windRect = window->portRect;
 
 OffsetRect( &pictRect, windRect.left - pictRect.left,
    windRect.top  - pictRect.top);
 OffsetRect( &pictRect,(windRect.right - pictRect.right)/2,
   (windRect.bottom - pictRect.bottom)/2);
 
 DrawPicture( pic, &pictRect );
}

GetBasePICTid() loads the appropriate ‘long’ resource, returning the first two bytes of the four bytes retrieved.


/* 35 */
GetBasePICTid
short GetBasePICTid( short menuID )
{
 Handle longHandle;
 long retrievedLong;
 
 longHandle = GetResource( 'long', menuID ); 
 retrievedLong = (*((long *)(*longHandle)));
 
 return( HiWord( retrievedLong ) );
}

Till Next Month

This is an incredibly busy month for me. Between MacWorld in DC, Ultimate Mac Programming (still due in August), and the World-Wide Developer’s Conference, I’ve definitely got my hands full. Not to worry, though. With all that’s going on, I’ll definitely have a lot of great material to draw from for next month’s column. See you in a month...

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Ableton Live 11.3.11 - Record music usin...
Ableton Live lets you create and record music on your Mac. Use digital instruments, pre-recorded sounds, and sampled loops to arrange, produce, and perform your music like never before. Ableton Live... Read more
Affinity Photo 2.2.0 - Digital editing f...
Affinity Photo - redefines the boundaries for professional photo editing software for the Mac. With a meticulous focus on workflow it offers sophisticated tools for enhancing, editing and retouching... Read more
SpamSieve 3.0 - Robust spam filter for m...
SpamSieve is a robust spam filter for major email clients that uses powerful Bayesian spam filtering. SpamSieve understands what your spam looks like in order to block it all, but also learns what... Read more
WhatsApp 2.2338.12 - Desktop client for...
WhatsApp is the desktop client for WhatsApp Messenger, a cross-platform mobile messaging app which allows you to exchange messages without having to pay for SMS. WhatsApp Messenger is available for... Read more
Fantastical 3.8.2 - Create calendar even...
Fantastical is the Mac calendar you'll actually enjoy using. Creating an event with Fantastical is quick, easy, and fun: Open Fantastical with a single click or keystroke Type in your event details... Read more
iShowU Instant 1.4.14 - Full-featured sc...
iShowU Instant gives you real-time screen recording like you've never seen before! It is the fastest, most feature-filled real-time screen capture tool from shinywhitebox yet. All of the features you... Read more
Geekbench 6.2.0 - Measure processor and...
Geekbench provides a comprehensive set of benchmarks engineered to quickly and accurately measure processor and memory performance. Designed to make benchmarks easy to run and easy to understand,... Read more
Quicken 7.2.3 - Complete personal financ...
Quicken makes managing your money easier than ever. Whether paying bills, upgrading from Windows, enjoying more reliable downloads, or getting expert product help, Quicken's new and improved features... Read more
EtreCheckPro 6.8.2 - For troubleshooting...
EtreCheck is an app that displays the important details of your system configuration and allow you to copy that information to the Clipboard. It is meant to be used with Apple Support Communities to... Read more
iMazing 2.17.7 - Complete iOS device man...
iMazing is the world’s favourite iOS device manager for Mac and PC. Millions of users every year leverage its powerful capabilities to make the most of their personal or business iPhone and iPad.... Read more

Latest Forum Discussions

See All

Motorsport legends NASCAR announce an up...
NASCAR often gets a bad reputation outside of America, but there is a certain charm to it with its close side-by-side action and its focus on pure speed, but it never managed to really massively break out internationally. Now, there's a chance... | Read more »
Skullgirls Mobile Version 6.0 Update Rel...
I’ve been covering Marie’s upcoming release from Hidden Variable in Skullgirls Mobile (Free) for a while now across the announcement, gameplay | Read more »
Amanita Design Is Hosting a 20th Anniver...
Amanita Design is celebrating its 20th anniversary (wow I’m old!) with a massive discount across its catalogue on iOS, Android, and Steam for two weeks. The announcement mentions up to 85% off on the games, and it looks like the mobile games that... | Read more »
SwitchArcade Round-Up: ‘Operation Wolf R...
Hello gentle readers, and welcome to the SwitchArcade Round-Up for September 21st, 2023. I got back from the Tokyo Game Show at 8 PM, got to the office here at 9:30 PM, and it is presently 11:30 PM. I’ve done what I can today, and I hope you enjoy... | Read more »
Massive “Dark Rebirth” Update Launches f...
It’s been a couple of months since we last checked in on Diablo Immortal and in that time the game has been doing what it’s been doing since its release in June of last year: Bringing out new seasons with new content and features. | Read more »
‘Samba De Amigo Party-To-Go’ Apple Arcad...
SEGA recently released Samba de Amigo: Party-To-Go () on Apple Arcade and Samba de Amigo: Party Central on Nintendo Switch worldwide as the first new entries in the series in ages. | Read more »
The “Clan of the Eagle” DLC Now Availabl...
Following the last paid DLC and free updates for the game, Playdigious just released a new DLC pack for Northgard ($5.99) on mobile. Today’s new DLC is the “Clan of the Eagle" pack that is available on both iOS and Android for $2.99. | Read more »
Let fly the birds of war as a new Clan d...
Name the most Norse bird you can think of, then give it a twist because Playdigious is introducing not the Raven clan, mostly because they already exist, but the Clan of the Eagle in Northgard’s latest DLC. If you find gathering resources a... | Read more »
Out Now: ‘Ghost Detective’, ‘Thunder Ray...
Each and every day new mobile games are hitting the App Store, and so each week we put together a big old list of all the best new releases of the past seven days. Back in the day the App Store would showcase the same games for a week, and then... | Read more »
Urban Open-World RPG ‘Project Mugen’ Fro...
Last month, NetEase Games revealed a new free to play open world RPG tentatively titled Project Mugen for mobile, PC, and consoles. I’ve liked the setting and aesthetic since its first trailer, and today’s new video has the Game Designer and... | Read more »

Price Scanner via MacPrices.net

Apple AirPods 2 with USB-C now in stock and o...
Amazon has Apple’s 2023 AirPods Pro with USB-C now in stock and on sale for $199.99 including free shipping. Their price is $50 off MSRP, and it’s currently the lowest price available for new AirPods... Read more
New low prices: Apple’s 15″ M2 MacBook Airs w...
Amazon has 15″ MacBook Airs with M2 CPUs and 512GB of storage in stock and on sale for $1249 shipped. That’s $250 off Apple’s MSRP, and it’s the lowest price available for these M2-powered MacBook... Read more
New low price: Clearance 16″ Apple MacBook Pr...
B&H Photo has clearance 16″ M1 Max MacBook Pros, 10-core CPU/32-core GPU/1TB SSD/Space Gray or Silver, in stock today for $2399 including free 1-2 day delivery to most US addresses. Their price... Read more
Switch to Red Pocket Mobile and get a new iPh...
Red Pocket Mobile has new Apple iPhone 15 and 15 Pro models on sale for $300 off MSRP when you switch and open up a new line of service. Red Pocket Mobile is a nationwide service using all the major... Read more
Apple continues to offer a $350 discount on 2...
Apple has Studio Display models available in their Certified Refurbished store for up to $350 off MSRP. Each display comes with Apple’s one-year warranty, with new glass and a case, and ships free.... Read more
Apple’s 16-inch MacBook Pros with M2 Pro CPUs...
Amazon is offering a $250 discount on new Apple 16-inch M2 Pro MacBook Pros for a limited time. Their prices are currently the lowest available for these models from any Apple retailer: – 16″ MacBook... Read more
Closeout Sale: Apple Watch Ultra with Green A...
Adorama haș the Apple Watch Ultra with a Green Alpine Loop on clearance sale for $699 including free shipping. Their price is $100 off original MSRP, and it’s the lowest price we’ve seen for an Apple... Read more
Use this promo code at Verizon to take $150 o...
Verizon is offering a $150 discount on cellular-capable Apple Watch Series 9 and Ultra 2 models for a limited time. Use code WATCH150 at checkout to take advantage of this offer. The fine print: “Up... Read more
New low price: Apple’s 10th generation iPads...
B&H Photo has the 10th generation 64GB WiFi iPad (Blue and Silver colors) in stock and on sale for $379 for a limited time. B&H’s price is $70 off Apple’s MSRP, and it’s the lowest price... Read more
14″ M1 Pro MacBook Pros still available at Ap...
Apple continues to stock Certified Refurbished standard-configuration 14″ MacBook Pros with M1 Pro CPUs for as much as $570 off original MSRP, with models available starting at $1539. Each model... Read more

Jobs Board

Omnichannel Associate - *Apple* Blossom Mal...
Omnichannel Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Cashier - *Apple* Blossom Mall - JCPenney (...
Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Mall Read more
Operations Associate - *Apple* Blossom Mall...
Operations Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Retail Key Holder- *Apple* Blossom Mall - Ba...
Retail Key Holder- APPLE BLOSSOM MALL Brand: Bath & Body Works Location: Winchester, VA, US Location Type: On-site Job ID: 03YM1 Job Area: Store: Sales and Support Read more
Omnichannel Associate - *Apple* Blossom Mal...
Omnichannel Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.