[comp.windows.open-look] Detecting and handling mouse double click with XView - paper

jcb@frisbee.Eng.Sun.COM (Jim Becker) (02/09/91)

	 Detecting and handling mouse double click with XView

				  by

			      Jim Becker

		Sun Microsystems, Inc. -- Feb 8, 1991

Are  you  interested  in  double  click  functionality  for your XView
application?  Many programmers are  rolling  their  own  double  click
algorithms for XView based applications.   Some  questions  have  been
raised  concerning  this, as little (if any) documentation covers this
topic.  Thus this paper has been created.

This is a primer on how to detect double-click  actions  by  the  user
within  the XView toolkit. And how to structure double click functions
semantically.


-- double click scenarios --

There are two main strategies for  alerting  a  program  of  end  user
double  clicking.  The first being generation of specific double click
events in the event stream. In this scenario the toolkit, specifically
the event dispatch logic, would recognize and emit actions significant
to double click. Thus, in addition to the ACTION_SELECT events,  there
would be an ACTION_DBL_SELECT when a double click was detected.

It would be possible to add a new action,  ACTION_DBL_SELECT,  to  the
XView logic.  Or a flag can be added to the Event structure indicating
multi-click has taken  place.   However,  one  can  provide  the  same
functionality  using  existing field releases of XView. One simply has
to add a snippet of code to their logic.  In the future we may  indeed
provide  a  new  action,  or  augment  the Event structure to indicate
double click selection; but you can do it yourself *today* in your own
home! (Well, workstation.)

The second scenario is detecting double  click  by  checking  for  the
delta  time between two successive mouse clicks. In the case of XView,
as well as Xlib, the second means is the suggested approach - as there
are no explicit double click events defined for either event stream.

Within XView, double click can be detected by calculating time deltas
between subsequent ACTION_SELECT messages. The user event handler  has
to cache the time value for the last ACTION_SELECT event, and use this
time for a delta calculation if another ACTION_SELECT comes  in  next.
If the time between two ACTION_SELECT events is within the multi click
timeout, the application program processes the second mouse click as a
double click.

Extensions of this into triple and quadruple (!) clicks can be done by
augmenting the logic. It would be up to  the  application  to  provide
semantic consistency for this, of course.


-- logical use of single/double click functionality --

It is important for the application programmer to  understand  that  a
double  click action should be an extension of the action taken with a
single click. Meaning that the application  process  the  first  click
without waiting to see if there is  another  click,  or  performing  a
fundamentally different action when there is a double click.

For  example,  a  smooth  interpretation  of  the  single/double click
paridigm would be:

	<single>	Save current file
	<double>	Save all files

or

	<single>	Select word
	<double>	Select line

Normally,  one  does  not  want  to  create semantic actions where the
double click means something fundamentally different from single:

	<single>	Save current file
	<double>	Load new file

Some  implementations  of  double click can be created to perform this
sort  of  functionality.  Specifically  the program can delve into the
coming  event  queue  searching for more mouse down events. Or a timer
can be used that goes off after the double click threshhold. 

Although there are sometimes reasons to do this, in the spirit of user
interface consistency we hope you refrain from this  if  possible.  If
you  desire  code  that  performs this function, we have some examples
(email me). However this sort of usage is generally discouraged.

-- code to test for double click in XView --

Within  the  code  to detect double clicks, the application programmer
needs  to  do several things. The programmer needs to add a few values
to the object data content associated with their textsw object.  These
values  are  used  to  store  the  last  time  value  for the previous
ACTION_SELECT event. The X/Y location of this  event  should  also  be
stored, as it is used to determine the delta  of  the  mouse  position
between subsequent mouse clicks.

Here is the code in current textsw that  determines  multi-click,  for
example.   This  code  is  from  the  file  txt_sel.c,  with  a little
formatting cleanup. I believe  this  is  representative  to  how  most
implementations  are  done.  (it is also somewhat incomplete - look at
the source for more detail.)

<existing code fragment from textsw>

	...

	switch (event_action(ie)) {
	
	case TXTSW_POINT:	/* aka ACTION_SELECT */
		int             delta;	/* in millisecs */

		if (folio->state & TXTSW_SHIFT_DOWN) 
			folio->track_state |= TXTSW_TRACK_ADJUST;
		else
			folio->track_state |= TXTSW_TRACK_POINT;

		delta 	 = (ie->ie_time.tv_sec - folio->last_point.tv_sec) * 1000;
		delta 	+= ie->ie_time.tv_usec / 1000;
		delta 	-= folio->last_point.tv_usec / 1000;

		if (delta >= folio->multi_click_timeout) {

			/* single click */
		} else {
			/* multi-click  */
		}

	....


In  this  case  time within the event is compared with the time of the
last  select event, and if it's within the `multi_click_timeout' range
then semantically the multi-click is selected. 

This  same  logic  can  be  combined  into  a  singular  function that
statically retains the information needed to detect the double  click.
The advantage of this approach is that it would not require changes to
the user's object data structures. An example would be:


<simple example to recognize doubleclick>

	/*
 	 *	return boolean if doubleclick based on last event info.
	 *	value based on time threshold along, which is passed
	 *	as a parameter.
 	 */

	extern	short
	app_check_if_double_click( event, time_threshold )
	Event		*event;
	int		time_threshold;
	{
		static	Event		last_event;
		int			delta;
		short			ret_value	= FALSE;

		/* only deal with the down events */
		if( event_is_up(event) )
			return ret_value;

		if( event_action(event)       == ACTION_SELECT &&
		    event_action(&last_event) == ACTION_SELECT ) {

			delta    = (event->ie_time.tv_sec - last_event.ie_time.tv_sec)
					 * 1000;
			delta 	+= event->ie_time.tv_usec / 1000;
			delta 	-= last_event.ie_time.tv_usec / 1000;

			if( delta <= time_threshold )
				ret_value	= TRUE;
		}
						
		last_event	= *event;

		return ret_value;
	}


Note  that  in  both  these cases one must compare against the timeout
threshold.   The  suggested  value  is  in  the  resource  database as
OpenWindows.MultiClickTimeout. In the actual  textsw  code  the  value
used is Mouse.Multiclick.Space. (don't ask - I don't know..)

To further insure  that  the  user  is  interested  in  a  multi-click
operation,  rather  than  a press-drag-release operation, the code can
include checking for X/Y proximity of the two events. The proper value
to be used from the resource  database  is  OpenWindows.DragThreshold.

The preferred version for  the  `definitive  doubleclick'  takes  into
account  these  other  factors. Here is a code snippett that also self
initializes.

<refined code to recognize doubleclick>

	/*
 	 *	return boolean if doubleclick based on last event info.
	 *	both time and mouse distance are taken into account.
 	 *	values are self initializing on first invocation.
 	 */

	extern	short
	app_check_if_double_click( event )
	Event		*event;
	{
		static	Event		last_event;
		static	int		time_threshold;
		static	int		dist_threshold;
		static	short		first_time	= TRUE;
		short			ret_value	= FALSE;
		int			delta_time;
		int			delta_x, delta_y;

		/* first time this is called init the thresholds */
		if( first_time ) {

			time_threshold	= textsw_get_from_defaults(TEXTSW_MULTI_CLICK_TIMEOUT);
			dist_threshold	= textsw_get_from_defaults(TEXTSW_MULTI_CLICK_SPACE);

			first_time	= FALSE;
		}

		/* only deal with the down events */
		if( event_is_up(event) )
			return ret_value;

		if( event_action(event)       == ACTION_SELECT &&
		    event_action(&last_event) == ACTION_SELECT ) {

			delta_time    	 = (event->ie_time.tv_sec - last_event.ie_time.tv_sec)
				            * 1000;
			delta_time	+= event->ie_time.tv_usec / 1000;
			delta_time 	-= last_event.ie_time.tv_usec / 1000;

			/* is the time within bounds? */
			if( delta_time <= time_threshold ) {

				/* check to see if the distance is ok */
				delta_x	= (last_event.ie_locx > event->ie_locx ?
					   last_event.ie_locx - event->ie_locx :
					   event->ie_locx - last_event.ie_locx);

				delta_y	= (last_event.ie_locy > event->ie_locy ?
					   last_event.ie_locy - event->ie_locy :
					   event->ie_locy - last_event.ie_locy);

				if( delta_x <= dist_threshold &&
				    delta_y <= dist_threshold )
					ret_value	= TRUE;
			}
		}
						
		last_event	= *event;

		return ret_value;
	}



Further discussion and comment on this topic is encouraged.  Hope this
helps everyone out!


-Jim

--
--    
	 Jim Becker / jcb%frisbee@sun.com  / Sun Microsystems

adam@ste.dyn.bae.co.uk (Adam Curtin) (02/12/91)

As a developer of a C++ interface to XView, I found Jim's paper very
interesting.  It's attractive to us to incorporate features that we feel have
been wrongly omitted from XView.  There was, however, one point that neither the
text of the paper, nor the code so far as I understood it, made clear: the fate
of the first ACTION_SELECT event of a (potential) multi-click sequence.

After receiving a click, the program can deliver the event as a single-click
(ie, ACTION_SELECT), or withhold it in anticipation of more clicks in a
multi-click sequence. Both choices have problems.  If the program delivers the
first click as a single-click, there's trouble if another click arrives within
the multi-click timeout.  If it withholds the event to compare against the next
click, it runs the risk of delaying the withheld click indefinitely.

So much for the passive processing of multi-clicks.  A couple of ideas occurred
to me immediately, both of which were discouraged by Jim's paper ...

In article <7698@exodus.Eng.Sun.COM> jcb@frisbee.Eng.Sun.COM (Jim Becker) writes:
>Some  implementations  of  double click can be created to perform this
>sort  of  functionality.  Specifically  the program can delve into the
>coming  event  queue  searching for more mouse down events. Or a timer
>can be used that goes off after the double click threshhold. 
>
>Although there are sometimes reasons to do this, in the spirit of user
>interface consistency we hope you refrain from this  if  possible.  If
>you  desire  code  that  performs this function, we have some examples
>(email me). However this sort of usage is generally discouraged.

Of course, neither of these solutions are ideal:  event-queue peeking could
still block, and setting timers in our toolkit could interfere with toolkit
users.

What would be _really_ good would be to post a next-click-should-have-arrived-
by-now event in the future, but you can't do that with XView.  Where'd that NeWS
manual go ...?

Adam
-- 
/home/research/adam/.signature: No such file or directory