Source: modules/keylistener.js

/**
 * KEY AND MOUSE EVENT LISTENER
 * @class
 */

/* global MenubarActions */
/* global debug */
/* global getId */
/* global getTranslation */
/* global grouptree */
/* global waitForFinalEvent */

/* jshint -W098 */
var keylistener = function () {
/* jshint +W098 */

    var self = this;

    this.altModifierPressed = false;
    this.menubaractions = new MenubarActions();
    this.overscroll = 0;
    this.overscrollTimeout = null;
    this.overscrollThreshhold = 4000;

    this.registeredListener = [];

    /**
     * Scrolllistener for the mouse
     * @param {object} event the scroll event
     */
    this.mouseScroll = function (event) {


        // positive or negative value for one turn
        var delta = event.wheelDelta || -event.detail || false;
        if (delta === false) {
            return;
        }

        if (event.ctrlKey || event.altKey) {

            self.stopEvent(event);
            if (delta > 0) {
                self.menubaractions.zoomIn(event);
            } else {
                self.menubaractions.zoomOut(event);
            }

            return;
        } else {
            // normal scrolling
            if (!getId('single-page').isCurrent()) {
                return;
            }

            // now check on the new ScrollPosition
            var wrapper = getId('__contentwrapper');

            var page = self.menubaractions.getCurrentTab();
            if (wrapper.scrollTop + wrapper.offsetHeight >= page.offsetHeight ||
                wrapper.scrollTop <= 0) {
                self.overscroll += -delta;

                var direction = self.overscroll >= self.overscrollThreshhold ? self.menubaractions.nextPage :
                    (self.overscroll <= -self.overscrollThreshhold ? self.menubaractions.previousPage : null);
                if (direction != null) {
                    debug(self.overscroll);

                    if (self.overscrollTimeout) {
                        clearTimeout(self.overscrollTimeout);
                    }
                    self.overscroll = 0;
                    direction(function (didSetPage) {
                        if (didSetPage === false) {
                            return;
                        }
                        wrapper.scrollTop = 0;
                    });
                }

                clearTimeout(self.overscrollTimeout);
                self.overscrollTimeout = setTimeout(function () {
                    debug("reset overscroll at:" + self.overscroll);
                    self.overscroll = 0;
                }, 500);
            }
        }
    };

    // Value to remember last touch distance. Reset when touch movement ends
    this.touchDistanceStart = 0;
    
    // DoubleTap to reset zoom in touch environment
    this.touchDetected = null;
    
    /*
     * Distance function for the two points
     */
    this.distance = function( x1, x2, y1, y2 ) {
        var a = x1-x2, b = y1-y2;
        return Math.sqrt( a*a + b*b );
    };
    
    /**
     * Start detecting touch movement for scaling events
     * @param {object} event the event
     */
    this.touchesStart = function (event) {

        if (!event.touches || event.touches.length != 2) {
            
            // Try to detect dpuble touch and reset zoom
            if ( self.touchDetected ) {
                clearTimeout( self.touchDetected );
                self.touchDetected = null;
                self.menubaractions.updateZoom(getTranslation("menuBar.zoom.pageFit"));
            } else {
                self.touchDetected = setTimeout(function(){
                    self.touchDetected = null;
                }, 300);
            }

            return;
        }

        var touch1 = event.touches[0],
            touch2 = event.touches[1];

        self.touchDistanceStart = self.distance( touch1.clientX, touch2.clientX, touch1.clientY, touch2.clientY );

        document.documentElement.addEvent('touchmove', self.touchesMove);
        document.documentElement.addEvent('touchend', self.touchesStop);
    };

    /**
     * Detect the motion of two touches fort pinching to zoom
     * @param {object} event the event
     */
    this.touchesMove = function (event) {

        if (!event.touches || event.touches.length != 2) {
            return;
        }

        self.stopEvent( event );

        var touch1 = event.touches[0],
            touch2 = event.touches[1],
            touchDistanceStart = self.distance( touch1.clientX, touch2.clientX, touch1.clientY, touch2.clientY ),
            touchDistanceDelta = Math.ceil(touchDistanceStart - self.touchDistanceStart);
        
        if (touchDistanceDelta > 0) {
            self.menubaractions.zoomIn(event, true);
        } else {
            self.menubaractions.zoomOut(event, true);
        }

        self.touchDistanceStart = touchDistanceStart;
    };

    /**
     * End the touches events for scaling the content
     * @param {Object} event the event
     */
    this.touchesStop = function (event) {
        self.stopEvent( event );
        document.documentElement.removeEvent('touchmove', self.touchesMove);
        document.documentElement.removeEvent('touchend', self.touchesStop);
        self.touchDistanceStart = 0;
    };

    /**
     * Keybindings for the whole viewer. Checks only for the press down.
     * @param {object} event the keyboard event
     */
    this.checkDownKey = function (event) {

        let keycode;
        if (event.which) {
            keycode = event.which;
        } else {
            keycode = event.keyCode;
        }

        var catchedEvent = true;

        switch (keycode) {
        case 13:
            if (this.nodeName == 'INPUT') {
                this.fireEvent('blur');
            }
            break;
        case 107: // +
            self.menubaractions.zoomIn(event);
            break;
        case 109: // -
            self.menubaractions.zoomOut(event);
            break;
        case 33: // page up
            self.menubaractions.previousPage();
            break;
        case 34: // page down
            self.menubaractions.nextPage();
            break;
        case 35: // end
            self.menubaractions.lastPage();
            break;
        case 36: // home
            self.menubaractions.firstPage();
            break;
        case 37: // left
        case 38: // up
        case 39: // right
        case 40: // down
            (new grouptree()).keyboardNavigation(keycode);
            break;
        default:
            catchedEvent = false;
        }

        if (catchedEvent) {
            self.stopEvent(event);
        }

    };

    /**
     * Check for a released key event
     * @param {object} event the keyboard event
     */
    this.checkUpKey = function (event) {
    };

    /**
     * Event cancellation
     * @param {object} [event=window.event] cancel event.
     */
    this.stopEvent = function (event) {
        event = event || window.event;

        if (event) {
            event.cancelBubble = true;
            event.returnValue = false;
            if (event.stopPropagation) {
                event.stopPropagation();
            }
            if (event.preventDefault) {
                event.preventDefault();
            }
        }
        
        return false;
    };
    
    /**
     * Add a keylistener to the list of know listeners for external access
     * @param {string} keyCode     The keycode combination as string
     * @param {string} description The description
     * 
     * */
    this.addKeyListener = function(keyCode, description) {

        // Check if the list already contains such object.
        var exists = this.registeredListener.filter(function(elem){
           return elem.keyCode == keyCode; 
        });

        if ( exists.length > 0 ) { return false; }

        this.registeredListener.push({
            keyCode: keyCode,
            description: description
        });
        
        // To ensure that we did add this entry to our list
        return true;
    };

    /**
     * initialise the keyboard events
     */
    this.init = function () {

        if (!getId('__contentwrapper')) { return; }

        window.addEvent('keydown', self.checkDownKey);
        window.addEvent('keyup', self.checkUpKey);

        getId('__contentwrapper').addEvent('mouseover', function () {
            debug("register event");
            window.addEvent('mousewheel', self.mouseScroll);
            document.documentElement.addEvent('mousewheel', self.mouseScroll); // This is for IE

            /* Mozialla Handling */
            if (window.addEventListener) {
                window.addEventListener('MozMousePixelScroll', self.mouseScroll, false);
            }
        });

        getId('__contentwrapper').addEvent('mouseout', function () {
            debug("remove event");
            window.removeEvent('mousewheel', self.mouseScroll);
            document.documentElement.removeEvent('mousewheel', self.mouseScroll); // This is for IE

            /* Mozialla Handling */
            if (window.removeEventListener) {
                window.removeEventListener('MozMousePixelScroll', self.mouseScroll, false);
            }
        });

        this.addKeyListener("+", getTranslation("menuBar.zoomIn") );
        this.addKeyListener("-", getTranslation("menuBar.zoomOut") );

        this.addKeyListener("Home", getTranslation("menuBar.first") );
        this.addKeyListener("Page Up", getTranslation("menuBar.previous") );
        this.addKeyListener("Page Down", getTranslation("menuBar.next") );
        this.addKeyListener("End", getTranslation("menuBar.last") );

        this.addKeyListener("Left", getTranslation("keyBinding.grouptree.close") );
        this.addKeyListener("Up", getTranslation("keyBinding.grouptree.previous") );
        this.addKeyListener("Right", getTranslation("keyBinding.grouptree.open") );
        this.addKeyListener("Down", getTranslation("keyBinding.grouptree.next") );

        this.addKeyListener("ESC", getTranslation("keyBinding.closeDialog") );

        // Touches Start for mobile Devices
        document.documentElement.addEvent('touchstart', self.touchesStart);
        
        // Resize Events
        window.addEvent('resize', waitForFinalEvent( function(){
            self.menubaractions.setZoom($.jStorage.get("menu.zoom"), {});
        }, 250, "resizeWindow")); // Update zoom with resizing
    };
};