Mouse in MacApp
Volume Number: 6
Issue Number: 12
Column Tag: Jörg's Folder
MacApp-Tracking the Mouse 
By Jörg Langowski, MacTutor Editorial Board
Note: Source code files accompanying article are located on MacTech CD-ROM orsource code disks.
As promised, I will show you this month how to add code that handles mouse
tracking to our last example. In the code as it was given in V6#11, we had no explicit
mouse tracking routine defined; in that case MacApp uses the default routine, which
simply draws the rectangle defined by the mouse starting point and the current mouse
position. For dragging an object, this default behavior is quite inconvenient, because
you don’t see the exact outline of the object being dragged and can’t position it exactly
where you want it.
We would like dragged objects to behave like they do in MacDraw, where an
outline of the object follows the mouse. How can one implement this?
All the changes we have do to are to the TDragger class. Specifically, we need to
define a TrackFeedback method in this class. This method is called by MacApp’s
TApplication.TrackMouse routine while it tracks the mouse. You may draw some shape
here, which is then ‘dragged around’ with the mouse: TrackMouse will set the pen to
patXOR mode before any calls to the feedback routine. When you move the mouse, the
shape is first redrawn at the old position (erasing it that way), then drawn at the new
position. All this is handled automatically by TrackMouse; you only need to provide the
drawing routine.
So all we would have to do is write a TrackFeedback method that draws the shape
at the current mouse position, maybe like this:
/* 1 */
pascal void TDragger::TrackFeedback(VPoint *anchorPoint,
VPoint *nextPoint, Boolean turnItOn, Boolean mouseDidMove)
{
Point delta;
if (mouseDidMove) {
delta = fTextView->ViewToQDPt(nextPoint);
SubPt(fTextView->ViewToQDPt(previousPoint), &delta);
OffsetRect(&fBox->fLocation,delta.h,delta.v);
fBox->DrawShape();
}
}
In the call to the routine, anchorPoint and nextPoint are the starting point of the
mouse drag and the current mouse position. mouseDidMove is true when the mouse
actually did move since the last call to this routine; and turnItOn is a variable that
controls whether feedback should be turned on or off; we don’t use it. To draw the shape
at the new mouse position, we calculate the difference between the two coordinates
(SubPt) and offset the shape rectangle accordingly; then we draw the shape.
This method should work without problems when you add it to last month’s
example (try it, and don’t forget to include the method in the class definition in the
header file). Since we change the location rectangle of fBox as we go, we may even
simplify the code that changes oldLocation and newLocation at the end of the
TDragger.TrackMouse routine; fBox->fLocation is already at its final value when the
mouse is released.
However, I would like to show you a different way to do the feedback which can
later be used in a much more general way, for instance if we want to drag an outline of
a group of different shapes, change the aspect of the shape being dragged around, etc.
The principle is taken from an example in the excellent book Programming with
MacApp by David Wilson, Larry Rosenstein, and Dan Shafer (Addison-Wesley). We
call the routines that we want to use for drawing the shape(s) to be dragged only once,
when the mouse is first clicked or at its first movement. At that point, we create a
picture, calling all the drawing routines, and store its handle in an instance variable.
The TrackFeedback routine will then take this picture handle and call DrawPicture
with it.
Creation of the picture, and destruction when we’re done with the dragging, is
handled by the TrackMouse routine. When this routine is called, one parameter
indicated what track phase we are in: whether the mouse button was just pressed
(trackPress), is being held down (trackMove), or has just been released
(trackRelease).
The track phase tells us what to do (see listing): when we are in trackPress, we
create the picture and save its handle; when we are in trackMove, we offset the
picture’s bounds rectangle by the distance the mouse has traveled; finally, in
trackRelease, we change the shape’s coordinates to the mouse position and dispose of
the picture. If we find out that the shape has not been moved at all out of its old
position, we return gNoChanges.
Note that the mouse tracking routine does not do any drawing at all; drawing the
dragged shape is done by the TrackFeedback routine, and the final draw of the shape at
its new position is of course done when the window is updated automatically. Another
point to remember is that you have to set the pen state to PenNormal and patXor when
you create the picture (see listing). The current pen state is part of the picture
information, so when the picture is created, that pen state will be remembered. The
pen state in the TrackMouse routine in the trackPress phase is not the one we want; if
you don’t set it to patXor yourself, it will be patCopy (I guess) when the picture is
drawn by the feedback routine. That means you will create copies of your shape all
over the screen when you drag it. Try it out.
Final words
This was a short Christmas column; next time will be longer, because we’ll add
code for different shapes and for dragging a selection of shapes. Also, it might be that
my test copy of MacFortran II comes in until then, so that we can finally see those
comprehensive benchmarks. Absoft, IwantmyM F Two...
Listing 1: Changes to the V6#9 example to support track feedback
// Dragging support with custom mouse track feedback
// © JL/MT 10/90
pascal void
TDragger::IDragger(TBox *itsBox,
TTEDocument *itsDocument, TTextView *itsView)
{
TScroller *aScroller;
aScroller = itsView->GetScroller(true);
ICommand(cDragBox, itsDocument, itsView, aScroller);
fTEDocument = itsDocument;
fTextView = itsView;
fBox = itsBox;
oldLocation = fBox->fLocation;
newLocation = fBox->fLocation;
}
pascal struct TCommand
*TDragger::TrackMouse(TrackPhase aTrackPhase,
VPoint *anchorPoint, VPoint *previousPoint,
VPoint *nextPoint, Boolean mouseDidMove)
{
Point delta;
Rect r;
RgnHandle oldClip;
PenState oldState;
if (aTrackPhase == trackPress) {
r = fBox->fLocation;
oldClip = MakeNewRgn(); // MacApp routine
// get the old environment for later restore
GetClip(oldClip);
GetPenState(&oldState);
// and open a new picture
fFeedbackPicture = OpenPicture(&r);
FailNIL(fFeedbackPicture); // be safe
ClipRect(&r);
// the following lines ARE necessary since the picture
// remembers the pen state that was in effect when this
// routine was called.
// Since we are in trackPress, the pen has NOT yet been
// set to patXOR. Comment out the next two lines,
// you’ll see interesting effects -- JL
PenNormal();
PenMode(patXor);
// draw the shape
fBox->DrawShape();
ClosePicture();
fPictureBounds = r;
// restore old environment
SetClip(oldClip);
DisposeRgn(oldClip);
SetPenState(&oldState);
if (EmptyRect(&(*fFeedbackPicture)->picFrame)) {
KillPicture(fFeedbackPicture);
fFeedbackPicture = nil;
FailNIL(fFeedbackPicture);
}
}
if ((aTrackPhase == trackMove) && mouseDidMove) {
delta = fTextView->ViewToQDPt(nextPoint);
SubPt(fTextView->ViewToQDPt(previousPoint), &delta);
// we don’t actually move the shape here, only its picture.
// the move is done in the last phase
OffsetRect(&fPictureBounds,delta.h,delta.v);
}
if ((aTrackPhase == trackRelease) && mouseDidMove) {
if (fFeedbackPicture != nil) {
// being paranoid: normally we should never get here if the
// picture couldn’t be created, but who knows
KillPicture(fFeedbackPicture);
fFeedbackPicture = nil; }
delta = fTextView->ViewToQDPt(nextPoint);
SubPt(fTextView->ViewToQDPt(anchorPoint), &delta);
newLocation = oldLocation;
if ((delta.h == 0) && (delta.v == 0))
{ return gNoChanges; }
// if we get here, something has been changed.
// move the shape to its new location
OffsetRect(&newLocation,delta.h,delta.v);
fBox->fLocation = newLocation;
}
return this;
}
pascal void
TDragger::TrackFeedback(VPoint *anchorPoint,
VPoint *nextPoint,
Boolean turnItOn, Boolean mouseDidMove)
{
Rect r;
if (mouseDidMove && (fFeedbackPicture != nil)) {
r = fPictureBounds;
DrawPicture(fFeedbackPicture,&r); // that’s all!!
}
}
pascal void TDragger::DoIt()
{
fTextView->InvalidRect(&newLocation);
fTextView->InvalidRect(&oldLocation);
}
pascal void TDragger::RedoIt()
{
fBox->fLocation = newLocation;
DoIt();
}
pascal void TDragger::UndoIt()
{
fBox->fLocation = oldLocation;
DoIt();
}
#ifdef qDebug
pascal void TDragger::Fields(pascal void (*DoToField)
(StringPtr fieldName, Ptr fieldAddr, short fieldType,
void *link), void *link)
{
DoToField(“\pTDragger”, nil, bClass, link);
DoToField(“\pfTEDocument”,
(Ptr) &fTEDocument, bObject, link);
DoToField(“\pfTextView”,
(Ptr) &fTextView, bObject, link);
DoToField(“\pfBox”, (Ptr) &fBox, bObject, link);
DoToField(“\poldLocation”,
(Ptr) &oldLocation, bRect, link);
DoToField(“\pnewLocation”,
(Ptr) &newLocation, bRect, link);
DoToField(“\pfFeedbackPicture”,
(Ptr) &fFeedbackPicture, bHandle, link);
DoToField(“\pfPictureBounds”,
(Ptr) &fPictureBounds, bRect, link);
inherited::Fields(DoToField, link);
}
#endif