Fix for iOS scroll by pixels, and pinch to zoom

Minor further changes by tml to match the coding style of surrounding
code mainly.

Change-Id: Ied6087a264f1c6b00763ea36fba9808329afede4
Reviewed-on: https://gerrit.libreoffice.org/5742
Tested-by: Tor Lillqvist <tml@collabora.com>
Reviewed-by: Tor Lillqvist <tml@collabora.com>
diff --git a/include/osl/detail/ios-bootstrap.h b/include/osl/detail/ios-bootstrap.h
index fa3d0c8..42c38e4 100644
--- a/include/osl/detail/ios-bootstrap.h
+++ b/include/osl/detail/ios-bootstrap.h
@@ -49,7 +49,8 @@ void lo_runMain();
void lo_set_view_size(int width, int height);
void lo_render_windows(CGContextRef context, CGRect rect);
void lo_tap(int x, int y);
void lo_pan(int x, int y);
void lo_pan(int deltaX, int deltaY);
void lo_zoom(int x, int y, float scale);
void lo_keyboard_input(int c);

#ifdef __cplusplus
diff --git a/include/touch/touch.h b/include/touch/touch.h
index 8fa2dd1..2fd7a9a 100644
--- a/include/touch/touch.h
+++ b/include/touch/touch.h
@@ -14,6 +14,10 @@

#if !HAVE_FEATURE_DESKTOP

#define MOBILE_MAX_ZOOM_IN 600
#define MOBILE_MAX_ZOOM_OUT 80
#define MOBILE_ZOOM_SCALE_MULTIPLIER 10000

// Functions to be implemented by the app-specifc upper or less
// app-specific but platform-specific medium layer on touch-based
// platforms. The same API is used on each such platform. There are
diff --git a/ios/experimental/LibreOffice/LibreOffice/View.m b/ios/experimental/LibreOffice/LibreOffice/View.m
index 8c490cb..4e347a2 100644
--- a/ios/experimental/LibreOffice/LibreOffice/View.m
+++ b/ios/experimental/LibreOffice/LibreOffice/View.m
@@ -50,21 +50,49 @@
{
    if ([gestureRecognizer state] == UIGestureRecognizerStateEnded) {
        CGPoint location = [gestureRecognizer locationInView: self];

        NSLog(@"tapGesture: at: (%d,%d)", (int)location.x, (int)location.y);

        lo_tap(location.x, location.y);

        [self->textView becomeFirstResponder];
    } else
    } else {
        NSLog(@"tapGesture: %@", gestureRecognizer);
    }
}

- (void)panGesture:(UIPanGestureRecognizer *)gestureRecognizer
{
    if ([gestureRecognizer state] == UIGestureRecognizerStateEnded) {
        CGPoint translation = [gestureRecognizer translationInView: self];
        NSLog(@"panGesture: pan: (%d,%d)", (int)translation.x, (int)translation.y);
        lo_pan(translation.x, translation.y);
    } else
        NSLog(@"panGesture: %@", gestureRecognizer);
    static CGFloat previousX = 0.0f, previousY = 0.0f;

    CGPoint translation = [gestureRecognizer translationInView: self];

    if (gestureRecognizer.state != UIGestureRecognizerStateBegan) {
        int deltaX = translation.x - previousX;
        int deltaY = translation.y - previousY;

        NSLog(@"panGesture: pan (delta): (%d,%d)", deltaX, deltaY);

        lo_pan(deltaX, deltaY);
    }

    previousX = translation.x;
    previousY = translation.y;
}

- (void)pinchGesture:(UIPinchGestureRecognizer *)gestureRecognizer
{
    CGPoint location = [gestureRecognizer locationInView: self];
    CGFloat scale = gestureRecognizer.scale;

    NSLog(@"pinchGesture: pinch: (%f) cords (%d,%d)", (float)scale, (int)location.x, (int)location.y );

    lo_zoom((int)location.x, (int)location.y, (float)scale);

    // to reset the gesture scaling
    if (gestureRecognizer.state==UIGestureRecognizerStateEnded) {
        lo_zoom(1, 1, 0.0f);
    }
}

@end
diff --git a/sw/source/ui/uiview/viewport.cxx b/sw/source/ui/uiview/viewport.cxx
index c137db6..4f34f2a 100644
--- a/sw/source/ui/uiview/viewport.cxx
+++ b/sw/source/ui/uiview/viewport.cxx
@@ -17,6 +17,8 @@
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include <config_features.h>

#include "hintids.hxx"
#include <vcl/help.hxx>
#include <svx/ruler.hxx>
@@ -37,6 +39,7 @@
#include <pagedesc.hxx>
#include <workctrl.hxx>
#include <crsskip.hxx>
#include <touch/touch.h>

#include <PostItMgr.hxx>

@@ -298,11 +301,12 @@ void SwView::SetVisArea( const Point &rPt, sal_Bool bUpdateScrollbar )
    // (fix: Bild.de, 200%) It does not work completly without alignment
    // Let's see how far we get with half BrushSize.
    Point aPt( rPt );
//  const long nTmp = GetWrtShell().IsFrameView() ? BRUSH_SIZE/2 : BRUSH_SIZE;
    const long nTmp = GetWrtShell().IsFrameView() ? 4 : 8;
    aPt = GetEditWin().LogicToPixel( aPt );
#if HAVE_FEATURE_DESKTOP
    const long nTmp = GetWrtShell().IsFrameView() ? 4 : 8;
    aPt.X() -= aPt.X() % nTmp;
    aPt.Y() -= aPt.Y() % nTmp;
#endif
    aPt = GetEditWin().PixelToLogic( aPt );

    if ( aPt == m_aVisArea.TopLeft() )
@@ -1258,19 +1262,107 @@ sal_Bool SwView::HandleWheelCommands( const CommandEvent& rCEvt )
    }
    else if( COMMAND_WHEEL_ZOOM_SCALE == pWData->GetMode() )
    {
        int newZoom = 100 * (m_pWrtShell->GetViewOptions()->GetZoom() / 100.0) * (pWData->GetDelta() / 100.0);
        SetZoom( SVX_ZOOM_PERCENT, std::max( 20, std::min( 600, newZoom ) ) );
        // COMMAND_WHEEL_ZOOM_SCALE is de facto used only for Android and iOS, I think

        // mobile touch zoom (pinch) section
        // last location in pixels is defaulted to an illegal location
        // (coordinates are always positive)
        static Point lastLocationInPixels(0,0);
        static const double NEW_ZOOM_START= -6666.66;
        static double initialZoom = NEW_ZOOM_START;
        static int rememberedZoom = 0;

        // the target should remain the same in logic, regardless of eventual zoom
        const Point & targetInLogic = GetEditWin().PixelToLogic(rCEvt.GetMousePosPixel());
        double scale = double(pWData->GetDelta()) / double(MOBILE_ZOOM_SCALE_MULTIPLIER);

        if( scale==0 )
        {
            // scale 0, means end of gesture, and zoom resets
            rememberedZoom=0;
        }
        else
        {
            int preZoomByVCL = m_pWrtShell->GetViewOptions()->GetZoom();
            bool isFirst = rememberedZoom != preZoomByVCL;

            if( isFirst )
            {
                // If this is the start of a new zoom action, we take the value from VCL.
                // Otherwise, we remeber the zoom from the previous action.
                // This way we can be more accurate than VCL
                initialZoom =(double) preZoomByVCL;
            }

            // each zooming event is scaling the initial zoom
            int zoomTarget = int(initialZoom * scale);

            // thresholding the zoom
            zoomTarget = std::max( MOBILE_MAX_ZOOM_OUT, std::min( MOBILE_MAX_ZOOM_IN, zoomTarget ) );
            long deltaX = 0, deltaY = 0;

            // no point zooming if the target zoom is the same as the current zoom
            if( zoomTarget != preZoomByVCL )
            {

                SetZoom( SVX_ZOOM_PERCENT, zoomTarget );

                // getting the VCL post zoom
                rememberedZoom = m_pWrtShell->GetViewOptions()->GetZoom();
            }
            else
            {
                rememberedZoom = preZoomByVCL;
            }

            // if there was no zoom
            if( rememberedZoom == preZoomByVCL )
            {
                if( !isFirst )
                {
                    // If this is not the first location of the zoom, there is a valid last location.
                    // Therefore, scroll the center of the gesture.
                    // Explanation: without a zoom transpiring, the view will not change.
                    // Therefore, we do a simple scrolll from screen center to screen center
                    deltaX = rCEvt.GetMousePosPixel().X() - lastLocationInPixels.X();
                    deltaY = rCEvt.GetMousePosPixel().Y() - lastLocationInPixels.Y();
                }
            }
            else
            {
                // Otherwise, there was a zoom.
                // Keep the on screen center of the pinch in the same on screen location
                const Point & postZoomTargetInPixels = GetEditWin().LogicToPixel(targetInLogic);

                deltaX = rCEvt.GetMousePosPixel().X() - postZoomTargetInPixels.X();
                deltaY = rCEvt.GetMousePosPixel().Y() - postZoomTargetInPixels.Y();
            }

            if( (deltaX!=0) || (deltaY!=0) )
            {
                // Scrolling the deltaX deltaY
                Point deltaPoint( deltaX, deltaY );
                CommandWheelData cmd( 0, 0, 0, COMMAND_WHEEL_SCROLL, 0, 0, true);
                CommandEvent event(deltaPoint , COMMAND_WHEEL, sal_True, &cmd );

                m_pEditWin->HandleScrollCommand(event, m_pHScrollbar, m_pVScrollbar);
            }

            // store the last location
            lastLocationInPixels = rCEvt.GetMousePosPixel();
            }

        bOk = sal_True;
    }
    else
    {
        if(pWData->GetMode()==COMMAND_WHEEL_SCROLL)
        if( pWData->GetMode()==COMMAND_WHEEL_SCROLL )
        {
            // This influences whether quick help is shown
            m_bWheelScrollInProgress=true;
        }

        if ((COMMAND_WHEEL_SCROLL==pWData->GetMode()) && (((sal_uLong)0xFFFFFFFF) == pWData->GetScrollLines()))
        if( (COMMAND_WHEEL_SCROLL==pWData->GetMode()) && (((sal_uLong)0xFFFFFFFF) == pWData->GetScrollLines()) )
        {
            if (pWData->GetDelta()<0)
                PhyPageDown();
diff --git a/vcl/ios/iosinst.cxx b/vcl/ios/iosinst.cxx
index dd92c6e..e7cec9f 100644
--- a/vcl/ios/iosinst.cxx
+++ b/vcl/ios/iosinst.cxx
@@ -426,17 +426,28 @@ void lo_tap(int x, int y)
}

extern "C"
void lo_pan(int x, int y)
void lo_pan(int deltaX, int deltaY)
{
    SalFrame *pFocus = IosSalInstance::getInstance()->getFocusFrame();
    if (pFocus) {
        SAL_INFO( "vcl.ios", "scroll: " << "(" << x << "," << y << ")" );
        ScrollEvent aEvent( x, y );
        SAL_INFO( "vcl.ios", "pan delta: " << "(" << deltaX << "," << deltaY << ") ");
        ScrollEvent aEvent( deltaX, deltaY );
        Application::PostScrollEvent(VCLEVENT_WINDOW_SCROLL, pFocus->GetWindow(), &aEvent);
    }
}

extern "C"
void lo_zoom(int x, int y, float scale)
{
    SalFrame *pFocus = IosSalInstance::getInstance()->getFocusFrame();
    if (pFocus) {
        SAL_INFO( "vcl.ios", "pinch: " << "(" << scale  << ") ");
        ZoomEvent aEvent( Point(x,y), scale);
        Application::PostZoomEvent(VCLEVENT_WINDOW_ZOOM, pFocus->GetWindow(), &aEvent);
    }
}

extern "C"
void lo_keyboard_input(int c)
{
    SalFrame *pFocus = IosSalInstance::getInstance()->getFocusFrame();
diff --git a/vcl/source/window/window2.cxx b/vcl/source/window/window2.cxx
index 475181c..49f7650 100644
--- a/vcl/source/window/window2.cxx
+++ b/vcl/source/window/window2.cxx
@@ -1111,6 +1111,38 @@ long Window::GetDrawPixel( OutputDevice* pDev, long nPixels ) const

// -----------------------------------------------------------------------

static void lcl_HandleScrollHelper( ScrollBar* pScrl, long nN, bool isMultiplyByLineSize )
{
    if ( pScrl && nN && pScrl->IsEnabled() && pScrl->IsInputEnabled() && ! pScrl->IsInModalMode() )
    {
        long nNewPos = pScrl->GetThumbPos();

        if ( nN == -LONG_MAX )
            nNewPos += pScrl->GetPageSize();
        else if ( nN == LONG_MAX )
            nNewPos -= pScrl->GetPageSize();
        else
        {
            // allowing both chunked and continuous scrolling
            if(isMultiplyByLineSize){
                nN*=pScrl->GetLineSize();
            }

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

            if ( fVal < LONG_MIN )
                nNewPos = LONG_MIN;
            else if ( fVal > LONG_MAX )
                nNewPos = LONG_MAX;
            else
                nNewPos = (long)fVal;
        }

        pScrl->DoScroll( nNewPos );
    }

}

sal_Bool Window::HandleScrollCommand( const CommandEvent& rCmd,
                                  ScrollBar* pHScrl, ScrollBar* pVScrl )
{
@@ -1152,24 +1184,70 @@ sal_Bool Window::HandleScrollCommand( const CommandEvent& rCmd,

                if ( pData && (COMMAND_WHEEL_SCROLL == pData->GetMode()) )
                {
                    sal_uLong nScrollLines = pData->GetScrollLines();
                    long nLines;
                    if ( nScrollLines == COMMAND_WHEEL_PAGESCROLL )
                    if (!pData->IsDeltaPixel())
                    {
                        if ( pData->GetDelta() < 0 )
                            nLines = -LONG_MAX;
                        sal_uLong nScrollLines = pData->GetScrollLines();
                        long nLines;
                        if ( nScrollLines == COMMAND_WHEEL_PAGESCROLL )
                        {
                            if ( pData->GetDelta() < 0 )
                                nLines = -LONG_MAX;
                            else
                                nLines = LONG_MAX;
                        }
                        else
                            nLines = LONG_MAX;
                    }
                    else
                        nLines = pData->GetNotchDelta() * (long)nScrollLines;
                    if ( nLines )
                    {
                        ImplHandleScroll( NULL,
                            nLines = pData->GetNotchDelta() * (long)nScrollLines;
                        if ( nLines )
                        {
                            ImplHandleScroll( NULL,
                                          0L,
                                          pData->IsHorz() ? pHScrl : pVScrl,
                                          nLines );
                        bRet = sal_True;
                            bRet = sal_True;
                        }
                    }
                    else
                    {
                        // Mobile / touch scrolling section
                        const Point & deltaPoint = rCmd.GetMousePosPixel();

                        double deltaXInPixels = double(deltaPoint.X());
                        double deltaYInPixels = double(deltaPoint.Y());

                        double visSizeX = double(pHScrl->GetVisibleSize());
                        double visSizeY = double(pVScrl->GetVisibleSize());

                        Size winSize = this->GetOutputSizePixel();

                        double ratioX = deltaXInPixels / double(winSize.getWidth());
                        double ratioY = deltaYInPixels / double(winSize.getHeight());

                        long deltaXInLogic = long(visSizeX * ratioX);
                        long deltaYInLogic = long(visSizeY * ratioY);

                        // Touch need to work by pixels. Did not apply this to
                        // Android, as android code may require adaptations
                        // to work with this scrolling code
#ifndef IOS
                        long lineSizeX = pHScrl->GetLineSize();
                        long lineSizeY = pVScrl->GetLineSize();

                        deltaXInLogic /= lineSizeX;
                        deltaYInLogic /= lineSizeY;
#endif

                        if ( deltaXInLogic || deltaYInLogic )
                        {
#ifndef IOS
                            bool isMultiplyByLineSize = true;
#else
                            bool isMultiplyByLineSize = false;
#endif
                            lcl_HandleScrollHelper( pHScrl, deltaXInLogic, isMultiplyByLineSize );
                            lcl_HandleScrollHelper( pVScrl, deltaYInLogic, isMultiplyByLineSize );

                            bRet = sal_True;
                        }
                    }
                }
            }
@@ -1197,32 +1275,7 @@ sal_Bool Window::HandleScrollCommand( const CommandEvent& rCmd,

// -----------------------------------------------------------------------

static void lcl_HandleScrollHelper( ScrollBar* pScrl, long nN )
{
    if ( pScrl && nN && pScrl->IsEnabled() && pScrl->IsInputEnabled() && ! pScrl->IsInModalMode() )
    {
        long nNewPos = pScrl->GetThumbPos();

        if ( nN == -LONG_MAX )
            nNewPos += pScrl->GetPageSize();
        else if ( nN == LONG_MAX )
            nNewPos -= pScrl->GetPageSize();
        else
        {
            const double fVal = (double)nNewPos - ((double)nN * pScrl->GetLineSize());

            if ( fVal < LONG_MIN )
                nNewPos = LONG_MIN;
            else if ( fVal > LONG_MAX )
                nNewPos = LONG_MAX;
            else
                nNewPos = (long)fVal;
        }

        pScrl->DoScroll( nNewPos );
    }

}

// Note that when called for COMMAND_WHEEL above, despite its name,
// pVScrl isn't necessarily the vertical scroll bar. Depending on
@@ -1233,8 +1286,8 @@ static void lcl_HandleScrollHelper( ScrollBar* pScrl, long nN )
void Window::ImplHandleScroll( ScrollBar* pHScrl, long nX,
                               ScrollBar* pVScrl, long nY )
{
    lcl_HandleScrollHelper( pHScrl, nX );
    lcl_HandleScrollHelper( pVScrl, nY );
    lcl_HandleScrollHelper( pHScrl, nX, true );
    lcl_HandleScrollHelper( pVScrl, nY, true );
}

DockingManager* Window::GetDockingManager()
diff --git a/vcl/source/window/winproc.cxx b/vcl/source/window/winproc.cxx
index 052395d..7a93680 100644
--- a/vcl/source/window/winproc.cxx
+++ b/vcl/source/window/winproc.cxx
@@ -35,6 +35,7 @@
#include <vcl/help.hxx>
#include <vcl/dockwin.hxx>
#include <vcl/menu.hxx>
#include <touch/touch.h>

#include <svdata.hxx>
#include <dbggui.hxx>
@@ -2623,6 +2624,7 @@ long ImplWindowFrameProc( Window* pWindow, SalFrame* /*pFrame*/,
            ImplHandleSurroundingTextSelectionChange( pWindow,
                              pEvt->mnStart,
                              pEvt->mnEnd );
            // Fallthrough really intended?
        }
        case SALEVENT_STARTRECONVERSION:
            ImplHandleStartReconversion( pWindow );
@@ -2631,14 +2633,12 @@ long ImplWindowFrameProc( Window* pWindow, SalFrame* /*pFrame*/,
            {
            ZoomEvent* pZoomEvent = (ZoomEvent*) pEvent;
            SalWheelMouseEvent aSalWheelMouseEvent;

            aSalWheelMouseEvent.mnTime = Time::GetSystemTicks();
            aSalWheelMouseEvent.mnX = pZoomEvent->GetCenter().getX();
            aSalWheelMouseEvent.mnY = pZoomEvent->GetCenter().getY();

            // Pass on the scale as a percentage of current zoom factor
            aSalWheelMouseEvent.mnDelta = (long) (pZoomEvent->GetScale() * 100);

            // Pass on the scale as a percentage * 100 of current zoom factor
            // so to assure zoom granularity
            aSalWheelMouseEvent.mnDelta = long(double(pZoomEvent->GetScale()) * double(MOBILE_ZOOM_SCALE_MULTIPLIER));
            // Other SalWheelMouseEvent fields ignored when the
            // scaleDirectly parameter to ImplHandleWheelEvent() is
            // true.
@@ -2649,49 +2649,18 @@ long ImplWindowFrameProc( Window* pWindow, SalFrame* /*pFrame*/,
            {
            ScrollEvent* pScrollEvent = (ScrollEvent*) pEvent;
            SalWheelMouseEvent aSalWheelMouseEvent;

            aSalWheelMouseEvent.mnTime = Time::GetSystemTicks();
            aSalWheelMouseEvent.mnX = 0; // ???
            aSalWheelMouseEvent.mnY = 0;

            // Note that it seems that the delta-is-pixels thing is
            // not actually implemented. The field is just passed on
            // but its value never tested and has no effect?
            aSalWheelMouseEvent.mbDeltaIsPixel = sal_True;

            // First scroll vertically, then horizontally
            aSalWheelMouseEvent.mnDelta = (long) pScrollEvent->GetYOffset();

            // No way to figure out correct amount of "lines" to
            // scroll, and for touch devices (for which this
            // SALEVENBT_EXTERNALSCROLL was introduced) we don't even
            // display the scroll bars. This means that the scroll
            // bars (which still exist as objects, all the scrolling
            // action goes through them) apparently use some dummy
            // default values for range, line size and page size
            // anyway, not related to actual contents of scrolled
            // window. This all is very broken. I really wish the
            // delta-is-pixels feature (which would be exactly what
            // one wants for touch devices) would work.
            aSalWheelMouseEvent.mnScrollLines = aSalWheelMouseEvent.mnDelta;

            if (aSalWheelMouseEvent.mnDelta != 0)
            // event location holds delta values instead
            aSalWheelMouseEvent.mnX = long(pScrollEvent->GetXOffset());
            aSalWheelMouseEvent.mnY = long(pScrollEvent->GetYOffset());
            aSalWheelMouseEvent.mnScrollLines = 0;
            if (aSalWheelMouseEvent.mnX != 0 || aSalWheelMouseEvent.mnY != 0)
            {
                aSalWheelMouseEvent.mnNotchDelta = (aSalWheelMouseEvent.mnDelta < 0) ? -1 : 1;
                aSalWheelMouseEvent.mnCode = 0;
                aSalWheelMouseEvent.mbHorz = sal_False;
                nRet = ImplHandleWheelEvent( pWindow, aSalWheelMouseEvent );
            }
            aSalWheelMouseEvent.mnDelta = (long) pScrollEvent->GetXOffset();
            if (aSalWheelMouseEvent.mnDelta != 0)
            {
                aSalWheelMouseEvent.mnNotchDelta = (aSalWheelMouseEvent.mnDelta < 0) ? -1 : 1;
                aSalWheelMouseEvent.mnCode = 0;
                aSalWheelMouseEvent.mbHorz = sal_True;
                nRet = ImplHandleWheelEvent( pWindow, aSalWheelMouseEvent );
            }
            }
            break;
        }
        break;
        case SALEVENT_QUERYCHARPOSITION:
            ImplHandleSalQueryCharPosition( pWindow, (SalQueryCharPositionEvent*)pEvent );
            break;