Source: modules/menubar.js

/*******************************************************************************
 * MENU BAR ACTIONS
 ******************************************************************************/
var _menubaractions = null;

/**
 * The action on the menu bar. This is a sigleton.
 * @class
 */
var menubaractions = function () {

    // make singleton
    if (_menubaractions != null) {
        return _menubaractions;
    }
    _menubaractions = this; // Singleton Instance

    var self = this;
    this.currentZoom = 100;
    this.maxPages = null;
    this.enabledFormats = null;

    this.currentPageNumber = (function () {
        var currentPage = -1;
        return {
            get: function () {
                if (currentPage > (self.maxPages || currentPage)) {
                    debug("CurrentPage is out of scope (using maxPage): " + currentPage + " > " + self.maxPages);
                    return self.maxPages;
                }

                debug("CurrentPage is good: " + currentPage);
                return currentPage;
            },
            set: function (page) {
                currentPage = Math.max(0, Math.min(page, self.maxPages || page));
                debug("SETTING PAGE NUMBER: " + page);
                // debug("TRACE: " + (new Error()).stack);
                return currentPage;
            }
        };
    })();

    /** Zoom further in */
    this.zoomIn = function (e, slow) {
        self.zoom( (slow?0.5:1) * (self.currentZoom >= 100 ? self.currentZoom / 4 : 5), e);
    };

    /** Zoom out */
    this.zoomOut = function (e, slow) {
        self.zoom((slow?0.5:1) * (self.currentZoom > 100 ? -self.currentZoom / 5 : -5), e);
    };

    // To be implemented, where needed
    this.updateZoomStatus = function (zoom) {};

    /**
     * Returns the page - element which is currently active, e.g. for loading
     * @param   {number} contentWrapperInnerNumber Number of content wrapper to generate name for
     * @param   {string} prefix                    Name prefix
     * @returns {string} name of the page for parameters
     */
    this.getCurrentPageName = function (contentWrapperInnerNumber, prefix) {

        var name = typeof prefix == 'undefined' ? '__content' : prefix;
        name += '-' + (new tabbedPanel()).frontMostTab();

        name += '-' + (getId('single-page').isCurrent() ? 1 : (contentWrapperInnerNumber != null ? contentWrapperInnerNumber : Math.max(1, self.currentPageNumber.get())));
        return name;
    };

    /**
     * Apply current zoom to inner Pages
     * @param {object} contentWrapperInner document element of the inner content wrapper
     * @param {object} content             document element of the content
     */
    this.applyZoom = function (contentWrapperInner, content) {
        if (!content) {
            return;
        }
        var newZoom = (self.currentZoom / 100);

        content = content.parentNode;

        var newHeight = (content.offsetHeight || content.scrollHeight) * newZoom,
            newWidth = content.offsetWidth * newZoom;
        contentWrapperInner.style.height = newHeight > 0 ? newHeight + 'px' : '';
        contentWrapperInner.style.width = newWidth > 0 ? newWidth + 'px' : '';

        if (typeof content.style.transform !== 'undefined') {
            // In addition the defaults as well
            content.style.transform = "scale(" + newZoom + ")";
        } else if (typeof content.style.MozTransform !== 'undefined') {
            content.style.MozTransform = "scale(" + newZoom + ")";
            content.style.MozTransformOrigin = "50% 0%";
        } else if (typeof content.style.webkitTransform !== 'undefined') {
            content.style.webkitTransform = "scale(" + newZoom + ")";
            content.style.webkitTransformOrigin = "50% 0%";
        } else if (typeof content.style.OTransform !== 'undefined') {
            content.style.OTransform = "scale(" + newZoom + ")";
            content.style.OTransformOrigin = "50% 0%";
        } else if (typeof content.style.msTransform !== 'undefined') {
            content.style.msTransform = "scale(" + newZoom + ")";
            content.style.msTransformOrigin = "50% 0%";
        } else {
            content.style.zoom = newZoom * 100 + '%';
        }

        content.style.marginLeft = Math.round(-(1 - newZoom) / 2 * content.offsetWidth) + "px";
    };

    /**
     * Calculates the optimal width of the current tab so that the page is fully visible
     * @returns {number} width in pixel
     */
    this.optimalWidth = function () {
        // Determine the width from the outer Wrapper. The wrapper will be changed by the group tree if set.
        var contentWrapper = getId('__contentwrapper');
        if ( contentWrapper.offsetWidth == 0 ) {
            contentWrapper = getId('__menuBarWrapper'); // For the time being. usually the init phase
        }

        var outerWidth = contentWrapper.offsetWidth;
        if ( !getId('__grouptreewrapper').isCurrent() ) {
            // If hte group tree is not visible, use the parent node of the contentwrapper
            // this is due to not knowing if this was already the case or we just changed it
            outerWidth = contentWrapper.parentNode.offsetWidth;
        }

        if ( contentWrapper.parentNode.offsetWidth === outerWidth ) {
            // The group tree is maybe not visible yet. if the width differs, it is probably visible
            // thus we need to include it
            outerWidth -= Number((contentWrapper.style.left || '0px').slice(0, -2));
        }
        
        var data = getPageCount.DATA || getPageCount.preliminaryData;
        var width = data && data.page ? data.page.width : outerWidth;
        if (window.getComputedStyle) {
            var style = window.getComputedStyle(getId(self.getCurrentPageName(null, '__contentwrapperinner')) || contentWrapper);
            outerWidth -= Number(style.paddingLeft.slice(0, -2)) + Number(style.paddingRight.slice(0, -2));
        } else {
            outerWidth -= 40;
        }

        return Math.floor(100 / width * outerWidth);
    };

    /**
     * Calculates the optimal height of the current tab so that the page is fully visible
     * @returns {number} height in pixel
     */
    this.optimalHeight = function () {
        var outerHeight = getId('__contentwrapper').offsetHeight;

        var data = getPageCount.DATA || getPageCount.preliminaryData;
        var height = data && data.page ? data.page.height : outerHeight;
        if (window.getComputedStyle) {
            var style = window.getComputedStyle(getId(self.getCurrentPageName(null, '__contentwrapperinner')) || getId('__contentwrapper') );
            outerHeight -= Number(style.paddingTop.slice(0, -2)) + Number(style.paddingBottom.slice(0, -2));
        } else {
            outerHeight -= 40;
        }

        return Math.floor(100 / height * outerHeight);
    };

    /**
     * Set a specific zoom value
     * @param {(string|number)} zoom number in percent, optionally with '%' or a specific string value
     * @param {Event} event   An event that triggered the change. may be null. can be used to determin the center point
     */
    this.setZoom = function (zoom, event) {

        if (HASNOZOOM) {
            return;
        }

        switch (zoom) {
        case getTranslation("menuBar.zoom.pageWidth"):
            zoom = self.optimalWidth() + '%';
            break;
        case getTranslation("menuBar.zoom.pageHeight"):
            zoom = self.optimalHeight() + '%';
            break;
        case getTranslation("menuBar.zoom.pageFit"):
            var width = self.optimalWidth();
            var height = self.optimalHeight();
            zoom = (width > height ? height : width) + '%';
            break;
        }

        var match = (zoom + '').match(new RegExp("^([0-9]+|([0-9]+)%*)$", "i"));
        if (!match || !match[1]) {
            self.setZoom(self.currentZoom, event);
            return; // No document set?
        }
        zoom = parseInt(match[2] ? match[2] : match[1]);

        self.currentZoom = zoom;

        if (self.currentZoom <= 10) {
            self.currentZoom = 10;
        }
        if (self.currentZoom >= 10000) {
            self.currentZoom = 10000;
        }

        // Disable if too small already
        (getId('zoomOut') || document.createElement('div')).setEnabled(self.currentZoom > 10);

        // Disable if too large
        (getId('zoomIn') || document.createElement('div')).setEnabled(self.currentZoom < 10000);

        // Change the content elements for good
        var contentWrapper = getId('__contentwrapper');
        var contentWrapperInner = (self.getCurrentTab() || contentWrapper).getElementsByClassName('__contentwrapperinner');
        for (var i = 0; i < contentWrapperInner.length; i++) {
            self.applyZoom(contentWrapperInner[i], getId(self.getCurrentPageName(i + 1)));
        }
        
        // Trigger Zoom which should reload a newly visible page
        if (getId('endless-mode').isCurrent()) {
            dispatchEvent(contentWrapper, 'scroll');
        }

        self.updateZoomStatus(self.currentZoom + '%');
    };

    /**
     * Do a zoom depending on current value and input.
     * @param {number} change New zoom
     * @param {Event} event   An event that triggered the change. may be null.
     */
    this.zoom = function (change, event) {

        var current = parseInt(self.currentZoom),
            change = parseInt(change);
        /*		
        		// von 100 als nullpunkt ausgehen
        		change = change/5 + (current - 100)^4;
        		change = change^(1/4) + 100;
        */
        // Did something change? select the function!
        (change != 0 && !isNaN(change) ? self.updateZoom : self.setZoom)(Math.max(10, (change||0) + current), event);

        // Fire Scroll Event for Page Loading in multipage - is done in setZoom/applyZoom
        /* if ( !getId('single-page').isCurrent() ) {
        	dispatchEvent(getId('__contentwrapper'), 'scroll');
        }*/
    };

    /**
     * Save zoom to storage and update the UI
     * @param {(number|string)} zoom the zoom to set
     * @param {Event} event   An event that triggered the change. may be null.
     */
    this.updateZoom = function (zoom, event) {

        // Set the new value for the zoom
        $.jStorage.set("menu.zoom", zoom);
        self.setZoom(zoom, event);
    }

    /**
     * To be implemented, where needed
     * @param {object} newPage the page
     */
    this.updatePageStatus = function (newPage) {};

    /**
     * Make sure that the addressed page is present in the DOM 
     * @param {number}   page          number of page
     * @param {string}   fragment      URL fragement 
     * @param {function} finalFunction function to call if page is loaded
     */
    this.assurePageIsPresent = function (page, fragment, finalFunction) {
        // Multipage Action - The Page HAS TO BE THERE
        if (!getId('single-page').isCurrent()) {

            // If already loaded, no problem
            var page = self.getCurrentPageName(page);
            var pageElement = page ? getId(page) : null;

            if (pageElement && !pageElement.hasBeenLoaded) {
                // if not loaded, this has to fire.
                pageElement.onLoad = finalFunction;
                pageElement.scrollIntoView(true);
            } else if (pageElement && typeof finalFunction == 'function') {
                // If page is present but already loaded, just call.
                finalFunction();
            }

            return;
        }

        // Single Page Handling with loadPage Request
        self.setPage(page, finalFunction, fragment);
    };

    this.lastLoadPage = null;

    /**
     * Set the paging button status. Will disable if needed
     * @param {number} currentPage the page that is currently set
     */
    this.setPagingButtonStatus = function (checkPage) {

        var prevEnabled = checkPage > 1 || checkPage == -1;
        var nextEnabled = checkPage < parseInt(self.maxPages) || checkPage == -1;

        getId('previous').setEnabled(prevEnabled);
        getId('first').setEnabled(prevEnabled);

        getId('next').setEnabled(nextEnabled || self.maxPages == '');
        getId('last').setEnabled(nextEnabled);

        if (checkPage >= 0) {
            self.updatePageStatus(checkPage + "/" + (self.maxPages || '' ));
        }
    };

    /**
     * Set the page and update loading status
     * @param {number}   page          number of page
     * @param {string}   fragment      URL fragement 
     * @param {function} finalFunction function to call if page is loaded
     */
    this.setPage = function (page, finalFunction, fragment) {

        if (!page) {
            page = self.currentPageNumber.get() || 1;
        } else if (page != self.currentPageNumber.get()) {
            page = self.currentPageNumber.set(page);
        } else {
            // Nothing to do
            if (typeof finalFunction == 'function') {
                finalFunction(false);
            }

            // Extra update - may be differently
            self.setPagingButtonStatus(page);
            return;
        }

        if (this.lastLoadPage != null) {
            // stop? clean old loadPage;
            this.lastLoadPage.stopLoading();
        }

        if (getId('single-page').isCurrent()) {
            this.lastLoadPage = new loadpage(page, finalFunction, fragment);
        } else {
            var element = getId(this.getCurrentPageName(page, '__contentwrapperinner'));
            if (element) {
                scrollToElement( element );
                dispatchEvent(getId('__contentwrapper'), 'scroll');
            }

            if (typeof finalFunction == 'function') {
                finalFunction(false);
            }
        }

        self.setPagingButtonStatus(page);
    };

    /**
     * Go to first Page
     * @param {function} finishFunction Function when the page has loaded
     * @param {string} fragment (optional) a fragment to jump to after being done.
     */
    this.firstPage = function (finishFunction, fragment) {
        self.setPage(1, finishFunction, fragment);
    };

    /**
     * Go to last Page
     * @param {function} finishFunction Function when the page has loaded
     * @param {string} fragment (optional) a fragment to jump to after being done.
     */
    this.lastPage = function (finishFunction, fragment) {
        self.setPage(self.maxPages, finishFunction, fragment);
    };

    /**
     * Go to next Page
     * @param {function} finishFunction Function when the page has loaded
     * @param {string} fragment (optional) a fragment to jump to after being done.
     */
    this.nextPage = function (finishFunction, fragment) {
        var page = parseInt(self.currentPageNumber.get()) + 1;
        if (self.maxPages && page > self.maxPages) {
            page = self.maxPages;
        }

        self.setPage(page, finishFunction, fragment);
    };

    /**
     * Go to previous Page
     * @param {function} finishFunction Function when the page has loaded
     * @param {string} fragment (optional) a fragment to jump to after being done.
     */
    this.previousPage = function (finishFunction, fragment) {
        var page = parseInt(self.currentPageNumber.get()) - 1;
        if (page < 1) {
            page = 1;
        }

        self.setPage(page, finishFunction, fragment);
    };

    /**
     * Print the report using our printListener if it is available
     * or use the window.print() function otherwise
     */
    this.printReport = function () {

        if (getPageCount.DATA === null) {
            return;
        }

        if (typeof printListener == 'object') {
            printListener.showPrint();
        } else {
            window.print();
        }
    };

    /**
     * Reload a report. reset prompts if needed
     */
    this.reloadReport = function () {
        vgen = null;
        VARIABLES.cmd = "rfsh";
        if (PROMPTONREFRESH) {
            VARIABLES.promptonrefresh = true;
        }

        _pageCache = new pageCache();

        // Start the loading indicator. Will be removed when the grouptree is done.
        menubarLoading.start('reload');
        self.refreshReport(null);
    };

    /**
     * Should promptonrefresh be displayed or not
     * this is determined by the global HASPROMPTS variable
     */
    this.activatePromptOnRefresh = function () {

        var hasGlobalPrompts = window.htmlviewer ? window.htmlviewer.HASPROMPTS : HASPROMPTS;
        var reloadButtonRef = getId('reload');
        if (reloadButtonRef) {
            reloadButtonRef.addRemoveClass("hasprompts", hasGlobalPrompts);
        }

        var promptOnRefreshButtonRef = getId('promptonrefresh');
        if (promptOnRefreshButtonRef) {
            promptOnRefreshButtonRef.setIsCurrent(PROMPTONREFRESH);

            if (hasGlobalPrompts) {
                promptOnRefreshButtonRef.addRemoveClass('visible', true);
            } else {
                promptOnRefreshButtonRef.addRemoveClass('visible', false);
            }
        }
    }

    /**
     * Is it active? Determined by the global PROMPTONREFRESH variable
     */
    this.promptonrefresh = function () {
        getId('promptonrefresh').setIsCurrent(!PROMPTONREFRESH);
        PROMPTONREFRESH = !PROMPTONREFRESH;
    };

    /**
     * Resets the grouptree, closes all popups and loads the page again
     * @param {boolean}  resetGroupTree full reset?
     * @param {function} finishFunction function when done loading the page
     */
    this.refreshReport = function (resetGroupTree, finishFunction) {
        (new grouptree(false)).reset(resetGroupTree);

        // Close Prompt Dialog
        closeAllPopUps && closeAllPopUps();

        this.activatePromptOnRefresh();

        // Next Action on reload depending on current Mode
        if (getId('single-page').isCurrent()) {
            self.singlePage(true, finishFunction);
        } else {
            self.endlessMode(true, finishFunction);
        }
    };

    this.showKeyBindings = function() {
        var popup = new popupHandler().show();
        popup.addHeader( getTranslation("keyBinding.header") );
        
        var body = document.createElement('p');
        body.appendChild( document.createTextNode( getTranslation("keyBinding.body") ) );
        popup.addBody( body );
        
        KEYBOARD_LISTENER.registeredListener.forEach(function(entry){
            popup.addDetail( "["+entry.keyCode+"]", entry.description, true);
        })
    };
    
    /**
     * Clean the content wrappers, full reset of the report pages
     */
    this.resetReportPages = function () {
        // Clear the events or they might get triggered instantly resulting in massive delays on large reports.
        clearEvents(getId('__contentwrapper'), 'scroll');
        // Clear current Tab - has to be reloaded anyways
        var contentWrapperInner = (self.getCurrentTab() || document).getElementsByClassName('__contentwrapperinner');
        var i = contentWrapperInner.length,
            elem = null;

        while (i > 0) {

            // Remove Content
            var elem = getId(self.getCurrentPageName(i, '__contentwrapperinner'));
            if ( elem ) {
                elem.hasBeenLoaded = true;
                elem.parentElement.removeChild(elem);
            } else if ( console.error ){
                // That should not happen!
                console.error("Element not found in reset: " + self.getCurrentPageName(i, '__contentwrapperinner') );
            }

            // Remove Style-Element if available
            var styleElement = getId(self.getCurrentPageName(i, '__pageStyles'));
            if (styleElement) {
                styleElement.parentNode.removeChild(styleElement);
            }

            i--;
        }
    };

    /**
     * Retursn the currenly active tab
     * @returns {object} the tab or null
     */
    this.getCurrentTab = function () {
        var tabPanel = new tabbedPanel();
        var tab = tabPanel.managedElements[tabPanel.frontMostTab()];
        return tab ? tab.tabWrapper : null;
    };

    /**
     * Enable single page mode
     * @param {boolean}  override       true to reset the report though single page mode is already the current mode
     * @param {function} finishFunction what to do when done loading the first page
     */
    this.singlePage = function (override, finishFunction) {
        if (getId('single-page').isCurrent() && !override) {
            return;
        }

        debug("Going into SINGLE Mode");
        self.resetReportPages();

        getId('single-page').setIsCurrent(true);
        getId('endless-mode').setIsCurrent(false);

        // Creat a new first page
        self.getCurrentTab().appendChild(self.createInnerContentWrapper());
        self.setPage(null, finishFunction);
    };

    /**
     * Enable endless page mode - this could be a huge performance hit for long reports
     * @param {boolean}  override       true to reset the report though single page mode is already the current mode
     * @param {function} finishFunction what to do when done loading the first page
     */
    this.endlessMode = function (override, finishFunction) {
        if (getId('endless-mode').isCurrent() && !override) {
            return;
        }

        // Somehow cancel the current loading action
        debug("Going into ENDLESS Mode");
        self.resetReportPages();

        getId('single-page').setIsCurrent(false);
        getId('endless-mode').setIsCurrent(true);
        self.loadEndlessPage(self.currentPageNumber.get(), finishFunction);
    };

    /**
     * Function to prepare loading all pages and load them when they scroll into view
     * @private
     */
    this.buildEndlessPageWithScrollListener = function (finishFunction) {

        var internalSelf = self;
        var containerElement = getId('__contentwrapper');
        var elementInViewport = function (el, mostlyVisible) {

            if (!el) {
                return false;
            }

            var top = el.absoluteOffsetTop();

            if (mostlyVisible) {
                // Check of the page is over the half.
                var center = windowSize().height / 2;
                
                return containerElement.scrollTop + center > top && 
                containerElement.scrollTop + center < top + el.offsetHeight;
/*                
                var height50 = el.offsetHeight / 2;
                return top + height50 < (containerElement.scrollTop + containerElement.offsetHeight) &&
                    (top + height50) > containerElement.scrollTop;
*/
            }
            return top < (containerElement.scrollTop + containerElement.offsetHeight) &&
                (top + el.offsetHeight) > containerElement.scrollTop;
        };

        var addScrollListener = function (pageNumber) {

            var timeoutHandler = null;
            var handlerFunction = function (me, isTimedCall) {

                var pageElement = getId(self.getCurrentPageName(pageNumber));
                var pageIsInViewPort = pageElement && elementInViewport(pageElement);
                if (pageIsInViewPort) {
                    var loadFunction = function () {
                        timeoutHandler = null;
                        debug("Loading (page is in viewport) for page #" + pageNumber)
                        removeEvent(containerElement, 'scroll', handler);

                        // Should have been canceld a long time ago
                        if (pageElement.hasBeenLoaded || getId('single-page').isCurrent()) {
                            return;
                        }

                        internalSelf.currentPageNumber.set(pageNumber);

                        new loadpage(pageNumber, function () {
                            if (pageElement.onLoad) {
                                pageElement.onLoad();
                            }
                            pageElement.hasBeenLoaded = true;
                        });
                    };

                    if (isTimedCall) {
                        // override !
                        debug("Timed Call of loadFunction for Page #" + pageNumber);
                        timeoutHandler = null;
                        loadFunction();
                    } else if (timeoutHandler == null) {
                        // delay
                        debug("Calling handler again for page #" + pageNumber);
                        timeoutHandler = window.setTimeout(function (me) {
                            handlerFunction(me, true);
                        }, 200);
                        return;
                    } else {
                        debug("this page must have been loaded already #" + pageNumber);
                        pageElement.hasBeenLoaded = true;
                    }
                } else if (!pageElement) {
                    debug("canceling scroll event (page not in view and pageElement empty) before loading for page #" + pageNumber)
                    removeEvent(containerElement, 'scroll', handler);
                } else {
                    debug("page #" + pageNumber + " not visible.");
                }

                timeoutHandler = null;
            };

            // Try if it is in view while construction.
            debug("Adding ScrollHandler for page " + pageNumber);

            // wait for final event for pages. but set pagenumbers already
            var handler = waitForFinalEvent(handlerFunction, 500, "scrollListener#" + pageNumber);

            addEvent(containerElement, 'scroll', handler);
            addEvent(containerElement, 'scroll', function () {
                var pageElement = getId(self.getCurrentPageName(pageNumber));
                if (pageElement && elementInViewport(pageElement, true)) {
                    if (pageNumber == self.currentPageNumber.get()) {
                        return;
                    }

                    self.currentPageNumber.set(pageNumber);
                    self.setPagingButtonStatus(pageNumber);
                }
            });

            handlerFunction();
        };


        (whenFinishedLoadingPageCount = function (finished) {

            // Wait with building the endless-page until the whole document is ready.
            // This is needed for long running reports.
            if (!finished) {
                return setTimeout(function () {
                    getPageCount.supplyWithData(whenFinishedLoadingPageCount);
                }, 250);
            }

            var previousCurrentPage = Math.max(0, self.currentPageNumber.get()); // Needs to be reset later or nothing works.
            var internalCurrentPage = 0;

            // Wait for the correct page count before adding the other pages and scroll listener
            // Especially when reloading in multipage view we would get the wrong page count otherwise 
            debug("Preparing endless pages. Previous Page was: " + previousCurrentPage);
            self.setPagingButtonStatus(-1);

            (prepareEndlessPageContent = function () {
                if (internalCurrentPage < self.maxPages && getId('endless-mode').isCurrent()) {

                    internalCurrentPage++
                    self.currentPageNumber.set(internalCurrentPage);
                    self.updatePageStatus("#" + internalCurrentPage);

                    // What is the current Page???
                    var nextPage = self.createInnerContentWrapper();
                    self.getCurrentTab().appendChild(nextPage);
                    addScrollListener(self.currentPageNumber.get());

                    // Try to fetch from cache
                    (new loadpage(internalCurrentPage, function () {
                        self.currentPageNumber.set(previousCurrentPage);
                    }, null, true)).startLoading(true);

                    setTimeout(prepareEndlessPageContent, 1); // Wait millisecond before proceeding. Should Deblock the GUI.
                } else {
                    self.currentPageNumber.set(previousCurrentPage);
                    self.setPagingButtonStatus(previousCurrentPage);
                    getPageCount.setSizeOfContent();
                    menubarLoading.stop('endless-mode');

                    if (typeof finishFunction == 'function') {
                        finishFunction();
                    }
                }
            })();
        })(false);
    };


    /**
     * Endlessly load pages until all are found and in the DOM
     * @param {function} finishFunction function to execute when all have been loaded
     */
    this.loadEndlessPage = function (currentPageNumber, finishFunction) {
        menubarLoading.start('endless-mode');
        var pageNode = self.createInnerContentWrapper(1);
        self.getCurrentTab().appendChild(pageNode);

        setTimeout(function () {
            debug("Starting to load endless page #1");
            new loadpage(1, function () {
                self.buildEndlessPageWithScrollListener(function () {
                    self.currentPageNumber.set(1);
                    self.setPage(currentPageNumber, finishFunction);
                });
            });
        }, 1);
    };

    /**
     * Open a subreport denoted by the link
     * @param {object}   link           subreport link
     * @param {function} finishFunction Function to call when the subreport is open
     */
    this.openSubreport = function (link, finishFunction) {
        // Get report Variables;
        var subreportVariables = {
            // subreport_ondemand is encoded here! do not again!
            subreport_ondemand: (typeof link.queryKey.subreport_ondemand != 'undefined' ? link.queryKey.subreport_ondemand : null),
            subreport: (typeof link.queryKey.subreport != 'undefined' ? link.queryKey.subreport : null),
            
            /* 2018-06-21 remove the decode from tabname. It should never be encoded but did have problems when the node name had percent signs in it. */
            /* 2018-07-17 decode again, it has been encoded in grouptree.js */
            name: (typeof link.queryKey.tabname != 'undefined' ? decodeURIComponent( link.queryKey.tabname ) : null) 
        };

        var tabPanel = new tabbedPanel();
        tabPanel.createOnlineOnlyTab(subreportVariables, null, finishFunction);
    };

    /**
     * Create an inner Container that may contain the body
     * @param   {number} contentWrapperInnerNumber the number of the inner content wrapper to create
     * @returns {object} the DOM element
     */
    this.createInnerContentWrapper = function (contentWrapperInnerNumber) {

        var id = self.getCurrentPageName(contentWrapperInnerNumber,
            '__contentwrapperinner');
        var idElem = getId(id);
        if (idElem) {
            idElem.parentNode.removeChild(idElem);
            return idElem;
        }

        var contentWrapper = document.createElement('div');
        contentWrapper.addClassName('__contentwrapperinner');
        contentWrapper.id = id;

        var pagewrapper = document.createElement('div');
        pagewrapper.addClassName('__pagewrapper');
        contentWrapper.appendChild(pagewrapper);

        var content = document.createElement('div');
        content.addClassName('__content');
        content.id = self.getCurrentPageName(contentWrapperInnerNumber);
        pagewrapper.appendChild(content);

        return contentWrapper;
    };


    /**
     * Update the permissions-based buttons
     * @param   {object}  the page permissions
     */
    this.updatePermissions = function (permissions) {

        
        var menu = new menubar(), printMenu = (printListener.printMenu || {}).submenu;
        if (permissions && typeof documentExport == 'object' && !HASNOEXPORTBUTTON) {
            documentExport.enabledFormats = permissions.enabledFormats;
            CANSHOWPERMALINK = permissions.canShowPermalink != null ? permissions.canShowPermalink : CANSHOWPERMALINK;

            amIOnline.check(function (isOnline) {
                if (isOnline && !documentExport.exportMenu && printMenu && documentExport.exportActions().length > 0) {
                    documentExport.buildExport(menu.createButton("S", "save", getTranslation("menuBar.save"), documentExport.showExport, printMenu, null, null), printMenu);
                }
            });
        }

        // Check permissions.
        if ((permissions && !permissions.allowprint) || HASNOPRINTBUTTON) {
            // Printing enabled.
            if (printMenu && printMenu.id == 'print') {

                var nextItem = printMenu.previousSibling;
                printMenu.parentNode.removeChild(printMenu);
                printMenu = nextItem;
            }
        }

        var numberOfPagesKnown = getPageCount.DATA != null;

        // Disable Printing until the report is finished
        if (printMenu && printMenu.id == 'print') {
            printMenu.setEnabled(numberOfPagesKnown);
        }

        // Enable the save menu
        (getId('save') || document.createElement('div')).setEnabled(numberOfPagesKnown);

        // Enable the search menu
        (getId('search') || document.createElement('div')).setEnabled(numberOfPagesKnown);
    };
};

/*******************************************************************************
 * MENU BAR
 ******************************************************************************/
var _menubar = null

/**
 * The MenuBar - this is a singleton
 * @class
 */
var menubar = function () {

    if (_menubar != null) {
        return _menubar;
    }

    var self = this;
    _menubar = this;
    this.menubaractions = new menubaractions();
    this.moreTabsSubmenu = null;
    this.endlessModeRef = null;

    /**
     * Create a button for the menubar
     * @param   {(object|string)} accesskey                 the access key code or object with { keycode: {string}, shift: {boolean} }
     * @param   {string}          imageClass                CSS class for the image to use and also the ID (not twice!)
     * @param   {string}          name                      Title of the button
     * @param   {function}        action                    function to execute when pressed or touch ended
     * @param   {object}          insertBefore              DOM Object to insert the new button before
     * @param   {string}          hint                      what the button is about
     * @param   {object}          [insertInto='#__menuBar'] Where to add the button.
     * @param   {string}          [iconClass='__icon']      CSS Class to add for an icon to be shown
     * @returns {object}          the Button
     */
    this.createButton = function (accesskey, imageClass, name, action, insertBefore, hint, insertInto, iconClass) {

        var button = document.createElement("div");
        button.id = imageClass;
        button.className = "__menuButton";
        button.appendChild(document.createTextNode(name));
        button.title = name;

        if (!insertInto) {
            insertInto = getId('__menuBar');
        }

        if (typeof iconClass == 'undefined') {
            iconClass = '__icon';
        }

        if (imageClass) {
            button.addClassName(iconClass)
            button.addClassName(imageClass.split('.').pop())
        }

        if (typeof insertBefore != 'undefined' && insertBefore != null) {
            insertInto.insertBefore(button, insertBefore);
        } else {
            insertInto.appendChild(button);
        }

        if (hint && hint.length > 0) {
            button.setAttribute('data-hint', hint);
        }

        var executeAction = function (e) {
            if (button.isEnabled()) {
                action(e);
            }
        };

        addEvent(button, 'click', executeAction);
        addEvent(button, 'touchend', function (e) {
            e.preventDefault();
            executeAction(e);
        });

        if (typeof accesskey == 'string') {
            accesskey = {
                keycode: accesskey,
                shift: false
            };
        }

        if (accesskey.keycode.length == 1 && accesskey.keycode >= 'A' && accesskey.keycode <= 'z') {

            var keyCode = "CTRL+";
            keyCode += accesskey.shift ? "SHIFT+" : '';
            keyCode += accesskey.keycode;
            
            button.title += " [" + keyCode + "]";
            button.setAttribute("tabindex", -1);
            
            var keydownEvent = function (event) {

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

                if ((event.metaKey || event.ctrlKey) && ((!event.shiftKey && !accesskey.shift) || (event.shiftKey && accesskey.shift)) && String.fromCharCode(keycode).toLowerCase() == accesskey.keycode.toLowerCase()) {
                    event.preventDefault();
                    button.focus();
                    executeAction(event);
                }
            };
            
            KEYBOARD_LISTENER.addKeyListener( keyCode, name);
            addEvent(window, 'keydown', keydownEvent);

            button.clearButton = function() {
                clearEvents(this, 'click');
                clearEvents(this, 'touchend');
                removeEvent(window, 'keydown', keydownEvent);
                this.parentNode.removeChild(this);
            };
        }

        return button;
    };

    /**
     * Create a group where, e.g. buttons can be added
     * @param   {object} [insertBefore=null] DOM element to insert the group before - at the end if null
     * @returns {object} DOM element of the group
     */
    this.createGroup = function (insertBefore) {
        var group = document.createElement("div");
        group.className = '__menuGroup';
        if (typeof insertBefore != 'undefined' && insertBefore != null) {
            getId('__menuBar').insertBefore(group, insertBefore);
        } else {
            getId('__menuBar').appendChild(group);
        }

        return group;
    };

    /**
     * Create a spacer with the size of a button
     * @param   {object} [insertBefore=null] DOM element to insert the group before - at the end if null
     * @returns {object} DOM element of the group
     */
    this.createSpacer = function (insertBefore) {
        var spacer = document.createElement("div");
        spacer.className = "__menuSpacer";

        if (typeof insertBefore != 'undefined') {
            getId('__menuBar').insertBefore(spacer, insertBefore);
        } else {
            getId('__menuBar').appendChild(spacer);
        }

        return spacer;
    };

    /**
     * Create a dropdown box
     * @param   {string}   name         Name and ID of the box
     * @param   {Array}    values       List of values
     * @param   {function} action       The action to call for a selected value
     * @param   {string}   defaultValue The default value from the list
     * @param   {object}   insertInto   Which button to replace with the dropdown
     * @param   {string}   className    Extra class names for the dropdown
     * @returns {object}   The dropdown element
     */
    this.createDropDown = function (name, values, action, defaultValue, insertInto, className) {

        var dropdown = new editableComboBox(name, className);
        dropdown.combobox.id = name;
        dropdown.combobox.addClassName(className);
        dropdown.combobox.addClassName("__menuDropDown");
        dropdown.updateAction = action;

        // Not an object / array? make one!
        if (typeof values != 'object') {
            values = [values];
        }

        // Add all options
        for (i = 0; i < values.length; i++) {
            dropdown.addOption(values[i], !isNaN(values[i]) ? '%' : '');
        }

        if (!insertInto) {
            insertInto = getId('__menuBar');
        }

        insertInto.appendChild(dropdown.combobox);
        if (typeof dropdown.updateAction == 'function') {
            dropdown.updateAction(defaultValue);
        }
        return dropdown;
    };

    /**
     * Set up the initial state of the menubar and create the buttons
     */
    this.init = function () {
        // Create Buttonbar
        // this.createSpacer().addClassName('hidden-mobile-xs');

        var singlePageRef = this.createButton("O", "single-page", getTranslation("menuBar.singlePage"),
            self.menubaractions.singlePage);
        singlePageRef.setIsCurrent(true);
        self.endlessModeRef = this.createButton("M", "endless-mode", getTranslation("menuBar.endlessPage"),
            self.menubaractions.endlessMode);
        self.endlessModeRef.setEnabled(false);
        //self.endlessModeRef.addClassName('hidden-mobile-xs');

        this.createSpacer();

        var pageGroup = this.createGroup();

        // Page Skip buttons
        this.createButton("<<", "first", getTranslation("menuBar.first"), self.menubaractions.firstPage, null, null, pageGroup);
        this.createButton("<", "previous", getTranslation("menuBar.previous"), self.menubaractions.previousPage, null, null, pageGroup);

        var pageDropper = this.createDropDown("page", [], null, 100, pageGroup);
        self.menubaractions.updatePageStatus = pageDropper.updateInputValue;
        // Umleiten der Action

        // If this is an online Version, print the buttons.
        amIOnline.check(function (isOnline) {
            if (!isOnline) {
                var printButton = self.createButton("P", "print", getTranslation("menuBar.print"), self.menubaractions.printReport, singlePageRef, null, null);
                printButton.addClassName('hidden-mobile-xs');
                printListener.buildPrint(printButton, singlePageRef, false);
                return;
            }

            self.createButton("R", "reload", getTranslation("menuBar.reload"), self.menubaractions.reloadReport, singlePageRef);
            self.createButton({
                keycode: 'R',
                shift: true
            }, "promptonrefresh", getTranslation("menuBar.promptonrefresh"), self.menubaractions.promptonrefresh, singlePageRef, getTranslation("menuBar.promptonrefresh.hint"));
            self.menubaractions.activatePromptOnRefresh();

            if (typeof documentSearch == 'object' && !HASNOTEXTSEARCH) {
                var searchButton = self.createButton("F", "search", getTranslation("menuBar.search"), documentSearch.showSearch, singlePageRef, null, null);
                searchButton.addClassName('hidden-mobile-xs');
                documentSearch.buildSearch(searchButton, singlePageRef);
            }

            self.createSpacer(singlePageRef).addClassName('hidden-mobile-xs');

            // self.createSpacer();
            // Insert this before the reloadbutton.
            var printButton = self.createButton("P", "print", getTranslation("menuBar.print"), self.menubaractions.printReport, singlePageRef, null);
            printButton.addClassName('hidden-mobile-xs');
            printListener.buildPrint(printButton, singlePageRef, true);

            self.createSpacer(singlePageRef);
            self.menubaractions.updatePermissions();
        });

        pageDropper.input.parentNode.addClassName('hidden-mobile-xs'); // Page selection only from sm upwards
        pageDropper.input.style.textAlign = 'center';
        pageDropper.input.style.width = '65px';

        pageDropper.updateAction = function (selectedValue) {
            // Match for number or number/number - otherwise nothing is done
            var match = selectedValue.match(new RegExp(
                "^([0-9]+|([0-9]+)\/[0-9]+)$", "i"));
            var newPage = match ? Math.min(Math.max(1, match[2] ? match[2] : match[1]),
                self.menubaractions.maxPages) : 1;
            if (newPage) {
                self.menubaractions.setPage(newPage);
            }
        };

        this.createButton(">", "next", getTranslation("menuBar.next"), self.menubaractions.nextPage, null, null, pageGroup);
        this.createButton(">>", "last", getTranslation("menuBar.last"), self.menubaractions.lastPage, null, null, pageGroup);

        this.createSpacer();

        // Zoom buttons
        if (!HASNOZOOM) {

            var zoomGroup = this.createGroup();

            this.createButton("-", "zoomOut", getTranslation("menuBar.zoomOut"), self.menubaractions.zoomOut, null, null, zoomGroup);
            var zoomStatus = this.createDropDown("zoom", [10, 25, 50, 75, 100, 150, 200, 300, 400,
			                                               getTranslation("menuBar.zoom.pageWidth"),
			                                               getTranslation("menuBar.zoom.pageHeight"),
			                                               getTranslation("menuBar.zoom.pageFit")],
                self.menubaractions.updateZoom, DEFAULTZOOM, zoomGroup, 'dark'); // DEFAULTZOOM because it will be stored again.

            zoomStatus.input.style.width = '114px';
            zoomStatus.combobox.addClassName('hidden-mobile');

            self.menubaractions.updateZoomStatus = zoomStatus.updateInputValue; // Umleiten der Action
            this.createButton("+", "zoomIn", getTranslation("menuBar.zoomIn"), self.menubaractions.zoomIn, null, null, zoomGroup);
        }

        this.createButton("?", "help", getTranslation("menuBar.help"), self.menubaractions.showKeyBindings).addClassName("right").addClassName('hidden-mobile');
    };

    // Init if there is the menubar wrapper
    if (getId('__menuBarWrapper')) {
        this.init();
    }
};