Files
openlayers/float-no-zero/closure-library/closure/goog/ui/equation/texpane.js
2014-03-07 10:55:12 +01:00

445 lines
12 KiB
JavaScript

// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// 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.
goog.provide('goog.ui.equation.TexPane');
goog.require('goog.Timer');
goog.require('goog.dom');
goog.require('goog.dom.TagName');
goog.require('goog.dom.selection');
goog.require('goog.events');
goog.require('goog.events.EventType');
goog.require('goog.events.InputHandler');
goog.require('goog.style');
goog.require('goog.ui.equation.ChangeEvent');
goog.require('goog.ui.equation.EditorPane');
goog.require('goog.ui.equation.ImageRenderer');
goog.require('goog.ui.equation.Palette');
goog.require('goog.ui.equation.PaletteEvent');
/**
* User interface for TeX equation editor tab pane.
* @param {Object} context The context this Tex editor pane runs in.
* @param {string} helpUrl The help link URL.
* @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper.
* @constructor
* @extends {goog.ui.equation.EditorPane}
*/
goog.ui.equation.TexPane = function(
context, helpUrl, opt_domHelper) {
goog.ui.equation.EditorPane.call(this, opt_domHelper);
this.setHelpUrl(helpUrl);
/**
* The palette manager instance.
* @type {goog.ui.equation.PaletteManager}
* @private
*/
this.paletteManager_ =
/** @type {goog.ui.equation.PaletteManager} */(
context.paletteManager);
};
goog.inherits(goog.ui.equation.TexPane,
goog.ui.equation.EditorPane);
/**
* The CSS class name for the preview container.
* @type {string}
*/
goog.ui.equation.TexPane.PREVIEW_CONTAINER_CSS_CLASS =
'ee-preview-container';
/**
* The CSS class name for section titles.
* @type {string}
*/
goog.ui.equation.TexPane.SECTION_TITLE_CSS_CLASS =
'ee-section-title';
/**
* The CSS class name for section titles that float left.
* @type {string}
*/
goog.ui.equation.TexPane.SECTION_TITLE_FLOAT_CSS_CLASS =
'ee-section-title-floating';
/**
* The CSS id name for the link to "Learn more".
* @type {string}
*/
goog.ui.equation.TexPane.SECTION_LEARN_MORE_CSS_ID =
'ee-section-learn-more';
/**
* The CSS class name for the Tex editor.
* @type {string}
*/
goog.ui.equation.TexPane.TEX_EDIT_CSS_CLASS = 'ee-tex';
/**
* The CSS class name for the preview container.
* @type {string}
*/
goog.ui.equation.TexPane.WARNING_CLASS =
'ee-warning';
/**
* The content div of the TeX editor.
* @type {Element}
* @private
*/
goog.ui.equation.TexPane.prototype.texEditorElement_ = null;
/**
* The container div for the server-generated image of the equation.
* @type {Element}
* @private
*/
goog.ui.equation.TexPane.prototype.previewContainer_;
/**
* An inner container used to layout all the elements in Tex Editor.
* @type {Element}
* @private
*/
goog.ui.equation.TexPane.prototype.innerContainer_;
/**
* The textarea for free form TeX.
* @type {Element}
* @private
*/
goog.ui.equation.TexPane.prototype.texEdit_;
/**
* The input handler for Tex editor.
* @type {goog.events.InputHandler}
* @private
*/
goog.ui.equation.TexPane.prototype.texInputHandler_;
/**
* The last text that was renderred as an image.
* @type {string}
* @private
*/
goog.ui.equation.TexPane.prototype.lastRenderredText_ = '';
/**
* A sequence number for text change events. Used to delay drawing
* until the user paused typing.
* @type {number}
* @private
*/
goog.ui.equation.TexPane.prototype.changeSequence_ = 0;
/** @override */
goog.ui.equation.TexPane.prototype.createDom = function() {
/** @desc Title for TeX editor tab in the equation editor dialog. */
var MSG_EE_TEX_EQUATION = goog.getMsg('TeX Equation');
/** @desc Title for equation preview image in the equation editor dialog. */
var MSG_EE_TEX_PREVIEW = goog.getMsg('Preview');
/** @desc Link text that leads to an info page about the equation dialog. */
var MSG_EE_LEARN_MORE = goog.getMsg('Learn more');
var domHelper = this.dom_;
var innerContainer;
var texEditorEl = domHelper.createDom(goog.dom.TagName.DIV,
{'style': 'display: none;'},
domHelper.createDom(goog.dom.TagName.SPAN,
{'class':
goog.ui.equation.TexPane.SECTION_TITLE_CSS_CLASS +
' ' +
goog.ui.equation.TexPane.SECTION_TITLE_FLOAT_CSS_CLASS},
MSG_EE_TEX_EQUATION),
this.getHelpUrl() ?
domHelper.createDom(goog.dom.TagName.A,
{'id':
goog.ui.equation.TexPane.SECTION_LEARN_MORE_CSS_ID,
'target': '_blank', 'href': this.getHelpUrl()},
MSG_EE_LEARN_MORE) : null,
domHelper.createDom(goog.dom.TagName.DIV,
{'style': 'clear: both;'}),
innerContainer = this.innerContainer_ =
domHelper.createDom(goog.dom.TagName.DIV,
{'style': 'position: relative'}));
// Create menu palette.
var menuPalette =
this.paletteManager_.setActive(
goog.ui.equation.Palette.Type.MENU);
// Render the menu palette.
menuPalette.render(innerContainer);
innerContainer.appendChild(domHelper.createDom(goog.dom.TagName.DIV,
{'style': 'clear:both'}));
var texEdit = this.texEdit_ = domHelper.createDom('textarea',
{'class': goog.ui.equation.TexPane.TEX_EDIT_CSS_CLASS,
'dir': 'ltr'});
innerContainer.appendChild(texEdit);
innerContainer.appendChild(
domHelper.createDom(goog.dom.TagName.DIV,
{'class':
goog.ui.equation.TexPane.SECTION_TITLE_CSS_CLASS},
MSG_EE_TEX_PREVIEW));
var previewContainer = this.previewContainer_ = domHelper.createDom(
goog.dom.TagName.DIV,
{'class':
goog.ui.equation.TexPane.PREVIEW_CONTAINER_CSS_CLASS});
innerContainer.appendChild(previewContainer);
this.setElementInternal(texEditorEl);
};
/** @override */
goog.ui.equation.TexPane.prototype.enterDocument = function() {
this.texInputHandler_ = new goog.events.InputHandler(this.texEdit_);
// Listen to changes in the edit box to redraw equation.
goog.events.listen(this.texInputHandler_,
goog.events.InputHandler.EventType.INPUT,
this.handleTexChange_, false, this);
// Add a keyup listener for Safari that does not support the INPUT event,
// and for users pasting with ctrl+v, which does not generate an INPUT event
// in some browsers.
this.getHandler().listen(
this.texEdit_, goog.events.EventType.KEYDOWN, this.handleTexChange_);
// Listen to the action event on the active palette.
this.getHandler().listen(this.paletteManager_,
goog.ui.equation.PaletteEvent.Type.ACTION,
this.handlePaletteAction_, false, this);
};
/** @override */
goog.ui.equation.TexPane.prototype.setVisible = function(visible) {
goog.base(this, 'setVisible', visible);
if (visible) {
goog.Timer.callOnce(this.focusTexEdit_, 0, this);
}
};
/**
* Sets the focus to the TeX edit box.
* @private
*/
goog.ui.equation.TexPane.prototype.focusTexEdit_ = function() {
this.texEdit_.focus();
goog.dom.selection.setCursorPosition(this.texEdit_,
this.texEdit_.value.length);
};
/**
* Handles input change within the TeX textarea.
* @private
*/
goog.ui.equation.TexPane.prototype.handleEquationChange_ = function() {
var text = this.getEquation();
if (text == this.lastRenderredText_) {
return; // No change, no need to re-draw
}
this.lastRenderredText_ = text;
var isEquationValid =
!goog.ui.equation.ImageRenderer.isEquationTooLong(text);
// Dispatch change so that dialog might update the state of its buttons.
this.dispatchEvent(
new goog.ui.equation.ChangeEvent(
isEquationValid));
var container = this.previewContainer_;
var dom = goog.dom.getDomHelper(container);
dom.removeChildren(container);
if (text) {
var childNode;
if (isEquationValid) {
// Show equation image.
var imgSrc = goog.ui.equation.ImageRenderer.getImageUrl(text);
childNode = dom.createDom(goog.dom.TagName.IMG, {'src': imgSrc});
} else {
// Show a warning message.
/**
* @desc A warning message shown when equation the user entered is too
* long to display.
*/
var MSG_EE_TEX_EQUATION_TOO_LONG =
goog.getMsg('Equation is too long');
childNode = dom.createDom(goog.dom.TagName.DIV,
{'class': goog.ui.equation.TexPane.WARNING_CLASS},
MSG_EE_TEX_EQUATION_TOO_LONG);
}
dom.appendChild(container, childNode);
}
};
/**
* Handles a change to the equation text.
* Queues a request to handle input change within the TeX textarea.
* Refreshing the image is done only after a short timeout, to combine
* fast typing events into one draw.
* @param {goog.events.Event} e The keyboard event.
* @private
*/
goog.ui.equation.TexPane.prototype.handleTexChange_ = function(e) {
this.changeSequence_++;
goog.Timer.callOnce(
goog.bind(this.handleTexChangeTimer_, this, this.changeSequence_),
500);
};
/**
* Handles a timer timeout on delayed text change redraw.
* @param {number} seq The change sequence number when the timer started.
* @private
*/
goog.ui.equation.TexPane.prototype.handleTexChangeTimer_ =
function(seq) {
// Draw only if this was the last change. If not, just wait for the last.
if (seq == this.changeSequence_) {
this.handleEquationChange_();
}
};
/**
* Handles an action generated by a palette click.
* @param {goog.ui.equation.PaletteEvent} e The event object.
* @private
*/
goog.ui.equation.TexPane.prototype.handlePaletteAction_ = function(e) {
var palette = e.getPalette();
var paletteManager = this.paletteManager_;
var activePalette = paletteManager.getActive();
var texEdit = this.texEdit_;
// This is a click on the menu palette.
if (palette.getType() == goog.ui.equation.Palette.Type.MENU) {
var idx = palette.getHighlightedIndex();
var action = (idx != -1) ? palette.getAction(idx) : null;
// Current palette is not menu. This means there's a palette popping up.
if (activePalette != palette && activePalette.getType() == action) {
// Deactivate the palette.
paletteManager.deactivateNow();
return;
}
// We are clicking on the menu palette and there's no sub palette opening.
// Then we just open the one corresponding to the item under the mouse.
if (action) {
var subPalette = this.paletteManager_.setActive(
/** @type {goog.ui.equation.Palette.Type} */ (action));
if (!subPalette.getElement()) {
subPalette.render(this.innerContainer_);
}
var el = subPalette.getElement();
goog.style.setPosition(el, 0, - el.clientHeight);
}
} else {
activePalette = this.paletteManager_.getActive();
var action = activePalette.getAction(activePalette.getHighlightedIndex());
// If the click is on white space in the palette, do nothing.
if (!action) {
return;
}
// Do actual insert async because IE8 does not move the selection
// position and inserts in the wrong place if called in flow.
// See bug 2066876
goog.Timer.callOnce(goog.bind(this.insert_, this, action + ' '), 0);
}
// Let the tex editor always catch the focus.
texEdit.focus();
};
/**
* Inserts text into the equation at the current cursor position.
* Moves the cursor to after the inserted text.
* @param {string} text Text to insert.
* @private
*/
goog.ui.equation.TexPane.prototype.insert_ = function(text) {
var texEdit = this.texEdit_;
var pos = goog.dom.selection.getStart(texEdit);
var equation = texEdit['value'];
equation = equation.substring(0, pos) + text + equation.substring(pos);
texEdit['value'] = equation;
goog.dom.selection.setCursorPosition(texEdit, pos + text.length);
this.handleEquationChange_();
};
/** @override */
goog.ui.equation.TexPane.prototype.getEquation = function() {
return this.texEdit_['value'];
};
/** @override */
goog.ui.equation.TexPane.prototype.setEquation =
function(equation) {
this.texEdit_['value'] = equation;
this.handleEquationChange_();
};
/** @override */
goog.ui.equation.TexPane.prototype.disposeInternal = function() {
this.texInputHandler_.dispose();
this.paletteManager_ = null;
goog.base(this, 'disposeInternal');
};