You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
PDCursesMod/common/mouse.c

248 lines
10 KiB
C

/* Platform-independent parts of mouse handling. This is surprisingly
hard to get right.
The idea here is that the various platforms detect "raw" mouse events
(buttons pressed and released, mouse and wheel movements) in very
different, platform-specific ways. But they all should use the same logic
to "cook" those raw events into the clicks, double-clicks, etc. that
the PDCursesMod library eventually returns, and to determine which events
get filtered.
This code will convert the raw event stream to "cooked" events. Some
events may be omitted, if the corresponding bits in SP->_trap_mbe aren't
set. Presses and releases will be combined into clicks _if_ we're actually
looking for clicks from that button. (If not, PDCursesMod may return a
press and a click... if, again, corresponding bits are turned on.) Two
clicks will be combined into a double-click, _if_ we're actually looking
for double-clicks. And so on.
In some cases, the raw event may leave us in an "incomplete" state.
Let's say a mouse button is pressed, and we're looking for clicks on that
button (i.e., the BUTTONn_CLICKED bit is set in SP->_trap_mbe). If so,
we need to wait up to SP->mouse_wait milliseconds for a release of that
button. If that happens, the button is clicked. If not, the button is
pressed (and presumably held, and sometime later, we should get a
'release' event for that button... should note that there's not actually
any guarantee that we won't get two presses or two releases in a row from
the same button.)
If we're looking for double-clicks on that button, we need to wait
yet another SP->mouse_wait milliseconds for another button press. If we
get one, we have to wait again to see if there's a button release. So
a double-click could be returned as
press, release, press, release (if we aren't looking for clicks,
_or_ if SP->mouse_wait == 0)
click, click (if we're looking for single-clicks but not double-clicks)
double-click (if we're looking for both)
And while these events may get generated, they may also never be
returned by the PDCursesMod library if corresponding bits in SP->_trap_mbe
are not set.
From the viewpoint of the platform code, the logic is :
While a "raw" event is pending and _add_raw_mouse_event() == TRUE, we're
in an incomplete state. Wait for up to SP->mouse_wait milliseconds
for another mouse event.
Filter out unwanted events.
If we have "cooked" mouse events left after this, queue them.
Each time a mouse event is returned, call _get_mouse_event() to retrieve
that event, put it into SP->mouse_status, and remove it.
Only button presses and releases get "cooked" (and, as described above,
even they are not always cooked). The _add_raw_mouse_event() will collect
the raw events and synthesize them into cooked ones. It returns TRUE
if the platform should wait SP->mouse_wait milliseconds, or FALSE if
there is no need to wait (i.e., the event cannot be combined with a
later event to make a click, double-click, or triple-click). The logic
it uses is :
Add the event to the queue.
If it's a mouse movement or wheel event, return FALSE to tell the
platform that it doesn't have to wait SP->mouse_wait milliseconds
for more mouse input.
(If we get this far, the event must be a press or release.)
If SP->mouse_wait == 0, or if BUTTONn_CLICKED isn't set in SP->_trap_mbe,
we can again return FALSE. In such cases, we're just returning
presses and releases without trying to combine them into clicks.
Pretty much the same as if it were a wheel event or a move event.
If it's a button-pressed event :
return TRUE (because we need to wait for a possible button release
to make a click, double-click, or triple)
Else, it's a button-release event :
If there's a preceding press event for that button, combine and
make it a click. (If not, it can't be combined with anything;
return FALSE.)
If, preceding the click, there's another click for that button, combine
them into a double-click. If BUTTONn_TRIPLE_CLICKED is set in
SP->_trap_mbe, return TRUE to indicate that we need to wait to see
if a third click happens.
If it's preceded by a double-click, combine them into a triple-click.
We don't look for BUTTONn_QUADRUPLE_CLICKED in SP->_trap_mbe,
because PDCursesMod doesn't handle quad-clicks. We return FALSE.
If the click can't be combined with previous events, then our new
single-click might be followed by another click. Check the
BUTTONn_DOUBLE_CLICKED flag in SP->_trap_mbe; if it's set, return
TRUE.
The current mouse state cannot be combined with further events to make
new "cooked" events, so there's no point in waiting for them.
Return FALSE.
A few notes :
(1) In the above, if we're in an incomplete state, we "wait up to
SP->mouse_wait milliseconds". Most of the code has traditionally waited
that long, even if the completing click or release came immediately. It's
better to take small naps over the SP->mouse_wait milliseconds, checking
to see if the completing event has come in. For example :
while( the platform has a new mouse event && TRUE == _add_raw_mouse_event( ))
{
const long t_end = PDC_millisecs( ) + SP->mouse_wait;
long ms_remaining;
while( (ms_remaining = t_end - PDC_millisecs()) > 0)
{
napms( ms_remaining > 20 ? 20 : ms_remaining);
if( there's a new mouse event pending)
break;
}
}
(2) In the above logic, you can't get double-clicks without also getting
single-clicks, and you can't get triple-clicks without getting doubles and
singles. This may be changed eventually, though it seems like an unlikely
use case.
(3) One _can_ use the PDCurses*-specific BUTTONn_MOVED masks, and
thereby get motion events only if the corresponding button(s) are held.
But for the sake of portability, you probably should instead use
REPORT_MOUSE_POSITION to get _all_ mouse events. Then keep track of which
buttons are held and filter out the events you aren't interested in. */
/* I don't think we can ever have more than three events in the list.
(For example, button 2 clicked, button 2 pressed, mouse moved.) */
#define MLIST_SIZE 3
static MOUSE_STATUS mouse_list[MLIST_SIZE];
static int _mlist_count = 0;
static short _button_change_flag( const int button)
{
return( (button < 3 ? 1 : 64) << button);
}
#define TRAPPING_EVENT( event, button) (SP->_trap_mbe & PDC_SHIFTED_BUTTON( event, (button) + 1))
bool _add_raw_mouse_event( const int button, const int event_type, const int modifiers,
const int x, const int y)
{
bool wait_for_next_event = FALSE;
MOUSE_STATUS *mptr = mouse_list + _mlist_count;
static int held_buttons = 0;
int i;
assert( _mlist_count >= 0 && _mlist_count < MLIST_SIZE);
assert( button >= 0 && button < PDC_MAX_MOUSE_BUTTONS);
mptr->x = x;
mptr->y = y;
for( i = 0; i < PDC_MAX_MOUSE_BUTTONS; i++)
mptr->button[i] = (short)modifiers;
switch( event_type)
{
case BUTTON_MOVED:
{
MOUSE_STATUS *prev = (_mlist_count ? mptr - 1 : &SP->mouse_status);
if( x != prev->x || y != prev->y)
{
mptr->changes = 0;
if( SP->_trap_mbe & REPORT_MOUSE_POSITION)
mptr->changes = PDC_MOUSE_MOVED;
for( i = 0; i < PDC_MAX_MOUSE_BUTTONS; i++)
if( TRAPPING_EVENT( BUTTON1_MOVED, i)
&& ((held_buttons >> i) & 1))
{
mptr->button[i] |= BUTTON_MOVED;
mptr->changes |= PDC_MOUSE_MOVED | (1 << i);
}
if( mptr->changes)
{
mptr->x = x;
mptr->y = y;
_mlist_count++;
}
}
}
break;
case PDC_MOUSE_WHEEL_UP:
case PDC_MOUSE_WHEEL_DOWN:
case PDC_MOUSE_WHEEL_LEFT:
case PDC_MOUSE_WHEEL_RIGHT:
if( SP->_trap_mbe & MOUSE_WHEEL_SCROLL)
{
_mlist_count++;
mptr->changes = event_type;
}
break;
case BUTTON_PRESSED:
mptr->button[button] = BUTTON_PRESSED | (short)modifiers;
mptr->changes = _button_change_flag( button);
held_buttons |= (1 << button);
_mlist_count++;
if( SP->mouse_wait && TRAPPING_EVENT( BUTTON1_CLICKED, button))
wait_for_next_event = TRUE; /* may need to synthesize a click */
break;
case BUTTON_RELEASED:
mptr->button[button] = BUTTON_RELEASED | (short)modifiers;
mptr->changes = _button_change_flag( button);
held_buttons &= ~(1 << button);
mptr->changes = _button_change_flag( button);
if( _mlist_count && mptr[-1].changes == mptr->changes
&& mptr[-1].button[button] == (BUTTON_PRESSED | modifiers))
{ /* can combine press with release to make a click */
mptr[-1].button[button] = BUTTON_CLICKED | (short)modifiers;
if( _mlist_count > 1 && mptr[-2].changes == mptr->changes)
{
if( mptr[-2].button[button] == (BUTTON_DOUBLE_CLICKED | modifiers))
{
mptr[-2].button[button] = BUTTON_TRIPLE_CLICKED | (short)modifiers;
_mlist_count--;
}
if( mptr[-2].button[button] == (BUTTON_CLICKED | modifiers))
{
mptr[-2].button[button] = BUTTON_DOUBLE_CLICKED | (short)modifiers;
_mlist_count--;
if( TRAPPING_EVENT( BUTTON1_TRIPLE_CLICKED, button))
wait_for_next_event = TRUE;
} /* wait to see if we get a triple-click */
}
else if( TRAPPING_EVENT( BUTTON1_DOUBLE_CLICKED, button))
wait_for_next_event = TRUE;
}
else if( TRAPPING_EVENT( BUTTON1_RELEASED, button))
_mlist_count++; /* just returning an uncooked release event */
break;
default:
fprintf( stderr, "Got event %d, button %d\n", event_type, button);
assert( 0);
break;
}
return( wait_for_next_event);
}
bool _get_mouse_event( MOUSE_STATUS *mstatus)
{
if( _mlist_count) /* should filter untrapped press/releases */
{
*mstatus = mouse_list[0];
_mlist_count--;
memmove( mouse_list, mouse_list + 1, _mlist_count * sizeof( MOUSE_STATUS));
return( TRUE);
}
else
return FALSE;
}