| > I'm writing a program where I want to use both mouse buttons. If I get
> a click on the left button, I want to do one thing. If I get a click on
> the right button, I want to do another.
This is nearly impossible. I asked this question of the Atari ROM
developers at WAACE, and they confirmed this. What you are telling
event_multi is "tell me when the mouse state is this".
The "button" parameter is a mask of buttons you are interested in, and the
"state" parameter is the state you are waiting for. Therefore, the
possible conditions you can wait for are:
Button Mask Meaning
1 0 Trigger when left is up, right is a don't care
2 0 Trigger when right is up, left is a don't care
3 0 Trigger when both buttons are up
1 1 Trigger when left is down, right is a don't care
3 1 Trigger when left is down and right is up
2 2 Trigger when right is down, left is a don't care
3 2 Trigger when right is down and left is up
3 3 Trigger when both are down
Note that there is no event for "trigger when left or right is down". The
best you can do is toggle between "left is down, right is don't care" and
"left is up, right is a don't care", and then check the value of the right
button at the time of the event. The result is that you have left-button
clicks, and left-button clicks that use the right button as a "shift-key",
if you know what I mean.
> When I do this, I get events continuously.
You start with button=1, state=1. When the event is triggered, you reset
state = (state ^ 1);, and this toggles between waiting for button down and
waiting for button up.
|
| Atari has an official, but inadequete, solution to this problem. The
following program is their solution. The general idea is to break into
the mouse event generator at interrupt level, and convert all mouse
events into left button events, leaving the actual button state around
in a global. Note that this has some problems, most notably with it's
interaction with desk accessories and programs that use the left button
as a shift key for the right button. I don't recommend it unless you
absolutely must have it.
#include <gemdefs.h> /* include file for definitions */
#include <osbind.h> /* include file for xbios calls */
/* Useful definitions for this program */
#define RETURN 0x1C0D /* Return key code */
/* This program prints different text at the mouse position */
/* determined by which mouse button (or combination) is pressed. */
/* Link the assembled new button handler at the end of link statement: */
/* */
/* apstart,FILE,vdibind,aesbind,osbind,BUTTON_HNDLR. */
/* */
/* To exit press the RETURN key. */
/* Global arrays */
int contrl[12], intin[256], ptsin[256], intout[256], ptsout[256];
int dummy;
extern int BUT_STATE; /* True button state */
extern NEW_BUT(); /* New button handler */
long BUT_ADDR; /* Old button handler */
unsigned key;
main()
{
int handle, i;
int charw, charh, boxw, boxh;
int mgbuf[8];
int xres,yres;
int abort;
unsigned int which=(MU_KEYBD|MU_BUTTON);
int selection;
int xyarray[4];
int clicks, button, buttonstate;
int mousex,mousey,width,depth;
/* Set the system up to do GEM calls*/
appl_init();
/* Get the handle of the desktop */
handle=graf_handle(&charw,&charh,&boxw,&boxh);
/* Open the workstation. */
intin[0] = Getrez()+2;
for (i=1; i<10; ++i) intin[i] = 1;
intin[10] = 2;
v_opnvwk(intin, &handle, intout);
/* Keep track of the size of the screen */
xres=intout[0];
yres=intout[1];
xyarray[1]=xyarray[0]=1;xyarray[2]=xres-1;xyarray[3]=yres-1;
vs_clip(handle,1,xyarray);
graf_mouse(ARROW,&dummy); /* Reset mouse form to arrow */
vex_butv(handle,&NEW_BUT,&BUT_ADDR); /* new button handler,old button handler */
clicks=1;
button=1;
buttonstate=1;
for (;;)
{
selection=evnt_multi(which,
clicks,button,buttonstate,
0,0,0,0,0,
0,0,0,0,0,
&mgbuf,
0,0,
&mousex,&mousey,
&dummy,
&dummy,
&key,
&dummy);
abort=0; /* reset flag */
if (selection|MU_KEYBD)
if (key == RETURN)
abort=1;
if (selection == MU_BUTTON) /* display text for button states */
{
if (BUT_STATE == 1)
v_gtext(handle,mousex,mousey," left");
else if (BUT_STATE == 2)
v_gtext(handle,mousex,mousey," right");
else if (BUT_STATE == 3)
v_gtext(handle,mousex,mousey,"both");
}
if (abort) break;
} /* for */
/* Cleanup - return to the system button handler */
vex_butv(handle,BUT_ADDR,&dummy);
v_clsvwk(handle); /* Close the workstation. */
appl_exit(); /* Release GEM calls */
}
|
| I expanded on Atari's solution - converting the single state variable
into a queue, and fixing up a few other things. The following methond
is what is used in Whack. I'm still not happy about the interaction it
has with desk accessories, or as a desk accessory, but it does work
better than Atari's solution. Personally, I'm going to migrate toward
the left-button-is-shift approach for all future applications, and might
even change Whack. Apparently I wrote this around July of 1988:
What follows is instructions for how to get reliable mouse events from
either mouse button using evnt_multi(). When used as directed, this code
can be used to detect and process both the down and the up transitions of
either mouse button, including double-click detection. (Only one down and
one up transition is reported when the double-click is detected.)
Problems to be solved:
---------------------
1. Although the GEM routine "evnt_multi()" accepts a parameter which
is a bit mask of mouse buttons to wait for and another parameter
for which state to expect, in actuality, GEM will not respond
until all of the buttons in the button mask are in the state
requested. This means that you can wait for left button do go
down, right button to go down, or for both buttons to go down, but
you can't tell GEM to wait for either the left button or the right
button to go down. This is, of course, what you usually want to
do.
2. Because of the double click timer, sometimes mouse button
state reported by evnt_multi is not as useful as you might like.
For example, if you wait for button 1 to go down, and the user
presses and releases the button in one quick motion, GEM will
report a single mouse event with a button state of "0" (all
buttons up) instead of one for the downstroke, and a second event
for the upstroke.
The solution:
-------------
The code that I give below addresses both of these problems. The
general idea is this:
1. Splice a routine into the mouse event interrupt handler that
queues up mouse events, and turns all mouse button events into
transitions of the left button.
2. Always tell evnt_multi to wait for events on the left button.
3. When an event comes in, look in the queue to see what really
happened.
Complications:
--------------
There are, of course, some things that complicate this technique,
and otherwise make it less than perfect:
1. The queue will fill up with events from ALL uses of the mouse.
You must filter out events that go to desk accessories, form
processing, and menu bar handling, or other localized uses of the
mouse outside of primary evnt_multi(). The code I show here works
for the cases I have tried.
2. Because GEM is now seeing only two states for the mouse, you
will only get events detected on the following two conditions:
- Transition from all buttons up to any button down.
- Transition from any button down to all buttons up.
The sequence left-down, right-down, left-up, right-up only
generates two events, one for the left-down, and one for the
right-up. This is not a problem so long as the user is only
required to press one mouse button at a time.
The code:
STMOUSE.S contains the routine that is used as the new mouse event
interrupt handler. All that this routine does is call two more mouse
event handlers, whose addresses are stored in the global symbols
"first_mhandler" and "second_mhandler". Because the default mouse handler
You need to assemble this routine and include it when you link your program.
----------------------------------------------------------------------
/ stmouse.s, a handler that preserves A0 across the call, and calls
/ two mouse routines.
/
/ 31-Dec-1987 Jeff Lomicka
/
/ mymouse( a0) calls two handlers with A0 one long parameter that is
/ both on the stack in ordinary C fashion, and in A0 as is required
/ by the default mouse handler.
/
.globl mymouse_
.globl first_mhandler_
.globl second_mhandler_
.shri
mymouse_: / Replacement mouse handler
move.l a0, -(a7) / Save value for second handler
move.l a0, -(a7) / Pass to first handler
movea.l first_mhandler_, a1 / Pick up second handler
jsr (a1) / Call first handler
addq $4,a7 / Recover stack
movea.l (a7), a0 / Recover A0
movea.l second_mhandler_, a1 / Pick up second handler
jsr (a1)
addq $4,a7 / Recover stack
rts
----------------------------------------------------------------------
The event handling module for your program will need to make the following
declarations:
----------------------------------------------------------------------
#include <osbind.h>
#include <xbios.h>
extern mymouse(); /* .s file for calling two mouse handlers */
int (*first_mhandler)(); /* Address of first handler */
int (*second_mhandler)(); /* Address of second handler */
struct kbdvbase *kv; /* Keyboard vector table */
static int msevntsiz = 0; /* Events in private mouse event queue */
static unsigned short msevntq[16];/* State of mouse keys at event */
static int msevntin = 0; /* Queue input ptr */
static int msevntout = 0; /* Queue output ptr */
static int mousekeys1 = 0; /* Last mouse key event */
static int nextbstate; /* Expect button state of next event */
/*
me_mh - Mouse interrupt Handler. This handler is called,
in addition to the regular mouse handler, in order
to have access to some more information about the mouse events.
What it does is:
- Queue a copy of the mouse button state at the actual moment of
the event, rather than after GEM is finished playing with timers.
- Convert all right-mouse-button events into left-mouse-button
events BEFORE GEM's mouse interrupt handler, so that GEM will generate
a left-mouse-button event.
Note that we do NOT attempt to check for overflow in the mouse
event queue. We will always want to look at the most recent
events in the mouse event queue, so we allow new events to wrap
around the circular queue, and overwrite older events which we
aren't going to be interested in anyway.
*/
me_mh( a)
char *a;
{
register unsigned mousekeys;
mousekeys = ("\0\002\001\003")[ a[ 0] & 3];
if( mousekeys != 0) a[ 0] = 0xFA; /* Any key down means button 1 down */
else a[ 0] = 0xf8; /* Otherwise button 1 up */
/*
We intentionally allow the value of msevntsiz to grow beyond 15,
because the event handler only actually looks at the most recent
four events or so.
*/
if( mousekeys != mousekeys1)
{ /* A button change is detected, insert it in the queue */
msevntq[ msevntin++] = mousekeys;
msevntin &= 15;
msevntsiz++;
mousekeys1 = mousekeys;
}
}
----------------------------------------------------------------------
The following routine is used to throw away mouse events down to the
last "n" most recent. Other (non-evnt_multi) parts of the program that
respond to changes in mouse state may wish/need to call this routine.
----------------------------------------------------------------------
int dtemptymse( n)
int n;
{
register int qsiz;
qsiz = msevntsiz;
while( qsiz > n)
{ /* Slurp up events until there are only two left */
qsiz--; msevntsiz--;
msevntout = (msevntout + 1) & 15;
}
return( qsiz);
}
----------------------------------------------------------------------
At initialization time, you will need to execute the following code in
order to initialize the mouse event queue handler:
----------------------------------------------------------------------
/*
Set up an interrupt handler for the mouse that performs both
me_mh() and the default mouse handling. The .s file "stmouse.s"
contains some special code for this purpose.
*/
first_mhandler = me_mh;
kv = Kbdvbase();
second_mhandler = kv->kb_mousevec;
kv->kb_mousevec = mymouse;
nextbstate = 0; /* Assume first event is button-down */
----------------------------------------------------------------------
It is absolutely essential that you execute the following code before your
program exits, or your system will crash shortly after the program exits.
This restore's the previous interrupt handling routine for the mouse.
----------------------------------------------------------------------
kv->kb_mousevec = second_mhandler; /* Restore original handler */
----------------------------------------------------------------------
The following code shows what is required for waiting on the mouse button
events in an event multi, and filtering out the events that are not for
this program. (Note that I have deleted some code that is not specific
to the processing of mouse events.)
----------------------------------------------------------------------
dtprocess()
{
register int ev_which; /* Event kind delivered */
int bstate; /* Current state of the mouse buttons */
int mx, my; /* Last known mouse location */
int shiftkeys; /* Last known state of the shift keys */
int mc, key; /* Click count, key code */
short msg[ 8]; /* Messages from GEM */
for(;;)
{
ev_which = evnt_multi(
MU_KEYBD | MU_BUTTON | MU_MESAG,
2, /* Maximum clicks to wait for */
1, /* Button mask of interesting buttons */
nextbstate,/* Button states that generate events */
0, 0, 0, 0, 0,/* enter/exit, x, y, w, h for rectangle 1 */
0, 0, 0, 0, 0,/* enter/exit, x, y, w, h for rectangle 2 */
msg, /* Buffer to receive mesasge */
/* Low and high order miliseconds of counter */
0, 0, /* Timeout interval */
&mx, &my, /* Mouse location */
&bstate, /* State of the mouse buttons */
&shiftkeys, /* State of the shift keys */
&key, /* Actual number of clicks */
&mc); /* Key pressed */
if( ev_which & MU_KEYBD) process_keystroke( shiftkeys, key);
if( ev_which & MU_BUTTON)
{
register int qsiz;
/*
Each detected event will have, at MOST, two actual mouse events
associated with it. All others are noise from menu bars, desk
accessories, or random forms, so throw away everything but the
last two.
*/
qsiz = dtemptymse( 2); /* Sample mouse queue length only once */
if( bstate)
{ /* If event was a down event, do not process spurious up
events, they are from some windowing system event. */
if( msevntq[ msevntout] == 0 && qsiz > 0)
{ /* Eat spurious up event */
qsiz--; msevntsiz--;
msevntout = (msevntout + 1) & 15;
}
}
/*
nextbstate is set to the next button transition we expect to detect
*/
nextbstate = (~bstate)&1; /* process_mouse() should change
the value of nextbstate if it uses
the mouse for anything */
while( qsiz > 0)
{ /* Process each queued event */
/*
Adjust mouse event queue before processing user's action routine,
which is allowed to wait for and read additional entries from the
queue. "bstate" is re-used here as a temporary register for
storing the dequeued mouse event.
*/
bstate = msevntq[ msevntout];
msevntout = (msevntout + 1) & 15;
msevntsiz--;
qsiz--;
process_mouse( mx, my, bstate, mc, shiftkeys);
/*
If the process_mouse() routine has accessed the mouse queue, we
must be sure that we do not overflow that actual number of events
that are remaining in the queue. To do this, we reassure ourselves
that the queue still holds enough events for the the remaining
events we have to process.
*/
if( msevntsiz < qsiz) break; /* Appl has cleared queue */
}
}
if( ev_which & MU_MESAG) dtgem_message( msg);
}
}
----------------------------------------------------------------------
I hope this information is helpful to somebody.
- Jeff
|