Index: doc/bind.n ================================================================== --- doc/bind.n +++ doc/bind.n @@ -171,15 +171,15 @@ .ta \w'\fBButton, ButtonPress\0\0\0\fR'u +\w'\fBKey, KeyPress\0\0\0\fR'u \fBActivate\fR \fBDestroy\fR \fBMap\fR \fBButton\fR, \fBButtonPress\fR \fBEnter\fR \fBMapRequest\fR \fBButtonRelease\fR \fBExpose\fR \fBMotion\fR \fBCirculate\fR \fBFocusIn\fR \fBMouseWheel\fR -\fBCirculateRequest\fR \fBFocusOut\fR \fBProperty\fR -\fBColormap\fR \fBGravity\fR \fBReparent\fR -\fBConfigure\fR \fBKey\fR, \fBKeyPress\fR \fBResizeRequest\fR -\fBConfigureRequest\fR \fBKeyRelease\fR \fBUnmap\fR -\fBCreate\fR \fBLeave\fR \fBVisibility\fR +\fBTouchpadScroll\fR \fBCirculateRequest\fR \fBFocusOut\fR +\fBProperty\fR \fBColormap\fR \fBGravity\fR +\fBReparent\fR \fBConfigure\fR \fBKey\fR, \fBKeyPress\fR +\fBResizeRequest\fR \fBConfigureRequest\fR \fBKeyRelease\fR +\fBUnmap\fR \fBCreate\fR \fBLeave\fR \fBVisibility\fR \fBDeactivate\fR .DE Most of the above events have the same fields and behaviors as events in the X Windowing system. You can find more detailed descriptions of these events in any X window programming book. A couple of the events @@ -196,11 +196,11 @@ sub-windows in a toplevel when it changes from being deactive to active. Likewise, the \fBDeactive\fR event is sent when the window's state changes from active to deactive. There are no useful percent substitutions you would make when binding to these events. .IP \fBMouseWheel\fR 5 -Many contemporary mice support a mouse wheel, which is used +Many contemporary mice include a mouse wheel, which is used for scrolling documents without using the scrollbars. By rolling the wheel, the system will generate \fBMouseWheel\fR events that the application can use to scroll. The event is routed to the window currently under the mouse pointer. When the event is received you can use the \fB%D\fR substitution to get the @@ -210,16 +210,30 @@ value determines which direction your widget should scroll. Positive values should scroll up and negative values should scroll down. .RS .PP Horizontal scrolling uses \fBShift-MouseWheel\fR events, with positive -\fB%D\fR \fIdelta\fR substitution indicating left scrolling and negative -right scrolling. -Horizontal scrolling events may fire from -many different hardware units such as tilt wheels or touchpads. Horizontal -scrolling can also be emulated by holding Shift and scrolling vertically. +\fB%D\fR \fIdelta\fR substitution indicating left scrolling and +negative right scrolling. Horizontal scrolling events are generated +tilt wheels on some mice. Horizontal scrolling can also be emulated +by holding Shift and scrolling vertically. .RE +.IP "\fBTouchpadScroll\fR" 5 +On some platforms (currently Windows and macOS) there is support for +high-resolution scrolling devices, such as touchpads. This is +provided via \fBTouchpadScroll\fR events. These events store two +16 bit delta values in the integer provided by the \fB%D\fR +substitution. The \fIX\fR delta is in the high order 16 bits and the +\fIY\fR delta is in the low order 16 bits. These values can be +unpacked by using the tk::PreciseScrollDeltas utility procedure. For +example: +.CS +lassign [tk::PreciseScrollDeltas %D] deltaX deltaY +.CE +The \fB$#\fR substitution is a counter for \fBTouchpadScroll\fR events +which can be used by widgets that only support scrolling by units to +ignore some portion of the events. .IP "\fBKeyPress\fR, \fBKeyRelease\fR" 5 The \fBKeyPress\fR and \fBKeyRelease\fR events are generated whenever a key is pressed or released. \fBKeyPress\fR and \fBKeyRelease\fR .IP "\fBKey\fR, \fBKeyRelease\fR" 5 The \fBKey\fR and \fBKeyRelease\fR events are generated Index: generic/tk.h ================================================================== --- generic/tk.h +++ generic/tk.h @@ -667,12 +667,14 @@ #define VirtualEvent (MappingNotify + 1) #define ActivateNotify (MappingNotify + 2) #define DeactivateNotify (MappingNotify + 3) #define MouseWheelEvent (MappingNotify + 4) -#define TK_LASTEVENT (MappingNotify + 5) +#define TouchpadScroll (MappingNotify + 5) +#define TK_LASTEVENT (MappingNotify + 6) +#define TouchpadScrollMask (1L << 27) #define MouseWheelMask (1L << 28) #define ActivateMask (1L << 29) #define VirtualEventMask (1L << 30) /* Index: generic/tkBind.c ================================================================== --- generic/tkBind.c +++ generic/tkBind.c @@ -526,10 +526,11 @@ {"Property", PropertyNotify, PropertyChangeMask}, {"Colormap", ColormapNotify, ColormapChangeMask}, {"Activate", ActivateNotify, ActivateMask}, {"Deactivate", DeactivateNotify, ActivateMask}, {"MouseWheel", MouseWheelEvent, MouseWheelMask}, + {"TouchpadScroll", TouchpadScroll, TouchpadScrollMask}, {"CirculateRequest", CirculateRequest, SubstructureRedirectMask}, {"ConfigureRequest", ConfigureRequest, SubstructureRedirectMask}, {"Create", CreateNotify, SubstructureNotifyMask}, {"MapRequest", MapRequest, SubstructureRedirectMask}, {"ResizeRequest", ResizeRequest, ResizeRedirectMask}, @@ -630,11 +631,12 @@ /* ClientMessage */ 0, /* MappingNotify */ 0, /* VirtualEvent */ VIRTUAL, /* Activate */ ACTIVATE, /* Deactivate */ ACTIVATE, - /* MouseWheel */ WHEEL + /* MouseWheel */ WHEEL, + /* TouchpadScroll */ WHEEL }; /* * The following table is used to map between the location where an generated * event should be queued and the string used to specify the location. @@ -5014,11 +5016,10 @@ } eventFlags = 0; if ((hPtr = Tcl_FindHashEntry(&eventTable, field))) { const EventInfo *eiPtr = (const EventInfo *)Tcl_GetHashValue(hPtr); - patPtr->eventType = eiPtr->type; eventFlags = flagArray[eiPtr->type]; eventMask = eiPtr->eventMask; p = GetField(SkipFieldDelims(p), field, sizeof(field)); } @@ -5089,11 +5090,10 @@ patPtr, 0, Tcl_NewStringObj("no event type or button # or keysym", TCL_INDEX_NONE), "UNMODIFIABLE"); } else if (patPtr->eventType == MotionNotify) { patPtr->info = ButtonNumberFromState(patPtr->modMask); } - p = SkipFieldDelims(p); if (*p != '>') { while (*p) { ++p; Index: generic/tkEvent.c ================================================================== --- generic/tkEvent.c +++ generic/tkEvent.c @@ -121,11 +121,12 @@ 0, /* ClientMessage */ 0, /* Mapping Notify */ VirtualEventMask, /* VirtualEvents */ ActivateMask, /* ActivateNotify */ ActivateMask, /* DeactivateNotify */ - MouseWheelMask /* MouseWheelEvent */ + MouseWheelMask, /* MouseWheelEvent */ + TouchpadScrollMask /* TouchpadScroll */ }; /* * For each exit handler created with a call to TkCreateExitHandler or * TkCreateThreadExitHandler there is a structure of the following type: Index: library/demos/cscroll.tcl ================================================================== --- library/demos/cscroll.tcl +++ library/demos/cscroll.tcl @@ -15,20 +15,20 @@ wm title $w "Scrollable Canvas Demonstration" wm iconname $w "cscroll" positionWindow $w set c $w.c -label $w.msg -font $font -wraplength 4i -justify left -text "This window displays a canvas widget that can be scrolled either using the scrollbars or by dragging with button 2 in the canvas. If you click button 1 on one of the rectangles, its indices will be printed on stdout." +label $w.msg -font $font -wraplength 4i -justify left -text "This window displays a canvas widget that can be scrolled by using the scrollbars, by dragging with button 2 in the canvas, by using a mouse wheel, or with the two-finger gesture on a touchpad. If you click button 1 on one of the rectangles, its indices will be printed on stdout." pack $w.msg -side top ## See Code / Dismiss buttons set btns [addSeeDismiss $w.buttons $w] pack $btns -side bottom -fill x frame $w.grid -ttk::scrollbar $w.hscroll -orient horizontal -command "$c xview" -ttk::scrollbar $w.vscroll -command "$c yview" +scrollbar $w.hscroll -orient horizontal -command "$c xview" +scrollbar $w.vscroll -command "$c yview" canvas $c -relief sunken -borderwidth 2 -scrollregion {-11c -11c 50c 20c} \ -xscrollcommand "$w.hscroll set" \ -yscrollcommand "$w.vscroll set" pack $w.grid -expand yes -fill both -padx 1 -pady 1 grid rowconfig $w.grid 0 -weight 1 -minsize 0 @@ -105,10 +105,16 @@ if {%D >= 0} { %W xview scroll [expr {%D/-3}] units } else { %W xview scroll [expr {(%D-2)/-3}] units } + } + bind $c { + lassign [tk::PreciseScrollDeltas %D] deltaX deltaY + if {$deltaX != 0 || $deltaY != 0} { + tk::ScrollByPixels %W $deltaX $deltaY + } } } if {[tk windowingsystem] eq "x11" && ![package vsatisfies [package provide Tk] 8.7-]} { # Support for mousewheels on Linux/Unix commonly comes through mapping Index: library/demos/items.tcl ================================================================== --- library/demos/items.tcl +++ library/demos/items.tcl @@ -31,10 +31,17 @@ -relief sunken -borderwidth 2 \ -xscrollcommand "$w.frame.hscroll set" \ -yscrollcommand "$w.frame.vscroll set" ttk::scrollbar $w.frame.vscroll -command "$c yview" ttk::scrollbar $w.frame.hscroll -orient horizontal -command "$c xview" + +bind $c { + lassign [tk::PreciseScrollDeltas %D] deltaX deltaY + if {$deltaX != 0 || $deltaY != 0} { + tk::ScrollByPixels %W $deltaX $deltaY + } +} grid $c -in $w.frame \ -row 0 -column 0 -rowspan 1 -columnspan 1 -sticky news grid $w.frame.vscroll \ -row 0 -column 1 -rowspan 1 -columnspan 1 -sticky news Index: library/listbox.tcl ================================================================== --- library/listbox.tcl +++ library/listbox.tcl @@ -173,22 +173,33 @@ %W scan mark %x %y } bind Listbox { %W scan dragto %x %y } - bind Listbox { - tk::MouseWheel %W y %D -40.0 + tk::MouseWheel %W y %D -40.0 units } bind Listbox { - tk::MouseWheel %W y %D -12.0 + tk::MouseWheel %W y %D -12.0 units } bind Listbox { - tk::MouseWheel %W x %D -40.0 + tk::MouseWheel %W x %D -40.0 units } bind Listbox { - tk::MouseWheel %W x %D -12.0 + tk::MouseWheel %W x %D -12.0 units +} +bind Listbox { + if {[expr {%# %% 15}] != 0} { + return + } + lassign [tk::PreciseScrollDeltas %D] deltaX deltaY + if {$deltaX != 0} { + %W xview scroll [expr {-$deltaX}] units + } + if {$deltaY != 0} { + %W yview scroll [expr {-$deltaY}] units + } } # ::tk::ListboxBeginSelect -- # # This procedure is typically invoked on button-1 presses. It begins Index: library/scrlbar.tcl ================================================================== --- library/scrlbar.tcl +++ library/scrlbar.tcl @@ -127,15 +127,33 @@ bind Scrollbar <> { tk::ScrollToPos %W 1 } } +bind Scrollbar {+ + set tk::Priv(xEvents) 0; set tk::Priv(yEvents) 0 +} bind Scrollbar { - tk::ScrollByUnits %W hv %D -40.0 + tk::ScrollByUnits %W vh %D -40.0 } bind Scrollbar { + tk::ScrollByUnits %W vh %D -12.0 +} +bind Scrollbar { + tk::ScrollByUnits %W hv %D -40.0 +} +bind Scrollbar { tk::ScrollByUnits %W hv %D -12.0 +} +bind Scrollbar { + lassign [tk::PreciseScrollDeltas %D] deltaX deltaY + if {$deltaX != 0 && [%W cget -orient] eq "horizontal"} { + tk::ScrollbarScrollByPixels %W h $deltaX + } + if {$deltaY != 0 && [%W cget -orient] eq "vertical"} { + tk::ScrollbarScrollByPixels %W v $deltaY + } } # tk::ScrollButtonDown -- # This procedure is invoked when a button is pressed in a scrollbar. # It changes the way the scrollbar is displayed and takes actions @@ -292,28 +310,86 @@ [expr {$y - $Priv(pressY)}]] ScrollToPos $w [expr {$Priv(initPos) + $delta}] } set Priv(initPos) "" } + +# ::tk::ScrollbarScrollByPixels -- +# This procedure tells the scrollbar's associated widget to scroll up +# or down by a given number of pixels. It only works with scrollbars +# because it uses the delta command. +# +# Arguments: +# w - The scrollbar widget. +# orient - Which kind of scrollbar this applies to: "h" for +# horizontal, "v" for vertical. +# amount - How many pixels to scroll. + +proc ::tk::ScrollbarScrollByPixels {w orient amount} { + set cmd [$w cget -command] + if {$cmd eq ""} { + return + } + set xyview [lindex [split $cmd] end] + if {$orient eq "v"} { + if {$xyview eq "xview"} { + return + } + set size [winfo height $w] + } + if {$orient eq "h"} { + if {$xyview eq "yview"} { + return + } + set size [winfo width $w] + } + + # The code below works with both the current and old syntax for + # the scrollbar get command. + + set info [$w get] + if {[llength $info] == 2} { + set first [lindex $info 0] + } else { + set first [lindex $info 2] + } + set pixels [expr {-$amount}] + uplevel #0 $cmd moveto [expr $first + [$w delta $pixels $pixels]] +} # ::tk::ScrollByUnits -- # This procedure tells the scrollbar's associated widget to scroll up # or down by a given number of units. It notifies the associated widget # in different ways for old and new command syntaxes. # # Arguments: # w - The scrollbar widget. # orient - Which kinds of scrollbars this applies to: "h" for -# horizontal, "v" for vertical, "hv" for both. +# horizontal, "v" for vertical, "hv" or "vh" for both. # amount - How many units to scroll: typically 1 or -1. proc ::tk::ScrollByUnits {w orient amount {factor 1.0}} { set cmd [$w cget -command] if {$cmd eq "" || ([string first \ [string index [$w cget -orient] 0] $orient] < 0)} { return } + + if {[string length $orient] == 2 && $factor != 1.0} { + # Count both the and + # events, and ignore the non-dominant ones + + variable ::tk::Priv + set axis [expr {[string index $orient 0] eq "h" ? "x" : "y"}] + incr Priv(${axis}Events) + if {($Priv(xEvents) + $Priv(yEvents) > 10) && + ($axis eq "x" && $Priv(xEvents) < $Priv(yEvents) || + $axis eq "y" && $Priv(yEvents) < $Priv(xEvents))} { + return + } + } + set info [$w get] if {[llength $info] == 2} { uplevel #0 $cmd scroll [expr {$amount/$factor}] units } else { uplevel #0 $cmd [expr {[lindex $info 2] + [expr {$amount/$factor}]}] Index: library/text.tcl ================================================================== --- library/text.tcl +++ library/text.tcl @@ -465,10 +465,19 @@ bind Text { tk::MouseWheel %W x [tk::ScaleNum %D] -4.0 pixels } bind Text { tk::MouseWheel %W x [tk::ScaleNum %D] -1.2 pixels +} +bind Text { + lassign [tk::PreciseScrollDeltas %D] deltaX deltaY + if {$deltaX != 0} { + %W xview scroll [tk::ScaleNum [expr {-$deltaX}]] pixels + } + if {$deltaY != 0} { + %W yview scroll [tk::ScaleNum [expr {-$deltaY}]] pixels + } } # ::tk::TextClosestGap -- # Given x and y coordinates, this procedure finds the closest boundary # between characters to the given coordinates and returns the index Index: library/tk.tcl ================================================================== --- library/tk.tcl +++ library/tk.tcl @@ -547,10 +547,17 @@ proc ::tk::MouseWheel {w dir amount {factor -120.0} {units units}} { $w ${dir}view scroll [expr {$amount/$factor}] $units } +## ::tk::PreciseScrollDeltas $dxdy +proc ::tk::PreciseScrollDeltas {dxdy} { + set deltaX [expr {$dxdy >> 16}] + set low [expr {$dxdy & 0xffff}] + set deltaY [expr {$low < 0x8000 ? $low : $low - 0x10000}] + return [list $deltaX $deltaY] +} # ::tk::TabToWindow -- # This procedure moves the focus to the given widget. # It sends a <> virtual event to the previous focus window, # if any, before changing the focus, and a <> event @@ -835,10 +842,25 @@ # Run the Ttk themed widget set initialization if {$::ttk::library ne ""} { uplevel \#0 [list source -encoding utf-8 $::ttk::library/ttk.tcl] } + +# Helper for smooth scrolling of widgets that support xview moveto, +# yview moveto, height and width. + +proc ::tk::ScrollByPixels {w deltaX deltaY} { + set width [expr {1.0 * [$w cget -width]}] + set height [expr {1.0 * [$w cget -height]}] + set X [lindex [$w xview] 0] + set Y [lindex [$w yview] 0] + set x [expr {$X - $deltaX / $width}] + set y [expr {$Y - $deltaY / $height}] + $w xview moveto $x + $w yview moveto $y +} + # Local Variables: # mode: tcl # fill-column: 78 # End: Index: library/ttk/combobox.tcl ================================================================== --- library/ttk/combobox.tcl +++ library/ttk/combobox.tcl @@ -50,12 +50,21 @@ bind TCombobox { ttk::combobox::Press "2" %W %x %y } bind TCombobox { ttk::combobox::Press "3" %W %x %y } bind TCombobox { ttk::combobox::Drag %W %x } bind TCombobox { ttk::combobox::Motion %W %x %y } -ttk::bindMouseWheel TCombobox [list ttk::combobox::Scroll %W] - +ttk::bindMouseWheel TCombobox { ttk::combobox::Scroll %W } +bind TCombobox { + # Ignore the event +} +bind TCombobox { + lassign [tk::PreciseScrollDeltas %D] deltaX deltaY + # TouchpadScroll events fire about 60 times per second. + if {$deltaY != 0 && [expr {%# %% 15}] == 0} { + ttk::combobox::Scroll %W [expr {$deltaY > 0 ? -1 : 1}] + } +} bind TCombobox <> { ttk::combobox::TraverseIn %W } ### Combobox listbox bindings. # bind ComboboxListbox { ttk::combobox::LBSelected %W } Index: library/ttk/notebook.tcl ================================================================== --- library/ttk/notebook.tcl +++ library/ttk/notebook.tcl @@ -14,11 +14,31 @@ catch { bind TNotebook { ttk::notebook::CycleTab %W -1; break } } bind TNotebook { ttk::notebook::Cleanup %W } -ttk::bindMouseWheel TNotebook [list ttk::notebook::CycleTab %W] +bind TNotebook { + set tk::Priv(xEvents) 0; set tk::Priv(yEvents) 0 +} +bind TNotebook { + ttk::notebook::CondCycleTab1 %W y %D -120.0 +} +bind TNotebook { + ttk::notebook::CondCycleTab1 %W y %D -12.0 +} +bind TNotebook { + ttk::notebook::CondCycleTab1 %W x %D -120.0 +} +bind TNotebook { + ttk::notebook::CondCycleTab1 %W x %D -12.0 +} +bind TNotebook { + # TouchpadScroll events fire about 60 times per second. + if {[expr {%# %% 30}] == 0} { + ttk::notebook::CondCycleTab2 %W %D + } +} # ActivateTab $nb $tab -- # Select the specified tab and set focus. # # Desired behavior: @@ -72,10 +92,45 @@ if {$select != $current} { ActivateTab $w $select } } } + +# CondCycleTab1 -- +# Conditionally invoke the ttk::notebook::CycleTab proc. +# +proc ttk::notebook::CondCycleTab1 {w axis dir {factor 1.0}} { + # Count both the and + # events, and ignore the non-dominant ones + + variable ::tk::Priv + incr Priv(${axis}Events) + if {($Priv(xEvents) + $Priv(yEvents) > 10) && + ($axis eq "x" && $Priv(xEvents) < $Priv(yEvents) || + $axis eq "y" && $Priv(yEvents) < $Priv(xEvents))} { + return + } + + CycleTab $w $dir $factor +} + +# CondCycleTab2 -- +# Conditionally invoke the ttk::notebook::CycleTab proc. +# +proc ttk::notebook::CondCycleTab2 {w dxdy} { + if {[set style [$w cget -style]] eq ""} { + set style TNotebook + } + set tabSide [string index [ttk::style lookup $style -tabposition {} nw] 0] + + lassign [tk::PreciseScrollDeltas $dxdy] deltaX deltaY + if {$tabSide in {n s} && $deltaX != 0} { + CycleTab $w [expr {$deltaX < 0 ? -1 : 1}] + } elseif {$tabSide in {w e} && $deltaY != 0} { + CycleTab $w [expr {$deltaY < 0 ? -1 : 1}] + } +} # MnemonicTab $nb $key -- # Scan all tabs in the specified notebook for one with the # specified mnemonic. If found, returns path name of tab; # otherwise returns "" Index: library/ttk/scrollbar.tcl ================================================================== --- library/ttk/scrollbar.tcl +++ library/ttk/scrollbar.tcl @@ -15,14 +15,21 @@ bind TScrollbar { ttk::scrollbar::Jump %W %x %y } bind TScrollbar { ttk::scrollbar::Drag %W %x %y } bind TScrollbar { ttk::scrollbar::Release %W %x %y } -# Redirect scrollwheel bindings to the scrollbar widget +# Copy the mouse wheel event bindings from Scrollbar to TScrollbar # -bind TScrollbar [bind Scrollbar ] -bind TScrollbar [bind Scrollbar ] +bind TScrollbar { + set tk::Priv(xEvents) 0; set tk::Priv(yEvents) 0 +} +foreach event { + + } { + bind TScrollbar $event [bind Scrollbar $event] +} +unset event proc ttk::scrollbar::Scroll {w n units} { set cmd [$w cget -command] if {$cmd ne ""} { uplevel #0 $cmd scroll $n $units Index: library/ttk/spinbox.tcl ================================================================== --- library/ttk/spinbox.tcl +++ library/ttk/spinbox.tcl @@ -21,11 +21,21 @@ bind TSpinbox { event generate %W <> } bind TSpinbox <> { ttk::spinbox::Spin %W +1 } bind TSpinbox <> { ttk::spinbox::Spin %W -1 } -ttk::bindMouseWheel TSpinbox [list ttk::spinbox::Spin %W] +ttk::bindMouseWheel TSpinbox { ttk::spinbox::Spin %W } +bind TSpinbox { + # Ignore the event +} +bind TSpinbox { + lassign [tk::PreciseScrollDeltas %D] deltaX deltaY + # TouchpadScroll events fire about 60 times per second. + if {$deltaY != 0 && [expr {%# %% 12}] == 0} { + ttk::spinbox::Spin %W [expr {$deltaY > 0 ? -1 : 1}] + } +} ## Motion -- # Sets cursor. # proc ttk::spinbox::Motion {w x y} { Index: library/ttk/utils.tcl ================================================================== --- library/ttk/utils.tcl +++ library/ttk/utils.tcl @@ -299,6 +299,20 @@ bind TtkScrollable \ { tk::MouseWheel %W x %D -40.0 } bind TtkScrollable \ { tk::MouseWheel %W x %D -12.0 } +## Touchpad scrolling +# +bind TtkScrollable { + if {[expr {%# %% 15}] != 0} { + return + } + lassign [tk::PreciseScrollDeltas %D] deltaX deltaY + if {$deltaX != 0} { + %W xview scroll [expr {-$deltaX}] units + } + if {$deltaY != 0} { + %W yview scroll [expr {-$deltaY}] units + } +} #*EOF* Index: macosx/tkMacOSXMouseEvent.c ================================================================== --- macosx/tkMacOSXMouseEvent.c +++ macosx/tkMacOSXMouseEvent.c @@ -23,17 +23,10 @@ Window window; Point global; Point local; } MouseEventData; -typedef struct { - uint64_t wheelTickPrev; /* For high resolution wheels. */ - double vWheelAcc; /* For high resolution wheels (vertical). */ - double hWheelAcc; /* For high resolution wheels (horizontal). */ -} ThreadSpecificData; -static Tcl_ThreadDataKey dataKey; - static Tk_Window captureWinPtr = NULL; /* Current capture window; may be * NULL. */ static int GenerateButtonEvent(MouseEventData *medPtr); @@ -548,58 +541,51 @@ } } else { Tk_UpdatePointer(target, global.x, global.y, state); } } else { - CGFloat delta; + static int scrollCounter = 0; + int delta; + CGFloat Delta; + Bool deltaIsPrecise = [theEvent hasPreciseScrollingDeltas]; XEvent xEvent = {0}; - ThreadSpecificData *tsdPtr = (ThreadSpecificData *) - Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); - - xEvent.type = MouseWheelEvent; xEvent.xbutton.x = win_x; xEvent.xbutton.y = win_y; xEvent.xbutton.x_root = global.x; xEvent.xbutton.y_root = global.y; xEvent.xany.send_event = false; xEvent.xany.display = Tk_Display(target); xEvent.xany.window = Tk_WindowId(target); - -#define WHEEL_DELTA 120 -#define WHEEL_DELAY 300000000 - uint64_t wheelTick = clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW); - Bool timeout = (wheelTick - tsdPtr->wheelTickPrev) >= WHEEL_DELAY; - if (timeout) { - tsdPtr->vWheelAcc = tsdPtr->hWheelAcc = 0; - } - tsdPtr->wheelTickPrev = wheelTick; - delta = [theEvent deltaY]; - if (delta != 0.0) { - delta = (tsdPtr->vWheelAcc += delta); - if (timeout && fabs(delta) < 1.0) { - delta = ((delta < 0.0) ? -1.0 : 1.0); - } - if (fabs(delta) >= 0.6) { - int intDelta = round(delta); + if (deltaIsPrecise) { + int deltaX = [theEvent scrollingDeltaX]; + int deltaY = [theEvent scrollingDeltaY]; + delta = (deltaX << 16) | (deltaY & 0xffff); + if (delta != 0) { + xEvent.type = TouchpadScroll; + xEvent.xbutton.state = state; + xEvent.xkey.keycode = delta; + xEvent.xany.serial = scrollCounter++; + Tk_QueueWindowEvent(&xEvent, TCL_QUEUE_TAIL); + } + } else { + /* + * A low precision scroll is 120 or -120 wheel units per click. + * Each click generates one event. + */ + Delta = [theEvent scrollingDeltaY]; + if (Delta != 0.0) { + xEvent.type = MouseWheelEvent; xEvent.xbutton.state = state; - xEvent.xkey.keycode = WHEEL_DELTA * intDelta; - tsdPtr->vWheelAcc -= intDelta; + xEvent.xkey.keycode = Delta > 0.0 ? 120 : -120; xEvent.xany.serial = LastKnownRequestProcessed(Tk_Display(tkwin)); Tk_QueueWindowEvent(&xEvent, TCL_QUEUE_TAIL); } - } - delta = [theEvent deltaX]; - if (delta != 0.0) { - delta = (tsdPtr->hWheelAcc += delta); - if (timeout && fabs(delta) < 1.0) { - delta = ((delta < 0.0) ? -1.0 : 1.0); - } - if (fabs(delta) >= 0.6) { - int intDelta = round(delta); + Delta = [theEvent scrollingDeltaX]; + if (Delta != 0.0) { + xEvent.type = MouseWheelEvent; xEvent.xbutton.state = state | ShiftMask; - xEvent.xkey.keycode = WHEEL_DELTA * intDelta; - tsdPtr->hWheelAcc -= intDelta; + xEvent.xkey.keycode = Delta > 0 ? 120 : -120; xEvent.xany.serial = LastKnownRequestProcessed(Tk_Display(tkwin)); Tk_QueueWindowEvent(&xEvent, TCL_QUEUE_TAIL); } } } Index: tests/scrollbar.test ================================================================== --- tests/scrollbar.test +++ tests/scrollbar.test @@ -703,10 +703,11 @@ pack [text .t -yscrollcommand {.s set}] -side left for {set i 1} {$i < 100} {incr i} {.t insert end "Line $i\n"} pack [scrollbar .s -command {.t yview}] -fill y -expand 1 -side left update focus -force .s + event generate .s event generate .s -delta -120 after 200 {set eventprocessed 1} ; vwait eventprocessed .t index @0,0 } -cleanup { destroy .t .s @@ -718,10 +719,11 @@ pack [text .t -xscrollcommand {.s set} -wrap none] -side top for {set i 1} {$i < 100} {incr i} {.t insert end "Char $i "} pack [scrollbar .s -command {.t xview} -orient horizontal] -fill x -expand 1 -side top update focus -force .s + event generate .s event generate .s -delta -120 after 200 {set eventprocessed 1} ; vwait eventprocessed .t index @0,0 } -cleanup { destroy .t .s @@ -732,10 +734,11 @@ pack [text .t -xscrollcommand {.s set} -wrap none] -side top for {set i 1} {$i < 100} {incr i} {.t insert end "Char $i "} pack [scrollbar .s -command {.t xview} -orient horizontal] -fill x -expand 1 -side top update focus -force .s + event generate .s event generate .s -delta -120 after 200 {set eventprocessed 1} ; vwait eventprocessed .t index @0,0 } -cleanup { destroy .t .s Index: tests/ttk/scrollbar.test ================================================================== --- tests/ttk/scrollbar.test +++ tests/ttk/scrollbar.test @@ -77,10 +77,11 @@ pack [text .t -yscrollcommand {.s set}] -side left for {set i 1} {$i < 100} {incr i} {.t insert end "Line $i\n"} pack [ttk::scrollbar .s -command {.t yview}] -fill y -expand 1 -side left update focus -force .s + event generate .s event generate .s -delta -120 after 200 {set eventprocessed 1} ; vwait eventprocessed .t index @0,0 } -cleanup { destroy .t .s @@ -92,10 +93,11 @@ pack [text .t -xscrollcommand {.s set} -wrap none] -side top for {set i 1} {$i < 100} {incr i} {.t insert end "Char $i "} pack [ttk::scrollbar .s -command {.t xview} -orient horizontal] -fill x -expand 1 -side top update focus -force .s + event generate .s event generate .s -delta -120 after 200 {set eventprocessed 1} ; vwait eventprocessed .t index @0,0 } -cleanup { destroy .t .s @@ -106,10 +108,11 @@ pack [text .t -xscrollcommand {.s set} -wrap none] -side top for {set i 1} {$i < 100} {incr i} {.t insert end "Char $i "} pack [ttk::scrollbar .s -command {.t xview} -orient horizontal] -fill x -expand 1 -side top update focus -force .s + event generate .s event generate .s -delta -120 after 200 {set eventprocessed 1} ; vwait eventprocessed .t index @0,0 } -cleanup { destroy .t .s Index: win/tkWinX.c ================================================================== --- win/tkWinX.c +++ win/tkWinX.c @@ -17,10 +17,11 @@ #include #ifdef _MSC_VER # pragma comment (lib, "comctl32.lib") # pragma comment (lib, "advapi32.lib") #endif + /* * The zmouse.h file includes the definition for WM_MOUSEWHEEL. */ @@ -32,10 +33,35 @@ */ #ifndef WM_MOUSEHWHEEL #define WM_MOUSEHWHEEL 0x020E #endif + +/* A WM_MOUSEWHEEL message sent by a trackpad contains the number of pixels as + * the delta value, while low precision scrollwheels always send an integer + * multiple of WHEELDELTA (= 120) as the delta value. + */ + +#define WHEELDELTA 120 + +/* + * Our heuristic for deciding whether a WM_MOUSEWHEEL message + * comes from a high resolution scrolling device is that we + * assume it is high resolution unless there are two consecutive + * delta values that are both multiples of 120. This is static, + * rather than thread-specific, since input devices are shared + * by all threads. + */ + +static int lastMod = 0; + +/* + * The serial field of TouchpadScroll events is a counter for + * events of this type only. + */ + +static int scrollCounter = 0; /* * imm.h is needed by HandleIMEComposition */ @@ -79,13 +105,10 @@ typedef struct { TkDisplay *winDisplay; /* TkDisplay structure that represents Windows * screen. */ int updatingClipboard; /* If 1, we are updating the clipboard. */ int surrogateBuffer; /* Buffer for first of surrogate pair. */ - DWORD wheelTickPrev; /* For high resolution wheels. */ - int vWheelAcc; /* For high resolution wheels (vertical). */ - int hWheelAcc; /* For high resolution wheels (horizontal). */ } ThreadSpecificData; static Tcl_ThreadDataKey dataKey; /* * Forward declarations of functions used in this file. @@ -532,13 +555,10 @@ tsdPtr->winDisplay =(TkDisplay *) ckalloc(sizeof(TkDisplay)); memset(tsdPtr->winDisplay, 0, sizeof(TkDisplay)); tsdPtr->winDisplay->display = display; tsdPtr->updatingClipboard = FALSE; - tsdPtr->wheelTickPrev = GetTickCount(); - tsdPtr->vWheelAcc = 0; - tsdPtr->hWheelAcc = 0; /* * Key map info must be available immediately, because of "send event". */ TkpInitKeymapInfo(tsdPtr->winDisplay); @@ -1124,86 +1144,69 @@ * Now set up event specific fields. */ switch (message) { case WM_MOUSEWHEEL: { - /* - * Support for high resolution wheels (vertical). - */ - - DWORD wheelTick = GetTickCount(); - BOOL timeout = wheelTick - tsdPtr->wheelTickPrev >= 300; - int intDelta; - - tsdPtr->wheelTickPrev = wheelTick; - if (timeout) { - tsdPtr->vWheelAcc = tsdPtr->hWheelAcc = 0; - } - tsdPtr->vWheelAcc += (short) HIWORD(wParam); - if (!tsdPtr->vWheelAcc || (!timeout && abs(tsdPtr->vWheelAcc) < WHEEL_DELTA * 6 / 10)) { - return; - } - - /* - * We have invented a new X event type to handle this event. It - * still uses the KeyPress struct. However, the keycode field has - * been overloaded to hold the zDelta of the wheel. Set nbytes to - * 0 to prevent conversion of the keycode to a keysym in + + /* + * Send an Xevent using a KeyPress struct, but with the type field + * set to MouseWheelEvent for low resolution scrolls and to + * TouchpadScroll for high resolution scroll events. The Y delta + * is stored in the low order 16 bits of the keycode field. Set + * nbytes to 0 to prevent conversion of the keycode to a keysym in * TkpGetString. [Bug 1118340]. */ - intDelta = (abs(tsdPtr->vWheelAcc) + WHEEL_DELTA/2) / WHEEL_DELTA * WHEEL_DELTA; - if (intDelta == 0) { - intDelta = (tsdPtr->vWheelAcc < 0) ? -WHEEL_DELTA : WHEEL_DELTA; - } else if (tsdPtr->vWheelAcc < 0) { - intDelta = -intDelta; - } - event.x.type = MouseWheelEvent; - event.x.xany.send_event = -1; - event.key.nbytes = 0; - event.x.xkey.keycode = intDelta; - tsdPtr->vWheelAcc -= intDelta; + int delta = (short) HIWORD(wParam); + int mod = delta % WHEELDELTA; + if ( mod != 0 || lastMod != 0) { + /* High resolution. */ + event.x.type = TouchpadScroll; + event.x.xany.send_event = -1; + event.key.nbytes = 0; + event.x.xkey.state = state; + event.x.xany.serial = scrollCounter++; + event.x.xkey.keycode = (unsigned int) delta; + } else { + event.x.type = MouseWheelEvent; + event.x.xany.send_event = -1; + event.key.nbytes = 0; + event.x.xkey.keycode = (unsigned int) delta; + } + lastMod = mod; break; } case WM_MOUSEHWHEEL: { - /* - * Support for high resolution wheels (horizontal). - */ - - DWORD wheelTick = GetTickCount(); - BOOL timeout = wheelTick - tsdPtr->wheelTickPrev >= 300; - int intDelta; - - tsdPtr->wheelTickPrev = wheelTick; - if (timeout) { - tsdPtr->vWheelAcc = tsdPtr->hWheelAcc = 0; - } - tsdPtr->hWheelAcc -= (short) HIWORD(wParam); - if (!tsdPtr->hWheelAcc || (!timeout && abs(tsdPtr->hWheelAcc) < WHEEL_DELTA * 6 / 10)) { - return; - } - - /* - * We have invented a new X event type to handle this event. It - * still uses the KeyPress struct. However, the keycode field has - * been overloaded to hold the zDelta of the wheel. Set nbytes to - * 0 to prevent conversion of the keycode to a keysym in - * TkpGetString. [Bug 1118340]. - */ - - intDelta = (abs(tsdPtr->hWheelAcc) + WHEEL_DELTA/2) / WHEEL_DELTA * WHEEL_DELTA; - if (intDelta == 0) { - intDelta = (tsdPtr->hWheelAcc < 0) ? -WHEEL_DELTA : WHEEL_DELTA; - } else if (tsdPtr->hWheelAcc < 0) { - intDelta = -intDelta; - } - event.x.type = MouseWheelEvent; - event.x.xany.send_event = -1; - event.key.nbytes = 0; - event.x.xkey.state |= ShiftMask; - event.x.xkey.keycode = intDelta; - tsdPtr->hWheelAcc -= intDelta; + + /* + * Send an Xevent using a KeyPress struct, but with the type field + * set to MouseWheelEvent for low resolution scrolls and to + * TouchpadScroll for high resolution scroll events. For low + * resolution scrolls the X delta is stored in the keycode field + * and For high resolution scrolls the X delta is in the high word + * of the keycode. Set nbytes to 0 to prevent conversion of the + * keycode to a keysym in TkpGetString. [Bug 1118340]. + */ + + int delta = (short) HIWORD(wParam); + int mod = delta % WHEELDELTA; + if ( mod != 0 || lastMod != 0) { + /* High resolution. */ + event.x.type = TouchpadScroll; + event.x.xany.send_event = -1; + event.key.nbytes = 0; + event.x.xkey.state = state; + event.x.xany.serial = scrollCounter++; + event.x.xkey.keycode = (unsigned int)(-(delta << 16)); + } else { + event.x.type = MouseWheelEvent; + event.x.xany.send_event = -1; + event.key.nbytes = 0; + event.x.xkey.state |= ShiftMask; + event.x.xkey.keycode = delta; + } + lastMod = mod; break; } case WM_SYSKEYDOWN: case WM_KEYDOWN: /*