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 Cs 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 MacApps 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 players 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 players ownership of the square. The pattern is also used to display a frame around the appropriate players 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 players 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 cDotsApps 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 applications inherited initialization method must be overridden. The Dots override for this method calls the inherited applications 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 documents 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, thats where its 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 documents main pane and if the document has no main pane, itsMainPane should be NULL. Many of the documents 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 documents 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 documents gopher as it should have been. The documents OpenFile() method had sent a Select() message to its window in order to activate the window. However, Select() only sets gGopher to the documents 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 documents OpenFile() method. This sets gGopher to the documents 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 documents 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 documents 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 panes 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 documents BuildWindow() method whenever a document is created. The score view displays each players score in the documents 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 views 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 views 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 players score, invalidate the area around a players 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 panes 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 panes 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 panes 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 documents 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 views 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 tasks 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 views DoClick() method whenever there is a mouse down in the dot matrix view. The Dots process for handling the mouse down is discussed in the section on the dot matrix view.
The mouse tasks 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 documents 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(); /* Dont need doc */
return;
}
/* Read in the data - dont 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
** lets 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 0s 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. Cant 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 */
/* Dont 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 theyre 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 players 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 players 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 players 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 panes port before invalidation
** otherwise draw in a different port */
Prepare();
InvalRect(&r);
}
/*** setTurn
Highlight current players 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, ¢er);
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, ¢er);
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, ¢er);
/* 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 lines rectangle */
line2Rect(direction, row, col, &r);
/* Set to dots matrix views 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 boxs rectangle */
/* Set to dots matrix views 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 wont 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);
}