Micro-draw
Volume Number: | | 6
|
Issue Number: | | 7
|
Column Tag: | | Jörg's Folder
|
C++ micro-Draw
By Jörg Langowski, MacTutor Editorial Board
Note: Source code files accompanying article are located on MacTech CD-ROM or source code disks.
C++ micro-Draw
Were going to expand on the Object Lists demo from V6#2 this month. Youll probably have noted quite a few deficiencies to that example - it would be nice to be able to draw not only three objects at fixed places within the window, but create some kind of micro-Draw utility where you could place as many ovals and rectangles where you like in the window, and also change their aspect.
This example, over the months, will (hopefully) evolve into a useful Draw utility. The first step towards that is to add some code which will allow us to move objects around in the window, change their drawing pattern, delete them etc. At the end of this example you will see that there are still many things left to do - we will add them in the course of the following columns. You should have copies of the preceding example code ready at hand, because we dont want to reprint everything from scratch each time. The listing at the end gives only the changes that were made compared to the code in V6#2.
Cleaning up the lists
The first thing that we have to take care of is the removal of objects and lists when a document is closed. This was not done in the original example. When a window is closed, the document destructor is called:
//1
TListDoc::~TListDoc(void)
{delete fObjList;
HideWindow(fDocWindow); }
and thus, the associated object list will be deleted. However, the objects in it still float around in the heap, since they have been created with new and have not been deleted explicitly! Therefore, we have to define a destructor for the object list as well, in which each object in the list is deleted:
//2
TObjList::~TObjList(void)
{
TObjLink* temp;
while (fHeader != nil)
{
temp = fHeader->GetNext();
delete fHeader->GetmyObj();
delete fHeader;
fHeader = temp;
}
fNumObjs = 0;
}
This code will clean up properly after us when we close a window.
Creating and Deleting Objects
The next feature that wed like to add is to be able to position objects by clicking in the window. We can select from the menu what type of object we want; the cursor will then change to a cross, and the object will be placed in the window at the point where we clicked.
To do this, we first define a new routine, ClickInWindow() (see listing); this function waits for a mouse down event and returns the location of the mouse click in the current ports local coordinates. This routine is called by the menu selection handlers (DoMenuCommand) at the appropriate selections. The center of the objects bounds rectangle is then set to the position of the mouse click, and the window redrawn. We have not added any means to change the size of an object yet; take this as an exercise, or wait until we give an example in this column.
To erase an object from the window, we simply click on it after the menu selection Erase. For implementing this behavior, we define the method FindObj(Point theLoc) which finds the object corresponding to a mouse click at a certain point in the window. This routine returns a pointer to a TDisplObj; to remove the object which we clicked at, we simply write
// 3
fCurListDoc->fObjList->
RemoveObj(fCurListDoc->fObjList->FindObj(theLoc));
A good example how compact some operations can be expressed in C++.
Changing draw patterns of objects is easy, too; I have added three menu selections that allow to draw objects in either black, gray or white. Again, we find the object that was clicked at, and then set the draw pattern to a new value. For this, we need to define a new instance variable fObjPat in TDisplObj which holds the drawing pattern, and the new method SetObjPat(Pattern pat) which sets this pattern. Caution: a pattern cant be simply assigned to another pattern variable as in
//4
Pattern myPat = yourPat;
because Pattern is an array, it is defined as unsigned char[8]. We have to write a little loop to do the job (see listing).
Moving objects around
The last method that we add to our example this time is one that allows us to drag an object around in the window. We define a method DragObj(Point *theLoc, Rect theRect) which is called right after the mouse has been clicked at theLoc and an object has been found there. We also need to define a method that allows us to set the bounds rectangle of the object, so it can actually be moved.
DragObj will first create a new region that corresponds to the outline of the object to be dragged. It will then call the Window Manager routine DragGrayRgn to move this outline around the window. The limit and slop rectangles for the dragging routine are set to the port rectangle of the document window, passed through the parameter theRect to DragObj. This means that we cannot exceed the windows boundaries when we drag the object.
DragGrayRgn waits until the mouse button is released and then returns a longint that contains the distances moved in the vertical and horizontal directions in its high and low words. This information is used to change the value of theLoc. When DragObj returns, theLoc will contain the new mouse coordinates. The main routine then resets the center of the objects bounds rectangle to this position. The window is redrawn, and the object will have moved.
There is still a small bug in the dragging code; when you drag outside the limit rectangle, the object will disappear. I encourage you to find the reason for this; the bug will be resolved in the next column.
Wish list
Were on our way to create a MacTutorDraw application; but we still have a long way to go, of course. Things that should be added in the future are:
scrap handling, so that copy, cut and paste are implemented;
replacement of the selection menu by a palette window;
changing the size and order of objects;
implementing more objects, such as lines, arcs, polygons and text boxes;
code for saving/restoring/printing documents.
Dont expect all this to happen in the next column, though...
More on Forth
At the end, I would like to tell the remaining Forth enthusiasts that there is very good news regarding object-oriented extensions to Forth. I have now seen three different ways to implement NEON-compatible systems, two of which use fast subroutine-threaded code. Ill give you news very soon. Until then, happy hacking.
Listing: Changes to the V6#2 example code
void TDocListApp::AdjustMenus(void)
{
WindowPtrfrontmost;
MenuHandle menu;
Boolean undo,cutCopyClear,paste;
TListDoc* fCurListDoc = (TListDoc*) fCurDoc;
frontmost = FrontWindow();
menu = GetMHandle(mFile);
if ( fDocList->NumDocs() < kMaxOpenDocuments )
EnableItem(menu, iNew);// New is enabled when we can open more documents
else DisableItem(menu, iNew);
if ( frontmost != (WindowPtr) nil )
EnableItem(menu, iClose);
// Close is enabled when there is a window to close
else DisableItem(menu, iClose);
undo = false; cutCopyClear = false; paste = false;
if ( fCurListDoc == nil )
{ // all editing is enabled for DA windows
undo = true; cutCopyClear = true;
paste = true; }
menu = GetMHandle(mEdit);
if ( undo )EnableItem(menu, iUndo);
else DisableItem(menu, iUndo);
if ( cutCopyClear )
{ EnableItem(menu, iCut);
EnableItem(menu, iCopy);
EnableItem(menu, iClear);}
else
{ DisableItem(menu, iCut);
DisableItem(menu, iCopy);
DisableItem(menu, iClear); }
if ( paste ) EnableItem(menu, iPaste);
else DisableItem(menu, iPaste);
menu = GetMHandle(myMenu);
EnableItem(menu, item1);
EnableItem(menu, item2);
EnableItem(menu, item3);
EnableItem(menu, item5);
EnableItem(menu, item7);
EnableItem(menu, item8);
EnableItem(menu, item9);
EnableItem(menu, item11);
} // AdjustMenus
Point ClickInWindow()
{
EventRecordtheEvent;
Point theLoc;
Booleanev;
CursHandle hCurs;
hCurs = GetCursor(crossCursor);
SetCursor(*hCurs);
while (!(Button())) {} ;
ev = WaitNextEvent(mDownMask,&theEvent,0,0);
theLoc = theEvent.where;
GlobalToLocal(&theLoc);
return theLoc;
}
void TDocListApp::DoMenuCommand(short menuID, short menuItem)
{
short itemHit;
Str255 daName;
short daRefNum;
WindowPtrwindow;
Rect brect;
Point theLoc;
TDisplObj*theObj;
TListDoc* fCurListDoc = (TListDoc*) fCurDoc;
window = FrontWindow();
switch ( menuID )
{ case mApple:
switch ( menuItem )
{ case iAbout: // About box
itemHit = Alert(rAboutAlert, nil);
break;
default: // DAs etc.
GetItem(GetMHandle(mApple), menuItem, daName);
daRefNum = OpenDeskAcc(daName);
break; }
break;
case mFile:
switch ( menuItem )
{ case iNew: DoNew(); break;
case iClose:
if (fCurListDoc != nil)
{ fDocList->RemoveDoc
(fCurListDoc);
delete fCurListDoc; }
else CloseDeskAcc(((WindowPeek)
fWhichWindow)->windowKind);
break;
case iQuit:Terminate(); break; }
break;
case mEdit:
// call SystemEdit for DA editing & MultiFinder
if ( !SystemEdit(menuItem-1) )
{ switch ( menuItem )
{ case iCut: break;
case iCopy: break;
case iPaste: break;
case iClear: break; }
}
break;
case myMenu:
if (fCurListDoc != nil)
{ switch ( menuItem )
{ case item1: // TRoundRect
theLoc=ClickInWindow();
SetRect(&brect,theLoc.h-40,
theLoc.v-20, theLoc.h+40, theLoc.v+20);
fCurListDoc->fObjList->AddObj(
new TRoundRect(brect));
break;
case item2: // TOval
theLoc=ClickInWindow();
SetRect(&brect,theLoc.h-40,
theLoc.v-20, theLoc.h+40, theLoc.v+20);
fCurListDoc->fObjList->AddObj(
new TOval(brect));
break;
case item3: // TRect
theLoc=ClickInWindow();
SetRect(&brect,theLoc.h-40,
theLoc.v-20, theLoc.h+40,
theLoc.v+20);
fCurListDoc->fObjList->AddObj(
new TRect(brect));
break;
case item5: // Erase
theLoc=ClickInWindow();
fCurListDoc->fObjList->RemoveObj(
fCurListDoc->fObjList->FindObj(theLoc));
break;
case item7: // White
theLoc=ClickInWindow();
theObj = fCurListDoc->fObjList->
FindObj(theLoc);
if (theObj != nil)
theObj->SetObjPat(qd.white);
break;
case item8: // Gray
theLoc=ClickInWindow();
theObj = fCurListDoc->fObjList->
FindObj(theLoc);
if (theObj != nil)
theObj->SetObjPat(qd.gray);
break;
case item9: // Black
theLoc=ClickInWindow();
theObj = fCurListDoc->fObjList->
FindObj(theLoc);
if (theObj != nil)
theObj->SetObjPat(qd.black);
break;
case item11: // Move
theLoc=ClickInWindow();
theObj = fCurListDoc->fObjList->
FindObj(theLoc);
if (theObj != nil)
{ theObj->DragObj(
&theLoc,window->portRect);
SetRect(&brect,theLoc.h-40,
theLoc.v-20, theLoc.h+40,
theLoc.v+20);
theObj->SetBoundRect(brect);
}
break;
}
InvalRect(&(window->portRect));
fCurListDoc->DoUpdate();
InitCursor();
}
break;
}
HiliteMenu(0);
} // DoMenuCommand
// List document class
class TListDoc : public TDocument {
protected:
void DrawWindow(void);
public:
TObjList*fObjList;// list of objects to be drawn
TListDoc(short resID);
~TListDoc(void);
// methods from TDocument we override
void DoUpdate(void);
};
const int kMinDocDim = 40;
// create and delete the document window
TListDoc::TListDoc(short resID) : (resID)
{
fObjList = new TObjList();
ShowWindow(fDocWindow);
}
void TListDoc::DrawWindow(void)
{TObjLink* temp;
SetPort(fDocWindow);
EraseRect(&fDocWindow->portRect);
if (fObjList->NumObjs() != 0)
for (temp = fObjList->Header();
temp != nil; temp = temp->GetNext())
temp->GetmyObj()->Draw(
temp->GetmyObj()->GetObjPat());
} // DrawWindow
class TDisplObj {
private:
Boolean HiLiteState;// true : highlighted
protected:
TDisplObj(Rect r);
Rect fBoundRect;
Pattern fObjPat;
public:
virtual void Draw(Pattern)
{DebugStr(\pCall to TDisplObj::Draw);}
virtual void Erase()
{DebugStr(\pCall to TDisplObj::Erase);}
virtual void DoContent()
{DebugStr(\pCall to TDisplObj::DoContent);}
virtual void DoIdle() { /* do nothing by default*/ }
Rect GetBoundRect() {return fBoundRect;}
void SetBoundRect(Rect theRect)
{fBoundRect = theRect;}
Pattern GetObjPat() {return fObjPat;}
void SetObjPat(Pattern pat);
void DragObj(Point*, Rect);
};
class TObjList {
TObjLink*fHeader;
int fNumObjs;
public:
TObjList(void); ~TObjList(void);
inline TObjLink* Header() { return fHeader; }
inline int NumObjs() { return fNumObjs; }
void AddObj(TDisplObj* obj);
void RemoveObj(TDisplObj* obj);
TDisplObj* FindObj(Point theLoc);
};
TObjList::~TObjList(void)
{
TObjLink* temp;
while (fHeader != nil)
{
temp = fHeader->GetNext();
delete fHeader->GetmyObj();
delete fHeader;
fHeader = temp;
}
fNumObjs = 0;
}
TDisplObj* TObjList::FindObj(Point theLoc)
{
TObjLink* temp;
Rect theRect;
for (temp = fHeader; temp != nil;
temp = temp->GetNext())
{ theRect = temp->GetmyObj()->GetBoundRect();
if (PtInRect(theLoc,&theRect))
return (temp->GetmyObj());
}
return nil;
}
TDisplObj::TDisplObj(Rect r)
{ fBoundRect = r; SetObjPat(qd.gray); }
void TDisplObj::SetObjPat(Pattern pat)
{ for (int i=0; i<8; i++) fObjPat[i] = pat[i]; }
void TDisplObj::DragObj(Point *theLoc, Rect theRect)
{
RgnHandle theRgn = NewRgn();
OpenRgn();
this->Draw(fObjPat);
CloseRgn(theRgn);
long res=DragGrayRgn(theRgn,*theLoc, &theRect,&theRect,0,0);
if (res != 0x8000)
{ theLoc->v += HiWord(res);
theLoc->h += LoWord(res);
}
DisposeRgn(theRgn);
}