[comp.sys.amiga.tech] Rubberbanding a rectangle

sutela@polaris.utu.fi (Kari Sutela) (08/05/90)

Here is a small problem which I have been unable to solve:

I need a function which would enable one to rubberband a rectangle
with the mouse. The user should click in the top/left corner where
he wants the rectangle to start. While holding down the left mouse
button he then drags the mouse to the bottom/right corner of the
rectangle. Now, this is simple.

Nevertheless, I haven't found an acceptable method of preventing the user
from dragging the bottom/right corner beyond the top/left corner. What I
mean is that the bottom/right corner should always be to the right and
below the top/left corner.

I have included an example which does the job reasonably well by feeding
input.device with some fake input events if the user has dragged the mouse
beyond the left/top corner. Nevertheless, if I really want to I can
momentarily drag above the top corner (when I move the mouse up fast enough).
The pointer is clearly above the bottom corner for a while. The left 'border'
is much more solid as I haven't been able to drag beyond it.

I can't think of reason why the program bevahes like it does. It seems that
the mouse moves moves a bit 'too fast' in the vertical direction and my
program can't keep up with these movements. How can it keep up with the
horizontal movements???

I have considered adding a temporary input handler which would remove
the 'illegal' mouse movements from the event chain, but I have had some
problems with this approach, too.
(See another post by me with subject "An Input handler problem" for more
information)

Any help would be appreciated. Please comment the code below if you see
any dangerous things there.

Kari Sutela	sutela@polaris.utu.fi

====== The example starts here (supply your own main()) ==================

#include <exec/types.h>
#include <devices/inputevent.h>
#include <exec/ports.h>
#include <intuition/intuitionbase.h>
#include <intuition/intuition.h>
#include <graphics/gfx.h>
#include <graphics/gfxmacros.h>
#include <graphics/gfxbase.h>
#include <functions.h>

#define TITLE(w,t) SetWindowTitles( (w), (t), (const char *) ~0 )

/*
 * This function draws a box.
 */

void box( struct Window *w, struct Rectangle *rec )
{
  SHORT vectors[10];

  if( rec->MaxX > rec->MinX && rec->MaxY > rec->MinY ) {
    vectors[0] = vectors[6] = vectors[8] = rec->MinX;
    vectors[1] = vectors[3] = rec->MinY;
    vectors[2] = vectors[4] = rec->MaxX;
    vectors[5] = vectors[7] = rec->MaxY;
    vectors[9] = rec->MinY+1;
    PolyDraw(w->RPort,5,&vectors[0]);
  }

}


/*
 * This function feeds some fake input events. The user should be unable
 * to rubberband a rectangle with a bottom/right corner to the left or
 * above the top/left corner.
 */

void fix_rect( struct Window *w, struct Rectangle *rec )
{
  struct MsgPort *ioport;
  struct IOStdReq *ioreq;
  struct InputEvent event;

  /*
   * Is the rectangle OK?
   */
  if( rec->MaxX > rec->MinX && rec->MaxY > rec->MinY )
    return;

  if( NULL == 
    (ioport = (struct MsgPort *) CreatePort("fooling intuition",0))
  ) {
    return;
  }

  if( NULL ==
    (ioreq = (struct IOStdReq *) CreateStdIO(ioport))
  ) {
    DeletePort(ioport);
    return;
  }

  if( OpenDevice("input.device",0,(struct IORequest *)ioreq,0) ) {
    DeleteStdIO(ioreq);
    DeletePort(ioport);
    return;
  }

  /*
   * All OK! Let's fill the message.
   */
  event.ie_Class = IECLASS_RAWMOUSE;
  event.ie_Code = IECODE_NOBUTTON;
  event.ie_Qualifier = IEQUALIFIER_RELATIVEMOUSE;
  event.ie_X = (rec->MaxX <= rec->MinX) ? (rec->MinX-rec->MaxX+1) : 0;
  event.ie_Y = (rec->MaxY <= rec->MinY) ? (rec->MinY-rec->MaxY+1) : 0;
  ioreq->io_Command = IND_WRITEEVENT;
  ioreq->io_Length = sizeof(struct InputEvent);
  ioreq->io_Data = (APTR) &event;
  DoIO((struct IORequest *)ioreq);

  /*
   * Done!
   */
  CloseDevice((struct IORequest *)ioreq);
  DeleteStdIO(ioreq);
  DeletePort(ioport);

}

/*
 * This function prompts the user to drag a rectangle and returns it
 * via the rec parameter. Returns FALSE if the rectangle would fall outside
 * the window.
 */

BOOL rubberband( struct Window *w, struct Rectangle *rec )
{
  ULONG old_flags;
  BOOL done=FALSE,cancelled=FALSE;
  BYTE old_dr;
  SHORT old_cpx, old_cpy;
  struct IntuiMessage *msg;

  TITLE(w,"Click the left mouse button in the upper/left corner!");

  old_cpx = w->RPort->cp_x;
  old_cpy = w->RPort->cp_y;
  old_flags = w->IDCMPFlags;
  ModifyIDCMP(w,MOUSEBUTTONS);
  old_dr = w->RPort->DrawMode;
  SetDrMd(w->RPort,COMPLEMENT);

  /*
   * Wait for a left mouse down!
   */
  while(!done) {

    struct IntuiMessage copy;

    msg = (struct IntuiMessage *) GetMsg(w->UserPort);
    if(!msg) {
      WaitPort(w->UserPort);
    } else {

      copy = *msg;
      ReplyMsg( (struct Message *) msg );

      if( copy.Class == MOUSEBUTTONS && copy.Code == SELECTDOWN ) {

      	/*
      	 * Is it within the window (exluding the borders here)?
      	 */
      	if( copy.MouseX >= w->BorderLeft &&
            copy.MouseX < w->Width - w->BorderRight &&
            copy.MouseY >= w->BorderTop &&
            copy.MouseY < w->Height - w->BorderBottom
        ) {
          rec->MinX = rec->MaxX = copy.MouseX;
          rec->MinY = rec->MaxY = copy.MouseY;
          TITLE(w,"Now drag to lower/right corner and release button!");
          done = TRUE;
          Move( w->RPort, rec->MinX, rec->MinY );
          box( w, rec );
        }

      } /* if( copy.Class ... ) */

    } /* if(!msg) */

  } /* while(!done) */

  /*
   * Let's hope this is atomic.
   */
  w->Flags |= REPORTMOUSE;
  ModifyIDCMP(w,MOUSEBUTTONS|MOUSEMOVE);
  done = FALSE;

  /*
   * Wait for left up!
   */
  while(!done) {

    struct IntuiMessage copy;

    msg = (struct IntuiMessage *) GetMsg(w->UserPort);
    if(!msg) {
      WaitPort(w->UserPort);
    } else {

      copy = *msg;
      ReplyMsg( (struct Message *) msg );
      box( w, rec ); /* remove the old rectangle */
      rec->MaxX = copy.MouseX;
      rec->MaxY = copy.MouseY;

      if( rec->MaxX >= w->Width - w->BorderRight ||
          rec->MaxY >= w->Height - w->BorderBottom
      ) {
        done = cancelled = TRUE;
        TITLE(w,"Rubberbanding cancelled!");
      } else {
        fix_rect( w, rec );
        if( copy.Class == MOUSEBUTTONS && copy.Code == SELECTUP ) {
          done = TRUE;
        } else {
          box( w, rec ); /* draw a new rectangle */
        }
      }

    } /* if(!msg) */

  } /* while(!done) */

  /*
   * Is this really necessary? Anyway, let's drain the userport.
   */
  while( msg = (struct IntuiMessage *) GetMsg(w->UserPort) )
    ReplyMsg( (struct Message *) msg );

  w->Flags &= ~REPORTMOUSE;
  ModifyIDCMP(w,old_flags);
  SetDrMd(w->RPort,old_dr);
  Move(w->RPort,old_cpx,old_cpy);
  return !cancelled;

}

======= end of example ========================================

rosenber@ra.abo.fi (Robin Rosenberg INF) (08/05/90)

Kari Sutela writes:

>Here is a small problem which I have been unable to solve:
>
>I need a function which would enable one to rubberband a rectangle
>with the mouse. The user should click in the top/left corner where
>he wants the rectangle to start. While holding down the left mouse
>button he then drags the mouse to the bottom/right corner of the
>rectangle. Now, this is simple.
>
>Nevertheless, I haven't found an acceptable method of preventing the user
>from dragging the bottom/right corner beyond the top/left corner. What I
>mean is that the bottom/right corner should always be to the right and
>below the top/left corner.

Why shouldn't the user be able to start with any corner. Forcing the user
to specify the topleft corner is not really necessary and it solves your 
problem. Using an input handler is quite farfetched. It is quite simple to
let the used specify any two points:

Write a function to take two points p1=the point the user selecte first and p2=the point represented by the current mouse position. Then it comes out with two new points such that 
p1fixed.x = min(p1.x,p2.x);
p2fixed.x = max(p1.x,p2.x);
p1fixed.y = min(p1.y,p2.y);
p2fiuxe.y = max(p1.y,p2.y);

For example the rectangle specfied using p1(2,5) and p2=(10,1) , i.e
p1 is the bottom left corner and p2 is the upper right corner. becomes
p1fixed=(2,1) and p2fixed=(10,5) which have the properties you said
you wanted.

Then p1fixed is above and left of p2fixed or at the same position.

Have another routine limit the values of p1fixed and p2fixed so they stay 
within the allowed area and yet another routine to draw a rectangle using the

	p1 = StartCoordinate;
	drawn=FALSE;
	while(dragging) {
		if (drawn)
			undrawrect(&p1fixed,&p2fixed);
		p2 = CurrentCoordinate;
		fixpoints(&p1,&p2,&p1fixed,&p2fixed);
		limit(&p1fixed,&p2fixed, minx,miny,maxx,maxy);
		drawrect(&p1fixed,&p2fixed);
		drawn = TRUE;
		...
	}
	undrawrect(&p1fixed,&p2fixed);

--
	Robin

disclaimer >nil: