TweetFollow Us on Twitter

Beginning OOPs
Volume Number:7
Issue Number:8
Column Tag:Beginning OOPs

Object Programming With TCL

By John Lengyel, St. Louis, MO

[John Lengyel is a software engineer for Digital Systems Consultants of St. Louis, Missouri. He is currently developing applications on VAX/VMS and Apollo/Unix platforms. He is looking for Macintosh software development opportunities.]

The Objective

Over the past several years numerous articles have appeared in MacTutor espousing the virtues of MacApp. Not being a Macintosh programmer by profession and already owning and enjoying THINK Pascal and THINK C, I managed to suppress my desire to purchase MacApp. So when my THINK C 4.0 upgrade arrived I wanted to immediately dig into its object-oriented capabilities.

After reading the well written manual discussing object-oriented programming and the THINK Class Library (THINK C’s version of MacApp) and looking over the sample projects provided with the upgrade I thought it would be a good idea to do one or two MacApp to THINK Class Library (TCL) conversions before jumping off on my own. The first program I converted is the topic of this article. The program is based upon a similar program that was written by Larry Rosenstein and presented in the December, 1986 issue of MacTutor titled “Dots Game Shows MacApp”.

TCL and MacApp

At this point, I considered writing the “What is TCL” section of the article. But anyone who has read or can read past articles on MacApp will have a very good idea of what TCL is all about. While converting Dots to TCL, the similarity of TCL to MacApp became increasingly noticeable.

For example, in the MacApp version of Dots, the classes requiring redefinition are TApplication, TDocument, TView and TCommand. In the TCL version they are respectively CApplication, CDocument, CView (actually subclasses of CView are redefined) and CMouseTask (similar to MacApp’s TCommand). The functionality provided by these corresponding classes is nearly identical. This similarity in name and function also extends, to a slightly lesser degree, down into the method level.

Studying MacApp articles and code should be helpful to anyone trying to learn how to write programs in TCL. As an additional TCL specific reference, an overview of TCL was provided by Alastair Dallas in the October, 1989 issue of MacTutor. Rather than restating what these people have said about MacApp and TCL I will concentrate my efforts on the classes that needed to be redefined for Dots and in particular on their overridden methods.

About Dots...

For those of you who while in high school actually studied during study hall and do not know how to play Dots, here is a description of the game. Dots is played on a matrix of dots. Players take turns drawing lines connecting adjacent dots. When a player draws a line that completes a square in the matrix, that player is awarded the square. A mark is placed in the square to indicate player ownership. The player completing the square may then take another turn. The game ends when all lines in the matrix are drawn. The player awarded the most squares wins the game.

In the TCL version of Dots, which has essentially the same design as its MacApp counterpart, the game is played in a window. The number of squares awarded each player is displayed in the top portion of the window. These numbers are hereafter referred to as the player’s scores. The dot matrix over which the game is played is displayed in the bottom portion of the window and is scrollable. Lines between adjacent dots are drawn with mouse clicks. When a square is completed, a graphic pattern associated with the moving player is used to fill an awarded square to indicate that player’s ownership of the square. The pattern is also used to display a frame around the appropriate player’s score. See Figure 1 for a view of the window used in Dots. As for as the playing of Dots, a correction to the original version was made so that when a player completes a square it is still that player’s turn.

Figure 1. Views of Dots' Document

Many useful features are exhibited in the Dots program. A multi-view window that can be scrolled, auto-scrolled, resized, zoomed and printed. Support for the undoing and redoing of moves. Multifinder compatibility and desk accessory support. Games can be saved and restored. Multiple windows can be opened and hence games played. All this and more in a fairly small amount of new, Dot specific code thanks to TCL.

Table 1 shows the five TCL classes that were redefined for Dots. These five subclasses and the “main” routine comprise all of the new code that was written for Dots. All other functionality is provided by the standard TCL code. The rest of this article will discuss each of the redefined classes and “main”.

TCL Subclass Abridged Description

CApplication cDotsApp Supervises program

CDocument cDotsDoc Manages window and its data

CPane cScorePane Displays scores

CPanorama cDotsPane Displays dot matrix

CMouseTask cDotsTask Handles mouse clicks in matrix

Table 1: TCL Redefines

Source Descriptions

The source files for Dots are organized in the manner recommended in the THINK C manual. Dots subclasses are defined in a “.h” header file and their methods in a “.c” file each having the name of the class. As an example, the subclass cDotsApp’s associated source files are cDotsApp.c and cDotsApp.h. I prefixed the Dots subclasses with a lower case “c” in order to distinguish them from the standard TCL classes which all start with an upper case “C”. There does not appear to be a standard for prefixing the names of class methods or variables in TCL. For class variables I followed the MacApp practice of preceding the variable name with a lower case “f”. As for class methods, standard TCL methods start with an upper case letter while Dot specific methods start in lower case.

The resources used in Dots were copied from the Starter Project supplied by THINK. A few modifications and additions were then made. A new string was added to STR# “Common” to supply text for displaying the “About...” alert. The “General” ALRT and DITL which are used to display the “About...” were then enlarged to accommodate the text. A DITL and DLOG were added to display a “Press CMD-. to stop printing” message. A WIND resource was modified for the document window. Finally, the string “Move” was added to STR# “Task Names”. This string is used by TCL to display the proper undo/redo command in the edit menu (“Undo Move” or “Redo Move”).

Main

The main routine when using TCL is rather trivial. It creates an application object and sends it an initialization message. Main then tells the application to start running. Finally, when the application finishes running, Main sends an exit message to the application. Exit() gives the application a last chance to clean up before the application terminates. For instance, temporary files may need to be deleted. Dots does not do anything in the Exit() routine. It was left in for illustrative purposes. All very neat, very orderly. Enough said.

cDotsApp::CApplication

Only one application is created for the entire project. As previously stated, the Main routine of the project creates the application object, sends the application an initialization request and then tells the application to begin running. While running, the application directs the processing of events, gives MultiFinder support, takes some wrinkles out of memory management, updates menus, handles DAs and is also capable of handling some of the basic File and Edit menu commands. All of this runtime support is provided in Dots by the standard TCL code.

To understand program flow using TCL it is important to know about the chain of command. A chain of command is used to determine which object handles a direct command. A direct command is a request that an object perform some action. They usually come about from menu selections. Direct commands that cannot be handled by an object move up the chain of command. The application is at the top of the chain of command. By the time the application receives the command it has been passed on by the views and the document. If the application does not handle the command, the command will not be handled. A chain of command for Dots is depicted in Figure 2.

Figure 2. The Chain of Command in Dots

The application’s inherited initialization method must be overridden. The Dots override for this method calls the inherited application’s initialization method. This must always be done because the inherited method initializes the Macintosh Toolbox, sets up menus, initializes application specific TCL globals and does a host of other very helpful but tedious things. The Dots application initialization method also sets up two global variables each of which are the patterns associated with a particular player. The use of these patterns is discussed in the cDotsPane and cScorePane sections of this article. These are application globals because they apply to all games being played and are not game dependent.

The application method SetUpFileParameters() was overridden in order to identify the types of files the Dots application recognizes. These types are used for the Macintosh Toolbox Standard File routines when opening a file. The application method DoCommand() was overridden so that a simple “About” alert could be posted when the “About Dots...” command was selected from the Apple menu. Unlike MacApp, TCL does not support an inherent about box. The application methods CreateDocument() and OpenDocument() were overridden so that Dots documents could be created whenever a “New...” or “Open...” was chosen from the File menu.

cDotsDoc::CDocument

The document associates a window with the data that is to be displayed in the window. The data contained in the document is displayed to the user in the window and may be stored on disk as a file. A subclass of CDocument or its superclass CDirector must be created for every type of window the application supports (CDirector does not have methods for filing or printing). A document object is created whenever “New” or “Open...” is chosen from the File menu.

Documents are in the chain of command. They manage the communication between windows, files and menu commands. If a document cannot handle a command, it passes it up to the application. Methods in the document class are provided to create, initialize and dispose of documents, open and close windows, read and write data to disk, access the document data, support printing, support the undoing and redoing of tasks and update menus.

cDotsDoc is the only document subclass in Dots. Each cDotsDoc object that is created associates the window and data for a particular game. The data needed to store the state of a Dots game must contain the following information: whether or not a line is to be drawn between each adjacent dot in the matrix, the ownership of each completed square, the player who is to move next.

The inherited document’s initialization method must be overridden. The Dots override for this method calls the inherited initialization method in order to put the document in the chain of command and initialize some document variables for filing, printing, recording moves, and menu updating. The Dots data is then initialized to a new game state.

The inherited document methods for creating a new file or opening an existing file must be overridden because they do not do anything. The code for these routines comes out of the sample programs provided with THINK C. Adaptations were made for Dots. The main adaptation involves storing the data read in from an opened file into the document. This is accomplished in the auxiliary method storeData() (Auxiliary means it is called by other methods within the object in which it is located but not by methods of other objects. THINK C 4.0 does not support private methods).

Both the new file and open file methods call an auxiliary method, BuildWindow(), to create a window and the views contained in the window. There are actually four views in the window. A subclass of CPane is used to display the score and current player turn. A subclass of CPanorama displays the view of the dot matrix. These subclasses, cScorePane and cDotsPane, are described in their respective sections. The two other views in the window are objects of CScrollPane and CSizeBox.

The dot matrix view must be scrollable which is why it is a subclass of CPanorama. CPanorama has methods that support scrolling. CScrollPane implements a view with scroll bars to control a panorama. Both CScrollPane and CPanorama are descendants of CPane which is itself a descendent of CView. They work in tandem to provide the scrolling capabilities of Dots.

CSizeBox, also a descendant of CPane, is used by CScrollPane to draw a grow icon in the lower right corner of any pane. By using an icon for the size box, TCL gains some flexibility in its placement of the size box. The size box no longer has to appear in the lower right corner of the window. However, in the case of Dots, that’s where it’s at man.

The code for BuildWindow() is also adapted for Dots from the sample programs. However, I spent a great deal of time reworking the BuildWindow() code for Dots. This was in large part caused by my lack of knowledge as to how some of the standard TCL code worked.

CDecorator is a TCL class that is used to arrange windows on the screen. One of its methods, PlaceNewWindow(), is used by BuildWindow() to implement window staggering. Some of the sample programs have BuildWindow() calling PlaceNewWindow() immediately after the window is created. I followed their example and got some bad results. The scroll bars for the dot matrix view were being placed beneath the window. It turned out that PlaceNewWindow() was shortening the window. Then when the scroll bars were created, they were placed according to the original size of the window. An easy solution was to make the call to PlaceNewWindow() at the end of BuildWindow() after all the views and scroll bars were in place.

Another problem I had involved the use of the inherited document variable itsMainPane. itsMainPane is a variable of type CPane. As stated in the THINK C manual, itsMainPane should point to the document’s main pane and if the document has no main pane, itsMainPane should be NULL. Many of the document’s print routines check to see if itsMainPane is NULL. If it is, they bypass their print code.

Since the Dots window is broken up into a score view and a dots matrix view it did not have what I considered to be a main view. But I wanted to be able to print and so did not want to leave itsMainPane NULL. My initial solution was to use itsMainPane as a view of the entire window and attach all of the other views directly or indirectly to it. This worked fine until I tried to print. All that printed was a page eject. After much digging, I found the problem.

In preparation for printing, the printer object was setting the current port to the printer. Then when the draw message went out to itsMainPane, which had no functionality of its own, it passed the message on to all of its attached views. CScrollPane was the first view that attempted to print. However, the scroll pane has scroll bars attached to it and scroll bars are not supposed to be printed. When the scroll bars received their draw message they set the current port to the window and left it there. Because of this, all of the other views poured themselves onto the screen as well.

After several other arrangements of views in BuildWindow() failed for various reasons I finally decided to just override all of the document’s print routines that used itsMainPane (there were only a few). In the override print routines, two variables were used to replace itsMainPane. One pointed to the score view object and the other to the dot matrix view object. Problem solved.

While I am on the topic of printing I may as well discuss a couple of problems that occurred when attempting to print from the Finder. Both problems were caused by errors in the standard TCL code. In correcting the problems I did not want to modify the TCL source code files in which the errors occurred. This should only be done by THINK. So the simplest solution, which occurred at 3 am after four hours of debugging, was to embed the corrections in a couple of document methods.

The first printing problem involved the use of the global variable gGopher. gGopher points to the first object in the chain of command that gets a chance to handle a command. If the gopher cannot handle the command it sends the command up the chain of command on to its supervisor. The document stores in one of its instance variables a pointer to a view that it encloses. That view is to be used as the gopher when the document is activated. When no document is active the application is the gopher.

When printing a document from the Finder, the application method Preload() sends the document an open file message and then tells the gopher to print. But when I tried to do this in Dots, the documents opened but did not attempt to print. The problem was that gGopher was pointing to the application object at this point rather than the document’s gopher as it should have been. The document’s OpenFile() method had sent a Select() message to its window in order to activate the window. However, Select() only sets gGopher to the document’s gopher if the application was in the background. Since Dots was not in the background gGopher remained pointing at the application. My solution was to force an activate after Select() was called from the document’s OpenFile() method. This sets gGopher to the document’s gopher which when told by Preload() to print passes the print message on to the document where it is finally handled.

The second printing problem was aesthetic in nature. While a document is printing the cursor is set to the watch. When the printing is finished the cursor gets set back to an arrow. Using TCL this arrow reset occurs during the main event loop when DispatchCursor() is called. However, when printing multiple documents from the Finder control does not return to the main event loop between print jobs. Succeeding print job dialogs appear with the watch cursor still in use. For a fix I set the cursor back to an arrow during the document’s DonePrinting() method.

The inherited document methods for handling “Save”, “Save As...” and “Revert” commands do nothing and must be overridden. Again, the code for these routines was easily adapted for Dots from the sample programs. No problem.

Finally, document methods are provided for accessing the Dots-specific instance variables for line states, square states and player turn. These are the only new methods that needed to be developed for the document. Admittedly, there were quite a few code changes that had to be made in the override methods. But the bulk of those changes involved that wonderful programming technique called “cut and paste”. Minor modifications were then made to the “pasted” code.

cScorePane::CPane

As stated in the TCL manual, a pane is a view that defines a drawing area within a window or another pane. All drawing takes place within a pane. Each pane has its own drawing environment and coordinate system. Panes are used for the drawing of non-scrolling views. Panes may also handle mouse clicks.

To understand how objects are drawn using TCL it is important to know about the visual hierarchy. The visual hierarchy is composed of all the visual objects in the application. At the top of the visual hierarchy is the desktop. Except for the desktop, all of the visual objects appearing on the screen are enclosed by other visual objects. The desktop encloses all of the windows. A window encloses all of its views. Views may enclose other views.

Figure 3 shows the visual hierarchy for a Dots application that currently has two games in progress. Note that the visual hierarchy changes as the application runs. Whenever a new game is started a document is created. The document’s window is added to the desktop and the views are added to the window. When a game is closed the window and its enclosed views are removed from the visual hierarchy.

Figure 3. Visual Hierarchy

When a window update or activate event occurs the message moves down the visual hierarchy from the desktop to the appropriate window. Each view attached to the window is passed a draw message. If any part of the view is contained in the area to be redrawn the view will call its own draw routine and then pass the draw message on to each of its subviews. The subviews will continue this process until the bottom of the visual hierarchy is reached.

Panes have draw methods, print methods, mouse methods, cursor methods, methods for changing their appearance, coordinate transformation methods and more. Because panes are views, they are part of the chain of command as well as the visual hierarchy. A pane’s supervisor in the chain of command is usually its director or document.

In Dots, the score view is a subclass of CPane. One score view object is created by the document’s BuildWindow() method whenever a document is created. The score view displays each player’s score in the document’s window and highlights the score of the player who is currently moving. The currently highlighted score is tracked by an instance variable contained in the score view object. The score view does not handle mouse clicks.

The inherited score view’s initialization method must be overridden. The Dots override for this method, IScorePane(), calls the inherited initialization method in order to put the score view in the chain of command and in the visual hierarchy and to initialize inherited instance variables. IScorePane() initializes its instance variable for the currently highlighted score to that of the player who is currently moving.

The only other inherited method to be overridden is the draw method. The code for drawing the line between the score view and the dot matrix view did not appear in the source listings of the MacApp version of Dots. My assumption is that MacApp was somehow tasked to draw the line. TCL has a similar capability through the use of a subclass of CPane called CBorder. Objects of CBorder are panes with the inherent capability of drawing a border around their perimeter. Rather than use CBorder for the score view’s superclass I chose to draw the line myself in the draw method. This is the only difference between the MacApp and TCL versions of Draw();

Four new methods were needed in the score view (they appeared in the MacApp version but are new in the sense that they are not inherent CPane class methods). The new methods are used to calculate the current score, obtain the rectangle around a player’s score, invalidate the area around a player’s score so that it will be redrawn with a window update event and switch the highlighted score when a player moves.

A final note about drawing. All drawing in a pane takes place in the pane’s own coordinate system. This simplifies the drawing of a pane since the pane may now be repositioned or modified possibly in other ways without affecting the drawing code. Panes become independent entities. During an update or activate event, before the pane’s draw routine is called, another inherent pane class method is called by the standard TCL code to prepare the pane for drawing. This prepare method sets up the port and coordinates for the pane.

Some of the new methods for the score view may be called directly from other objects. When this happens the preparation method for the score view has not been called because an update or activate is not involved. This means that the current port and coordinate system are quite possibly not that of the score view. This means that erroneous drawing is quite possible. This means I was having a drawing problem until I started putting calls to prepare the score view for drawing before the calls for invalidating and erasing rectangles in the score view were made.

cDotsPane::CPanorama

A panorama is a pane with the added capability of scrolling. CPanorama is a subclass of a CPane. Panoramas inherit all of the qualities of a pane that were described in the section discussing the score view.

A panorama must be installed in a scroll pane to make scrolling possible. A scroll pane is a pane with a panorama and scroll bars to control what is being displayed in the pane. The scroll bars and panorama communicate through the scroll pane to scroll or shift an image. These objects, if they are to be used, should be created when the document is created. Attach the scroll pane to the window and the panorama to the scroll pane. The scroll bars are created and attached to the scroll pane by the inherent TCL code during the scroll pane’s initialization process.

The dot matrix view in Dots is a subclass of CPanorama. One dot matrix object and its scroll pane are created by the document’s BuildWindow() method whenever a document is created. The dot matrix view displays the dot matrix, the lines connecting adjacent dots and the awarded squares. Mouse clicks are handled by the dot matrix view so that players may select the lines connecting the dots.

The inherited dot matrix view’s initialization method must be overridden. The Dots override for this method, IDotsPane(), calls the inherited initialization method in order to put the score view in the chain of command and in the visual hierarchy and to initialize inherited instance variables. IDotsPane() sets the scrolling scale to the pixel distance between the dots in the matrix as opposed to the default distance of one dot. When a click is made in one of the scroll bars, the image will now scroll by the dot spacing amount rather than one pixel at a time.

The inherited draw method must be overridden. The override method is the same as that appearing in the MacApp version of Dots. In fact, all of the new methods for the dot matrix view are essentially the same as in the MacApp version. The only modification is that some of the routines required that the drawing preparation method be used to set the port and coordinate system to that of the dot matrix view. The reasons for this were discussed in the score view section of this article.

Mouse tracking is handled a little differently in TCL than MacApp. The inherited method DoClick() must be overridden to enable mouse tracking. In Dots, the DoClick() method creates a mouse task to handle the mouse down. The inherited method TrackMouse() is called to follow the mouse down until the mouse button is released. DoClick() must notify the document of the move so that the previous move may be disposed of and the current move saved for the undo process.

The TrackMouse() method called by DoClick() sends the newly created mouse task a BeginTracking() message to give the mouse task an opportunity to make any adjustments before mouse tracking starts. TrackMouse() then repeatedly calls the mouse task method KeepTracking() while the mouse button remains down. When the button is finally released, TrackMouse() sends an EndTracking() message to the mouse task. It is up to the mouse task to initiate any action based upon the mouse click.

cDotsTask::CMouseTask

Mouse tasks are used to track mouse downs within a pane. A mouse task is a subclass of a task. Mouse tasks therefore inherit the task’s methods for implementing undoable actions.

Dots uses a mouse task to track mouse downs in the dot matrix view and to provide for the undoing and redoing of moves. A mouse task is created by the dot matrix view’s DoClick() method whenever there is a mouse down in the dot matrix view. The Dot’s process for handling the mouse down is discussed in the section on the dot matrix view.

The mouse task’s initialization method is a good place to initialize the instance variables that will be saved in the document for undoing the mouse click. The inherent initialization method should be called to store the undo/redo string in the edit menu.

The inherent mouse tracking methods BeginTracking(), KeepTracking() and EndTracking() do not do anything. BeginTracking() should be overridden if the starting point for the mouse down needs to be altered or some initial actions need to be performed for the mouse down. KeepTracking() and EndTracking() must always be overridden. EndTracking() should store the information needed to undo the mouse task in the instance variables of the mouse task.

Dots overrides all three of these mouse tracking methods in order to provide autoscrolling, line selection, the highlighting and unhighlighting of selectable lines as the cursor moves across the dot matrix view and the storage of the move for undoing the mouse task. The TCL code for handling mouse clicks in Dots varies considerably from the code in the MacApp version. However, the functionality is the same and the steps that must be taken to achieve that functionality are fairly easy to follow.

The methods for doing, undoing and redoing a mouse task in Dots are all overrides that mimic their MacApp counterparts. The only new method in the mouse task is used by the document to check on the validity of the move. If the just completed mouse task performed a valid move then the mouse task is saved in the document for undo. Invalid moves are disposed of.

Conclusion

Overall, working with TCL was an enjoyable experience. The inherited code for menu updating, scrolling, Multifinder and DA support, window manipulation and memory management is quite extensive and greatly simplifies the programming process. Good job THINK.

As pointed out in the article, there were a few TCL glitches that caused me hours of consternation. These problems will clear up over time. But one thing I noticed as I tried to track down the errors was that TCL programs and probably object-oriented programs in general can be hard to debug.

Sometimes it is difficult to tell, by looking at the source code, what object is being passed a message and hence where a problem may be occurring. For example, the TCL global variable gGopher is a handle to an object that is a subclass of CBureaucrat. These objects may be applications, documents, views or instances of other classes that inherit bureaucratic traits. By looking at a line like ‘gGopher->DoCommand(theCommand)’ it is impossible to tell which type of bureaucrat is being referenced. The code must be stepped through at runtime to identify the bureaucrat. Program flow can also be a bit confusing to follow because it jumps around a lot from method to method and object to object (at least it was confusing to me).

To help overcome these difficulties the TCL code is well commented and the manual well written. The THINK C runtime debugger is an excellent tool for stepping through code and tracking down errors. It is also a good tool with which to learn exactly how TCL operates.

/********************************************
 dotsTypes.h
Types and constants used throughout Dots
*********************************************/
 
#include <Constants.h>

 /* Include this file only once*/
#define _H_dotsTypes
 /* Rows and columns are 0 thru n. Size of dot
 ** matrix should be even to avoid ties
 ** (n+1 should be even) */
#define kMaxCol  5
#define kMaxRow  5
 /* Player square IDs */
#define  kBoxPlayer1 4
#define kBoxPlayer25
 /* Player IDs */
#define kPlayer1 0
#define kPlayer2 1
 /* Number of sides completed. 4 means completed
 ** by player 1, 5 means completed by player 2 */
typedef short  tBoxState;
 /* Line State - true if line drawn */
typedef Boolean  tLineState;
 /* Line directions (horiz, vert) */
typedef enum {hLine, vLine} tLineDir;

/****** myGlobals.c
Declarations of Global Variables for Dots ******************************************/

 /* The patterns of the 2 players */
Pattern dgPlayerPats[2];

/****************************
 Dots.c
The main file for Dots.
Uses the THINK Class Library
*****************************/

#include “cDotsApp.h”

extern  CApplication *gApplication;

void main()
{
 /* Create Dots application */
 gApplication = new(cDotsApp);
 /* Init Dots application */
 ((cDotsApp *)gApplication)->IDotsApp();     
 gApplication->Run();
 gApplication->Exit();
}

/**************************************
 cDotsApp.h
Application class for Dots game
***************************************/

#define _H_cDotsApp
#include <CApplication.h>

struct cDotsApp : CApplication
{
 /* Initialization Methods */
 void IDotsApp(void);
 
 /* Override Methods */ 
 void SetUpFileParameters(void);
 void DoCommand(long theCommand);
 void CreateDocument(void);
 void OpenDocument(SFReply *macSFReply);
};

/****************************
 cDotsApp.c
SUPERCLASS = CApplication
Dots application methods
*****************************/

#include <CError.h>
#include <Commands.h>
#include <Constants.h>
#include “cDotsApp.h”
#include “cDotsDoc.h”
#include “dotsTypes.h”

 /*** Class Constants ***/
 /* About... string index in STRcommon */
#define I_ABOUT  8
 
 /*** TCL Defined Globals ***/
extern  CError *gError;
extern  OSType gSignature;
 /*** Dots Defined Globals ***/
extern  Pattern  *dgPlayerPats;

/***** I N I T I A L I Z A T I O N *****/

/*** IDotsApp
Initialize the application. At least call the inherited method. Initialize 
application instance variables or globals.
*/
void cDotsApp::IDotsApp(void)
{
 /*Set moreMasters, rainyDayFund, creditLimit*/
 CApplication::IApplication(4, 20480L, 2048L);
 
 /* Set patterns of each player */
 BlockMove(ltGray, &dgPlayerPats[kPlayer1],
 sizeof(Pattern));
 BlockMove(dkGray, &dgPlayerPats[kPlayer2],
 sizeof(Pattern));
}

/*** SetUpFileParameters {OVERRIDE}
Specify the kinds of files the application opens
*/
void cDotsApp::SetUpFileParameters(void)
{
 inherited::SetUpFileParameters();
 gSignature = ‘dots’;/* File creator */
 sfNumTypes = 1;
 sfFileTypes[0] = ‘DATA’;
}

/********** C O M M A N D ********/

/*** DoCommand {OVERRIDE}
Last chance to respond to menu selection. Call the default method for 
standard commands.
*/
void cDotsApp::DoCommand(long theCommand)
{
 switch (theCommand) {
 
 case cmdAbout:
 /* Show the ‘about’ alert */
 gError->PostAlert(STRcommon, I_ABOUT);
 break;
 default:
 inherited::DoCommand(theCommand);
 break;
 }
}

/********** D O C U M E N T ********/

/*** CreateDocument{OVERRIDE}
New was chosen from the File menu. Create a document and send it a NewFile() 
message.
*/
void cDotsApp::CreateDocument()
{
 cDotsDoc *theDocument;
 
 theDocument = new(cDotsDoc);
 theDocument->IDotsDoc(this, TRUE);
 theDocument->NewFile();
}

/*** OpenDocument{OVERRIDE}
Open  was chosen from the File menu. Create a document and send it an 
OpenFile() message. The macSFReply is a good SFReply record that contains 
name and vRefNum of file user chose to open.
*/ 
void cDotsApp::OpenDocument(SFReply *macSFReply)
{
 cDotsDoc *theDocument;
 
 theDocument = new(cDotsDoc);
 theDocument->IDotsDoc(this, TRUE);
 theDocument->OpenFile(macSFReply);
}

/************************************
 cDotsDoc.h
Document class for Dots game
*************************************/

#define _H_cDotsDoc
#include <CDocument.h>

#include “cDotsPane.h”
#include “cScorePane.h”
#include “dotsTypes.h”

 /*** Class Types ***/
 /* Types used to store state of game */
typedef tLineState
 tLines[vLine+1][kMaxRow+1][kMaxCol+1];
typedef tBoxState tBoxes[kMaxRow+1][kMaxCol+1];

struct cDotsDoc : CDocument
{
 /* Instance Variables */
 cDotsPane*fDotsPane;/* Matrix view */
 cScorePane *fScorePane;  /* Score view */
 BooleanfPlayerTurn; /* Whose turn */
 tLines fLines;  /* State of every line */
 tBoxes fBoxes;  /* State of every box */
 DialogPtrprintBanner;  /* Print box */

 /* Initialization Methods */
 void   IDotsDoc(CBureaucrat *aSupervisor,
 Boolean printable);

 /* Override Methods */
 void   NewFile(void);
 void   OpenFile(SFReply *macSFReply);
 BooleanDoSave(void);
 BooleanDoSaveAs(SFReply *macSFReply);
 void   DoRevert(void);
 short  PageCount(void);
 void   AboutToPrint(short *firstPage,
 short *lastPage);
 void   PrintPageOfDoc(short pageNum);
 void   DonePrinting(void);
 /* Auxiliary Methods */
 void BuildWindow(Handle theData);

 /* New Methods */
 void   storeData(Handle theData);
 tBoxStategetBoxState(int row, int col);
 BooleangetLineState(tLineDir direction,
 int row, int col);
 void setLineState(tLineDir direction, int row,
 int col, Boolean thePlayer, Boolean newState);
 BooleanchangeBoxState(int row, int col,
 Boolean thePlayer, Boolean addingLine);
 BooleangetPlayerTurn(void);
};

/************************************
 cDotsDoc.c
 SUPERCLASS = CDocument
Methods for the Dots Document. The document has one window. The window 
containts a scrollable dots pane where the matrix is displayed and a 
non-scrolling score pane which displays the score and current player 
turn.
*************************************/

#include <CApplication.h>
#include <CBartender.h>
#include <CDataFile.h>
#include <CDecorator.h>
#include <CDesktop.h>
#include <CError.h>
#include <Commands.h>
#include <Constants.h>
#include <CPanorama.h>
#include <CPrinter.h>
#include <CScrollPane.h>
#include <TBUtilities.h>

#include “cDotsDoc.h”
#include “cDotsPane.h”
#include “cScorePane.h”
#include “dotsTypes.h”

 /*** Class Constants ***/
#define DLOG_PRINT 2000 /* Print dialog*/
#define kBoxFree 0 /* Initial box state */
#define kDotsTop   44
#define kScoreBottom (kDotsTop)
#define strABORTPRINT9
#define WINDDots 128

 /*** TCL Defined Globals ***/
extern  CApplication *gApplication;
extern  CBartender *gBartender;
extern  CDecorator *gDecorator;
extern  CDesktop *gDesktop;
extern  OSType   gSignature;
extern  CError   *gError;

/***** C O N S T R U C T I O N *****/

/*** IDotsDoc
The document’s initialization method. Initialize documents instance variables. 
Always invoke the default method.
*/
void cDotsDoc::IDotsDoc(
 CBureaucrat*aSupervisor,
 Boolean printable)
{
 int    row;
 int    col;
 
 /* Init doc and make printable */
 CDocument::IDocument(aSupervisor, printable);

 fPlayerTurn = kPlayer1;  /* Player 1 first */

 /* Clear matrix and boxes */
 for (row = 0; row <= kMaxRow; row++)
 {
 for (col = 0; col <= kMaxCol; col++)
 {
 fLines[hLine][row][col] = false;
 fLines[vLine][row][col] = false;
 fBoxes[row][col] = kBoxFree;
 }
 }

}

/********** F I L E **********/

/*** NewFile{OVERRIDE}
When the user chooses New from the File menu, the CreateDocument() method 
in the Application class will send a newly created document this message. 
This method needs to create a new window, ready to work on a new document.
*/
void cDotsDoc::NewFile()
{
 Str255 wTitle;  /* Window title */
 short  wCount;  /* Index of new window*/
 Str63  wNumber; /* Index as a string */

 /* Create empty window */
 BuildWindow(NULL);
 /* Append index to window name */
 itsWindow->GetTitle(wTitle);
 wCount = gDecorator->GetWCount();
 NumToString((long)wCount, wNumber);
 ConcatPStrings (wTitle, (StringPtr) “\p-”);
 ConcatPStrings(wTitle, wNumber);
 itsWindow->SetTitle(wTitle);
 
 itsWindow->Select();/* active window */
}

/*** OpenFile  {OVERRIDE}
When Open  is chosen from the File menu, the OpenDocument() method in 
the Application class will let the user choose a file and then send a 
newly created document this message. The information about the file is 
in the SFReply record. In this method, open the file and display it in 
a window.
*/
void cDotsDoc::OpenFile(SFReply *macSFReply)
{
 CDataFile*theFile;
 Handle theData;
 Str63  theName;
 OSErr  theError;
 
 /* Create a file and send it an SFSpecify()
 **   message to set up the name, volume, and
 ** directory */
 theFile = new(CDataFile);
 theFile->IDataFile();
 theFile->SFSpecify(macSFReply);
 itsFile = theFile;

 theError = theFile->Open(fsRdWrPerm);

 /* If open error, CheckOSError reports
 ** error in alert and returns false */
 if (!gError->CheckOSError(theError)) {
 Dispose(); /* Don’t need doc */
 return;
 }

 /* Read in the data - don’t use rainy
 ** day fund, its ok to fail */
 gApplication->RequestMemory(FALSE, TRUE);
 theFile->ReadAll(&theData);
 gApplication->RequestMemory(FALSE, FALSE);

 /* If not enough memory to open,
 ** post error and dispose doc */
 if (theData == NULL) {
 gError->CheckOSError(MemError());
 Dispose();
 return;
 }
 /* Create empty window */
 BuildWindow(NULL);

 /* Store data as instance variables in
 ** document class */
 storeData(theData);
 DisposHandle(theData);

 /* Put file name into window title */
 itsFile->GetName(theName);
 itsWindow->SetTitle(theName);
 itsWindow->Select(); /* Activate window*/
 /* Select only activates if application
 ** was in background. This may cause
 ** problems (see cDotsDoc section of
 ** article on finder print problem). So
 ** let’s force activate here. */
 ((CDirector*)itsWindow->
 itsSupervisor)->Activate();
}

/*** BuildWindow
Create window  */
void cDotsDoc::BuildWindow (Handle theData)
{
 CScrollPane*theScrollPane;
 Rect   r;
 
 /* Create window */
 itsWindow = new(CWindow);
 itsWindow->IWindow(WINDDots, FALSE,
 gDesktop, this);
 /* Set windows max and min sizes */
 itsWindow->GetInterior(&r);
 SetRect(&r, MIN_WSIZE, MIN_WSIZE,
 r.right - r.left, r.bottom - r.top);
 itsWindow->SetSizeRect(&r);

 /* Create scroll pane in the window */
 theScrollPane = new(CScrollPane);
 theScrollPane->IScrollPane(itsWindow,
 this, 0, 0, 0, 0, sizELASTIC,
 sizELASTIC, TRUE, TRUE, TRUE);
 theScrollPane->FitToEnclFrame(TRUE, TRUE);
 /* Leave space at top of window for
 ** score pane, it does not scroll */
 SetRect(&r, 0, kDotsTop, 0, 0);
 theScrollPane->ChangeSize(&r, FALSE);
 
 /* Create dots pane in scroll pane */
 fDotsPane = new(cDotsPane);
 fDotsPane->IDotsPane(theScrollPane, this,
 0, 0, 0, 0, sizELASTIC, sizELASTIC);
 /* Fit dots pane to interior of scroll
 **pane Interior excludes scroll bars */
 fDotsPane->FitToEnclosure(TRUE, TRUE);
 /* Bounds were initialized to 0’s so
 ** must set bounds to new frame size */
 fDotsPane->GetFrameSpan(&r.right, &r.bottom);
 SetRect(&r, 0, 0, r.right, r.bottom);
 fDotsPane->SetBounds(&r);
 /* Put dots pane in panorama */
 theScrollPane->InstallPanorama(fDotsPane);
 itsGopher = fDotsPane;
 
 /* Create score pane in the window */
 fScorePane = new(cScorePane);
 fScorePane->IScorePane(itsWindow, this,
 0, 0, 0, 0, sizELASTIC, sizFIXEDTOP);
 fScorePane->FitToEnclFrame(TRUE, TRUE);
 /*  Position above dots pane */
 fScorePane->GetFrame(&r);
 SetRect(&r, 0, 0, 0, kScoreBottom - r.bottom);
 fScorePane->ChangeSize(&r, FALSE);
 
 /* Allow for staggered window placement (NOTE:          ** If this routine 
is to be used at all, it
 ** must come after subviews are placed in
 ** window or strange things happen) */
 gDecorator->PlaceNewWindow(itsWindow);
}

/*** DoSave {OVERRIDE}
Save doc data to disk. ‘Save’ chosen from menu. */
Boolean cDotsDoc::DoSave()
{
 OSErr  errCode;
 long   theLength;
 long   totalLength;
 int    x;
 
 if (itsFile == NULL)
 return(DoSaveFileAs());
 else
 {
 /* Go to beginning of file */
 errCode = ((CDataFile *)itsFile)->
 SetMark(0, fsFromStart);
 if (errCode != noErr)
 return(gError->CheckOSError(errCode));
 
 /* Write player turn */
 if (fPlayerTurn == kPlayer1)
 x = 1;
 else
 x = 2;
 errCode = ((CDataFile*)itsFile)->
 WriteSome((Ptr)&x, 2L);
 if (errCode != noErr)
 return(gError->CheckOSError(errCode));
 totalLength = 2;
 
 /* Write line states */
 theLength = sizeof(tLines);
 errCode = ((CDataFile*)itsFile)->
 WriteSome((Ptr)fLines, theLength);
 if (errCode != noErr)
 return(gError->CheckOSError(errCode));
 totalLength += theLength;
 
 /* Write box states */
 theLength = (long)sizeof(tBoxes);
 errCode = ((CDataFile*)itsFile)->
 WriteSome((Ptr)fBoxes, theLength);
 if (errCode != noErr)
 return(gError->CheckOSError(errCode));
 totalLength += theLength;
 
 /* Set file length in case previous
 ** contents was bigger. Can’t happen
 ** in Dots but left in for example*/
 errCode = ((CDataFile *)itsFile)->
 SetLength(totalLength);
 if (errCode == noErr)    /* Force write*/
 errCode = FlushVol(“”, itsFile->volNum);
 if (errCode != noErr)  
 return(gError->CheckOSError(errCode));

 dirty = FALSE;  /* Document now clean */
 gBartender->DisableCmd(cmdSave);
 return(TRUE);
 }
}

/*** DoSaveAs {OVERRIDE}
Save As  chosen from File menu. The default DoCommand() method for documents 
sends a DoSaveFileAs() message which displays a standard put file dialog 
and sends this message. The SFReply record contains all the information 
about the file to create.
*/
Boolean cDotsDoc::DoSaveAs(SFReply *macSFReply)
{
 if (itsFile != NULL)/*Close already open file*/ 
 itsFile->Dispose(); 

 /* Display standard file dialog and
 ** create new file */
 itsFile = new(CDataFile);
 ((CDataFile *)itsFile)->IDataFile();
 itsFile->SFSpecify(macSFReply);
 itsFile->CreateNew(gSignature, ‘DATA’);
 itsFile->Open(fsRdWrPerm);

 /* Set window title to new file name */
 itsWindow->SetTitle(macSFReply->fName);

 return( DoSave() ); /* save normally */
}

/*** DoRevert {OVERRIDE}
Close current file (without writing out) and read the last saved version 
of file. */
void cDotsDoc::DoRevert()
{
 Point  homePos;
 Handle theData;
 
 /* Make sure file is open */
 if (itsFile == NULL)
 return;/* Could use an error message here */

 /* Close file and reopen */
 itsFile->Close();
 if (!gError->CheckOSError(itsFile->
 Open(fsRdWrPerm)))
 return;

 /* Get rid of last undo */
 if (lastTask != NULL) {
 lastTask->Dispose();
 lastTask = NULL;
 }
 
 /* Read in the data */
 /* Don’t tap fund, can fail */
 gApplication->RequestMemory(FALSE, TRUE);
 ((CDataFile *)itsFile)->ReadAll(&theData);
 gApplication->RequestMemory(FALSE, FALSE);

 /* Check to see if data was read */
 if (theData == NULL) {
 gError->CheckOSError(MemError());
 Dispose();
 return;
 }
 
 /* Store the data into the document */
 storeData(theData);
 DisposHandle(theData);

 /* Set panoramas to home position */
 ((CPanorama*)fDotsPane)->
 GetHomePosition(&homePos);
 ((CPanorama*)fDotsPane)->
 ScrollTo(homePos, FALSE);
 
 /* Force redraw of panes on update */
 fDotsPane->Refresh();
 fScorePane->Refresh();
 
 dirty = FALSE;
}

/*** storeData
Store the file data as as instance variables in the document */
void cDotsDoc::storeData(Handle theData)
{
 int    x;
 long count;

 HLock(theData);
 /* read whose turn it is */
 BlockMove(*theData, &x, 2);
 if (x == 1)
 fPlayerTurn = kPlayer1;
 else
 fPlayerTurn = kPlayer2;
 
 /* read the line states */
 x = sizeof(tLines);
 count = (long)x;
 BlockMove(*theData+2, fLines, count);
 
 /* read the box states */
 count = (long)sizeof(tBoxes);
 BlockMove(*theData+x+2, fBoxes, count);
 HUnlock(theData);
}

/******* P R I N T I N G ********/
/*** PageCount {OVERRIDE}
Return the number of pages in a Document */
short cDotsDoc::PageCount()
{
 long pixWidth;
 long pixHeightDots;
 long pixHeightScore;
 
 /* Get dimensions of dots pane */
 fDotsPane->GetPixelExtent(&pixWidth,
 &pixHeightDots);
 /* Get dimensions of score pane */
 fScorePane->GetPixelExtent(&pixWidth,
 &pixHeightScore);
 /* Pages have a fixed pixel height */
 return((pixHeightDots + pixHeightScore) /
 pageHeight + 1);
}
 
/*** AboutToPrint{OVERRIDE}
The specified range of pages is about to be printed, display print banner 
and let panes know they are about to be printed.
*/
void  cDotsDoc::AboutToPrint(
 short  *firstPage,
 short  *lastPage)
{
 Str255 docName;
 
 inherited::AboutToPrint(firstPage, lastPage);
 
 /* Let panes know they’re to print */
 fDotsPane->AboutToPrint(firstPage, lastPage);
 fScorePane->AboutToPrint(firstPage, lastPage);
 
 /* Let user know about printing */
 PositionDialog(‘DLOG’, DLOG_PRINT);
 printBanner = GetNewDialog(DLOG_PRINT,
 NULL, (WindowPtr) -1L);
 DrawDialog(printBanner);
 
 /* Doc name for LaserWriter status */
 GetName(docName); 
 SetWTitle(printBanner, docName);
}
 
/*** PrintPageOfDoc {OVERRIDE}
Print a single page of the document. */
void  cDotsDoc::PrintPageOfDoc(short pageNum)
{
 fDotsPane->PrintPage(pageNum, pageWidth,
 pageHeight);
 fScorePane->PrintPage(pageNum, pageWidth,
 pageHeight);
}
 
/***  DonePrinting {OVERRIDE}
Print loop has been completed. */
void  cDotsDoc::DonePrinting()
{
 short  itemType;
 Handle item;
 Rect   box;
 Str63  abortStr;
 long   ticks;
 
 /* If print aborted, display message */
 if (PrError() == iPrAbort) {
 GetDItem(printBanner, 1, &itemType, &item, &box);
 GetIndString(abortStr, STRcommon, strABORTPRINT);
 SetIText(item, abortStr);
 Delay(120, &ticks);
 }
 DisposDialog(printBanner);

 fDotsPane->DonePrinting();
 fScorePane->DonePrinting();

 /* Changing cursor to arrow. May not
 ** be returning to main event loop */
 SetCursor(&arrow);
}

/********* G A M E ***********/
/** getBoxState
Get state of a box. 3 or less means not yet completed, 4 means completed 
by player 1, 5 means completed by player 2. */
tBoxState cDotsDoc::getBoxState(int row, int col)
{
 return(fBoxes[row][col]);
}

/*** changeBoxState
Change state of box */
Boolean cDotsDoc::changeBoxState(int row, 
 int col, Boolean thePlayer,
 Boolean addingLine)
{
 tBoxStateboxState;
 Booleanchanged;
 int    delta;
 
 changed = false; /* Assume same box owner */
 boxState = fBoxes[row][col];
 
 if (addingLine) {
 /* Is box completed? */
 if (boxState < kBoxPlayer1) {
 boxState = boxState + 1;
 if (boxState == kBoxPlayer1) {
 changed = true; /* box ownership changed */
 
 /* Did player 2 complete box? */
 if (thePlayer == kPlayer2)
 boxState = kBoxPlayer2;
 
 /* Change box and score */
 fDotsPane->invalBox(row, col);
 fScorePane->invalScore(thePlayer);
 }
 }
 } else { /* Remove line */
 /* If box completed redraw box and score */
 if (boxState >= kBoxPlayer1) {
 changed = true; /* box ownership changed */
 fDotsPane->invalBox(row, col);
 fScorePane->invalScore(thePlayer);
 }
 
 /* Update state of box */
 if (boxState == kBoxPlayer2)
 boxState = 3;
 else
 boxState = boxState - 1;
 }
 fBoxes[row][col] = boxState;
 return changed;
}

/*** getLineState
Get state of line. True means there is a line in direction from the input 
dot, false no line. */
tLineState cDotsDoc::getLineState(tLineDir direction, int row, int col)
{
 return(fLines[direction][row][col]);
}

/*** setLineState
Change state of line. Set to true for a line or false for no line. Set 
to next player turn if
box ownership did not change. */
void cDotsDoc::setLineState(tLineDir direction,
 int row, int col, Boolean thePlayer,
 tLineState newState)
{
 BooleanaChange, bChange;
 BooleanoldState;
 
 oldState = getLineState(direction, row, col);
 /* Assume box ownership did not change */
 aChange = bChange = false;

 if (oldState != newState) {
 fLines[direction][row][col] = newState;
 /* must redraw line */
 fDotsPane->invalLine(direction, row, col);
 
 /* Change state of any box to right
 ** or under line */
 if ((row < kMaxRow) && (col < kMaxCol))
 aChange = changeBoxState (row, col,
 thePlayer,newState);
 /* Change state of any box to left or
 ** above line */
 if (direction == hLine) {
 if (row > 0)
 bChange = changeBoxState(row-1, col,
 thePlayer, newState);
 } else {
 if (col > 0)
 bChange = changeBoxState(row, col-1,
 thePlayer, newState);
 }
 /* If box ownership did not change it is
 ** other player’s turn */
 if (!aChange && !bChange) {
 fPlayerTurn = !fPlayerTurn;
 fScorePane->setTurn(fPlayerTurn);
 }
 }
}

/*** getPlayerTurn
Identify player whose move it is */
Boolean cDotsDoc::getPlayerTurn()
{
 return(fPlayerTurn);
}

/**************************************
 cScorePane.h
Class for score view
****************************************/
#define _H_cScorePane
#include <CPane.h>

 /*** Class Types ***/
typedef int tScores[2];

struct cScorePane : CPane
{
 /* Instance variables */
 BooleanfPlayerHilite;  /* Player highlighted */
 
 /* Initialization Methods */
 void IScorePane(CView *anEnclosure, 
 CBureaucrat *aSupervisor, short aWidth, 
 short aHeight, short aHEncl, short aVEncl,
 SizingOption aHSizing, SizingOption aVSizing);

 /* Override Methods */
 void Draw(Rect *area);

 /* New Methods */
 void calculateScore(tScores score);
 void   getPlayerRect(Boolean thePlayer, Rect *r);
 void invalScore(Boolean thePlayer);
 void setTurn(Boolean thePlayer);
};

/**************************************
 cScorePane.c
 SUPERCLASS = CPane
Methods for the score pane. The score pane displays the current player 
turn and calculates and displays the game score.
****************************************/
#include “cDotsDoc.h”
#include “cScorePane.h”
#include “dotsTypes.h”
#include <Global.h>

 /*** Class Constants ***/
#define kScorePlace30
#define kHalfSpacing (kScorePlace / 2)
#define kScoreSpacing(3*kScorePlace)
#define kScoreInset6

 /*** Globals ***/
extern  Pattern  *dgPlayerPats;

/******** C O N S T R U C T I O N ********/
/*** IScorePane
Initialize instance variables */
void cScorePane::IScorePane(CView *anEnclosure,    CBureaucrat *aSupervisor, 
short aWidth,
 short aHeight, short aHEncl, short aVEncl,
 SizingOption aHSizing, SizingOption aVSizing)
{
 /* Highlighted score is player to move next */
 fPlayerHilite = 
 ((cDotsDoc *)aSupervisor)->getPlayerTurn();
 
 /* Call inherited method */
 IPane(anEnclosure, aSupervisor, aWidth, aHeight,
 aHEncl, aVEncl, aHSizing, aVSizing);
}

/******** D R A W I N G **********/
/*** Draw {OVERRIDE}
The area parameter gives the portion of pane that needs to be redrawn. 
Area is in frame coordinates. */
void cScorePane::Draw(Rect *area)
{
 tScoresscore;
 Booleanplayer;
 Rect r;
 Str255 s;
 int    offset;
 
 TextFont(monaco);
 TextFace(NULL);
 TextSize(9);
 PenNormal();
 
 /* Draw line across bottom of pane */
 GetFrame(&r);
 MoveTo(r.left, r.bottom-1);
 LineTo(r.right, r.bottom-1);
 
 PenSize(kScoreInset, kScoreInset);
 calculateScore(score);
 
 for (player = kPlayer1; player <= kPlayer2;
 player++) {
 NumToString(score[player], &s);
 
 /* Center score in player’s rectangle */
 getPlayerRect(player, &r);
 offset = (r.right-r.left-StringWidth(s)) / 2;
 MoveTo(r.left + offset, r.bottom - 3);
 DrawString(s);
 InsetRect(&r, -kScoreInset, -kScoreInset);
 PenPat(&dgPlayerPats[player]);
 FrameRect(&r);
 }
 /* Hilight/Unhighlight player score */
 getPlayerRect(fPlayerHilite, &r);
 InvertRect(&r);
}

/*** calculateScore
Computes the current score */
void cScorePane::calculateScore(tScores theScore)
{
 tBoxStateboxState;
 int    row, col;
 
 theScore[kPlayer1] = 0;
 theScore[kPlayer2] = 0;
 
 /* Check box states for entire matrix */
 for (row = 0; row < kMaxRow; row++) {
 for (col = 0; col < kMaxCol; col++) {
 boxState = ((cDotsDoc *)
 itsSupervisor)->getBoxState(row, col);
 switch (boxState) {
 case kBoxPlayer1:
 theScore[kPlayer1] =theScore[kPlayer1]+1;
 break;
 case kBoxPlayer2:
 theScore[kPlayer2] =theScore[kPlayer2]+1;
 break;
 }
 }
 }
}

/*** getPlayerRect
Gets rectangle in which to draw player’s score */
void cScorePane::getPlayerRect(Boolean thePlayer, Rect *r)
{
 SetRect(r, kScorePlace, kHalfSpacing,
 2*kScorePlace, kScorePlace);
 if (thePlayer == kPlayer2)
 OffsetRect(r, kScoreSpacing, 0);
}

/*** invalScore
Invalidate player score rectangle so it will redraw on update
*/
void cScorePane::invalScore(Boolean thePlayer)
{
 Rect r;
 
 /* Get display rectangle for player score */
 getPlayerRect(thePlayer, &r);

 /* Set score pane’s port before invalidation
 ** otherwise draw in a different port */
 Prepare();
 InvalRect(&r);
}

/*** setTurn
Highlight current player’s turn. Actually draw on screen since it is 
easy to update turn indicator. */
void cScorePane::setTurn(Boolean thePlayer)
{
 Rect r;
 
 if (thePlayer != fPlayerHilite) {
 Prepare(); /* Sets up pane for drawing */
 
 /* Unhighlight currently highlighted score */
 getPlayerRect(fPlayerHilite, &r);
 InvertRect(&r);
 /* Highlight other player score */
 getPlayerRect(thePlayer, &r);
 InvertRect(&r);
 
 fPlayerHilite = thePlayer;
 }
}

/**************************************
 cDotsPane.h
Class for Dot Matrix View
****************************************/
#define _H_cDotsPane
#include <CPanorama.h>
#include “dotsTypes.h”

struct cDotsPane : CPanorama
{
 /* Initialization Methods */
 void   IDotsPane(CView *anEnclosure,
 CBureaucrat *aSupervisor, short aWidth,
 short aHeight, short aHEncl, short aVEncl,
 SizingOption aHSizing, SizingOption aVSizing);

 /* Override Methods */
 void Draw(Rect *area);
 void DoClick(Point hitPt, short modifierKeys,
 long when);

 /* New Methods */
 void drawCorner(int row, int col);
 void box2Rect(int row, int col, Rect *r);
 void dot2Rect(int row, int col, Rect *r);
 void dotCenter(int row, int col, Point *center);
 void line2Pt(tLineDir direction, int row,
 int col, Point *pt);
 void line2Rect(tLineDir direction,
 int row, int col, Rect *r);
 Booleanpt2Line(Point pt, tLineDir
 *direction, int *row, int *col);
 void invalLine(tLineDir direction,
 int row, int col);
 void   invalBox(int row, int col);
};

/*************************************************
 cDotsPane.c

 SUPERCLASS = CPanorama
Methods for the dot matrix view. The dot matrix view contains the grid 
where the game is actually played. Line segments are selected by clicking 
on the area between two horizontal or two vertical dots. When the four 
line segments surrounding a grid cell are selected, the pattern associated 
with the  player completing the ‘box’ is placed inside the cell.
*************************************************/
#include <CScrollPane.h>
#include “cDotsDoc.h”
#include “cDotsPane.h”
#include “cDotsTask.h”
#include “dotsTypes.h”

 /*** Class Constants ***/
#define kHalfDotSize 3
#define kFullDotSize (2*kHalfDotSize+1)
#define kHalfLineSize1
#define kFullLineSize(2*kHalfLineSize+1)
#define kLineOffset
 ((kFullDotSize - kFullLineSize) / 2)
#define kDotSpacing(4*kFullDotSize)
#define UNDOMoveIndex1

 /*** Globals ***/
extern Pattern *dgPlayerPats;

/******* C O N S T R U C T I O N *******/
/*** IDotsPane {OVERRIDE}
Initialize instance variables */
void cDotsPane::IDotsPane(CView *anEnclosure,      CBureaucrat *aSupervisor, 
short aWidth,
 short aHeight, short aHEncl, short aVEncl,
 SizingOption aHSizing, SizingOption aVSizing)
{
 IPanorama(anEnclosure, aSupervisor, aWidth,
 aHeight, aHEncl, aVEncl, aHSizing, aVSizing);
 /* Set scrolling scales */
 SetScales(kDotSpacing, kDotSpacing);
 SetWantsClicks(true); /*For line select*/
}

/******** D R A W I N G **********/
/*** Draw {OVERRIDE}
Draw the entire Dot Matrix View. Area input paramater is ignored. */
void cDotsPane::Draw(Rect *area)
{
 int  row, col;

 for (row = 0; row <= kMaxRow; row++)
 for (col = 0; col <= kMaxCol; col++)
 drawCorner(row, col);
}

/*** drawCorner
Draws the grid dots, grid lines and fills in player boxes */
void cDotsPane::drawCorner(int row,
 int col)
{
 Rect   r;
 tBoxStateboxState;
 
 if ((row < kMaxRow) && (col < kMaxCol)) {
 PenNormal();
 box2Rect(row, col, &r);  /* Get box */
 
 /* first draw the box if needed */
 boxState = ((cDotsDoc*)itsSupervisor)->
 getBoxState(row, col);
 if (boxState == kBoxPlayer1) {
 FillRect(&r, &dgPlayerPats[kPlayer1]);
 FrameRect(&r);
 }
 else if (boxState == kBoxPlayer2) {
 FillRect(&r, &dgPlayerPats[kPlayer2]);
 FrameRect(&r);
 }
 }
 dot2Rect(row, col, &r);  /* Get dot rectangle */
 
 PenNormal();
 PenSize(kFullLineSize, kFullLineSize);
 
 /* Draw line down from dot (if any) */
 if ((row < kMaxRow) &&
 (((cDotsDoc*)itsSupervisor)->
 getLineState(vLine, row, col))) {
 MoveTo(r.left+kLineOffset, r.top+kLineOffset);
 Line(0, kDotSpacing);
 }
 /* Draw line right from dot (if any) */
 if ((col < kMaxCol) &&
 (((cDotsDoc*)itsSupervisor)->
 getLineState(hLine, row, col))) {
 MoveTo(r.left+kLineOffset, r.top+kLineOffset);
 Line(kDotSpacing, 0);
 }
 FillOval(&r, black);/* Draw the dot */
}

/*** box2Rect
Returns the rectangle defining the box */
void cDotsPane::box2Rect(int row, int col,Rect *r)
{
 Point  center;
 
 dotCenter(row, col, &center);
 SetRect(r, 0, 0, kDotSpacing+1, kDotSpacing+1);
 OffsetRect(r, center.h, center.v);
 InsetRect(r, kHalfLineSize+4, kHalfLineSize+4);
}

/*** dot2Rect
Returns the rectangle defining a dot */
void cDotsPane::dot2Rect(int row, int col, Rect *r)
{
 Point  center;
 
 dotCenter(row, col, &center);
 SetRect(r, -kHalfDotSize, -kHalfDotSize,
 kHalfDotSize+1, kHalfDotSize+1);
 OffsetRect(r, center.h, center.v);
}

/*** dotCenter
Returns center coord of a given dot */
void cDotsPane::dotCenter(int row, int col,
 Point *center)
{
 SetPt(center, kDotSpacing*(col+1),
 kDotSpacing*(row+1));
}

/*** line2Pt
Converts coord of line to a pt on grid */
void cDotsPane::line2Pt(tLineDir direction,
 int row, int col, Point *pt)
{
 /* Set pt to center of dot */
 dotCenter(row, col, pt);
 /* Offset it to be on requested line */
 if (direction == hLine)
 pt->h = pt->h + kHalfDotSize + 1;
 else
 pt->v = pt->v + kHalfDotSize + 1;
}

/*** line2Rect
Convert coordinates of a line to a rectangle. Used for highlighting while 
tracking and for invalidating when line is chosen. */    
void cDotsPane::line2Rect(tLineDir direction,
 int row, int col, Rect *r)
{
 Point  center;
 
 /* Set pt to center of dot */
 dotCenter(row, col, &center);
 /* Offset it to be on requested line */
 if (direction == hLine)
 SetRect(r, 0, -kHalfLineSize,
 kDotSpacing+1, kHalfLineSize+1);
 else
 SetRect(r, -kHalfLineSize, 0,
 kHalfLineSize+1, kDotSpacing+1);
 OffsetRect(r, center.h, center.v);
}

/*** pt2Line
Convert point in grid line coordinates */
Boolean cDotsPane::pt2Line(Point pt,
 tLineDir *direction, int *row, int *col)
{
 Booleanok;
 int    modH;
 int    modV;
 
 ok = FALSE;
 
 /* Offset pt by slop allowed before line */
 pt.h = pt.h + kFullDotSize;
 pt.v = pt.v + kFullDotSize;
 
 /* Determine values of row and col */
 *row = pt.v / kDotSpacing - 1;
 *col = pt.h / kDotSpacing - 1;

 /* Check if point selected is on grid*/
 ok = (*row >= 0) && (*row <= kMaxRow) &&
 (*col >= 0) && (*col <= kMaxCol);
 if (ok) {
 /* Consider taking pt.v % kDotSpacing. In
 ** order to be on a horizontal line, this
 ** must be in the range 0..2*kFullDotSize */
 modH = pt.h % kDotSpacing;
 modV = pt.v % kDotSpacing;
 
 /* Pick the closest line to point */
 if (modH < modV) {
 *direction = vLine;
 ok = (*row < kMaxRow) && 
 (modH <= 2*kFullDotSize);
 } else {
 *direction = hLine;
 ok = (*col < kMaxCol) && 
 (modV <= 2*kFullDotSize);
 }
 }
 if (!ok)
 *row = *col = -1;
 return(ok);
}

/*** invalLine
Invalidate line so it redraws on update */
void cDotsPane::invalLine(tLineDir
 direction, int row, int col)
{
 Rect r;
 
 /* Get line’s rectangle */
 line2Rect(direction, row, col, &r); 
 /* Set to dots matrix view’s port
 ** before invalidation otherwise may be
 ** drawing in a different port */
 Prepare();
 InvalRect(&r);
}

/*** invalBox
Invalidate box so it redraws on update */
void cDotsPane::invalBox(int row, int col)
{
 Rect r;

 box2Rect(row, col, &r);  /* Get box’s rectangle */      
 /* Set to dots matrix view’s port
 ** before invalidation otherwise may be
 ** drawing in a different port */
 Prepare();
 InvalRect(&r);
}

/*** M O U S E    T R A C K I N G *****/
/*** DoClick {OVERRIDE}
Respond to a mouse down in the dots pane
*/ 
void cDotsPane::DoClick(Point hitPt,
 short modifierKeys, long when)
{
 Rect   bounds;
 cDotsTask*theDotsTask;
 short  theHScale;
 short  theVScale;

 /* Set up a task both for tracking the
 ** mouse and for do/undo */
 theDotsTask = new(cDotsTask);
 theDotsTask->IDotsTask(UNDOMoveIndex,
 this, (cDotsDoc *)itsSupervisor);
 
 /* Set up a pin rect for autoScroll
 ** based on panorama bounds */
 GetScales(&theHScale, &theVScale);
 GetBounds(&bounds);
 bounds.top *= theVScale;
 bounds.left *= theHScale;
 bounds.bottom *= theVScale;
 bounds.right *= theHScale;
 
 TrackMouse(theDotsTask, hitPt, &bounds);

 /*Only save for undo if move is valid*/
 if (theDotsTask->validMove())
   /* Let doc save for undo */
 itsSupervisor->Notify(theDotsTask);
 else
 theDotsTask->Dispose();
}

/**********************************************
 cDotsTask.h
Class for handling dot matrix view mouse downs
**********************************************/
#define _H_cDotsTask

#include “cDotsDoc.h”
#include “cDotsPane.h”
#include “dotsTypes.h”

struct cDotsTask : CMouseTask
{
 /** Instance Variables **/
 cDotsDoc *fDotsDoc; /* Document of pane */
 cDotsPane*fDotsPane;/* Pane of mouse action */
 BooleanfPlayerMoved;/* Player making move */
 BooleanfValidMove;/* True if previously
 ** unselected line is selected */
 tLineDir fDirection;/* These 3 fields record */
 int    fRow;    /* the line chosen by    */
 int    fCol;    /* the user              */
 
 /** Initialization Methods **/
 void   IDotsTask(short aNameIndex, 
 cDotsPane *theDotsPane, cDotsDoc *theDotsDoc);
 
 /** Override Methods **/
 void   BeginTracking(Point *startPt);
 void   KeepTracking(Point *currPt, 
 Point *prevPt, Point *startPt);
 void   EndTracking(Point *currPt, Point *prevPt,
 Point *startPt);
 void   Do(void);
 void   Undo(void);
 void   Redo(void);

 /** New Methods **/
 BooleanvalidMove(void);
};

/***************************************
 cDotsTask.c
 SUPERCLASS = CMouseTask
Handles mouse clicks in the dots pane.
Also provides undo/redo capability.
****************************************/
#include “cDotsTask.h”
#include “dotsTypes.h”

/******** C O N S T R U C T I O N ********/

/*** IDotsTask
Initialize a DotsTask object
*/
void  cDotsTask::IDotsTask(short aNameIndex,
 cDotsPane*theDotsPane, cDotsDoc *theDotsDoc)
{
 inherited::IMouseTask(aNameIndex);

 /* Initialize instance variables */
 fDotsPane = theDotsPane;
 fDotsDoc = theDotsDoc;
 fPlayerMoved = fDotsDoc->getPlayerTurn();
 fValidMove = false;
 fDirection = hLine;
 fRow = -1;
 fCol = -1;
}

/*****  M O U S E   T R A C K I N G ********/
/*** BeginTracking {OVERRIDE}
Mouse tracking starting. Adjust starting pt. */
void  cDotsTask::BeginTracking(
 Point  *startPt)
{
 
 tLineDir direction;
 int    row;
 int    col;
 Rect   r;

 /* See if pt is on a line */ 
 if (fDotsPane->
 pt2Line(*startPt, &direction, &row, &col)) {
 /* Convert line coords to a pt on the grid */
 fDotsPane->line2Pt(direction, 
 row, col, startPt);
 /* If line unoccupied then fill it */
 if (!fDotsDoc->
 getLineState(direction, row, col)) {
 fDotsPane->line2Rect(direction, row, col,&r);
 FillRect(&r, black);
 }
 }
}

/*** KeepTracking {OVERRIDE}
Continuous mouse tracking while the mouse button is down. Draw only when 
the mouse moves or the panorama autoscrolls. */
void  cDotsTask::KeepTracking(
 Point  *currPt,
 Point  *prevPt,
 Point  *startPt)
{
 
 int    col;
 tLineDir direction;
 BooleaneraseIt;
 Rect   r;
 int    row;

 /* Only track if not autoScrolling */
 if (!fDotsPane->AutoScroll(*currPt)) {
 /* assume won’t erase line previously chosen
 ** by this mouse down */
 eraseIt = false;
 
 /* Check for current pt on a line */
 if (fDotsPane->
 pt2Line(*currPt, &direction, &row, &col)) {
 /* Convert line coords to a pt on grid */
 fDotsPane->
 line2Pt(direction, row, col, currPt);
 /* Check for new chosen line */
   if (!EqualPt(*currPt, *startPt)) {
 /* If new chosen line unoccupied 
 ** then fill it */
 if (!fDotsDoc->
 getLineState(direction, row, col)) {
 fDotsPane->line2Rect(direction,
 row, col, &r);
 FillRect(&r, black);
 }
 eraseIt = true; /* erase previous line */
 }
 } else /* Erase any previously chosen line */
 eraseIt = true;
 
 /* If no longer over a previously chosen line
 ** then erase it */
 if (eraseIt) {
 /* Check if start pt on a line */
 if (fDotsPane->
 pt2Line(*startPt, &direction, &row, &col)){
 /* Only erase if start line unoccupied */
 if (!fDotsDoc->
 getLineState(direction, row, col)) {
 /* erase the previouly chosen line */
 fDotsPane->
 line2Rect(direction, row, col, &r);
 FillRect(&r, white);
 /* redraw both dots */
 fDotsPane->dot2Rect(row, col, &r);
 FillOval(&r, black);
 if (direction == hLine)
 col++;
 else
 row++;
 fDotsPane->dot2Rect(row, col, &r);
 FillOval(&r, black);
 }
 }
 /* Reset startPt to new point */
 SetPt(startPt, currPt->h, currPt->v);
 }
 }
}

/*** EndTracking {OVERRIDE}
Mouse down tracking has ended because the user has released the mouse 
button. Its now time to complete the move and set up the undo. */
void  cDotsTask::EndTracking(Point *currPt, 
 Point   *prevPt, Point *startPt)
{
 tLineDir direction;
 int    row;
 int    col;

 /* The only valid move is selection of a previously unselected line 
so check if selected point is on line and the line is unoccupied */ 
 if ((fDotsPane->
 pt2Line(*prevPt, &direction, &row, &col)) &&
 (!fDotsDoc->
 getLineState(direction, row, col)))
 {
 fValidMove = true;
 fDirection = direction;
 fRow = row;
 fCol = col;
 
 Do();  /* Act on users choice */
 }
}

/*********** D O   /   U N D O ***********/
/*** Do  {OVERRIDE}
Perform a dots pane task, user chose a line */
void  cDotsTask::Do()
{
 fDotsDoc->setLineState(fDirection, fRow, fCol,
 fPlayerMoved, true);
}

/*** Undo {OVERRIDE}
Undo an action performed by a dots pane task */
void  cDotsTask::Undo()
{
 fDotsDoc->setLineState(fDirection, fRow, fCol,
 fPlayerMoved, false);
}

/*** Redo {OVERRIDE}
Redo an action performed by a dots pane task */
void  cDotsTask::Redo()
{
 fDotsDoc->setLineState(fDirection, fRow, fCol,
 fPlayerMoved, true);
}

/*********** A C C E S S ***********/
/*** validMove
Return value of fValidMove instance variable. The value will be true 
if previously unselected line was selected during this mouse task, else 
false. */
Boolean cDotsTask::validMove()
{
 return(fValidMove);
}

 
AAPL
$97.49
Apple Inc.
+0.46
MSFT
$44.50
Microsoft Corpora
+0.10
GOOG
$588.94
Google Inc.
-4.41

MacTech Search:
Community Search:

Software Updates via MacUpdate

TinkerTool 5.3 - Expanded preference set...
TinkerTool is an application that gives you access to additional preference settings Apple has built into Mac OS X. This allows to activate hidden features in the operating system and in some of the... Read more
Audio Hijack Pro 2.11.0 - Record and enh...
Audio Hijack Pro drastically changes the way you use audio on your computer, giving you the freedom to listen to audio when you want and how you want. Record and enhance any audio with Audio Hijack... Read more
Intermission 1.1.1 - Pause and rewind li...
Intermission allows you to pause and rewind live audio from any application on your Mac. Intermission will buffer up to 3 hours of audio, allowing users to skip through any assortment of audio... Read more
Autopano Giga 3.6 - Stitch multiple imag...
Autopano Giga allows you to stitch 2, 20, or 2,000 images. Version 3.0 integrates impressive new features that will definitely make you adopt Autopano Pro or Autopano Giga: Choose between 9... Read more
Airfoil 4.8.7 - Send audio from any app...
Airfoil allows you to send any audio to AirPort Express units, Apple TVs, and even other Macs and PCs, all in sync! It's your audio - everywhere. With Airfoil you can take audio from any... Read more
Microsoft Remote Desktop 8.0.8 - Connect...
With Microsoft Remote Desktop, you can connect to a remote PC and your work resources from almost anywhere. Experience the power of Windows with RemoteFX in a Remote Desktop client designed to help... Read more
xACT 2.30 - Audio compression toolkit. (...
xACT stands for X Aaudio Compression Toolkit, an application that encodes and decodes FLAC, SHN, Monkey’s Audio, TTA, Wavpack, and Apple Lossless files. It also can encode these formats to MP3, AAC... Read more
Firefox 31.0 - Fast, safe Web browser. (...
Firefox for Mac offers a fast, safe Web browsing experience. Browse quickly, securely, and effortlessly. With its industry-leading features, Firefox is the choice of Web development professionals... Read more
Little Snitch 3.3.3 - Alerts you to outg...
Little Snitch gives you control over your private outgoing data. Track background activityAs soon as your computer connects to the Internet, applications often have permission to send any... Read more
Thunderbird 31.0 - Email client from Moz...
As of July 2012, Thunderbird has transitioned to a new governance model, with new features being developed by the broader free software and open source community, and security fixes and improvements... Read more

Latest Forum Discussions

See All

Garfield: Survival of the Fattest Coming...
Garfield: Survival of the Fattest Coming to iOS this Fall Posted by Jennifer Allen on July 25th, 2014 [ permalink ] Who loves lasagna? Me. Also everyone’s favorite grumpy fat cat, Garfield. | Read more »
Happy Flock Review
Happy Flock Review By Andrew Fisher on July 25th, 2014 Our Rating: :: HERD IT ALL BEFOREUniversal App - Designed for iPhone and iPad Underneath the gloss of Happy Flock’s visuals is a game of very little substance. It’s cute, but... | Read more »
Square Register Updates Adds Offline Pay...
Square Register Updates Adds Offline Payments Posted by Ellis Spice on July 25th, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Looking For Group – Hearthstone’s Curse...
For the first time since its release (which has thankfully been a much shorter window for iPad players than their PC counterparts), Blizzard’s wildly successful Hearthstone: Heroes of Warcraft CCG is sporting some brand new content: the single... | Read more »
Poptile Review
Poptile Review By Jennifer Allen on July 25th, 2014 Our Rating: :: SIMPLY FUNUniversal App - Designed for iPhone and iPad Simple yet a little bit glorious, Poptile is a satisfying entertaining puzzle game with oodles of the ‘one... | Read more »
Modern Combat 5: Blackout Review
Modern Combat 5: Blackout Review By Brittany Vincent on July 25th, 2014 Our Rating: :: LESS QQ, MORE PEW PEWUniversal App - Designed for iPhone and iPad The fifth entry into the blockbuster Modern Combat series is what mobile... | Read more »
Watch and Share Mobile Gameplay Videos W...
Watch and Share Mobile Gameplay Videos With Kamcord Posted by Jennifer Allen on July 25th, 2014 [ permalink ] iPhone App - Designed for the iPhone, compatible with the iPad | Read more »
THE KING OF FIGHTERS '98 (Games)
THE KING OF FIGHTERS '98 1.0 Device: iOS Universal Category: Games Price: $3.99, Version: 1.0 (iTunes) Description: Series’ masterpiece “KOF ’98” finally joins the battle on iPhone! FEATURES:■ The best game balance in the “KOF”... | Read more »
LEX Goes Free For One Day In Honor of Ne...
LEX Goes Free For One Day In Honor of New Update Posted by Jennifer Allen on July 24th, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Thomas Was Alone Goes Universal, Slashes...
Thomas Was Alone Goes Universal, Slashes Price to $3.99 Posted by Ellis Spice on July 24th, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »

Price Scanner via MacPrices.net

Mac minis on sale for $100 off MSRP, starting...
Best Buy has Mac minis on sale for $100 off MSRP. Choose free shipping or free instant local store pickup. Prices are for online orders only, in-store prices may vary: 2.5GHz Mac mini: $499.99 2.3GHz... Read more
Global Tablet Market Grows 11% in Q2/14 Notwi...
Worldwide tablet sales grew 11.0 percent year over year in the second quarter of 2014, with shipments reaching 49.3 million units according to preliminary data from the International Data Corporation... Read more
New iPhone 6 Models to Have Staggered Release...
Digitimes’ Cage Chao and Steve Shen report that according to unnamed sources in Apple’s upstream iPhone supply chain, the new 5.5-inch iPhone will be released several months later than the new 4.7-... Read more
New iOS App Helps People Feel Good About thei...
Mobile shoppers looking for big savings at their favorite stores can turn to the Goodshop app, a new iOS app with the latest coupons and deals at more than 5,000 online stores. In addition to being a... Read more
Save on 5th generation refurbished iPod touch...
The Apple Store has Apple Certified Refurbished 5th generation iPod touches available starting at $149. Apple’s one-year warranty is included with each model, and shipping is free. Many, but not all... Read more
What Should Apple’s Next MacBook Priority Be;...
Stabley Times’ Phil Moore says that after expanding its iMac lineup with a new low end model, Apple’s next Mac hardware decision will be how it wants to approach expanding its MacBook lineup as well... Read more
ArtRage For iPhone Painting App Free During C...
ArtRage for iPhone is currently being offered for free (regularly $1.99) during Comic-Con San Diego #SDCC, July 24-27, in celebration of the upcoming ArtRage 4.5 and other 64-bit versions of the... Read more
With The Apple/IBM Alliance, Is The iPad Now...
Almost since the iPad was rolled out in 2010, and especially after Apple made a 128 GB storage configuration available in 2012, there’s been debate over whether the iPad is a serious tool for... Read more
MacBook Airs on sale starting at $799, free s...
B&H Photo has the new 2014 MacBook Airs on sale for up to $100 off MSRP for a limited time. Shipping is free, and B&H charges NY sales tax only. They also include free copies of Parallels... Read more
Apple 27″ Thunderbolt Display (refurbished) a...
The Apple Store has Apple Certified Refurbished 27″ Thunderbolt Displays available for $799 including free shipping. That’s $200 off the cost of new models. Read more

Jobs Board

*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
Sr. Project Manager for *Apple* Campus 2 -...
…the design and construction of one building or building components of the New Apple Campus located in Cupertino, CA. They will provide project management oversight for Read more
WW Sales Program Manager, *Apple* Online St...
**Job Summary** Imagine what you could do here. At Apple , great ideas have a way of becoming great products, services, and customer experiences very quickly. Bring Read more
*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
Project Manager / Business Analyst, WW *Appl...
…a senior project manager / business analyst to work within our Worldwide Apple Fulfillment Operations and the Business Process Re-engineering team. This role will work Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.