Resolves: tdf#103174 & rhbz#1367846 improve gtk3 trackpad scrolling

convert number of "lines" scrolled to double and allow
fractional parts of lines/columns

Change-Id: Ib99c815cfc8823e22fc1d76e201903c34ed0f61b

Related: rhbz#1367846 queue and merge scroll events

Reviewed-on: https://gerrit.libreoffice.org/37779
Reviewed-by: Caolán McNamara <caolanm@redhat.com>
Tested-by: Caolán McNamara <caolanm@redhat.com>
(cherry picked from commit 7f60978b2ccd0e17816b78bde60c6e0e60a9d52e)

Change-Id: Ib45f61bbb35bd240829491ac8a79803222974778
Reviewed-on: https://gerrit.libreoffice.org/37913
Tested-by: Jenkins <ci@libreoffice.org>
Reviewed-by: Miklos Vajna <vmiklos@collabora.co.uk>
diff --git a/include/vcl/commandevent.hxx b/include/vcl/commandevent.hxx
index 5b9cc09..371fd54 100644
--- a/include/vcl/commandevent.hxx
+++ b/include/vcl/commandevent.hxx
@@ -140,7 +140,7 @@ class VCL_DLLPUBLIC CommandWheelData
private:
    long              mnDelta;
    long              mnNotchDelta;
    sal_uLong         mnLines;
    double            mnLines;
    CommandWheelMode  mnWheelMode;
    sal_uInt16        mnCode;
    bool              mbHorz;
@@ -149,13 +149,13 @@ private:
public:
                    CommandWheelData();
                    CommandWheelData( long nWheelDelta, long nWheelNotchDelta,
                                      sal_uLong nScrollLines,
                                      double nScrollLines,
                                      CommandWheelMode nWheelMode, sal_uInt16 nKeyModifier,
                                      bool bHorz, bool bDeltaIsPixel = false );

    long            GetDelta() const { return mnDelta; }
    long            GetNotchDelta() const { return mnNotchDelta; }
    sal_uLong       GetScrollLines() const { return mnLines; }
    double          GetScrollLines() const { return mnLines; }
    bool            IsHorz() const { return mbHorz; }
    bool            IsDeltaPixel() const { return mbDeltaIsPixel; }

diff --git a/include/vcl/window.hxx b/include/vcl/window.hxx
index 4fdb805..7d6f69c 100644
--- a/include/vcl/window.hxx
+++ b/include/vcl/window.hxx
@@ -719,7 +719,7 @@ private:
    SAL_DLLPRIVATE void                 ImplCallActivateListeners(vcl::Window*);
    SAL_DLLPRIVATE void                 ImplCallDeactivateListeners(vcl::Window*);

    SAL_DLLPRIVATE static void          ImplHandleScroll( ScrollBar* pHScrl, long nX, ScrollBar* pVScrl, long nY );
    SAL_DLLPRIVATE static void          ImplHandleScroll(ScrollBar* pHScrl, double nX, ScrollBar* pVScrl, double nY);

    SAL_DLLPRIVATE Rectangle            ImplOutputToUnmirroredAbsoluteScreenPixel( const Rectangle& rRect ) const;
    SAL_DLLPRIVATE long                 ImplGetUnmirroredOutOffX();
diff --git a/vcl/inc/salwtype.hxx b/vcl/inc/salwtype.hxx
index b7f803e..c25ca80 100644
--- a/vcl/inc/salwtype.hxx
+++ b/vcl/inc/salwtype.hxx
@@ -149,7 +149,7 @@ struct SalWheelMouseEvent
    long            mnY;            // Y-Position (Pixel, TopLeft-Output)
    long            mnDelta;        // Number of rotations
    long            mnNotchDelta;   // Number of fixed rotations
    sal_uLong       mnScrollLines;  // Actual number of lines to scroll
    double          mnScrollLines;  // Actual number of lines to scroll
    sal_uInt16      mnCode;         // SV-Modifiercode (KEY_SHIFT|KEY_MOD1|KEY_MOD2|MOUSE_LEFT|MOUSE_MIDDLE|MOUSE_RIGHT)
    bool        mbHorz;         // Horizontal
    bool        mbDeltaIsPixel; // delta value is a pixel value (on touch devices)
diff --git a/vcl/inc/unx/gtk/gtkframe.hxx b/vcl/inc/unx/gtk/gtkframe.hxx
index 3deb4c4..fcb44985 100644
--- a/vcl/inc/unx/gtk/gtkframe.hxx
+++ b/vcl/inc/unx/gtk/gtkframe.hxx
@@ -30,6 +30,7 @@
#include <gdk/gdkkeysyms.h>

#include <salframe.hxx>
#include <vcl/idle.hxx>
#include <vcl/sysdata.hxx>
#include <unx/nativewindowhandleprovider.hxx>
#include <unx/saltype.h>
@@ -281,7 +282,7 @@ class GtkSalFrame : public SalFrame
    static gboolean     signalKey( GtkWidget*, GdkEventKey*, gpointer );
    static gboolean     signalDelete( GtkWidget*, GdkEvent*, gpointer );
    static gboolean     signalWindowState( GtkWidget*, GdkEvent*, gpointer );
    static gboolean     signalScroll( GtkWidget*, GdkEventScroll*, gpointer );
    static gboolean     signalScroll( GtkWidget*, GdkEvent*, gpointer );
    static gboolean     signalCrossing( GtkWidget*, GdkEventCrossing*, gpointer );
    static gboolean     signalVisibility( GtkWidget*, GdkEventVisibility*, gpointer );
    static void         signalDestroy( GtkWidget*, gpointer );
@@ -347,6 +348,8 @@ public:
#if GTK_CHECK_VERSION(3,0,0)
    cairo_surface_t*                m_pSurface;
    DamageHandler                   m_aDamageHandler;
    std::vector<GdkEvent*>          m_aPendingScrollEvents;
    Idle                            m_aSmoothScrollIdle;
    int                             m_nGrabLevel;
    bool                            m_bSalObjectSetPosSize;
#endif
@@ -427,6 +430,10 @@ public:
    void removeGrabLevel();

    void nopaint_container_resize_children(GtkContainer*);

    void LaunchAsyncScroll(GdkEvent* pEvent);
    DECL_LINK(AsyncScroll, Idle *, void);

#endif
    virtual ~GtkSalFrame() override;

diff --git a/vcl/source/window/commandevent.cxx b/vcl/source/window/commandevent.cxx
index e7f7dcd..8ef928f 100644
--- a/vcl/source/window/commandevent.cxx
+++ b/vcl/source/window/commandevent.cxx
@@ -73,7 +73,7 @@ CommandWheelData::CommandWheelData()
{
    mnDelta         = 0;
    mnNotchDelta    = 0;
    mnLines         = 0;
    mnLines         = 0.0;
    mnWheelMode     = CommandWheelMode::NONE;
    mnCode          = 0;
    mbHorz          = false;
@@ -81,7 +81,7 @@ CommandWheelData::CommandWheelData()
}

CommandWheelData::CommandWheelData( long nWheelDelta, long nWheelNotchDelta,
                                    sal_uLong nScrollLines,
                                    double nScrollLines,
                                    CommandWheelMode nWheelMode, sal_uInt16 nKeyModifier,
                                    bool bHorz, bool bDeltaIsPixel )
{
diff --git a/vcl/source/window/window2.cxx b/vcl/source/window/window2.cxx
index 24d1fa7..9607be6 100644
--- a/vcl/source/window/window2.cxx
+++ b/vcl/source/window/window2.cxx
@@ -664,7 +664,7 @@ long Window::GetDrawPixel( OutputDevice* pDev, long nPixels ) const
    return nP;
}

static void lcl_HandleScrollHelper( ScrollBar* pScrl, long nN, bool isMultiplyByLineSize )
static void lcl_HandleScrollHelper( ScrollBar* pScrl, double nN, bool isMultiplyByLineSize )
{
    if ( pScrl && nN && pScrl->IsEnabled() && pScrl->IsInputEnabled() && ! pScrl->IsInModalMode() )
    {
@@ -681,7 +681,7 @@ static void lcl_HandleScrollHelper( ScrollBar* pScrl, long nN, bool isMultiplyBy
                nN*=pScrl->GetLineSize();
            }

            const double fVal = (double)(nNewPos - nN);
            const double fVal = nNewPos - nN;

            if ( fVal < LONG_MIN )
                nNewPos = LONG_MIN;
@@ -737,8 +737,8 @@ bool Window::HandleScrollCommand( const CommandEvent& rCmd,
                {
                    if (!pData->IsDeltaPixel())
                    {
                        sal_uLong nScrollLines = pData->GetScrollLines();
                        long nLines;
                        double nScrollLines = pData->GetScrollLines();
                        double nLines;
                        if ( nScrollLines == COMMAND_WHEEL_PAGESCROLL )
                        {
                            if ( pData->GetDelta() < 0 )
@@ -747,7 +747,7 @@ bool Window::HandleScrollCommand( const CommandEvent& rCmd,
                                nLines = LONG_MAX;
                        }
                        else
                            nLines = pData->GetNotchDelta() * (long)nScrollLines;
                            nLines = pData->GetNotchDelta() * nScrollLines;
                        if ( nLines )
                        {
                            ImplHandleScroll( nullptr,
@@ -860,8 +860,8 @@ bool Window::HandleScrollCommand( const CommandEvent& rCmd,
// horizontal or vertical scroll bar. nY is correspondingly either
// the horizontal or vertical scroll amount.

void Window::ImplHandleScroll( ScrollBar* pHScrl, long nX,
                               ScrollBar* pVScrl, long nY )
void Window::ImplHandleScroll( ScrollBar* pHScrl, double nX,
                               ScrollBar* pVScrl, double nY )
{
    lcl_HandleScrollHelper( pHScrl, nX, true );
    lcl_HandleScrollHelper( pVScrl, nY, true );
diff --git a/vcl/unx/gtk/gtksalframe.cxx b/vcl/unx/gtk/gtksalframe.cxx
index cfc6f45..2bb6718 100644
--- a/vcl/unx/gtk/gtksalframe.cxx
+++ b/vcl/unx/gtk/gtksalframe.cxx
@@ -2788,8 +2788,9 @@ gboolean GtkSalFrame::signalButton( GtkWidget*, GdkEventButton* pEvent, gpointer
    return true;
}

gboolean GtkSalFrame::signalScroll( GtkWidget*, GdkEventScroll* pEvent, gpointer frame )
gboolean GtkSalFrame::signalScroll(GtkWidget*, GdkEvent* pInEvent, gpointer frame)
{
    GdkEventScroll& rEvent = pInEvent->scroll;
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);

    static sal_uLong        nLines = 0;
@@ -2801,16 +2802,16 @@ gboolean GtkSalFrame::signalScroll( GtkWidget*, GdkEventScroll* pEvent, gpointer
            nLines = SAL_WHEELMOUSE_EVENT_PAGESCROLL;
    }

    bool bNeg = (pEvent->direction == GDK_SCROLL_DOWN || pEvent->direction == GDK_SCROLL_RIGHT );
    bool bNeg = (rEvent.direction == GDK_SCROLL_DOWN || rEvent.direction == GDK_SCROLL_RIGHT );
    SalWheelMouseEvent aEvent;
    aEvent.mnTime           = pEvent->time;
    aEvent.mnX              = (sal_uLong)pEvent->x;
    aEvent.mnY              = (sal_uLong)pEvent->y;
    aEvent.mnTime           = rEvent.time;
    aEvent.mnX              = (sal_uLong)rEvent.x;
    aEvent.mnY              = (sal_uLong)rEvent.y;
    aEvent.mnDelta          = bNeg ? -120 : 120;
    aEvent.mnNotchDelta     = bNeg ? -1 : 1;
    aEvent.mnScrollLines    = nLines;
    aEvent.mnCode           = GetMouseModCode( pEvent->state );
    aEvent.mbHorz           = (pEvent->direction == GDK_SCROLL_LEFT || pEvent->direction == GDK_SCROLL_RIGHT);
    aEvent.mnCode           = GetMouseModCode( rEvent.state );
    aEvent.mbHorz           = (rEvent.direction == GDK_SCROLL_LEFT || rEvent.direction == GDK_SCROLL_RIGHT);

    // --- RTL --- (mirror mouse pos)
    if( AllSettings::GetLayoutRTL() )
diff --git a/vcl/unx/gtk3/gtk3gtkframe.cxx b/vcl/unx/gtk3/gtk3gtkframe.cxx
index 93a3d01..f22e8eb 100644
--- a/vcl/unx/gtk3/gtk3gtkframe.cxx
+++ b/vcl/unx/gtk3/gtk3gtkframe.cxx
@@ -801,6 +801,9 @@ void GtkSalFrame::InvalidateGraphics()

GtkSalFrame::~GtkSalFrame()
{
    m_aSmoothScrollIdle.Stop();
    m_aSmoothScrollIdle.SetIdleHdl(Link<Idle *, void>());

    if (m_pDropTarget)
    {
        m_pDropTarget->deinitialize();
@@ -986,6 +989,8 @@ void GtkSalFrame::InitCommon()
    m_aDamageHandler.handle = this;
    m_aDamageHandler.damaged = ::damaged;

    m_aSmoothScrollIdle.SetIdleHdl(LINK(this, GtkSalFrame, AsyncScroll));

    m_pTopLevelGrid = GTK_GRID(gtk_grid_new());
    gtk_container_add(GTK_CONTAINER(m_pWindow), GTK_WIDGET(m_pTopLevelGrid));

@@ -2649,59 +2654,107 @@ gboolean GtkSalFrame::signalButton( GtkWidget*, GdkEventButton* pEvent, gpointer
    return true;
}

gboolean GtkSalFrame::signalScroll(GtkWidget*, GdkEventScroll* pEvent, gpointer frame)
void GtkSalFrame::LaunchAsyncScroll(GdkEvent* pEvent)
{
    UpdateLastInputEventTime(pEvent->time);
    //if we don't match previous pending states, flush that queue now
    if (!m_aPendingScrollEvents.empty() && pEvent->scroll.state != m_aPendingScrollEvents.back()->scroll.state)
    {
        m_aSmoothScrollIdle.Stop();
        m_aSmoothScrollIdle.Invoke();
        assert(m_aPendingScrollEvents.empty());
    }
    //add scroll event to queue
    m_aPendingScrollEvents.push_back(gdk_event_copy(pEvent));
    if (!m_aSmoothScrollIdle.IsActive())
        m_aSmoothScrollIdle.Start();
}

    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
IMPL_LINK_NOARG(GtkSalFrame, AsyncScroll, Idle *, void)
{
    assert(!m_aPendingScrollEvents.empty());

    SalWheelMouseEvent aEvent;

    aEvent.mnTime = pEvent->time;
    aEvent.mnX = (sal_uLong)pEvent->x;
    GdkEvent* pEvent = m_aPendingScrollEvents.back();

    aEvent.mnTime = pEvent->scroll.time;
    aEvent.mnX = (sal_uLong)pEvent->scroll.x;
    // --- RTL --- (mirror mouse pos)
    if (AllSettings::GetLayoutRTL())
        aEvent.mnX = maGeometry.nWidth - 1 - aEvent.mnX;
    aEvent.mnY = (sal_uLong)pEvent->scroll.y;
    aEvent.mnCode = GetMouseModCode( pEvent->scroll.state );

    double delta_x(0.0), delta_y(0.0);
    for (auto pSubEvent : m_aPendingScrollEvents)
    {
        delta_x += pSubEvent->scroll.delta_x;
        delta_y += pSubEvent->scroll.delta_y;
        gdk_event_free(pSubEvent);
    }
    m_aPendingScrollEvents.clear();

    // rhbz#1344042 "Traditionally" in gtk3 we tool a single up/down event as
    // equating to 3 scroll lines and a delta of 120. So scale the delta here
    // by 120 where a single mouse wheel click is an incoming delta_x of 1
    // and divide that by 40 to get the number of scroll lines
    if (delta_x != 0.0)
    {
        aEvent.mnDelta = -delta_x * 120;
        aEvent.mnNotchDelta = aEvent.mnDelta < 0 ? -1 : +1;
        if (aEvent.mnDelta == 0)
            aEvent.mnDelta = aEvent.mnNotchDelta;
        aEvent.mbHorz = true;
        aEvent.mnScrollLines = std::abs(aEvent.mnDelta) / 40.0;
        CallCallbackExc(SalEvent::WheelMouse, &aEvent);
    }

    if (delta_y != 0.0)
    {
        aEvent.mnDelta = -delta_y * 120;
        aEvent.mnNotchDelta = aEvent.mnDelta < 0 ? -1 : +1;
        if (aEvent.mnDelta == 0)
            aEvent.mnDelta = aEvent.mnNotchDelta;
        aEvent.mbHorz = false;
        aEvent.mnScrollLines = std::abs(aEvent.mnDelta) / 40.0;
        CallCallbackExc(SalEvent::WheelMouse, &aEvent);
    }
}

gboolean GtkSalFrame::signalScroll(GtkWidget*, GdkEvent* pInEvent, gpointer frame)
{
    GdkEventScroll& rEvent = pInEvent->scroll;

    UpdateLastInputEventTime(rEvent.time);

    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);

    if (rEvent.direction == GDK_SCROLL_SMOOTH)
    {
        pThis->LaunchAsyncScroll(pInEvent);
        return true;
    }

    //if we have smooth scrolling previous pending states, flush that queue now
    if (!pThis->m_aPendingScrollEvents.empty())
    {
        pThis->m_aSmoothScrollIdle.Stop();
        pThis->m_aSmoothScrollIdle.Invoke();
        assert(pThis->m_aPendingScrollEvents.empty());
    }

    SalWheelMouseEvent aEvent;

    aEvent.mnTime = rEvent.time;
    aEvent.mnX = (sal_uLong)rEvent.x;
    // --- RTL --- (mirror mouse pos)
    if (AllSettings::GetLayoutRTL())
        aEvent.mnX = pThis->maGeometry.nWidth - 1 - aEvent.mnX;
    aEvent.mnY = (sal_uLong)pEvent->y;
    aEvent.mnCode = GetMouseModCode( pEvent->state );
    aEvent.mnY = (sal_uLong)rEvent.y;
    aEvent.mnCode = GetMouseModCode(rEvent.state);

    switch (pEvent->direction)
    switch (rEvent.direction)
    {
        case GDK_SCROLL_SMOOTH:
            // rhbz#1344042 "Traditionally" in gtk3 we tool a single up/down event as
            // equating to 3 scroll lines and a delta of 120. So scale the delta here
            // by 120 where a single mouse wheel click is an incoming delta_x of 1
            // and divide that by 40 to get the number of scrollines
            if (pEvent->delta_x != 0.0)
            {
                aEvent.mnDelta = -pEvent->delta_x * 120;
                aEvent.mnNotchDelta = aEvent.mnDelta < 0 ? -1 : +1;
                if (aEvent.mnDelta == 0)
                    aEvent.mnDelta = aEvent.mnNotchDelta;
                aEvent.mbHorz = true;
                aEvent.mnScrollLines = std::abs(aEvent.mnDelta) / 40;
                if (aEvent.mnScrollLines == 0)
                    aEvent.mnScrollLines = 1;

                pThis->CallCallbackExc(SalEvent::WheelMouse, &aEvent);
            }

            if (pEvent->delta_y != 0.0)
            {
                aEvent.mnDelta = -pEvent->delta_y * 120;
                aEvent.mnNotchDelta = aEvent.mnDelta < 0 ? -1 : +1;
                if (aEvent.mnDelta == 0)
                    aEvent.mnDelta = aEvent.mnNotchDelta;
                aEvent.mbHorz = false;
                aEvent.mnScrollLines = std::abs(aEvent.mnDelta) / 40;
                if (aEvent.mnScrollLines == 0)
                    aEvent.mnScrollLines = 1;

                pThis->CallCallbackExc(SalEvent::WheelMouse, &aEvent);
            }

            break;

        case GDK_SCROLL_UP:
            aEvent.mnDelta = 120;
            aEvent.mnNotchDelta = 1;
@@ -2733,6 +2786,8 @@ gboolean GtkSalFrame::signalScroll(GtkWidget*, GdkEventScroll* pEvent, gpointer 
            aEvent.mbHorz = true;
            pThis->CallCallbackExc(SalEvent::WheelMouse, &aEvent);
            break;
        default:
            break;
    }

    return true;