Include code in midend.c to sanitise streams of mouse events so that
authorsimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Tue, 25 Jan 2005 14:07:46 +0000 (14:07 +0000)
committersimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Tue, 25 Jan 2005 14:07:46 +0000 (14:07 +0000)
back ends can be assured of always receiving them in a sensible
sequence (button-down, followed by zero or more drags, followed by
button-up, and never changing button in the middle of such a
sequence). I have a suspicion this issue was the cause of the
mysterious Pattern grid updates seen by Dan during testing last
night.

git-svn-id: svn://svn.tartarus.org/sgt/puzzles@5208 cda61777-01e9-0310-a592-d414129be87e

midend.c
puzzles.h

index a28f3db..f8e922d 100644 (file)
--- a/midend.c
+++ b/midend.c
@@ -32,6 +32,8 @@ struct midend_data {
     float anim_time, anim_pos;
     float flash_time, flash_pos;
     int dir;
+
+    int pressed_mouse_button;
 };
 
 #define ensure(me) do { \
@@ -66,6 +68,7 @@ midend_data *midend_new(frontend *fe, const game *ourgame)
     me->flash_time = me->flash_pos = 0.0F;
     me->dir = 0;
     me->ui = NULL;
+    me->pressed_mouse_button = 0;
 
     sfree(randseed);
 
@@ -114,6 +117,7 @@ void midend_new_game(midend_data *me)
     if (me->ui)
         me->ourgame->free_ui(me->ui);
     me->ui = me->ourgame->new_ui(me->states[0]);
+    me->pressed_mouse_button = 0;
 }
 
 void midend_restart_game(midend_data *me)
@@ -180,7 +184,7 @@ static void midend_stop_anim(midend_data *me)
     }
 }
 
-int midend_process_key(midend_data *me, int x, int y, int button)
+static int midend_really_process_key(midend_data *me, int x, int y, int button)
 {
     game_state *oldstate = me->ourgame->dup_game(me->states[me->statepos - 1]);
     float anim_time;
@@ -255,6 +259,99 @@ int midend_process_key(midend_data *me, int x, int y, int button)
     return 1;
 }
 
+int midend_process_key(midend_data *me, int x, int y, int button)
+{
+    int ret = 1;
+
+    /*
+     * Harmonise mouse drag and release messages.
+     * 
+     * Some front ends might accidentally switch from sending, say,
+     * RIGHT_DRAG messages to sending LEFT_DRAG, half way through a
+     * drag. (This can happen on the Mac, for example, since
+     * RIGHT_DRAG is usually done using Command+drag, and if the
+     * user accidentally releases Command half way through the drag
+     * then there will be trouble.)
+     * 
+     * It would be an O(number of front ends) annoyance to fix this
+     * in the front ends, but an O(number of back ends) annoyance
+     * to have each game capable of dealing with it. Therefore, we
+     * fix it _here_ in the common midend code so that it only has
+     * to be done once.
+     * 
+     * Semantics:
+     * 
+     *  - when we receive a button down message, we remember which
+     *    button went down.
+     * 
+     *  - if we receive a drag or release message for a button we
+     *    don't currently think is pressed, we fabricate a
+     *    button-down for it before sending it.
+     * 
+     *  - if we receive (or fabricate) a button down message
+     *    without having seen a button up for the previously
+     *    pressed button, we invent the button up before sending
+     *    the button down.
+     * 
+     * Therefore, front ends can just send whatever data they
+     * happen to be conveniently able to get, and back ends can be
+     * guaranteed of always receiving a down, zero or more drags
+     * and an up for a single button at a time.
+     */
+    if (IS_MOUSE_DRAG(button) || IS_MOUSE_RELEASE(button)) {
+        int which;
+
+        if (IS_MOUSE_DRAG(button))
+            which = button + (LEFT_BUTTON - LEFT_DRAG);
+        else
+            which = button + (LEFT_BUTTON - LEFT_RELEASE);
+
+        if (which != me->pressed_mouse_button) {
+            /*
+             * Fabricate a button-up for the currently pressed
+             * button, if any.
+             */
+            if (me->pressed_mouse_button) {
+                ret = ret && midend_really_process_key
+                    (me, x, y, (me->pressed_mouse_button +
+                                (LEFT_RELEASE - LEFT_BUTTON)));
+            }
+
+            /*
+             * Now fabricate a button-down for this one.
+             */
+            ret = ret && midend_really_process_key(me, x, y, which);
+
+            /*
+             * And set the currently pressed button to this one.
+             */
+            me->pressed_mouse_button = which;
+        }
+    } else if (IS_MOUSE_DOWN(button) && me->pressed_mouse_button) {
+        /*
+         * Fabricate a button-up for the previously pressed button.
+         */
+        ret = ret && midend_really_process_key
+            (me, x, y, (me->pressed_mouse_button +
+                        (LEFT_RELEASE - LEFT_BUTTON)));
+    }
+
+    /*
+     * Now send on the event we originally received.
+     */
+    ret = ret && midend_really_process_key(me, x, y, button);
+
+    /*
+     * And update the currently pressed button.
+     */
+    if (IS_MOUSE_RELEASE(button))
+        me->pressed_mouse_button = 0;
+    else if (IS_MOUSE_DOWN(button))
+        me->pressed_mouse_button = button;
+
+    return ret;
+}
+
 void midend_redraw(midend_data *me)
 {
     if (me->statepos > 0 && me->drawstate) {
index 4de81a7..c80f69b 100644 (file)
--- a/puzzles.h
+++ b/puzzles.h
@@ -34,6 +34,13 @@ enum {
     CURSOR_DOWN_RIGHT
 };
 
+#define IS_MOUSE_DOWN(m) ( (unsigned)((m) - LEFT_BUTTON) <= \
+                               (unsigned)(RIGHT_BUTTON - LEFT_BUTTON))
+#define IS_MOUSE_DRAG(m) ( (unsigned)((m) - LEFT_DRAG) <= \
+                               (unsigned)(RIGHT_DRAG - LEFT_DRAG))
+#define IS_MOUSE_RELEASE(m) ( (unsigned)((m) - LEFT_RELEASE) <= \
+                               (unsigned)(RIGHT_RELEASE - LEFT_RELEASE))
+
 #define IGNOREARG(x) ( (x) = (x) )
 
 typedef struct frontend frontend;