TweetFollow Us on Twitter

July 01 PowerPlant Workshop

Volume Number: 17 (2001)
Issue Number: 07
Column Tag: PowerPlant Workshop

Basic Files

by Aaron Montgomery

How one goes about writing a PowerPlant application

These Articles

This is the fourth article in a series of articles about Metrowerks' PowerPlant application framework. The first article introduced how the framework deals with commands, the second article discussed the debugging facilities of the framework and the third introduced windows. This article focuses on the file classes in the framework (opening and saving files). This series assumes familiarity with the C++ language, the Macintosh Toolbox API and the CodeWarrior IDE. The articles were written using CodeWarrior 6 with the net-update to IDE 4.1.0.3 and no other modifications. Throughout this and future articles, I will assume that you are using the class browser and the debugger to explore the PowerPlant code more fully.

Setting Up Navigation Services

Before you can use Navigation Services, you need to do some setup. The first step is to modify the common prefix file to let PowerPlant know whether you want to always use the classic file dialogs, require Navigation Services, or use Navigation Services if it is available and the classic dialogs otherwise. This is done in the project's prefix file.

CommonPrefix.h
#if PP_Target_Carbon
   #define PP_StdDialogs_Option \
      PP_StdDialogs_NavServicesOnly
   #define   SetTryNavServices_            do { } while (false)
#else
   #define PP_StdDialogs_Option   \
         PP_StdDialogs_Conditional
   #define SetTryNavServices_             \
      PowerPlant::UConditionalDialogs::SetTryNavServices(\
         0x01108000)
#endif

For Carbon targets, the code sets the dialogs to be Navigation Services at all times and the SetTryNavServices_ macro does nothing. For the other targets, the code uses conditional dialogs. The conditional dialogs option will use Navigation Services if it is available and classic dialogs otherwise. The SetTryNavServices_ macro tells PowerPlant to use Navigation services only if it has version at least 1.1 (PowerPlant stationery explains that earlier versions can cause problems).

The macro PP_StdDialogs_Option affects the UStandardDialogs.h header by setting the PowerPlant::StandardDialogs namespace (abbreviated to PP_StandardDialogs in the code) to equal one of PowerPlant::UNavServicesDialogs, PowerPlant::UConditionalDialogs, or PowerPlant::UClassicDialogs. When using the class browser to identify classes and functions, you will often find that the same class or function will appear in each of these three namespaces and you will need to look at the appropriate one.

The other necessary change to the code for Navigation Services is the need to load them at application startup and to unload them at application shutdown. This is done in CDocumentApp's constructor and destructor. In the constructor, the code uses the macro SetTryNavServices_ to establish what version it requires and then calls the function PP_StandardDialogs::Load(). In the destructor, the code calls PP_StandardDialogs::Unload().

As a final touch, I have also added BNDL, open, and kind resources to AppResources.rsrc. This provides us with custom icons and the finder with information about what types of files we can open.

Dirty Documents

Before discussing the methods used to open and save files, we will discuss how PowerPlant determines if a file has been modified (in other words, if the document is dirty). The LDocument class has an IsModified() method which should return true if the file has been modified since last read from disk and false otherwise. This allows the framework to enable and disable the Save and Revert menu commands appropriately. In order to make the system work, we will need to use the SetModified() functions when the file has been saved to the disk and when it has been modified by the user.

The first change to the code adds an iAmModified data member to the CHTMLTextView class. The LTextEditView class from which it derives provides the virtual method UserChangedText() which will be called anytime the user types (or deletes) something from the LTextEditView.

UserChangedText() in CHTMLTextView.cp
void CHTMLTextView::UserChangedText()
{
   if (IsModified() == false)
   {
      SetUpdateCommandStatus(true);
      SetModified(true);
   }
}

The code only needs to do something if this is the first modification. In that case the code tells the menus that they will need to be updated and then sets the object's flag to indicate that it has been modified. If you directly manipulate the text in an LTextEditView, the UserChangedText() method will not be called automatically. In our case, the InsertTag() method needs to indicate that it has modified the document and it does this with a call to UserChangedText(). The IsModified() and SetModified() methods of CHTMLView are inline functions which access and set the object's flag.

In the CTextDocument class, we change the IsModified() method so that it checks its CHTMLTextView's IsModified() method before returning a result. In addition, we update the SetModified() method so that it also calls the CHTMLTextView's SetModified() method. Note that PowerPlant does some housekeeping in LDocument's SetModified() method and so we call it from within the new method definition. You could also just copy the code, but that would mean that you would need to update your code if more housekeeping was done within LDocument's method.

Opening Files

We are now ready to consider how PowerPlant opens and saves files. The code presented here is modeled on the code in the stationery files provided with PowerPlant. There is one limitation with the strategy employed there that may make it unsuitable for your application. The problem arises because of the need to call ::NavCompleteSave() if Navigation Services have been used to save the file. The code in the PowerPlant stationery handles this by holding the file open on the disk until the document is closed. This allows the CTextDocument object to save the information necessary for the call to ::NavCompleteSave() until the document is closed. The disadvantage is that the file cannot be manipulated by another application while it is held open by HTMLedit. If you need to allow external tools to modify files while your application holds them open, you will need to adjust the code to permit this.

The first change in the code is the removal of some lines that were added in the first article. Now that our application can open files, the lines in FindCommandStatus() which disabled the open command can be removed. There is no need to change ObeyCommand() since PowerPlant's LDocApplication class already has the necessary code. Two CDocumentApp methods are necessary for opening files. The first, ChooseDocument(), interacts with the user to determine which file should be opened and the second, OpenDocument(), opens the document. We start with ChooseDocument().

ChooseDocument in CDocumentApp.cp
void
CDocumentApp::ChooseDocument()
{

   LFileChooser   theChooser;
   
   NavDialogOptions*   theOptions =
                                 theChooser.GetDialogOptions();
   if (theOptions != 0)
   {
   theOptions->dialogOptionFlags |= kNavSelectAllReadableItem;
   }

   if (theChooser.AskOpenFile(LFileTypeList()))
   {
      AEDescList      theDocList;
      theChooser.GetFileDescList(theDocList);
      SendAEOpenDocList(theDocList);
   }
}

There are really three LFileChooser classes, one in each of the namespaces mentioned earlier. At the top of the source file, the code indicates that the one in PowerPlant::StandardDialogs should be used. Therefore, the actual class used will be determined by the macros in the prefix file. All three of the LFileChooser classes allow you to obtain a pointer to a NavDialogOptions structure. The pointer will be 0 if the application is using classic dialogs. If the application is running under Navigation Services, we adjust the flags so that All Readable Items is the default choice from the popup menu in the dialog.

Next, the code creates an LFileTypeList (the default constructor uses the open resource with ID 128). In the case of HTMLedit, only TEXT files can be opened. If AskOpenFile() returns true, then the user requested a file be opened, and the code gets the AEDescList that describes the file from theChooser and sends an Open Apple Event to the application. PowerPlant will convert this Apple Event into a call to OpenDocument(). One nice feature is that there was almost no need to write separate code for the three possible situations (Navigation, Conditional, and Classic). Furthermore, you obtain the ability to open multiple files under Navigation Services without doing any extra work: PowerPlant will turn these lists into a sequence of individual open commands. We now turn our attention to the place where the document is actually opened.

OpenDocument() in CDocumentApp.cp

void
CDocumentApp::OpenDocument(
   FSSpec*      inMacFSSpec)
{
   LDocument*   theDoc =
                     LDocument::FindByFileSpec(*inMacFSSpec);
   
   if (theDoc != 0)
   {
      ValidateObject_(theDoc);
      theDoc->MakeCurrent();
   }
   else
      try
      {
         theDoc = NEW CTextDocument(this, inMacFSSpec);
         ValidateObject_(theDoc);
      }
      catch(LException& theErr)
      {
         if (theErr.GetErrorCode() != noErr)
         {
            throw;
         }
      }
}

First, the code tries to find the document from among the currently open documents using the static LDocument method FindByFileSpec(). If FindByFileSpec() returns a pointer to an LDocument, then the code brings it to the front and is finished. If the document is not already open, then the code needs to create a new document and other than the error handling, this is accomplished to CTextDocument's constructor.

The error handling code deserves some mention here. The constructor of CTextDocument may throw an exception if the document contains more than 32K of text. While this problem is handled in the OpenFile() method of CTextDocument (presented below), an exception is the only way to inform the CDocumentApp that the file was not opened. My personal convention is to use an LException whose error code is noErr to indicate that everything has been handled but that the document is invalid. When the exception is thrown, DebugNew will report that the document leaked. If this were a "real" application, I would spend some time determining whether this is a real leak or if DebugNew cannot handle exceptions thrown from constructors. Since this is supposed to be a teaching article, I will leave this task as an exercise for the reader (watch out, Metrowerks pools its memory allocations).

You have now seen all of the significant changes to the CDocumentApp class and we turn our attention to the CTextDocument class. The CTextDocument constructor is modified slightly to accept an optional FSSpec. However almost all of the work is passed on to its OpenFile() method which is where we will pick up the trail. Just as LSingleDoc contains a pointer to an LWindow as one of its data members, it also contains a pointer to an LFile as another member. This is the real purpose of the LDocument class: to tie together the visual presentation (LWindow) with the data on the disk (LFile). The LSingleDoc class assumes that each document uses only one window and one file. Unlike LWindow which can be complicated (due to the visual hierarchy), the LFile class is simple. It will take care of handling the file reference numbers for both the data and resource forks and most of the methods of the class do exactly what their names indicate.

OpenFile() in CTextDocument.cp
void CTextDocument::OpenFile(FSSpec& inFileSpec)
{
   
   mFile = nil;
   
   try
   {
      StDeleter   theFile(NEW LFile(inFileSpec));
      ValidateObject_(theFile.Get());
      
      theFile->OpenDataFork(fsRdWrPerm);
      StHandleBlock theTextH(theFile->ReadDataFork());
      ValidateHandle_(theTextH.Get());
      ValidateObject_(myTextView);
      myTextView->SetTextHandle(theTextH);
      myTextView->SetModified(false);
      
      ValidateObject_(mWindow);
      mWindow->SetDescriptor(inFileSpec.name);
      
      mIsSpecified = true;
      
      mFile = theFile.Release();
   }
   catch (LException& theErr)
   {
      if (theErr.GetErrorCode() == err_32kLimit)
      {
         UModalAlerts::StopAlert(ALRT_BigFile);
         throw LException(noErr);
      }
      else
      {
         throw;
      }
   }
}

The first goal of OpenFile() is to provide an LFile for the CTextDocument object. The code starts by creating a pointer to an LFile. The StDeleter class is analogous to the standard library's auto_ptr template and it is used here so that we do not leak the LFile pointer if an exception is thrown. The call to the StDeleter's Get() method returns the actual pointer and the code verifies that it is valid.

The code then opens the data fork of the LFile, copies the text into a handle (the StHandleBlock guarantees the memory is deleted at the end of the scope), and passes the handle to the document's CHTMLTextView object. Since the data in the CHTMLTextView is fresh from the disk, the code indicates that the CHTMLView is unmodified.

The code then sets the title of the window to match the name of the file. The mIsSpecified data member of the LDocument class indicates that this document has a file associated with it (important since it determines if the Revert command is valid). If everything went well, the code sets mFile. The call to Release() causes the StDeleter to disown the LFile pointer, failing to do this will cause DebugNew to (correctly) report a double-delete (once when the StDeleter goes out of scope and later in the CTextDocument's destructor).

The call to SetTextHandle() will throw an exception if the handle contains more than 32k characters. Since we know how to handle this problem here, we do so. The UModalAlerts method simply displays an alert stating that the file was too big (you need to supply the resource and PowerPlant does the rest). Since the code needs to let the calling function know that the file was not opened and there is no return value, I have opted to throw an LException with error code noErr. By personal convention, this means that something bad has happened but that no further remedies are needed. The first place where this error can be ignored should catch the exception and ignore it (you saw this in the OpenDocument() method of CDocumentApp). That completes the tour of the code necessary to open files using PowerPlant.

Although reversion of a document to the data on the disk is not essential, it is easy to add this ability to a PowerPlant application. PowerPlant stationery assumes that you will do this since it provides the Revert command in its File menu and adding the code usually adds little work.

DoRevert() in CTextDocument.cp
void CTextDocument::DoRevert()
{
   ValidateObject_(mFile);
   StHandleBlock   theTextH(mFile->ReadDataFork());
   ValidateHandle_(theTextH.Get());
   
   ValidateObject_(myTextView);
   myTextView->SetTextHandle(theTextH);
   
   SetModified(false);
   
   myTextView->Refresh();
}

The code reads the data from the CTextDocument's mFile into a handle (using a StHandleBlock to prevent a leak). Then it updates the text in the CHTMLTextView. The code then indicates that the document is now clean. The last step is to make a call to Refresh(). One important thing to be careful with here is that the call to Refresh() resolves into a call to ::InvalPortRect(). This means that no actual drawing will be done until the next Update event is handled. Therefore, if you have stopped the event processing queue, then calls to Refresh() will appear to do nothing.

Although I will not discuss it in this article, I have also made the NameNewDocument() method a little more sophisticated. In retrospect, this really should have been done in the third article where we discussed windows. You might want to examine the code on your own. Now we turn to saving (which is a little more complicated).

Saving Files

All saving is handled by the CTextDocument class (and not by the CDocumentApp class). Three methods need to be implemented for PowerPlant to handle saving files. The first method, AskSaveAs(), presents the user with a dialog to determine where to save the file. The second method, DoAESave(), method implements as Save As operation (which handles the Save Apple Event). The third method, DoSave(), is the method that actually writes the data to the disk.

The LDocument class has an implementation for AskSaveAs(). Unfortunately, it has not been updated to handle the need for a ::NavCompleteSave() call under Navigation Services. In order to handle this, we need to rewrite the AskSaveAs() command as well as retain a new data member: myFileDesignator.

AskSaveAs() in CTextDocument.cp
Boolean CTextDocument::AskSaveAs(
   FSSpec& outFSSpec, Boolean inRecordIt)
{

   Boolean      didSave = false;

   StDeleter
      theDesignator(NEW LFileDesignator);
   ValidateSimpleObject_(theDesignator.Get());
   
   theDesignator->SetFileType(GetFileType());
   NavDialogOptions*   theOptions =
      theDesignator->GetDialogOptions();
   if (theOptions != 0)
   {
      theOptions->dialogOptionFlags |= kNavNoTypePopup;
   }
   
   Str255   theDefaultName;
   if (theDesignator->AskDesignateFile(
      GetDescriptor(theDefaultName)))
   {
   
      theDesignator->GetFileSpec(outFSSpec);
      if (UsesFileSpec(outFSSpec))
      {

         if (inRecordIt)
         {
            SendSelfAE(kAECoreSuite, kAESave, ExecuteAE_No);
         }
         
         DisposeOf_(myFileDesignator);
         myFileDesignator = theDesignator.Release();
         
         DoSave();
         
         didSave = true;
         
      }
      else
      {
      
         if (inRecordIt)
         {
            SendAESaveAs(outFSSpec, GetFileType(),
               ExecuteAE_No);
         }
         
         if (theDesignator->IsReplacing())
         {
            ThrowIfOSErr_(::FSpDelete(&outFSSpec));
         }
         
         if (myFileDesignator != 0)
         {
            ValidateSimpleObject_(myFileDesignator);
            myFileDesignator->CompleteSave();
            DisposeOf_(myFileDesignator);
         }
         
         myFileDesignator = theDesignator.Release();

         DoAESave(outFSSpec, fileType_Default);
                  
         didSave = true;
      }
   }
   
   return didSave;
}

The first thing the code does is to create a new LFileDesignator (again, a StDeleter is used to prevent leaks). Before interacting with the user, we need to adjust some settings for the dialog. Just as with the LFileChooser in ChooseDocument() above, the GetDialogOptions() will return 0 if it is running under classic dialogs. We adjust the options so that the user will not be presented with a type popup menu. This is consistent with the fact that we only save TEXT documents from this application. The code also sets the default name for the file to the name of the window.

The call to AskDesignateFile() returns true if the user has decided to save the file (and false if they cancel the dialog). All the information obtained from the dialog is available through theDesignator. The first thing we do is set outFSSpec to the FSSpec obtained from interaction with the user. Then we determine if the document's existing file is being overwritten or the document is writing to another file. The two possibilities are similar and we discuss them in parallel.

In both cases the first thing the code does is to send the application an Apple Event if the application's Apple Events are being recorded. The actual Apple Event sent is different, but the code is very similar. The parameter ExecuteAE_No indicates that the Apple Event should not be executed once it is received. This is appropriate since the command is being handled here and there is no need for the code to save the file twice.

If the user is saving to a different file and that file already exists, then the file needs to be deleted and this is done next (in the second branch). Now it is time to handle the changes to the myFileDesignator data member. If we are overwriting our original file on disk, then the old LFileDesignator is deleted and the new designator is saved. Since the file is not being closed, the code does not need to call CompleteSave(). In the other case (where the file on the disk is changing and the LFileDesignator is not 0) the code calls CompleteSave() before disposing of the old LFileDesignator. Next either DoSave() or DoAESave() is called. In both of these cases, the function will then return true because the file has been saved.

Next we examine the DoAESave() method. This method implements a Save As command and calls DoSave() to do the actual work of writing to the disk. Almost all of the code is generic and you should be able to use it "out of the box." In fact, only the OSType_Creator constant is application specific and could easily be factored into a call to a new virtual function with the name GetCreatorType().

DoAESave() in CTextDocument.cp
void CTextDocument::DoAESave(
   FSSpec& inFileSpec, OSType inFileType)
{
   DisposeOf_(mFile);
   mIsSpecified = false;

   StDeleter   theFile(NEW LFile(inFileSpec));
   ValidateObject_(theFile.Get());

   OSType   theFileType = GetFileType();
   if (inFileType != fileType_Default)
   {
      theFileType = inFileType;
   }
   
   theFile->CreateNewFile(OSType_Creator, theFileType);
   theFile->OpenDataFork(fsRdWrPerm);
   theFile->OpenResourceFork(fsRdWrPerm);

   mFile = theFile.Release();
   
   DoSave();

   ValidateObject_(mWindow);
   mWindow->SetDescriptor(inFileSpec.name);

   mIsSpecified = true;
}

Since this is a Save As operation, the code first eliminates the existing LFile object. This does not delete the file on the disk, but it will close the file if necessary. Notice that this is done even before we know that the new file is valid. My reasoning is that after an attempted Save As command, the goal is to protect the original file's data (and prevent a Save command from clobbering it). The DisposeOf_ macro will set the mFile data member to 0 and setting mIsSpecified to false will prompt the user with another AskSaveAs() dialog if they try to close the window after a failed Save As command.

Next, the code creates the file on the disk (with another StDeleter). Once we know the file has been successfully created and opened, the CTextDocument object will take control of the LFile pointer (again the call to Release() is important to avoid a double-delete). The DoSave() command expects that both the resource and data forks of the file are open for writing. Once DoSave() writes the data to the disk, the document's window title is updated and its mIsSpecified data member is set to true. We now turn our attention to the one method that requires knowledge of the data layout in the files: DoSave().

DoSave() in CTextDocument.cp
void CTextDocument::DoSave()
{
   ValidateObject_(myTextView);
   Handle   theTextH = myTextView->GetTextHandle();
   ValidateHandle_(theTextH);
   Size   theTextSize = ::GetHandleSize(theTextH);
   
   StHandleLocker   theLock(theTextH);
   
   ValidateObject_(mFile);
   mFile->WriteDataFork(*theTextH, theTextSize);

   SetModified(false);
}

Saving text files is rather simple: get a handle to the text and write it out to the file's data fork. The last line indicates that the document currently matches the data on the disk. Somewhat anti-climatic, but it works. The actual PowerPlant stationery file saves some information in the resource fork as well, but I will leave that for you to explore on your own.

Concluding Remarks

Although Opening, Reverting and Saving files requires a significant amount of code, most of the work is done by PowerPlant. In fact, of the methods you need to implement, most of them can be lifted almost verbatim from the PowerPlant stationery files. At this point, I think I have covered the code presented in the PowerPlant Advanced stationery file (with a few omissions that you should be able to work out using the class browser and debugger). In the next article (the fifth of this series), I will spend some time talking about control classes which can be used to liven up your dialogs. Once that topic is covered, I feel I will have covered the essential core of PowerPlant. At this point, I will reiterate my request that people should indicate what topics they would like to see. Here is a short list of topics I have considered: more on menus, more on panes, threads, drag & drop, tables, actions (and undo strategies), Apple Events, contextual menus. I am flexible and willing to look at other topics if they are suggested.

PowerPlant References

I could not find as many references on files as on some of the other topics, there is a single chapter in The PowerPlant Book entitled "File I/O". You should also spend some time sifting through the source code of PowerPlant as well as the example files (there is a StdDialogs demo as well as a TextDocument demo).


Aaron teaches in the Mathematics Department at Central Washington University in Ellensburg, WA. Outside of his job, he spends time riding his mountain bike, watching movies and entertaining his wife and two sons. You can email him at montgoaa@cwu.edu, try to catch his attention in the newsgroup comp.sys.mac.oop.powerplant or visit his web site at mac69108.math.cwu.edu:8080/.

 
AAPL
$111.78
Apple Inc.
-0.87
MSFT
$47.66
Microsoft Corpora
+0.14
GOOG
$516.35
Google Inc.
+5.25

MacTech Search:
Community Search:

Software Updates via MacUpdate

CleanApp 5.0.0 Beta 5 - Application dein...
CleanApp is an application deinstaller and archiver.... Your hard drive gets fuller day by day, but do you know why? CleanApp 5 provides you with insights how to reclaim disk space. There are... Read more
Monolingual 1.6.2 - Remove unwanted OS X...
Monolingual is a program for removing unnecesary language resources from OS X, in order to reclaim several hundred megabytes of disk space. It requires a 64-bit capable Intel-based Mac and at least... Read more
NetShade 6.1 - Browse privately using an...
NetShade is an Internet security tool that conceals your IP address on the web. NetShade routes your Web connection through either a public anonymous proxy server, or one of NetShade's own dedicated... Read more
calibre 2.13 - Complete e-library manage...
Calibre is a complete e-book library manager. Organize your collection, convert your books to multiple formats, and sync with all of your devices. Let Calibre be your multi-tasking digital librarian... Read more
Mellel 3.3.7 - Powerful word processor w...
Mellel is the leading word processor for OS X and has been widely considered the industry standard since its inception. Mellel focuses on writers and scholars for technical writing and multilingual... Read more
ScreenFlow 5.0.1 - Create screen recordi...
Save 10% with the exclusive MacUpdate coupon code: AFMacUpdate10 Buy now! ScreenFlow is powerful, easy-to-use screencasting software for the Mac. With ScreenFlow you can record the contents of your... Read more
Simon 4.0 - Monitor changes and crashes...
Simon monitors websites and alerts you of crashes and changes. Select pages to monitor, choose your alert options, and customize your settings. Simon does the rest. Keep a watchful eye on your... Read more
BBEdit 11.0.2 - Powerful text and HTML e...
BBEdit is the leading professional HTML and text editor for the Mac. Specifically crafted in response to the needs of Web authors and software developers, this award-winning product provides a... Read more
ExpanDrive 4.2.1 - Access cloud storage...
ExpanDrive builds cloud storage in every application, acts just like a USB drive plugged into your Mac. With ExpanDrive, you can securely access any remote file server directly from the Finder or... Read more
Adobe After Effects CC 2014 13.2 - Creat...
After Effects CC 2014 is available as part of Adobe Creative Cloud for as little as $19.99/month (or $9.99/month if you're a previous After Effects customer). After Effects CS6 is still available... Read more

Latest Forum Discussions

See All

Make your own Tribez Figures (and More)...
Make your own Tribez Figures (and More) with Toyze Posted by Jessica Fisher on December 19th, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
So Many Holiday iOS Sales Oh My Goodness...
The holiday season is in full-swing, which means a whole lot of iOS apps and games are going on sale. A bunch already have, in fact. Naturally this means we’re putting together a hand-picked list of the best discounts and sales we can find in order... | Read more »
It’s Bird vs. Bird in the New PvP Mode f...
It’s Bird vs. Bird in the New PvP Mode for Angry Birds Epic Posted by Jessica Fisher on December 19th, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Telltale Games and Mojang Announce Minec...
Telltale Games and Mojang Announce Minecraft: Story Mode – A Telltale Games Series Posted by Jessica Fisher on December 19th, 2014 [ permalink ] | Read more »
WarChest and Splash Damage Annouce Their...
WarChest and Splash Damage Annouce Their New Game: Tempo Posted by Jessica Fisher on December 19th, 2014 [ permalink ] WarChest Ltd and Splash Damage Ltd are teaming up again to work | Read more »
BulkyPix Celebrates its 6th Anniversary...
BulkyPix Celebrates its 6th Anniversary with a Bunch of Free Games Posted by Jessica Fisher on December 19th, 2014 [ permalink ] BulkyPix has | Read more »
Indulge in Japanese cuisine in Cooking F...
Indulge in Japanese cuisine in Cooking Fever’s new sushi-themed update Posted by Simon Reed on December 19th, 2014 [ permalink ] Lithuanian developer Nordcurrent has yet again updated its restaurant simulat | Read more »
Badland Daydream Level Pack Arrives to C...
Badland Daydream Level Pack Arrives to Celebrate 20 Million Downloads Posted by Ellis Spice on December 19th, 2014 [ permalink ] | Read more »
Far Cry 4, Assassin’s Creed Unity, Desti...
Far Cry 4, Assassin’s Creed Unity, Destiny, and Beyond – AppSpy Takes a Look at AAA Companion Apps Posted by Rob Rich on December 19th, 2014 [ permalink ] These day | Read more »
A Bunch of Halfbrick Games Are Going Fre...
A Bunch of Halfbrick Games Are Going Free for the Holidays Posted by Ellis Spice on December 19th, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »

Price Scanner via MacPrices.net

The Apple Store offering free next-day shippi...
The Apple Store is now offering free next-day shipping on all in stock items if ordered before 12/23/14 at 10:00am PT. Local store pickup is also available within an hour of ordering for any in stock... Read more
It’s 1992 Again At Sony Pictures, Except For...
Techcrunch’s John Biggs interviewed a Sony Pictures Entertainment (SPE) employee, who quite understandably wished to remain anonymous, regarding post-hack conditions in SPE’s L.A office, explaining “... Read more
Holiday sales this weekend: MacBook Pros for...
 B&H Photo has new MacBook Pros on sale for up to $300 off MSRP as part of their Holiday pricing. Shipping is free, and B&H charges NY sales tax only: - 15″ 2.2GHz Retina MacBook Pro: $1699... Read more
Holiday sales this weekend: MacBook Airs for...
B&H Photo has 2014 MacBook Airs on sale for up to $120 off MSRP, for a limited time, for the Thanksgiving/Christmas Holiday shopping season. Shipping is free, and B&H charges NY sales tax... Read more
Holiday sales this weekend: iMacs for up to $...
B&H Photo has 21″ and 27″ iMacs on sale for up to $200 off MSRP including free shipping plus NY sales tax only. B&H will also include a free copy of Parallels Desktop software: - 21″ 1.4GHz... Read more
Holiday sales this weekend: Mac minis availab...
B&H Photo has new 2014 Mac minis on sale for up to $80 off MSRP. Shipping is free, and B&H charges NY sales tax only: - 1.4GHz Mac mini: $459 $40 off MSRP - 2.6GHz Mac mini: $629 $70 off MSRP... Read more
Holiday sales this weekend: Mac Pros for up t...
B&H Photo has Mac Pros on sale for up to $500 off MSRP. Shipping is free, and B&H charges sales tax in NY only: - 3.7GHz 4-core Mac Pro: $2599, $400 off MSRP - 3.5GHz 6-core Mac Pro: $3499, $... Read more
Save up to $400 on MacBooks with Apple Certif...
The Apple Store has Apple Certified Refurbished 2014 MacBook Pros and MacBook Airs available for up to $400 off the cost of new models. An Apple one-year warranty is included with each model, and... Read more
Save up to $300 on Macs, $30 on iPads with Ap...
Purchase a new Mac or iPad at The Apple Store for Education and take up to $300 off MSRP. All teachers, students, and staff of any educational institution qualify for the discount. Shipping is free,... Read more
iOS and Android OS Targeted by Man-in-the-Mid...
Cloud services security provider Akamai Technologies, Inc. has released, through the company’s Prolexic Security Engineering & Research Team (PLXsert), a new cybersecurity threat advisory. The... Read more

Jobs Board

*Apple* Store Leader Program (US) - Apple, I...
…Summary Learn and grow as you explore the art of leadership at the Apple Store. You'll master our retail business inside and out through training, hands-on experience, Read more
Project Manager, *Apple* Financial Services...
**Job Summary** Apple Financial Services (AFS) offers consumers, businesses and educational institutions ways to finance Apple purchases. We work with national and Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
*Apple* Retail - Multiple Positions (US) - A...
Job Description: Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.