Mouse in MacApp
Volume Number: | | 6
|
Issue Number: | | 12
|
Column Tag: | | Jörg's Folder
|
Related Info: Event Manager
MacApp-Tracking the Mouse
By Jörg Langowski, MacTutor Editorial Board
Note: Source code files accompanying article are located on MacTech CD-ROM or source 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 dont see the exact outline of the object being dragged and cant 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 MacApps 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 dont 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 months example (try it, and dont 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 were 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 pictures bounds rectangle by the distance the mouse has traveled; finally, in trackRelease, we change the shapes 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 dont 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 well 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, I want my M 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;
RgnHandleoldClip;
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,
// youll 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 dont 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 couldnt 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); // thats 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