diff --git a/web/hand_tool.js b/web/hand_tool.js index b929a1ed1..70d04f07c 100644 --- a/web/hand_tool.js +++ b/web/hand_tool.js @@ -51,6 +51,17 @@ var HandTool = { } }.bind(this), function rejected(reason) {}); }.bind(this)); + + window.addEventListener('presentationmodechanged', function (evt) { + if (evt.detail.switchInProgress) { + return; + } + if (evt.detail.active) { + this.enterPresentationMode(); + } else { + this.exitPresentationMode(); + } + }.bind(this)); } }, diff --git a/web/pdf_history.js b/web/pdf_history.js index e15e03a66..4000803ba 100644 --- a/web/pdf_history.js +++ b/web/pdf_history.js @@ -14,7 +14,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* globals PDFJS, PresentationMode */ 'use strict'; @@ -31,6 +30,7 @@ var PDFHistory = { this.reInitialized = false; this.allowHashChange = true; this.historyUnlocked = true; + this.isViewerInPresentationMode = false; this.previousHash = window.location.hash.substring(1); this.currentBookmark = ''; @@ -122,6 +122,10 @@ var PDFHistory = { // the 'DOMContentLoaded' event is not fired on 'pageshow'. window.addEventListener('beforeunload', pdfHistoryBeforeUnload, false); }, false); + + window.addEventListener('presentationmodechanged', function(e) { + self.isViewerInPresentationMode = !!e.detail.active; + }); }, _isStateObjectDefined: function pdfHistory_isStateObjectDefined(state) { @@ -286,7 +290,7 @@ var PDFHistory = { return null; } var params = { hash: this.currentBookmark, page: this.currentPage }; - if (PresentationMode.active) { + if (this.isViewerInPresentationMode) { params.hash = null; } return params; diff --git a/web/pdf_presentation_mode.js b/web/pdf_presentation_mode.js new file mode 100644 index 000000000..3dd028595 --- /dev/null +++ b/web/pdf_presentation_mode.js @@ -0,0 +1,399 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ +/* Copyright 2012 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* globals PDFViewerApplication */ + +'use strict'; + +var DELAY_BEFORE_RESETTING_SWITCH_IN_PROGRESS = 1500; // in ms +var DELAY_BEFORE_HIDING_CONTROLS = 3000; // in ms +var ACTIVE_SELECTOR = 'pdfPresentationMode'; +var CONTROLS_SELECTOR = 'pdfPresentationModeControls'; + +/** + * @typedef {Object} PDFPresentationModeOptions + * @property {HTMLDivElement} container - The container for the viewer element. + * @property {HTMLDivElement} viewer - (optional) The viewer element. + * @property {PDFThumbnailViewer} pdfThumbnailViewer - (optional) The thumbnail + * viewer. + * @property {Array} contextMenuItems - (optional) The menuitems that are added + * to the context menu in Presentation Mode. + */ + +/** + * @class + */ +var PDFPresentationMode = (function PDFPresentationModeClosure() { + /** + * @constructs PDFPresentationMode + * @param {PDFPresentationModeOptions} options + */ + function PDFPresentationMode(options) { + this.container = options.container; + this.viewer = options.viewer || options.container.firstElementChild; + this.pdfThumbnailViewer = options.pdfThumbnailViewer || null; + var contextMenuItems = options.contextMenuItems || null; + + this.active = false; + this.args = null; + this.contextMenuOpen = false; + this.mouseScrollTimeStamp = 0; + this.mouseScrollDelta = 0; + + if (contextMenuItems) { + for (var i = 0, ii = contextMenuItems.length; i < ii; i++) { + var item = contextMenuItems[i]; + item.element.addEventListener('click', function (handler) { + this.contextMenuOpen = false; + handler(); + }.bind(this, item.handler)); + } + } + } + + PDFPresentationMode.prototype = { + /** + * Request the browser to enter fullscreen mode. + * @returns {boolean} Indicating if the request was successful. + */ + request: function PDFPresentationMode_request() { + if (this.switchInProgress || this.active || + !this.viewer.hasChildNodes()) { + return false; + } + this._addFullscreenChangeListeners(); + this._setSwitchInProgress(); + this._notifyStateChange(); + + if (this.container.requestFullscreen) { + this.container.requestFullscreen(); + } else if (this.container.mozRequestFullScreen) { + this.container.mozRequestFullScreen(); + } else if (this.container.webkitRequestFullscreen) { + this.container.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT); + } else if (this.container.msRequestFullscreen) { + this.container.msRequestFullscreen(); + } else { + return false; + } + + this.args = { + page: PDFViewerApplication.page, + previousScale: PDFViewerApplication.currentScaleValue + }; + + return true; + }, + + /** + * Switches page when the user scrolls (using a scroll wheel or a touchpad) + * with large enough motion, to prevent accidental page switches. + * @param {number} delta - The delta value from the mouse event. + */ + mouseScroll: function PDFPresentationMode_mouseScroll(delta) { + if (!this.active) { + return; + } + var MOUSE_SCROLL_COOLDOWN_TIME = 50; + var PAGE_SWITCH_THRESHOLD = 120; + var PageSwitchDirection = { + UP: -1, + DOWN: 1 + }; + + var currentTime = (new Date()).getTime(); + var storedTime = this.mouseScrollTimeStamp; + + // If we've already switched page, avoid accidentally switching again. + if (currentTime > storedTime && + currentTime - storedTime < MOUSE_SCROLL_COOLDOWN_TIME) { + return; + } + // If the scroll direction changed, reset the accumulated scroll delta. + if ((this.mouseScrollDelta > 0 && delta < 0) || + (this.mouseScrollDelta < 0 && delta > 0)) { + this._resetMouseScrollState(); + } + this.mouseScrollDelta += delta; + + if (Math.abs(this.mouseScrollDelta) >= PAGE_SWITCH_THRESHOLD) { + var pageSwitchDirection = (this.mouseScrollDelta > 0) ? + PageSwitchDirection.UP : PageSwitchDirection.DOWN; + var page = PDFViewerApplication.page; + this._resetMouseScrollState(); + + // If we're at the first/last page, we don't need to do anything. + if ((page === 1 && pageSwitchDirection === PageSwitchDirection.UP) || + (page === PDFViewerApplication.pagesCount && + pageSwitchDirection === PageSwitchDirection.DOWN)) { + return; + } + PDFViewerApplication.page = (page + pageSwitchDirection); + this.mouseScrollTimeStamp = currentTime; + } + }, + + get isFullscreen() { + return !!(document.fullscreenElement || + document.mozFullScreen || + document.webkitIsFullScreen || + document.msFullscreenElement); + }, + + /** + * @private + */ + _notifyStateChange: function PDFPresentationMode_notifyStateChange() { + var event = document.createEvent('CustomEvent'); + event.initCustomEvent('presentationmodechanged', true, true, { + active: this.active, + switchInProgress: !!this.switchInProgress + }); + window.dispatchEvent(event); + }, + + /** + * Used to initialize a timeout when requesting Presentation Mode, + * i.e. when the browser is requested to enter fullscreen mode. + * This timeout is used to prevent the current page from being scrolled + * partially, or completely, out of view when entering Presentation Mode. + * NOTE: This issue seems limited to certain zoom levels (e.g. page-width). + * @private + */ + _setSwitchInProgress: function PDFPresentationMode_setSwitchInProgress() { + if (this.switchInProgress) { + clearTimeout(this.switchInProgress); + } + this.switchInProgress = setTimeout(function switchInProgressTimeout() { + this._removeFullscreenChangeListeners(); + delete this.switchInProgress; + this._notifyStateChange(); + }.bind(this), DELAY_BEFORE_RESETTING_SWITCH_IN_PROGRESS); + }, + + /** + * @private + */ + _resetSwitchInProgress: + function PDFPresentationMode_resetSwitchInProgress() { + if (this.switchInProgress) { + clearTimeout(this.switchInProgress); + delete this.switchInProgress; + } + }, + + /** + * @private + */ + _enter: function PDFPresentationMode_enter() { + this.active = true; + this._resetSwitchInProgress(); + this._notifyStateChange(); + this.container.classList.add(ACTIVE_SELECTOR); + + // Ensure that the correct page is scrolled into view when entering + // Presentation Mode, by waiting until fullscreen mode in enabled. + setTimeout(function enterPresentationModeTimeout() { + PDFViewerApplication.page = this.args.page; + PDFViewerApplication.setScale('page-fit', true); + }.bind(this), 0); + + this._addWindowListeners(); + this._showControls(); + this.contextMenuOpen = false; + this.container.setAttribute('contextmenu', 'viewerContextMenu'); + + // Text selection is disabled in Presentation Mode, thus it's not possible + // for the user to deselect text that is selected (e.g. with "Select all") + // when entering Presentation Mode, hence we remove any active selection. + window.getSelection().removeAllRanges(); + }, + + /** + * @private + */ + _exit: function PDFPresentationMode_exit() { + var page = PDFViewerApplication.page; + this.container.classList.remove(ACTIVE_SELECTOR); + + // Ensure that the correct page is scrolled into view when exiting + // Presentation Mode, by waiting until fullscreen mode is disabled. + setTimeout(function exitPresentationModeTimeout() { + this.active = false; + this._removeFullscreenChangeListeners(); + this._notifyStateChange(); + + PDFViewerApplication.setScale(this.args.previousScale, true); + PDFViewerApplication.page = page; + this.args = null; + }.bind(this), 0); + + this._removeWindowListeners(); + this._hideControls(); + this._resetMouseScrollState(); + this.container.removeAttribute('contextmenu'); + this.contextMenuOpen = false; + + if (this.pdfThumbnailViewer) { + this.pdfThumbnailViewer.ensureThumbnailVisible(page); + } + }, + + /** + * @private + */ + _mouseDown: function PDFPresentationMode_mouseDown(evt) { + if (this.contextMenuOpen) { + this.contextMenuOpen = false; + evt.preventDefault(); + return; + } + if (evt.button === 0) { + // Enable clicking of links in presentation mode. Please note: + // Only links pointing to destinations in the current PDF document work. + var isInternalLink = (evt.target.href && + evt.target.classList.contains('internalLink')); + if (!isInternalLink) { + // Unless an internal link was clicked, advance one page. + evt.preventDefault(); + PDFViewerApplication.page += (evt.shiftKey ? -1 : 1); + } + } + }, + + /** + * @private + */ + _contextMenu: function PDFPresentationMode_contextMenu() { + this.contextMenuOpen = true; + }, + + /** + * @private + */ + _showControls: function PDFPresentationMode_showControls() { + if (this.controlsTimeout) { + clearTimeout(this.controlsTimeout); + } else { + this.container.classList.add(CONTROLS_SELECTOR); + } + this.controlsTimeout = setTimeout(function showControlsTimeout() { + this.container.classList.remove(CONTROLS_SELECTOR); + delete this.controlsTimeout; + }.bind(this), DELAY_BEFORE_HIDING_CONTROLS); + }, + + /** + * @private + */ + _hideControls: function PDFPresentationMode_hideControls() { + if (!this.controlsTimeout) { + return; + } + clearTimeout(this.controlsTimeout); + this.container.classList.remove(CONTROLS_SELECTOR); + delete this.controlsTimeout; + }, + + /** + * Resets the properties used for tracking mouse scrolling events. + * @private + */ + _resetMouseScrollState: + function PDFPresentationMode_resetMouseScrollState() { + this.mouseScrollTimeStamp = 0; + this.mouseScrollDelta = 0; + }, + + /** + * @private + */ + _addWindowListeners: function PDFPresentationMode_addWindowListeners() { + this.showControlsBind = this._showControls.bind(this); + this.mouseDownBind = this._mouseDown.bind(this); + this.resetMouseScrollStateBind = this._resetMouseScrollState.bind(this); + this.contextMenuBind = this._contextMenu.bind(this); + + window.addEventListener('mousemove', this.showControlsBind); + window.addEventListener('mousedown', this.mouseDownBind); + window.addEventListener('keydown', this.resetMouseScrollStateBind); + window.addEventListener('contextmenu', this.contextMenuBind); + }, + + /** + * @private + */ + _removeWindowListeners: + function PDFPresentationMode_removeWindowListeners() { + window.removeEventListener('mousemove', this.showControlsBind); + window.removeEventListener('mousedown', this.mouseDownBind); + window.removeEventListener('keydown', this.resetMouseScrollStateBind); + window.removeEventListener('contextmenu', this.contextMenuBind); + + delete this.showControlsBind; + delete this.mouseDownBind; + delete this.resetMouseScrollStateBind; + delete this.contextMenuBind; + }, + + /** + * @private + */ + _fullscreenChange: function PDFPresentationMode_fullscreenChange() { + if (this.isFullscreen) { + this._enter(); + } else { + this._exit(); + } + }, + + /** + * @private + */ + _addFullscreenChangeListeners: + function PDFPresentationMode_addFullscreenChangeListeners() { + this.fullscreenChangeBind = this._fullscreenChange.bind(this); + + window.addEventListener('fullscreenchange', this.fullscreenChangeBind); + window.addEventListener('mozfullscreenchange', this.fullscreenChangeBind); +//#if !(FIREFOX || MOZCENTRAL) + window.addEventListener('webkitfullscreenchange', + this.fullscreenChangeBind); + window.addEventListener('MSFullscreenChange', this.fullscreenChangeBind); +//#endif + }, + + /** + * @private + */ + _removeFullscreenChangeListeners: + function PDFPresentationMode_removeFullscreenChangeListeners() { + window.removeEventListener('fullscreenchange', this.fullscreenChangeBind); + window.removeEventListener('mozfullscreenchange', + this.fullscreenChangeBind); +//#if !(FIREFOX || MOZCENTRAL) + window.removeEventListener('webkitfullscreenchange', + this.fullscreenChangeBind); + window.removeEventListener('MSFullscreenChange', + this.fullscreenChangeBind); +//#endif + + delete this.fullscreenChangeBind; + } + }; + + return PDFPresentationMode; +})(); diff --git a/web/pdf_viewer.css b/web/pdf_viewer.css index d1e59ab7e..47f2450b6 100644 --- a/web/pdf_viewer.css +++ b/web/pdf_viewer.css @@ -57,22 +57,22 @@ box-shadow: 0px 2px 10px #ff0; } -:-webkit-full-screen .pdfViewer .page { +.pdfPresentationMode:-webkit-full-screen .pdfViewer .page { margin-bottom: 100%; border: 0; } -:-moz-full-screen .pdfViewer .page { +.pdfPresentationMode:-moz-full-screen .pdfViewer .page { margin-bottom: 100%; border: 0; } -:-ms-fullscreen .pdfViewer .page { +.pdfPresentationMode:-ms-fullscreen .pdfViewer .page { margin-bottom: 100% !important; border: 0; } -:fullscreen .pdfViewer .page { +.pdfPresentationMode:fullscreen .pdfViewer .page { margin-bottom: 100%; border: 0; } diff --git a/web/pdf_viewer.js b/web/pdf_viewer.js index 9cee8bf4c..16315bbed 100644 --- a/web/pdf_viewer.js +++ b/web/pdf_viewer.js @@ -374,11 +374,8 @@ var PDFViewer = (function pdfViewer() { if (!noScroll) { var page = this._currentPageNumber, dest; - var inPresentationMode = - this.presentationModeState === PresentationModeState.CHANGING || - this.presentationModeState === PresentationModeState.FULLSCREEN; - if (this.location && !inPresentationMode && - !IGNORE_CURRENT_POSITION_ON_ZOOM) { + if (this.location && !IGNORE_CURRENT_POSITION_ON_ZOOM && + !(this.isInPresentationMode || this.isChangingPresentationMode)) { page = this.location.pageNumber; dest = [null, { name: 'XYZ' }, this.location.left, this.location.top, null]; @@ -402,11 +399,9 @@ var PDFViewer = (function pdfViewer() { if (!currentPage) { return; } - var inPresentationMode = - this.presentationModeState === PresentationModeState.FULLSCREEN; - var hPadding = (inPresentationMode || this.removePageBorders) ? + var hPadding = (this.isInPresentationMode || this.removePageBorders) ? 0 : SCROLLBAR_PADDING; - var vPadding = (inPresentationMode || this.removePageBorders) ? + var vPadding = (this.isInPresentationMode || this.removePageBorders) ? 0 : VERTICAL_PADDING; var pageWidthScale = (this.container.clientWidth - hPadding) / currentPage.width * currentPage.scale; @@ -452,8 +447,7 @@ var PDFViewer = (function pdfViewer() { dest) { var pageView = this.pages[pageNumber - 1]; - if (this.presentationModeState === - PresentationModeState.FULLSCREEN) { + if (this.isInPresentationMode) { if (this.linkService.page !== pageView.id) { // Avoid breaking getVisiblePages in presentation mode. this.linkService.page = pageView.id; @@ -607,7 +601,7 @@ var PDFViewer = (function pdfViewer() { currentId = visiblePages[0].id; } - if (this.presentationModeState !== PresentationModeState.FULLSCREEN) { + if (!this.isInPresentationMode) { this.currentPageNumber = currentId; } @@ -632,13 +626,21 @@ var PDFViewer = (function pdfViewer() { this.container.blur(); }, + get isInPresentationMode() { + return this.presentationModeState === PresentationModeState.FULLSCREEN; + }, + + get isChangingPresentationMode() { + return this.PresentationModeState === PresentationModeState.CHANGING; + }, + get isHorizontalScrollbarEnabled() { - return (this.presentationModeState === PresentationModeState.FULLSCREEN ? + return (this.isInPresentationMode ? false : (this.container.scrollWidth > this.container.clientWidth)); }, _getVisiblePages: function () { - if (this.presentationModeState !== PresentationModeState.FULLSCREEN) { + if (!this.isInPresentationMode) { return getVisibleElements(this.container, this.pages, true); } else { // The algorithm in getVisibleElements doesn't work in all browsers and @@ -709,13 +711,11 @@ var PDFViewer = (function pdfViewer() { * @returns {TextLayerBuilder} */ createTextLayerBuilder: function (textLayerDiv, pageIndex, viewport) { - var isViewerInPresentationMode = - this.presentationModeState === PresentationModeState.FULLSCREEN; return new TextLayerBuilder({ textLayerDiv: textLayerDiv, pageIndex: pageIndex, viewport: viewport, - findController: isViewerInPresentationMode ? null : this.findController + findController: this.isInPresentationMode ? null : this.findController }); }, diff --git a/web/presentation_mode.js b/web/presentation_mode.js deleted file mode 100644 index f94ef7b08..000000000 --- a/web/presentation_mode.js +++ /dev/null @@ -1,268 +0,0 @@ -/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ -/* Copyright 2012 Mozilla Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/* globals scrollIntoView, HandTool, PDFViewerApplication */ - -'use strict'; - -var DELAY_BEFORE_HIDING_CONTROLS = 3000; // in ms -var SELECTOR = 'presentationControls'; -var DELAY_BEFORE_RESETTING_SWITCH_IN_PROGRESS = 1000; // in ms - -var PresentationMode = { - active: false, - args: null, - contextMenuOpen: false, -//#if (GENERIC || CHROME) - prevCoords: { x: null, y: null }, -//#endif - - initialize: function presentationModeInitialize(options) { - this.container = options.container; - this.secondaryToolbar = options.secondaryToolbar; - - this.viewer = this.container.firstElementChild; - - this.firstPage = options.firstPage; - this.lastPage = options.lastPage; - this.pageRotateCw = options.pageRotateCw; - this.pageRotateCcw = options.pageRotateCcw; - - this.firstPage.addEventListener('click', function() { - this.contextMenuOpen = false; - this.secondaryToolbar.firstPageClick(); - }.bind(this)); - this.lastPage.addEventListener('click', function() { - this.contextMenuOpen = false; - this.secondaryToolbar.lastPageClick(); - }.bind(this)); - - this.pageRotateCw.addEventListener('click', function() { - this.contextMenuOpen = false; - this.secondaryToolbar.pageRotateCwClick(); - }.bind(this)); - this.pageRotateCcw.addEventListener('click', function() { - this.contextMenuOpen = false; - this.secondaryToolbar.pageRotateCcwClick(); - }.bind(this)); - }, - - get isFullscreen() { - return (document.fullscreenElement || - document.mozFullScreen || - document.webkitIsFullScreen || - document.msFullscreenElement); - }, - - /** - * Initialize a timeout that is used to specify switchInProgress when the - * browser transitions to fullscreen mode. Since resize events are triggered - * multiple times during the switch to fullscreen mode, this is necessary in - * order to prevent the page from being scrolled partially, or completely, - * out of view when Presentation Mode is enabled. - * Note: This is only an issue at certain zoom levels, e.g. 'page-width'. - */ - _setSwitchInProgress: function presentationMode_setSwitchInProgress() { - if (this.switchInProgress) { - clearTimeout(this.switchInProgress); - } - this.switchInProgress = setTimeout(function switchInProgressTimeout() { - delete this.switchInProgress; - this._notifyStateChange(); - }.bind(this), DELAY_BEFORE_RESETTING_SWITCH_IN_PROGRESS); - }, - - _resetSwitchInProgress: function presentationMode_resetSwitchInProgress() { - if (this.switchInProgress) { - clearTimeout(this.switchInProgress); - delete this.switchInProgress; - } - }, - - request: function presentationModeRequest() { - if (!PDFViewerApplication.supportsFullscreen || this.isFullscreen || - !this.viewer.hasChildNodes()) { - return false; - } - this._setSwitchInProgress(); - this._notifyStateChange(); - - if (this.container.requestFullscreen) { - this.container.requestFullscreen(); - } else if (this.container.mozRequestFullScreen) { - this.container.mozRequestFullScreen(); - } else if (this.container.webkitRequestFullscreen) { - this.container.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT); - } else if (this.container.msRequestFullscreen) { - this.container.msRequestFullscreen(); - } else { - return false; - } - - this.args = { - page: PDFViewerApplication.page, - previousScale: PDFViewerApplication.currentScaleValue - }; - - return true; - }, - - _notifyStateChange: function presentationModeNotifyStateChange() { - var event = document.createEvent('CustomEvent'); - event.initCustomEvent('presentationmodechanged', true, true, { - active: PresentationMode.active, - switchInProgress: !!PresentationMode.switchInProgress - }); - window.dispatchEvent(event); - }, - - enter: function presentationModeEnter() { - this.active = true; - this._resetSwitchInProgress(); - this._notifyStateChange(); - - // Ensure that the correct page is scrolled into view when entering - // Presentation Mode, by waiting until fullscreen mode in enabled. - // Note: This is only necessary in non-Mozilla browsers. - setTimeout(function enterPresentationModeTimeout() { - PDFViewerApplication.page = this.args.page; - PDFViewerApplication.setScale('page-fit', true); - }.bind(this), 0); - - window.addEventListener('mousemove', this.mouseMove, false); - window.addEventListener('mousedown', this.mouseDown, false); - window.addEventListener('contextmenu', this.contextMenu, false); - - this.showControls(); - HandTool.enterPresentationMode(); - this.contextMenuOpen = false; - this.container.setAttribute('contextmenu', 'viewerContextMenu'); - - // Text selection is disabled in Presentation Mode, thus it's not possible - // for the user to deselect text that is selected (e.g. with "Select all") - // when entering Presentation Mode, hence we remove any active selection. - window.getSelection().removeAllRanges(); - }, - - exit: function presentationModeExit() { - var page = PDFViewerApplication.page; - - // Ensure that the correct page is scrolled into view when exiting - // Presentation Mode, by waiting until fullscreen mode is disabled. - // Note: This is only necessary in non-Mozilla browsers. - setTimeout(function exitPresentationModeTimeout() { - this.active = false; - this._notifyStateChange(); - - PDFViewerApplication.setScale(this.args.previousScale, true); - PDFViewerApplication.page = page; - this.args = null; - }.bind(this), 0); - - window.removeEventListener('mousemove', this.mouseMove, false); - window.removeEventListener('mousedown', this.mouseDown, false); - window.removeEventListener('contextmenu', this.contextMenu, false); - - this.hideControls(); - PDFViewerApplication.clearMouseScrollState(); - HandTool.exitPresentationMode(); - this.container.removeAttribute('contextmenu'); - this.contextMenuOpen = false; - - // Ensure that the thumbnail of the current page is visible - // when exiting presentation mode. - scrollIntoView(document.getElementById('thumbnailContainer' + page)); - }, - - showControls: function presentationModeShowControls() { - if (this.controlsTimeout) { - clearTimeout(this.controlsTimeout); - } else { - this.container.classList.add(SELECTOR); - } - this.controlsTimeout = setTimeout(function hideControlsTimeout() { - this.container.classList.remove(SELECTOR); - delete this.controlsTimeout; - }.bind(this), DELAY_BEFORE_HIDING_CONTROLS); - }, - - hideControls: function presentationModeHideControls() { - if (!this.controlsTimeout) { - return; - } - this.container.classList.remove(SELECTOR); - clearTimeout(this.controlsTimeout); - delete this.controlsTimeout; - }, - - mouseMove: function presentationModeMouseMove(evt) { -//#if (GENERIC || CHROME) - // Workaround for a bug in WebKit browsers that causes the 'mousemove' event - // to be fired when the cursor is changed. For details, see: - // http://code.google.com/p/chromium/issues/detail?id=103041. - - var currCoords = { x: evt.clientX, y: evt.clientY }; - var prevCoords = PresentationMode.prevCoords; - PresentationMode.prevCoords = currCoords; - - if (currCoords.x === prevCoords.x && currCoords.y === prevCoords.y) { - return; - } -//#endif - PresentationMode.showControls(); - }, - - mouseDown: function presentationModeMouseDown(evt) { - var self = PresentationMode; - if (self.contextMenuOpen) { - self.contextMenuOpen = false; - evt.preventDefault(); - return; - } - - if (evt.button === 0) { - // Enable clicking of links in presentation mode. Please note: - // Only links pointing to destinations in the current PDF document work. - var isInternalLink = (evt.target.href && - evt.target.classList.contains('internalLink')); - if (!isInternalLink) { - // Unless an internal link was clicked, advance one page. - evt.preventDefault(); - PDFViewerApplication.page += (evt.shiftKey ? -1 : 1); - } - } - }, - - contextMenu: function presentationModeContextMenu(evt) { - PresentationMode.contextMenuOpen = true; - } -}; - -(function presentationModeClosure() { - function presentationModeChange(e) { - if (PresentationMode.isFullscreen) { - PresentationMode.enter(); - } else { - PresentationMode.exit(); - } - } - - window.addEventListener('fullscreenchange', presentationModeChange, false); - window.addEventListener('mozfullscreenchange', presentationModeChange, false); - window.addEventListener('webkitfullscreenchange', presentationModeChange, - false); - window.addEventListener('MSFullscreenChange', presentationModeChange, false); -})(); diff --git a/web/secondary_toolbar.js b/web/secondary_toolbar.js index 7c94b3185..0b3f3d596 100644 --- a/web/secondary_toolbar.js +++ b/web/secondary_toolbar.js @@ -25,7 +25,6 @@ var SecondaryToolbar = { initialize: function secondaryToolbarInitialize(options) { this.toolbar = options.toolbar; - this.presentationMode = options.presentationMode; this.documentProperties = options.documentProperties; this.buttonContainer = this.toolbar.firstElementChild; @@ -72,7 +71,7 @@ var SecondaryToolbar = { // Event handling functions. presentationModeClick: function secondaryToolbarPresentationModeClick(evt) { - this.presentationMode.request(); + PDFViewerApplication.requestPresentationMode(); this.close(); }, diff --git a/web/viewer.css b/web/viewer.css index 4432b302b..c248633b7 100644 --- a/web/viewer.css +++ b/web/viewer.css @@ -47,7 +47,7 @@ select { display: none !important; } -#viewerContainer:-webkit-full-screen { +#viewerContainer.pdfPresentationMode:-webkit-full-screen { top: 0px; border-top: 2px solid transparent; background-color: #000; @@ -58,7 +58,7 @@ select { -webkit-user-select: none; } -#viewerContainer:-moz-full-screen { +#viewerContainer.pdfPresentationMode:-moz-full-screen { top: 0px; border-top: 2px solid transparent; background-color: #000; @@ -69,7 +69,7 @@ select { -moz-user-select: none; } -#viewerContainer:-ms-fullscreen { +#viewerContainer.pdfPresentationMode:-ms-fullscreen { top: 0px !important; border-top: 2px solid transparent; width: 100%; @@ -79,11 +79,11 @@ select { -ms-user-select: none; } -#viewerContainer:-ms-fullscreen::-ms-backdrop { +#viewerContainer.pdfPresentationMode:-ms-fullscreen::-ms-backdrop { background-color: #000; } -#viewerContainer:fullscreen { +#viewerContainer.pdfPresentationMode:fullscreen { top: 0px; border-top: 2px solid transparent; background-color: #000; @@ -96,36 +96,40 @@ select { -ms-user-select: none; } -:-webkit-full-screen a:not(.internalLink) { +.pdfPresentationMode:-webkit-full-screen a:not(.internalLink) { display: none; } -:-moz-full-screen a:not(.internalLink) { +.pdfPresentationMode:-moz-full-screen a:not(.internalLink) { display: none; } -:-ms-fullscreen a:not(.internalLink) { +.pdfPresentationMode:-ms-fullscreen a:not(.internalLink) { display: none !important; } -:fullscreen a:not(.internalLink) { +.pdfPresentationMode:fullscreen a:not(.internalLink) { display: none; } -:-webkit-full-screen .textLayer > div { +.pdfPresentationMode:-webkit-full-screen .textLayer > div { cursor: none; } -:-moz-full-screen .textLayer > div { +.pdfPresentationMode:-moz-full-screen .textLayer > div { cursor: none; } -:fullscreen .textLayer > div { +.pdfPresentationMode:-ms-fullscreen .textLayer > div { cursor: none; } -#viewerContainer.presentationControls, -#viewerContainer.presentationControls .textLayer > div { +.pdfPresentationMode:fullscreen .textLayer > div { + cursor: none; +} + +.pdfPresentationMode.pdfPresentationModeControls > *, +.pdfPresentationMode.pdfPresentationModeControls .textLayer > div { cursor: default; } diff --git a/web/viewer.html b/web/viewer.html index 41adfb626..70144eb9f 100644 --- a/web/viewer.html +++ b/web/viewer.html @@ -81,7 +81,7 @@ http://sourceforge.net/adobe/cmap/wiki/License/ - + diff --git a/web/viewer.js b/web/viewer.js index 62c9f9182..33e11f1d5 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -15,10 +15,10 @@ * limitations under the License. */ /* globals PDFJS, PDFBug, FirefoxCom, Stats, Cache, ProgressBar, - DownloadManager, getFileName, scrollIntoView, getPDFFileNameFromURL, + DownloadManager, getFileName, getPDFFileNameFromURL, PDFHistory, Preferences, SidebarView, ViewHistory, Stats, PDFThumbnailViewer, URL, noContextMenuHandler, SecondaryToolbar, - PasswordPrompt, PresentationMode, HandTool, Promise, + PasswordPrompt, PDFPresentationMode, HandTool, Promise, DocumentProperties, PDFOutlineView, PDFAttachmentView, OverlayManager, PDFFindController, PDFFindBar, getVisibleElements, watchScroll, PDFViewer, PDFRenderingQueue, PresentationModeState, @@ -84,7 +84,7 @@ var mozL10n = document.mozL10n || document.webL10n; //#include pdf_find_controller.js //#include pdf_history.js //#include secondary_toolbar.js -//#include presentation_mode.js +//#include pdf_presentation_mode.js //#include hand_tool.js //#include overlay_manager.js //#include password_prompt.js @@ -107,12 +107,12 @@ var PDFViewerApplication = { pdfThumbnailViewer: null, /** @type {PDFRenderingQueue} */ pdfRenderingQueue: null, + /** @type {PDFPresentationMode} */ + pdfPresentationMode: null, pageRotation: 0, updateScaleControls: true, isInitialViewSet: false, animationStartedPromise: null, - mouseScrollTimeStamp: 0, - mouseScrollDelta: 0, preferenceSidebarViewOnLoad: SidebarView.NONE, preferencePdfBugEnabled: false, preferenceShowPreviousViewOnLoad: true, @@ -174,7 +174,6 @@ var PDFViewerApplication = { SecondaryToolbar.initialize({ toolbar: document.getElementById('secondaryToolbar'), - presentationMode: PresentationMode, toggleButton: document.getElementById('secondaryToolbarToggle'), presentationModeButton: document.getElementById('secondaryPresentationMode'), @@ -190,14 +189,24 @@ var PDFViewerApplication = { documentPropertiesButton: document.getElementById('documentProperties') }); - PresentationMode.initialize({ - container: container, - secondaryToolbar: SecondaryToolbar, - firstPage: document.getElementById('contextFirstPage'), - lastPage: document.getElementById('contextLastPage'), - pageRotateCw: document.getElementById('contextPageRotateCw'), - pageRotateCcw: document.getElementById('contextPageRotateCcw') - }); + if (this.supportsFullscreen) { + var toolbar = SecondaryToolbar; + this.pdfPresentationMode = new PDFPresentationMode({ + container: container, + viewer: viewer, + pdfThumbnailViewer: this.pdfThumbnailViewer, + contextMenuItems: [ + { element: document.getElementById('contextFirstPage'), + handler: toolbar.firstPageClick.bind(toolbar) }, + { element: document.getElementById('contextLastPage'), + handler: toolbar.lastPageClick.bind(toolbar) }, + { element: document.getElementById('contextPageRotateCw'), + handler: toolbar.pageRotateCwClick.bind(toolbar) }, + { element: document.getElementById('contextPageRotateCcw'), + handler: toolbar.pageRotateCcwClick.bind(toolbar) } + ] + }); + } PasswordPrompt.initialize({ overlayName: 'passwordOverlay', @@ -318,8 +327,8 @@ var PDFViewerApplication = { get supportsFullscreen() { var doc = document.documentElement; - var support = doc.requestFullscreen || doc.mozRequestFullScreen || - doc.webkitRequestFullScreen || doc.msRequestFullscreen; + var support = !!(doc.requestFullscreen || doc.mozRequestFullScreen || + doc.webkitRequestFullScreen || doc.msRequestFullscreen); if (document.fullscreenEnabled === false || document.mozFullScreenEnabled === false || @@ -1334,72 +1343,21 @@ var PDFViewerApplication = { this.pdfViewer.scrollPageIntoView(pageNumber); }, - /** - * This function flips the page in presentation mode if the user scrolls up - * or down with large enough motion and prevents page flipping too often. - * - * @this {PDFView} - * @param {number} mouseScrollDelta The delta value from the mouse event. - */ - mouseScroll: function pdfViewMouseScroll(mouseScrollDelta) { - var MOUSE_SCROLL_COOLDOWN_TIME = 50; - - var currentTime = (new Date()).getTime(); - var storedTime = this.mouseScrollTimeStamp; - - // In case one page has already been flipped there is a cooldown time - // which has to expire before next page can be scrolled on to. - if (currentTime > storedTime && - currentTime - storedTime < MOUSE_SCROLL_COOLDOWN_TIME) { + requestPresentationMode: function pdfViewRequestPresentationMode() { + if (!this.pdfPresentationMode) { return; } - - // In case the user decides to scroll to the opposite direction than before - // clear the accumulated delta. - if ((this.mouseScrollDelta > 0 && mouseScrollDelta < 0) || - (this.mouseScrollDelta < 0 && mouseScrollDelta > 0)) { - this.clearMouseScrollState(); - } - - this.mouseScrollDelta += mouseScrollDelta; - - var PAGE_FLIP_THRESHOLD = 120; - if (Math.abs(this.mouseScrollDelta) >= PAGE_FLIP_THRESHOLD) { - - var PageFlipDirection = { - UP: -1, - DOWN: 1 - }; - - // In presentation mode scroll one page at a time. - var pageFlipDirection = (this.mouseScrollDelta > 0) ? - PageFlipDirection.UP : - PageFlipDirection.DOWN; - this.clearMouseScrollState(); - var currentPage = this.page; - - // In case we are already on the first or the last page there is no need - // to do anything. - if ((currentPage === 1 && pageFlipDirection === PageFlipDirection.UP) || - (currentPage === this.pagesCount && - pageFlipDirection === PageFlipDirection.DOWN)) { - return; - } - - this.page += pageFlipDirection; - this.mouseScrollTimeStamp = currentTime; - } + this.pdfPresentationMode.request(); }, /** - * This function clears the member attributes used with mouse scrolling in - * presentation mode. - * - * @this {PDFView} + * @param {number} delta - The delta value from the mouse event. */ - clearMouseScrollState: function pdfViewClearMouseScrollState() { - this.mouseScrollTimeStamp = 0; - this.mouseScrollDelta = 0; + scrollPresentationMode: function pdfViewScrollPresentationMode(delta) { + if (!this.pdfPresentationMode) { + return; + } + this.pdfPresentationMode.mouseScroll(delta); } }; //#if GENERIC @@ -1976,11 +1934,12 @@ function handleMouseWheel(evt) { evt.wheelDelta / MOUSE_WHEEL_DELTA_FACTOR; var direction = (ticks < 0) ? 'zoomOut' : 'zoomIn'; - if (PresentationMode.active) { + if (PDFViewerApplication.pdfViewer.isInPresentationMode) { evt.preventDefault(); - PDFViewerApplication.mouseScroll(ticks * MOUSE_WHEEL_DELTA_FACTOR); + PDFViewerApplication.scrollPresentationMode(ticks * + MOUSE_WHEEL_DELTA_FACTOR); } else if (evt.ctrlKey || evt.metaKey) { - // Only zoom the pages, not the entire viewer + // Only zoom the pages, not the entire viewer. evt.preventDefault(); PDFViewerApplication[direction](Math.abs(ticks)); } @@ -1990,15 +1949,9 @@ window.addEventListener('DOMMouseScroll', handleMouseWheel); window.addEventListener('mousewheel', handleMouseWheel); window.addEventListener('click', function click(evt) { - if (!PresentationMode.active) { - if (SecondaryToolbar.opened && + if (SecondaryToolbar.opened && PDFViewerApplication.pdfViewer.containsElement(evt.target)) { - SecondaryToolbar.close(); - } - } else if (evt.button === 0) { - // Necessary since preventDefault() in 'mousedown' won't stop - // the event propagation in all circumstances in presentation mode. - evt.preventDefault(); + SecondaryToolbar.close(); } }, false); @@ -2013,15 +1966,13 @@ window.addEventListener('keydown', function keydown(evt) { (evt.shiftKey ? 4 : 0) | (evt.metaKey ? 8 : 0); + var pdfViewer = PDFViewerApplication.pdfViewer; + var isViewerInPresentationMode = pdfViewer && pdfViewer.isInPresentationMode; + // First, handle the key bindings that are independent whether an input // control is selected or not. if (cmd === 1 || cmd === 8 || cmd === 5 || cmd === 12) { // either CTRL or META key with optional SHIFT. - var pdfViewer = PDFViewerApplication.pdfViewer; - var inPresentationMode = pdfViewer && - (pdfViewer.presentationModeState === PresentationModeState.CHANGING || - pdfViewer.presentationModeState === PresentationModeState.FULLSCREEN); - switch (evt.keyCode) { case 70: // f if (!PDFViewerApplication.supportsIntegratedFind) { @@ -2040,7 +1991,7 @@ window.addEventListener('keydown', function keydown(evt) { case 107: // FF '+' and '=' case 187: // Chrome '+' case 171: // FF with German keyboard - if (!inPresentationMode) { + if (!isViewerInPresentationMode) { PDFViewerApplication.zoomIn(); } handled = true; @@ -2048,14 +1999,14 @@ window.addEventListener('keydown', function keydown(evt) { case 173: // FF/Mac '-' case 109: // FF '-' case 189: // Chrome '-' - if (!inPresentationMode) { + if (!isViewerInPresentationMode) { PDFViewerApplication.zoomOut(); } handled = true; break; case 48: // '0' case 96: // '0' on Numpad of Swedish keyboard - if (!inPresentationMode) { + if (!isViewerInPresentationMode) { // keeping it unhandled (to restore page zoom to 100%) setTimeout(function () { // ... and resetting the scale after browser adjusts its scale @@ -2083,7 +2034,7 @@ window.addEventListener('keydown', function keydown(evt) { if (cmd === 3 || cmd === 10) { switch (evt.keyCode) { case 80: // p - SecondaryToolbar.presentationModeClick(); + PDFViewerApplication.requestPresentationMode(); handled = true; break; case 71: // g @@ -2117,15 +2068,15 @@ window.addEventListener('keydown', function keydown(evt) { case 38: // up arrow case 33: // pg up case 8: // backspace - if (!PresentationMode.active && - PDFViewerApplication.currentScaleValue !== 'page-fit') { + if (!isViewerInPresentationMode && + PDFViewerApplication.currentScaleValue !== 'page-fit') { break; } /* in presentation mode */ /* falls through */ case 37: // left arrow // horizontal scrolling using arrow keys - if (PDFViewerApplication.pdfViewer.isHorizontalScrollbarEnabled) { + if (pdfViewer.isHorizontalScrollbarEnabled) { break; } /* falls through */ @@ -2148,14 +2099,14 @@ window.addEventListener('keydown', function keydown(evt) { case 40: // down arrow case 34: // pg down case 32: // spacebar - if (!PresentationMode.active && + if (!isViewerInPresentationMode && PDFViewerApplication.currentScaleValue !== 'page-fit') { break; } /* falls through */ case 39: // right arrow // horizontal scrolling using arrow keys - if (PDFViewerApplication.pdfViewer.isHorizontalScrollbarEnabled) { + if (pdfViewer.isHorizontalScrollbarEnabled) { break; } /* falls through */ @@ -2166,13 +2117,13 @@ window.addEventListener('keydown', function keydown(evt) { break; case 36: // home - if (PresentationMode.active || PDFViewerApplication.page > 1) { + if (isViewerInPresentationMode || PDFViewerApplication.page > 1) { PDFViewerApplication.page = 1; handled = true; } break; case 35: // end - if (PresentationMode.active || (PDFViewerApplication.pdfDocument && + if (isViewerInPresentationMode || (PDFViewerApplication.pdfDocument && PDFViewerApplication.page < PDFViewerApplication.pagesCount)) { PDFViewerApplication.page = PDFViewerApplication.pagesCount; handled = true; @@ -2180,7 +2131,7 @@ window.addEventListener('keydown', function keydown(evt) { break; case 72: // 'h' - if (!PresentationMode.active) { + if (!isViewerInPresentationMode) { HandTool.toggle(); } break; @@ -2193,7 +2144,7 @@ window.addEventListener('keydown', function keydown(evt) { if (cmd === 4) { // shift-key switch (evt.keyCode) { case 32: // spacebar - if (!PresentationMode.active && + if (!isViewerInPresentationMode && PDFViewerApplication.currentScaleValue !== 'page-fit') { break; } @@ -2207,25 +2158,25 @@ window.addEventListener('keydown', function keydown(evt) { } } - if (!handled && !PresentationMode.active) { + if (!handled && !isViewerInPresentationMode) { // 33=Page Up 34=Page Down 35=End 36=Home // 37=Left 38=Up 39=Right 40=Down if (evt.keyCode >= 33 && evt.keyCode <= 40 && - !PDFViewerApplication.pdfViewer.containsElement(curElement)) { + !pdfViewer.containsElement(curElement)) { // The page container is not focused, but a page navigation key has been // pressed. Change the focus to the viewer container to make sure that // navigation by keyboard works as expected. - PDFViewerApplication.pdfViewer.focus(); + pdfViewer.focus(); } // 32=Spacebar if (evt.keyCode === 32 && curElementTagName !== 'BUTTON') { //#if (FIREFOX || MOZCENTRAL) // // Workaround for issue in Firefox, that prevents scroll keys from // // working when elements with 'tabindex' are focused. (#3498) -// PDFViewerApplication.pdfViewer.blur(); +// pdfViewer.blur(); //#else - if (!PDFViewerApplication.pdfViewer.containsElement(curElement)) { - PDFViewerApplication.pdfViewer.focus(); + if (!pdfViewer.containsElement(curElement)) { + pdfViewer.focus(); } //#endif } @@ -2234,13 +2185,13 @@ window.addEventListener('keydown', function keydown(evt) { if (cmd === 2) { // alt-key switch (evt.keyCode) { case 37: // left arrow - if (PresentationMode.active) { + if (isViewerInPresentationMode) { PDFHistory.back(); handled = true; } break; case 39: // right arrow - if (PresentationMode.active) { + if (isViewerInPresentationMode) { PDFHistory.forward(); handled = true; } @@ -2250,7 +2201,6 @@ window.addEventListener('keydown', function keydown(evt) { if (handled) { evt.preventDefault(); - PDFViewerApplication.clearMouseScrollState(); } });