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