diff --git a/gtkarea/area.c b/gtkarea/area.c index b93a0dae..e415b20b 100644 --- a/gtkarea/area.c +++ b/gtkarea/area.c @@ -175,7 +175,7 @@ static uiModifiers toModifiers(guint state) uiModifiers m; m = 0; - if ((state & GDK_CONTROL_MASK) != 0)) + if ((state & GDK_CONTROL_MASK) != 0) m |= uiModifierCtrl; if ((state & GDK_META_MASK) != 0) m |= uiModifierAlt; @@ -280,7 +280,7 @@ static gboolean areaWidget_motion_notify_event(GtkWidget *w, GdkEventMotion *e) // we want switching away from the control to reset the double-click counter, like with WM_ACTIVATE on Windows // according to tristan in irc.gimp.net/#gtk+, doing this on enter-notify-event and leave-notify-event is correct (and it seems to be true in my own tests; plus the events DO get sent when switching programs with the keyboard (just pointing that out)) // differentiating between enter-notify-event and leave-notify-event is unimportant -gboolean areaWidget_enterleave_notify_event(GtkWidget *widget, GdkEventCrossing *e) +gboolean areaWidget_enterleave_notify_event(GtkWidget *w, GdkEventCrossing *e) { struct areaPrivate *ap = areaWidget(w)->priv; diff --git a/gtkarea/area.h b/gtkarea/area.h index fff3aabf..242b25ec 100644 --- a/gtkarea/area.h +++ b/gtkarea/area.h @@ -28,6 +28,7 @@ struct areaWidgetClass { extern GType areaWidget_get_type(void); #include "ui.h" +#include "uipriv.h" extern GtkWidget *newArea(uiAreaHandler *ah); extern void areaUpdateScroll(GtkWidget *area); diff --git a/gtkarea/events.c b/gtkarea/events.c new file mode 100644 index 00000000..fd30ac6f --- /dev/null +++ b/gtkarea/events.c @@ -0,0 +1,161 @@ +// 29 march 2014 +// TODO remove +#include +#include "ui.h" +#include "uipriv.h" + +/* +Windows and GTK+ have a limit of 2 and 3 clicks, respectively, natively supported. Fortunately, we can simulate the double/triple-click behavior to build higher-order clicks. We can use the same algorithm Windows uses on both: + http://blogs.msdn.com/b/oldnewthing/archive/2004/10/18/243925.aspx +For GTK+, we pull the double-click time and double-click distance, which work the same as the equivalents on Windows (so the distance is in all directions), from the GtkSettings system. + +On GTK+ this will also allow us to discard the GDK_BUTTON_2PRESS and GDK_BUTTON_3PRESS events, so the button press stream will be just like on other platforms. + +Thanks to mclasen, garnacho_, halfline, and tristan in irc.gimp.net/#gtk+. +*/ + +// x, y, xdist, ydist, and c.rect must have the same units +// so must time, maxTime, and c.prevTime +uintmax_t clickCounterClick(clickCounter *c, uintmax_t button, intmax_t x, intmax_t y, uintptr_t time, uintptr_t maxTime, intmax_t xdist, intmax_t ydist) +{ + // different button than before? if so, don't count + if (button != c->curButton) + c->count = 0; + + // (x, y) in the allowed region for a double-click? if not, don't count + if (x < c->rectX0) + c->count = 0; + if (y < c->rectY0) + c->count = 0; + if (x >= c->rectX1) + c->count = 0; + if (y >= c->rectY1) + c->count = 0; + + // too slow? if so, don't count + // note the below expression; time > (c.prevTime + maxTime) can overflow! + if ((time - c->prevTime) > maxTime) // too slow; don't count + c->count = 0; + + c->count++; // if either of the above ifs happened, this will make the click count 1; otherwise it will make the click count 2, 3, 4, 5, ... + + // now we need to update the internal structures for the next test + c->curButton = button; + c->prevTime = time; + c->rectX0 = x - xdist; + c->rectY0 = y - ydist; + c->rectX1 = x + xdist; + c->rectY1 = y + ydist; + + return c->count; +} + +void clickCounterReset(clickCounter *c) +{ + c->curButton = 0; + c->rectX0 = 0; + c->rectY0 = 0; + c->rectX1 = 0; + c->rectY1 = 0; + c->prevTime = 0; + c->count = 0; +} + +/* +For position independence across international keyboard layouts, typewriter keys are read using scancodes (which are always set 1). +Windows provides the scancodes directly in the LPARAM. +GTK+ provides the scancodes directly from the underlying window system via GdkEventKey.hardware_keycode. +On X11, this is scancode + 8 (because X11 keyboard codes have a range of [8,255]). +Wayland is guaranteed to give the same result (thanks ebassi in irc.gimp.net/#gtk+). +On Linux, where evdev is used instead of polling scancodes directly from the keyboard, evdev's typewriter section key code constants are the same as scancodes anyway, so the rules above apply. +Typewriter section scancodes are the same across international keyboards with some exceptions that have been accounted for (see KeyEvent's documentation); see http://www.quadibloc.com/comp/scan.htm for details. +Non-typewriter keys can be handled safely using constants provided by the respective backend API. + +Because GTK+ keysyms may or may not obey Num Lock, we also handle the 0-9 and . keys on the numeric keypad with scancodes (they match too). +*/ + +/* +TODO + +// use uintptr to be safe; the size of the scancode/hardware key code field on each platform is different +var scancodeKeys = map[uintptr]byte{ + 0x02: '1', + 0x03: '2', + 0x04: '3', + 0x05: '4', + 0x06: '5', + 0x07: '6', + 0x08: '7', + 0x09: '8', + 0x0A: '9', + 0x0B: '0', + 0x0C: '-', + 0x0D: '=', + 0x0E: '\b', + 0x0F: '\t', + 0x10: 'q', + 0x11: 'w', + 0x12: 'e', + 0x13: 'r', + 0x14: 't', + 0x15: 'y', + 0x16: 'u', + 0x17: 'i', + 0x18: 'o', + 0x19: 'p', + 0x1A: '[', + 0x1B: ']', + 0x1C: '\n', + 0x1E: 'a', + 0x1F: 's', + 0x20: 'd', + 0x21: 'f', + 0x22: 'g', + 0x23: 'h', + 0x24: 'j', + 0x25: 'k', + 0x26: 'l', + 0x27: ';', + 0x28: '\'', + 0x29: '`', + 0x2B: '\\', + 0x2C: 'z', + 0x2D: 'x', + 0x2E: 'c', + 0x2F: 'v', + 0x30: 'b', + 0x31: 'n', + 0x32: 'm', + 0x33: ',', + 0x34: '.', + 0x35: '/', + 0x39: ' ', +} + +var scancodeExtKeys = map[uintptr]ExtKey{ + 0x47: N7, + 0x48: N8, + 0x49: N9, + 0x4B: N4, + 0x4C: N5, + 0x4D: N6, + 0x4F: N1, + 0x50: N2, + 0x51: N3, + 0x52: N0, + 0x53: NDot, +} + +func fromScancode(scancode uintptr) (ke KeyEvent, ok bool) { + if key, ok := scancodeKeys[scancode]; ok { + ke.Key = key + return ke, true + } + if extkey, ok := scancodeExtKeys[scancode]; ok { + ke.ExtKey = extkey + return ke, true + } + return ke, false +} + +*/ diff --git a/gtkarea/main.c b/gtkarea/main.c index 3237aea4..058ecbc6 100644 --- a/gtkarea/main.c +++ b/gtkarea/main.c @@ -104,6 +104,20 @@ static int handlerRedrawOnResize(uiAreaHandler *a, uiArea *area) return 1; } +static void handlerMouseEvent(uiAreaHandler *a, uiArea *area, uiAreaMouseEvent *e) +{ + printf("mouse (%d,%d):(%d,%d) dn:%d up:%d count:%d mods:%x held:%x\n", + (int) e->X, + (int) e->Y, + (int) e->HScrollPos, + (int) e->VScrollPos, + (int) e->Down, + (int) e->Up, + (int) e->Count, + (uint32_t) e->Modifiers, + e->Held1To64); +} + static void recalcScroll(GtkSpinButton *sb, gpointer data) { areaUpdateScroll(area); @@ -130,6 +144,7 @@ int main(void) h.ah.HScrollMax = handlerHScrollMax; h.ah.VScrollMax = handlerVScrollMax; h.ah.RedrawOnResize = handlerRedrawOnResize; + h.ah.MouseEvent = handlerMouseEvent; gtk_init(NULL, NULL); diff --git a/gtkarea/ui.h b/gtkarea/ui.h index 987fc8b5..7d7c7ecb 100644 --- a/gtkarea/ui.h +++ b/gtkarea/ui.h @@ -138,6 +138,8 @@ struct uiAreaMouseEvent { uintmax_t Down; uintmax_t Up; + uintmax_t Count; + uiModifiers Modifiers; uint64_t Held1To64; diff --git a/gtkarea/uipriv.h b/gtkarea/uipriv.h new file mode 100644 index 00000000..d0e1c7d9 --- /dev/null +++ b/gtkarea/uipriv.h @@ -0,0 +1,14 @@ +typedef struct clickCounter clickCounter; +// you should call Reset() to zero-initialize a new instance +// it doesn't matter that all the non-count fields are zero: the first click will fail the curButton test straightaway, so it'll return 1 and set the rest of the structure accordingly +struct clickCounter { + uintmax_t curButton; + intmax_t rectX0; + intmax_t rectY0; + intmax_t rectX1; + intmax_t rectY1; + uintptr_t prevTime; + uintmax_t count; +}; +extern uintmax_t clickCounterClick(clickCounter *, uintmax_t, intmax_t, intmax_t, uintptr_t, uintptr_t, intmax_t, intmax_t); +extern void clickCounterReset(clickCounter *);