From 8f8782701b8fc2a5a57b22cf5bc2a879805215aa Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Wed, 27 Jul 2011 16:40:45 +0200 Subject: [PATCH] move theme and text size to the window model --- build_support/boot_textarea.js | 2 +- lib/ace/ace.js | 3 +- lib/ace/editor.js | 4 +- lib/ace/model/window.js | 43 ++++++++++- lib/ace/split.js | 2 +- lib/ace/view/layer/text.js | 82 -------------------- lib/ace/view/measure_text.js | 133 +++++++++++++++++++++++++++++++++ lib/ace/view/window_view.js | 81 +++++++++----------- lib/ace/window_controller.js | 3 + 9 files changed, 220 insertions(+), 133 deletions(-) create mode 100644 lib/ace/view/measure_text.js diff --git a/build_support/boot_textarea.js b/build_support/boot_textarea.js index 9fdeb8c9..cb845355 100644 --- a/build_support/boot_textarea.js +++ b/build_support/boot_textarea.js @@ -62,7 +62,7 @@ window.__ace_shadowed__.edit = function(el) { doc.setUndoManager(new UndoManager()); el.innerHTML = ''; - var editor = new Editor(new Renderer(new Window(), el, "ace/theme/textmate")); + var editor = new Editor(new Renderer(new Window("ace/theme/textmate"), el)); editor.setSession(doc); var env = {}; diff --git a/lib/ace/ace.js b/lib/ace/ace.js index b0f4accf..722c9328 100644 --- a/lib/ace/ace.js +++ b/lib/ace/ace.js @@ -58,7 +58,8 @@ define(function(require, exports, module) { doc.setUndoManager(new UndoManager()); el.innerHTML = ''; - var editor = new Editor(new Renderer(new Window(), el, require("ace/theme/textmate"))); + var theme = require("ace/theme/textmate"); + var editor = new Editor(new Renderer(new Window(theme), el)); editor.setSession(doc); var env = {}; diff --git a/lib/ace/editor.js b/lib/ace/editor.js index 1902ac42..15330f73 100644 --- a/lib/ace/editor.js +++ b/lib/ace/editor.js @@ -224,11 +224,11 @@ var Editor = function(windowView, buffer) { }; this.setTheme = function(theme) { - this.renderer.setTheme(theme); + this.windowModel.setTheme(theme); }; this.getTheme = function() { - return this.renderer.getTheme(); + return this.windowModel.getTheme(); }; this.setStyle = function(style) { diff --git a/lib/ace/model/window.js b/lib/ace/model/window.js index 3f7360a7..7928af4c 100644 --- a/lib/ace/model/window.js +++ b/lib/ace/model/window.js @@ -47,7 +47,10 @@ var EventEmitter = require("pilot/event_emitter").EventEmitter; * * */ -var Window = exports.Window = function() { +var Window = exports.Window = function(theme) { + this.theme = null; + this.setTheme(theme); + this._buffer = null; this.layerConfig = { @@ -71,6 +74,11 @@ var Window = exports.Window = function() { scrollerWidth: 0 }; + this.characterSize = { + height: 0, + width: 0 + }; + this.showInvisibles = false; this.showPrintMargin = true; this.printMarginColumn = 80; @@ -92,6 +100,31 @@ var Window = exports.Window = function() { this._emit("changeBuffer", {oldValue: oldValue, value: buffer}); }; + this.setTheme = function(theme) { + var _self = this; + + this.$themeValue = theme; + (function(next) { + if (!theme || typeof theme == "string") { + theme = theme || "ace/theme/textmate"; + require([theme], function(theme) { + next(theme); + }); + } else { + next(theme); + } + })(function next(theme) { + if (_self.theme == theme) + return; + + _self.theme = theme; + _self._emit("changeTheme"); + }); + }; + + this.getTheme = function() { + return this.theme; + }; this.setShowInvisibles = function(showInvisibles) { if (this.showInvisibles == showInvisibles) @@ -164,6 +197,14 @@ var Window = exports.Window = function() { this.getHScrollBarAlwaysVisible = function() { return this.horizScrollAlwaysVisible; }; + + this.setComputedCharacterSize = function(size) { + if (this.characterSize.height == size.height && this.characterSize.width == size.width) + return; + + this.characterSize = size; + this._emit("changeCharacterSize") + } }).call(Window.prototype); diff --git a/lib/ace/split.js b/lib/ace/split.js index 9c29513c..d112727d 100644 --- a/lib/ace/split.js +++ b/lib/ace/split.js @@ -78,7 +78,7 @@ var Split = function(container, theme, splits) { el.style.cssText = "position: absolute; top:0px; bottom:0px"; this.$container.appendChild(el); var session = new Buffer(""); - var editor = new Editor(new Renderer(new Window(), el, this.$theme)); + var editor = new Editor(new Renderer(new Window(this.$theme), el)); editor.on("focus", function() { this._emit("focus", editor); diff --git a/lib/ace/view/layer/text.js b/lib/ace/view/layer/text.js index d5572a50..281a076d 100644 --- a/lib/ace/view/layer/text.js +++ b/lib/ace/view/layer/text.js @@ -53,9 +53,6 @@ var Text = function(model, parentEl) { this.element.className = "ace_layer ace_text-layer"; this.element.style.width = "auto"; parentEl.appendChild(this.element); - - this.$characterSize = this.$measureSizes() || {width: 0, height: 0}; - this.$pollSizeChanges(); }; (function() { @@ -67,85 +64,6 @@ var Text = function(model, parentEl) { this.TAB_CHAR = "→"; this.SPACE_CHAR = "·"; - this.getLineHeight = function() { - return this.$characterSize.height || 1; - }; - - this.getCharacterWidth = function() { - return this.$characterSize.width || 1; - }; - - this.checkForSizeChanges = function() { - var size = this.$measureSizes(); - if (size && (this.$characterSize.width !== size.width || this.$characterSize.height !== size.height)) { - this.$characterSize = size; - this._dispatchEvent("changeCharaterSize", {data: size}); - } - }; - - this.$pollSizeChanges = function() { - var self = this; - this.$pollSizeChangesTimer = setInterval(function() { - self.checkForSizeChanges(); - }, 500); - }; - - this.$fontStyles = { - fontFamily : 1, - fontSize : 1, - fontWeight : 1, - fontStyle : 1, - lineHeight : 1 - }; - - this.$measureSizes = function() { - var n = 1000; - if (!this.$measureNode) { - var measureNode = this.$measureNode = dom.createElement("div"); - var style = measureNode.style; - - style.width = style.height = "auto"; - style.left = style.top = (-n * 40) + "px"; - - style.visibility = "hidden"; - style.position = "absolute"; - style.overflow = "visible"; - style.whiteSpace = "nowrap"; - - // in FF 3.6 monospace fonts can have a fixed sub pixel width. - // that's why we have to measure many characters - // Note: characterWidth can be a float! - measureNode.innerHTML = lang.stringRepeat("Xy", n); - - if (document.body) { - document.body.appendChild(measureNode); - } else { - var container = this.element.parentNode; - while (!dom.hasCssClass(container, "ace_editor")) - container = container.parentNode; - container.appendChild(measureNode); - } - - } - - var style = this.$measureNode.style; - var computedStyle = dom.computedStyle(this.element); - for (var prop in this.$fontStyles) - style[prop] = computedStyle[prop]; - - var size = { - height: this.$measureNode.offsetHeight, - width: this.$measureNode.offsetWidth / (n * 2) - }; - - // Size and width can be null if the editor is not visible or - // detached from the document - if (size.width == 0 && size.height == 0) - return null; - - return size; - }; - this.setSession = function(session) { this.session = session; }; diff --git a/lib/ace/view/measure_text.js b/lib/ace/view/measure_text.js new file mode 100644 index 00000000..19aff254 --- /dev/null +++ b/lib/ace/view/measure_text.js @@ -0,0 +1,133 @@ +/* vim:ts=4:sts=4:sw=4: + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (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.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Ajax.org Code Editor (ACE). + * + * The Initial Developer of the Original Code is + * Ajax.org B.V. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Fabian Jakobs + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +define(function(require, exports, module) { + +var oop = require("pilot/oop"); +var dom = require("pilot/dom"); +var lang = require("pilot/lang"); +var useragent = require("pilot/useragent"); +var EventEmitter = require("pilot/event_emitter").EventEmitter; + +var MeasureText = exports.MeasureText = function(parentEl, interval) { + this.parentEl = parentEl; + this.$characterSize = this.$measureSizes() || {width: 0, height: 0}; + this.$pollSizeChanges(interval); +}; + +(function() { + + oop.implement(this, EventEmitter); + + this.getCharacterSize = function() { + return { + height: this.$characterSize.height || 1, + width: this.$characterSize.width || 1 + } + }; + + this.checkForSizeChanges = function() { + var size = this.$measureSizes(); + if (size && (this.$characterSize.width !== size.width || this.$characterSize.height !== size.height)) { + this.$characterSize = size; + this._emit("changeCharacterSize", {data: size}); + } + }; + + this.$pollSizeChanges = function(interval) { + var self = this; + this.$pollSizeChangesTimer = setInterval(function() { + self.checkForSizeChanges(); + }, interval); + }; + + this.$fontStyles = { + fontFamily : 1, + fontSize : 1, + fontWeight : 1, + fontStyle : 1, + lineHeight : 1 + }; + + this.$measureSizes = function() { + var n = 1000; + if (!this.$measureNode) { + var measureNode = this.$measureNode = dom.createElement("div"); + var style = measureNode.style; + + style.width = style.height = "auto"; + style.left = style.top = (-n * 40) + "px"; + + style.visibility = "hidden"; + style.position = "absolute"; + style.overflow = "visible"; + style.whiteSpace = "nowrap"; + + // in FF 3.6 monospace fonts can have a fixed sub pixel width. + // that's why we have to measure many characters + // Note: characterWidth can be a float! + measureNode.innerHTML = lang.stringRepeat("Xy", n); + + this.parentEl.appendChild(measureNode); + } + + var style = this.$measureNode.style; + var computedStyle = dom.computedStyle(this.parentEl); + for (var prop in this.$fontStyles) + style[prop] = computedStyle[prop]; + + var size = { + height: this.$measureNode.offsetHeight, + width: this.$measureNode.offsetWidth / (n * 2) + }; + + // Size and width can be null if the editor is not visible or + // detached from the document + if (size.width == 0 && size.height == 0) + return null; + + return size; + }; + + this.destroy = function() { + clearInterval(this.$pollSizeChangesTimer); + }; + +}).call(MeasureText.prototype); + +}); diff --git a/lib/ace/view/window_view.js b/lib/ace/view/window_view.js index 4452b7f1..c96be8fb 100644 --- a/lib/ace/view/window_view.js +++ b/lib/ace/view/window_view.js @@ -50,19 +50,18 @@ var TextLayer = require("ace/view/layer/text").Text; var CursorLayer = require("ace/view/layer/cursor").Cursor; var ScrollBar = require("ace/scrollbar").ScrollBar; var RenderLoop = require("ace/renderloop").RenderLoop; +var MeasureText = require("ace/view/measure_text").MeasureText; var EventEmitter = require("pilot/event_emitter").EventEmitter; var editorCss = require("text!ace/view/css/editor.css"); // import CSS once dom.importCssString(editorCss); -var WindowView = function(windowModel, container, theme) { +var WindowView = function(windowModel, container) { this.model = windowModel; this.container = container; dom.addCssClass(this.container, "ace_editor"); - this.setTheme(theme); - this.$gutter = dom.createElement("div"); this.$gutter.className = "ace_gutter"; this.container.appendChild(this.$gutter); @@ -83,8 +82,11 @@ var WindowView = function(windowModel, container, theme) { this.$markerFront = new MarkerLayer(windowModel, this.content); - this.characterWidth = textLayer.getCharacterWidth(); - this.lineHeight = textLayer.getLineHeight(); + var measureText = this.$measureText = new MeasureText(this.container, 300); + measureText.on("changeCharacterSize", function() { + windowModel.setComputedCharacterSize(measureText.getCharacterSize()); + }); + windowModel.setComputedCharacterSize(measureText.getCharacterSize()); this.$cursorLayer = new CursorLayer(windowModel, this.content); this.$cursorPadding = 8; @@ -95,16 +97,6 @@ var WindowView = function(windowModel, container, theme) { this.scrollBar.addEventListener("scroll", this.onScroll.bind(this)); this.scrollTop = 0; - - var _self = this; - this.$textLayer.addEventListener("changeCharaterSize", function() { - _self.characterWidth = textLayer.getCharacterWidth(); - _self.lineHeight = textLayer.getLineHeight(); - _self.updatePrintMargin(); - _self.onResize(true); - - _self.$loop.schedule(_self.CHANGE_FULL); - }); event.addListener(this.$gutter, "click", this.$onGutterClick.bind(this)); event.addListener(this.$gutter, "dblclick", this.$onGutterClick.bind(this)); @@ -114,6 +106,8 @@ var WindowView = function(windowModel, container, theme) { this.updatePadding(); this.updatePrintMargin(); this.updateHorizScroll(); + this.updateCharacterSize(); + this.updateTheme(); }; (function() { @@ -186,7 +180,7 @@ var WindowView = function(windowModel, container, theme) { * Triggers resize of the editor */ this.onResize = function(force) { - if (! this.scroller || !this.container) + if (!this.scroller || !this.container || !this.session) return; var changes = this.CHANGE_SIZE; @@ -323,6 +317,16 @@ var WindowView = function(windowModel, container, theme) { this.$loop.schedule(this.CHANGE_SCROLL); }; + this.updateCharacterSize = function() { + var size = this.$measureText.getCharacterSize(); + this.characterWidth = size.width; + this.lineHeight = size.height; + + this.updatePrintMargin(); + this.onResize(true); + this.$loop.schedule(this.CHANGE_FULL); + }; + this.onScroll = function(e) { this.scrollToY(e.data); }; @@ -708,38 +712,25 @@ var WindowView = function(windowModel, container, theme) { style.left = "-10000px"; }; - this.setTheme = function(theme) { - var _self = this; + this.updateTheme = function() { + var theme = this.model.theme.cssClass; + + if (this.$theme == theme) + return; + + if (this.$theme) + this.unsetStyle(this.$theme); - this.$themeValue = theme; - (function(next) { - if (!theme || typeof theme == "string") { - theme = theme || "ace/theme/textmate"; - require([theme], function(theme) { - next(theme); - }); - } else { - next(theme); - } - })(function next(theme) { - if (_self.$theme) - dom.removeCssClass(_self.container, _self.$theme); + this.$theme = theme; - _self.$theme = theme ? theme.cssClass : null; + if (this.$theme) + this.setStyle(this.$theme); - if (_self.$theme) - dom.addCssClass(_self.container, _self.$theme); - - // force re-measure of the gutter width - if (_self.model.size) { - _self.model.size.width = 0; - _self.onResize(); - } - }); - }; - - this.getTheme = function() { - return this.$themeValue; + // force re-measure of the gutter width + if (this.model.size) { + this.model.size.width = 0; + this.onResize(); + } }; // Methods allows to add / remove CSS classnames to the editor element. diff --git a/lib/ace/window_controller.js b/lib/ace/window_controller.js index 0db5c164..a6309fd2 100644 --- a/lib/ace/window_controller.js +++ b/lib/ace/window_controller.js @@ -46,6 +46,9 @@ var WindowController = exports.WindowController = function(model, view) { model.on("changeShowGutter", view.updateShowGutter.bind(view)); model.on("changePadding", view.updatePadding.bind(view)); model.on("changeHorizScroll", view.updateHorizScroll.bind(view)); + model.on("changeTheme", view.updateTheme.bind(view)); + model.on("changeCharacterSize", view.updateCharacterSize.bind(view)); + }; (function() {