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
$100.96
Apple Inc.
-0.83
MSFT
$47.52
Microsoft Corpora
+0.84
GOOG
$596.08
Google Inc.
+6.81

MacTech Search:
Community Search:

Software Updates via MacUpdate

Audio Hijack Pro 2.11.3 - 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
Airfoil 4.8.9 - 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
WhatRoute 1.13.0 - Geographically trace...
WhatRoute is designed to find the names of all the routers an IP packet passes through on its way from your Mac to a destination host. It also measures the round-trip time from your Mac to the... Read more
Chromium 37.0.2062.122 - Fast and stable...
Chromium is an open-source browser project that aims to build a safer, faster, and more stable way for all Internet users to experience the web. FreeSMUG-Free OpenSource Mac User Group build is... Read more
Attachment Tamer 3.1.14b9 - Take control...
Attachment Tamer gives you control over attachment handling in Apple Mail. It fixes the most annoying Apple Mail flaws, ensures compatibility with other email software, and allows you to set up how... Read more
Duplicate Annihilator 5.0 - Find and del...
Duplicate Annihilator takes on the time-consuming task of comparing the images in your iPhoto library using effective algorithms to make sure that no duplicate escapes. Duplicate Annihilator detects... Read more
jAlbum Pro 12.2 - Organize your digital...
jAlbum Pro has all the features you love in jAlbum, but comes with a commercial license. With jAlbum, you can create gorgeous custom photo galleries for the Web without writing a line of code!... Read more
jAlbum 12.2 - Create custom photo galler...
With jAlbum, you can create gorgeous custom photo galleries for the Web without writing a line of code! Beginner-friendly, with pro results Simply drag and drop photos into groups, choose a design... Read more
Quicken 2015 2.0.4 - Complete personal f...
Quicken 2015 helps you manage all your personal finances in one place, so you can see where you're spending and where you can save. Quicken automatically categorizes your financial transactions,... Read more
iMazing 1.0 - Complete iOS device manage...
iMazing (formerly DiskAid) is the ultimate iOS device manager with capabilities far beyond what iTunes offers. With iMazing and your iOS device (iPhone, iPad, or iPod), you can: Copy music to and... Read more

Latest Forum Discussions

See All

View Source – HTML, JavaScript and CSS...
View Source – HTML, JavaScript and CSS 1.0 Device: iOS Universal Category: Utilities Price: $.99, Version: 1.0 (iTunes) Description: View Source is an app plus an iOS 8 Safari extension that makes it easy to do one key web developer... | Read more »
Avenged Sevenfold’s Hail To The King: De...
Avenged Sevenfold’s Hail To The King: Deathbat is Coming to iOS on October 16th Posted by Jessica Fisher on September 19th, 2014 [ permalink ] Just in time for Halloween, on October 16 Avenged Sevenfold will be launching | Read more »
Talisman Has Gone Universal – Can Now be...
Talisman Has Gone Universal – Can Now be Played on the iPhone Posted by Jessica Fisher on September 19th, 2014 [ permalink ] | Read more »
Tap Army Review
Tap Army Review By Jennifer Allen on September 19th, 2014 Our Rating: :: SHOOT EM ALLUniversal App - Designed for iPhone and iPad Mindless but fun, Tap Army is a lane-based shooter that should help you relieve some stress.   | Read more »
Monsters! Volcanoes! Loot! Epic Island f...
Monsters! Volcanoes! Loot! | Read more »
Plunder Pirates: Tips, Tricks, Strategie...
Ahoy There, Seadogs: Interested in knowing our thoughts on all this plundering and pirating? Check out our Plunder Pirates Review! Have you just downloaded the rather enjoyable pirate-em-up Plunder Pirates and are in need of some assistance? Never... | Read more »
Goat Simulator Review
Goat Simulator Review By Lee Hamlet on September 19th, 2014 Our Rating: :: THE GRUFFEST OF BILLY GOATSUniversal App - Designed for iPhone and iPad Unleash chaos as a grumpy goat in this humorous but short-lived casual game.   | Read more »
A New and Improved Wunderlist is Here fo...
A New and Improved Wunderlist is Here for iOS 8 Posted by Jessica Fisher on September 19th, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Evernote Update for iOS 8 Adds Web Clipp...
Evernote Update for iOS 8 Adds Web Clipping, Quick Notes, and More Posted by Ellis Spice on September 19th, 2014 [ permalink ] | Read more »
Apple Names Ultimate Productivity Bundl...
Apple Names Ultimate Productivity Bundle by Readdle as the Essential Bundle on the App Store Posted by Jessica Fisher on September 19th, 2014 [ permalink | Read more »

Price Scanner via MacPrices.net

iFixIt Tears Down iPhone 6; Awards Respectabl...
iFixit notes that even the smaller 4.7″ iPhone 6 is a giant among iPhones; so big that Apple couldn’t fit it into the familiar iPhone form factor. In a welcome reversal of a recent trend to more or... Read more
Phone 6 Guide – Tips Book For Both iPhone 6...
iOS Guides has announced its latest eBook: iPhone 6 Guide. Brought to you by the expert team at iOS Guides, and written by best-selling technology author Tom Rudderham, iPhone 6 Guide is packed with... Read more
How to Upgrade iPhone iPad to iOS 8 without D...
PhoneClean, a iPhone cleaner utility offered by iMobie Inc., reveals a solution for upgrading iPhone and iPad to iOS 8 without deleting photos, apps, the new U2 album or anything. Thanks to more than... Read more
Inpaint 6 – Photo Retouching Tool Gets Faster...
TeoreX has announced Inpaint 6, a simple retouching tool for end users that helps remove scratches, watermarks, and timestamps as well as more complex objects like strangers, unwanted elements and... Read more
Worldwide PC Monitor Market Sees Growth in To...
Worldwide PC monitor shipments totaled 32.5 million units in the second quarter of 2014 (2Q14), a year-over-year decline of -2.9%, according to the International Data Corporation (IDC) Worldwide... Read more
Updated Price Trackers
We’ve updated our Mac Price Trackers with the latest information on prices, bundles, and availability on systems from Apple’s authorized internet/catalog resellers: - 15″ MacBook Pros - 13″ MacBook... Read more
Mac Pros available for up to $260 off MSRP
Adorama has Mac Pros on sale for up to $260 off MSRP. Shipping is free, and Adorama charges sales tax in NY & NJ only: - 4-core Mac Pro: $2839.99, $160 off MSRP - 6-core Mac Pro: $3739.99, $260... Read more
13-inch 2.6GHz/256GB Retina MacBook Pros avai...
B&H Photo has the 13″ 2.6GHz/256GB Retina MacBook Pro on sale for $1379 including free shipping plus NY sales tax only. Their price is $120 off MSRP. Read more
Previous-generation 15-inch 2.0GHz Retina Mac...
B&H Photo has leftover previous-generation 15″ 2.0GHz Retina MacBook Pros now available for $1599 including free shipping plus NY sales tax only. Their price is $400 off original MSRP. B&H... Read more
21″ 2.7GHz iMac available for $1179, save $12...
Adorama has 21″ 2.7GHz Hawell iMacs on sale for $1179.99 including free shipping. Their price is $120 off MSRP. NY and NJ sales tax only. Read more

Jobs Board

*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
Position Opening at *Apple* - Apple (United...
**Job Summary** At the Apple Store, you connect business professionals and entrepreneurs with the tools they need in order to put Apple solutions to work in their Read more
Position Opening at *Apple* - Apple (United...
**Job Summary** The Apple Store is a retail environment like no other - uniquely focused on delivering amazing customer experiences. As an Expert, you introduce people Read more
Position Opening at *Apple* - Apple (United...
**Job Summary** As businesses discover the power of Apple computers and mobile devices, it's your job - as a Solutions Engineer - to show them how to introduce these Read more
Position Opening at *Apple* - Apple (United...
…Summary** As a Specialist, you help create the energy and excitement around Apple products, providing the right solutions and getting products into customers' hands. You Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.