summaryrefslogtreecommitdiff
path: root/widgets.js
diff options
context:
space:
mode:
authorjmoenig <jens@moenig.org>2013-03-16 09:02:16 +0100
committerjmoenig <jens@moenig.org>2013-03-16 09:02:16 +0100
commit0b510366d230399a38f18a8db92dd396a0dad42e (patch)
treeffb841d64bca04799ebc470701b71e59e726017e /widgets.js
downloadsnap-byow-0b510366d230399a38f18a8db92dd396a0dad42e.tar.gz
snap-byow-0b510366d230399a38f18a8db92dd396a0dad42e.zip
Creation
Diffstat (limited to 'widgets.js')
-rw-r--r--widgets.js2912
1 files changed, 2912 insertions, 0 deletions
diff --git a/widgets.js b/widgets.js
new file mode 100644
index 0000000..6740ef5
--- /dev/null
+++ b/widgets.js
@@ -0,0 +1,2912 @@
+/*
+
+ widgets.js
+
+ additional GUI elements for morphic.js
+
+ written by Jens Mšnig
+ jens@moenig.org
+
+ Copyright (C) 2013 by Jens Mšnig
+
+ This file is part of Snap!.
+
+ Snap! is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as
+ published by the Free Software Foundation, either version 3 of
+ the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+ prerequisites:
+ --------------
+ needs blocks.js and objects.js
+
+
+ I. hierarchy
+ -------------
+ the following tree lists all constructors hierarchically,
+ indentation indicating inheritance. Refer to this list to get a
+ contextual overview:
+
+ Morph*
+ AlignmentMorph
+ DialogBoxMorph
+ InputFieldMorph
+ TriggerMorph*
+ PushButtonMorph
+ ToggleButtonMorph
+ TabMorph
+ ToggleMorph
+ ToggleElementMorph
+
+ * from Morphic.js
+
+
+ II. toc
+ -------
+ the following list shows the order in which all constructors are
+ defined. Use this list to locate code in this document:
+
+ PushButtonMorph
+ ToggleButtonMorph
+ TabMorph
+ ToggleMorph
+ ToggleElementMorph
+ DialogBoxMorph
+ AlignmentMorph
+ InputFieldMorph
+
+*/
+
+// Global settings /////////////////////////////////////////////////////
+
+/*global TriggerMorph, modules, Color, Point, BoxMorph, radians,
+newCanvas, StringMorph, Morph, TextMorph, nop, detect, StringFieldMorph,
+HTMLCanvasElement, fontHeight, SymbolMorph, localize, SpeechBubbleMorph,
+ArrowMorph, MenuMorph, isString*/
+
+modules.widgets = '2013-February-26';
+
+var PushButtonMorph;
+var ToggleButtonMorph;
+var TabMorph;
+var ToggleMorph;
+var ToggleElementMorph;
+var DialogBoxMorph;
+var AlignmentMorph;
+var InputFieldMorph;
+
+// PushButtonMorph /////////////////////////////////////////////////////
+
+// I am a Button with rounded corners and 3D-ish graphical effects
+
+// PushButtonMorph inherits from TriggerMorph:
+
+PushButtonMorph.prototype = new TriggerMorph();
+PushButtonMorph.prototype.constructor = PushButtonMorph;
+PushButtonMorph.uber = TriggerMorph.prototype;
+
+// PushButtonMorph preferences settings:
+
+PushButtonMorph.prototype.fontSize = 10;
+PushButtonMorph.prototype.fontStyle = 'sans-serif';
+PushButtonMorph.prototype.labelColor = new Color(0, 0, 0);
+PushButtonMorph.prototype.labelShadowColor = new Color(255, 255, 255);
+PushButtonMorph.prototype.labelShadowOffset = new Point(1, 1);
+
+PushButtonMorph.prototype.color = new Color(220, 220, 220);
+PushButtonMorph.prototype.pressColor = new Color(115, 180, 240);
+PushButtonMorph.prototype.highlightColor
+ = PushButtonMorph.prototype.pressColor.lighter(50);
+PushButtonMorph.prototype.outlineColor = new Color(30, 30, 30);
+PushButtonMorph.prototype.outlineGradient = false;
+PushButtonMorph.prototype.contrast = 60;
+
+PushButtonMorph.prototype.edge = 2;
+PushButtonMorph.prototype.corner = 5;
+PushButtonMorph.prototype.outline = 1.00001;
+PushButtonMorph.prototype.padding = 3;
+
+// PushButtonMorph instance creation:
+
+function PushButtonMorph(
+ target,
+ action,
+ labelString,
+ environment,
+ hint,
+ template
+) {
+ this.init(
+ target,
+ action,
+ labelString,
+ environment,
+ hint,
+ template
+ );
+}
+
+PushButtonMorph.prototype.init = function (
+ target,
+ action,
+ labelString,
+ environment,
+ hint,
+ template
+) {
+ // additional properties:
+ this.target = target || null;
+ this.action = action || null;
+ this.environment = environment || null;
+ this.labelString = labelString || null;
+ this.label = null;
+ this.labelMinExtent = new Point(0, 0);
+ this.hint = hint || null;
+ this.template = template || null; // for pre-computed backbrounds
+ // if a template is specified, its background images are used as cache
+
+ // initialize inherited properties:
+ TriggerMorph.uber.init.call(this);
+
+ // override inherited properites:
+ this.color = PushButtonMorph.prototype.color;
+ this.drawNew();
+ this.fixLayout();
+};
+
+// PushButtonMorph layout:
+
+PushButtonMorph.prototype.fixLayout = function () {
+ // make sure I at least encompass my label
+ if (this.label !== null) {
+ var padding = this.padding * 2 + this.outline * 2 + this.edge * 2;
+ this.setExtent(new Point(
+ Math.max(this.label.width(), this.labelMinExtent.x) + padding,
+ Math.max(this.label instanceof StringMorph ?
+ this.label.rawHeight() :
+ this.label.height(), this.labelMinExtent.y) + padding
+ ));
+ this.label.setCenter(this.center());
+ }
+};
+
+// PushButtonMorph events
+
+PushButtonMorph.prototype.mouseDownLeft = function () {
+ PushButtonMorph.uber.mouseDownLeft.call(this);
+ if (this.label) {
+ this.label.setCenter(this.center().add(1));
+ }
+};
+
+PushButtonMorph.prototype.mouseClickLeft = function () {
+ PushButtonMorph.uber.mouseClickLeft.call(this);
+ if (this.label) {
+ this.label.setCenter(this.center());
+ }
+};
+
+PushButtonMorph.prototype.mouseLeave = function () {
+ PushButtonMorph.uber.mouseLeave.call(this);
+ if (this.label) {
+ this.label.setCenter(this.center());
+ }
+};
+
+// PushButtonMorph drawing:
+
+PushButtonMorph.prototype.outlinePath = BoxMorph.prototype.outlinePath;
+
+PushButtonMorph.prototype.drawOutline = function (context) {
+ var outlineStyle;
+
+ if (!this.outline) {return null; }
+ if (this.outlineGradient) {
+ outlineStyle = context.createLinearGradient(
+ 0,
+ 0,
+ 0,
+ this.height()
+ );
+ outlineStyle.addColorStop(1, 'white');
+ outlineStyle.addColorStop(0, this.outlineColor.darker().toString());
+ } else {
+ outlineStyle = this.outlineColor.toString();
+ }
+ context.fillStyle = outlineStyle;
+ context.beginPath();
+ this.outlinePath(
+ context,
+ this.corner,
+ 0
+ );
+ context.closePath();
+ context.fill();
+};
+
+PushButtonMorph.prototype.drawBackground = function (context, color) {
+ context.fillStyle = color.toString();
+ context.beginPath();
+ this.outlinePath(
+ context,
+ Math.max(this.corner - this.outline, 0),
+ this.outline
+ );
+ context.closePath();
+ context.fill();
+ context.lineWidth = this.outline;
+};
+
+PushButtonMorph.prototype.drawEdges = function (
+ context,
+ color,
+ topColor,
+ bottomColor
+) {
+ var minInset = Math.max(this.corner, this.outline + this.edge),
+ w = this.width(),
+ h = this.height(),
+ gradient;
+
+ // top:
+ gradient = context.createLinearGradient(
+ 0,
+ this.outline,
+ 0,
+ this.outline + this.edge
+ );
+ gradient.addColorStop(0, topColor.toString());
+ gradient.addColorStop(1, color.toString());
+
+ context.strokeStyle = gradient;
+ context.lineCap = 'round';
+ context.lineWidth = this.edge;
+ context.beginPath();
+ context.moveTo(minInset, this.outline + this.edge / 2);
+ context.lineTo(w - minInset, this.outline + this.edge / 2);
+ context.stroke();
+
+ // top-left corner:
+ gradient = context.createRadialGradient(
+ this.corner,
+ this.corner,
+ Math.max(this.corner - this.outline - this.edge, 0),
+ this.corner,
+ this.corner,
+ Math.max(this.corner - this.outline, 0)
+ );
+ gradient.addColorStop(1, topColor.toString());
+ gradient.addColorStop(0, color.toString());
+
+ context.strokeStyle = gradient;
+ context.lineCap = 'round';
+ context.lineWidth = this.edge;
+ context.beginPath();
+ context.arc(
+ this.corner,
+ this.corner,
+ Math.max(this.corner - this.outline - this.edge / 2, 0),
+ radians(180),
+ radians(270),
+ false
+ );
+ context.stroke();
+
+ // left:
+ gradient = context.createLinearGradient(
+ this.outline,
+ 0,
+ this.outline + this.edge,
+ 0
+ );
+ gradient.addColorStop(0, topColor.toString());
+ gradient.addColorStop(1, color.toString());
+
+ context.strokeStyle = gradient;
+ context.lineCap = 'round';
+ context.lineWidth = this.edge;
+ context.beginPath();
+ context.moveTo(this.outline + this.edge / 2, minInset);
+ context.lineTo(this.outline + this.edge / 2, h - minInset);
+ context.stroke();
+
+ // bottom:
+ gradient = context.createLinearGradient(
+ 0,
+ h - this.outline,
+ 0,
+ h - this.outline - this.edge
+ );
+ gradient.addColorStop(0, bottomColor.toString());
+ gradient.addColorStop(1, color.toString());
+
+ context.strokeStyle = gradient;
+ context.lineCap = 'round';
+ context.lineWidth = this.edge;
+ context.beginPath();
+ context.moveTo(minInset, h - this.outline - this.edge / 2);
+ context.lineTo(w - minInset, h - this.outline - this.edge / 2);
+ context.stroke();
+
+ // bottom-right corner:
+ gradient = context.createRadialGradient(
+ w - this.corner,
+ h - this.corner,
+ Math.max(this.corner - this.outline - this.edge, 0),
+ w - this.corner,
+ h - this.corner,
+ Math.max(this.corner - this.outline, 0)
+ );
+ gradient.addColorStop(1, bottomColor.toString());
+ gradient.addColorStop(0, color.toString());
+
+ context.strokeStyle = gradient;
+ context.lineCap = 'round';
+ context.lineWidth = this.edge;
+ context.beginPath();
+ context.arc(
+ w - this.corner,
+ h - this.corner,
+ Math.max(this.corner - this.outline - this.edge / 2, 0),
+ radians(0),
+ radians(90),
+ false
+ );
+ context.stroke();
+
+ // right:
+ gradient = context.createLinearGradient(
+ w - this.outline,
+ 0,
+ w - this.outline - this.edge,
+ 0
+ );
+ gradient.addColorStop(0, bottomColor.toString());
+ gradient.addColorStop(1, color.toString());
+
+ context.strokeStyle = gradient;
+ context.lineCap = 'round';
+ context.lineWidth = this.edge;
+ context.beginPath();
+ context.moveTo(w - this.outline - this.edge / 2, minInset);
+ context.lineTo(w - this.outline - this.edge / 2, h - minInset);
+ context.stroke();
+};
+
+PushButtonMorph.prototype.createBackgrounds = function () {
+ var context,
+ ext = this.extent();
+
+ if (this.template) { // take the backgrounds images from the template
+ this.image = this.template.image;
+ this.normalImage = this.template.normalImage;
+ this.highlightImage = this.template.highlightImage;
+ this.pressImage = this.template.pressImage;
+ return null;
+ }
+
+ this.normalImage = newCanvas(ext);
+ context = this.normalImage.getContext('2d');
+ this.drawOutline(context);
+ this.drawBackground(context, this.color);
+ this.drawEdges(
+ context,
+ this.color,
+ this.color.lighter(this.contrast),
+ this.color.darker(this.contrast)
+ );
+
+ this.highlightImage = newCanvas(ext);
+ context = this.highlightImage.getContext('2d');
+ this.drawOutline(context);
+ this.drawBackground(context, this.highlightColor);
+ this.drawEdges(
+ context,
+ this.highlightColor,
+ this.highlightColor.lighter(this.contrast),
+ this.highlightColor.darker(this.contrast)
+ );
+
+ this.pressImage = newCanvas(ext);
+ context = this.pressImage.getContext('2d');
+ this.drawOutline(context);
+ this.drawBackground(context, this.pressColor);
+ this.drawEdges(
+ context,
+ this.pressColor,
+ this.pressColor.darker(this.contrast),
+ this.pressColor.lighter(this.contrast)
+ );
+
+ this.image = this.normalImage;
+};
+
+PushButtonMorph.prototype.createLabel = function () {
+ if (this.label !== null) {
+ this.label.destroy();
+ }
+ if (this.labelString instanceof SymbolMorph) {
+ this.label = this.labelString.fullCopy();
+ this.label.shadowOffset = this.labelShadowOffset;
+ this.label.shadowColor = this.labelShadowColor;
+ this.label.color = this.labelColor;
+ this.label.drawNew();
+ } else {
+ this.label = new StringMorph(
+ localize(this.labelString),
+ this.fontSize,
+ this.fontStyle,
+ true,
+ false,
+ false,
+ this.labelShadowOffset,
+ this.labelShadowColor,
+ this.labelColor
+ );
+ }
+ this.add(this.label);
+};
+
+// ToggleButtonMorph ///////////////////////////////////////////////////////
+
+/*
+ I am a two-state PushButton. When my state is "true" I keep my "pressed"
+ background color. I can also be set to not auto-layout my bounds, in
+ which case my label will left-align.
+*/
+
+// ToggleButtonMorph inherits from PushButtonMorph:
+
+ToggleButtonMorph.prototype = new PushButtonMorph();
+ToggleButtonMorph.prototype.constructor = ToggleButtonMorph;
+ToggleButtonMorph.uber = PushButtonMorph.prototype;
+
+// ToggleButton settings
+
+ToggleButtonMorph.prototype.contrast = 30;
+
+// ToggleButtonMorph instance creation:
+
+function ToggleButtonMorph(
+ colors, // color overrides, <array>: [normal, highlight, pressed]
+ target,
+ action, // a toggle function
+ labelString,
+ query, // predicate/selector
+ environment,
+ hint,
+ template, // optional, for cached background images
+ minWidth, // <num> optional, if specified label will left-align
+ hasPreview, // <bool> show press color on left edge (e.g. category)
+ isPicture // treat label as picture, i.e. don't apply typography
+) {
+ this.init(
+ colors,
+ target,
+ action,
+ labelString,
+ query,
+ environment,
+ hint,
+ template,
+ minWidth,
+ hasPreview,
+ isPicture
+ );
+}
+
+ToggleButtonMorph.prototype.init = function (
+ colors,
+ target,
+ action,
+ labelString,
+ query,
+ environment,
+ hint,
+ template,
+ minWidth,
+ hasPreview,
+ isPicture
+) {
+ // additional properties:
+ this.state = false;
+ this.query = query || function () {return true; };
+ this.minWidth = minWidth || null;
+ this.hasPreview = hasPreview || false;
+ this.isPicture = isPicture || false;
+ this.trueStateLabel = null;
+
+ // initialize inherited properties:
+ ToggleButtonMorph.uber.init.call(
+ this,
+ target,
+ action,
+ labelString,
+ environment,
+ hint,
+ template
+ );
+
+ // override default colors if others are specified
+ if (colors) {
+ this.color = colors[0];
+ this.highlightColor = colors[1];
+ this.pressColor = colors[2];
+ }
+
+ this.refresh();
+ this.drawNew();
+};
+
+// ToggleButtonMorph events
+
+ToggleButtonMorph.prototype.mouseEnter = function () {
+ if (!this.state) {
+ this.image = this.highlightImage;
+ this.changed();
+ }
+ if (this.hint) {
+ this.bubbleHelp(this.hint);
+ }
+};
+
+ToggleButtonMorph.prototype.mouseLeave = function () {
+ if (!this.state) {
+ this.image = this.normalImage;
+ this.changed();
+ }
+ if (this.hint) {
+ this.world().hand.destroyTemporaries();
+ }
+};
+
+ToggleButtonMorph.prototype.mouseDownLeft = function () {
+ if (!this.state) {
+ this.image = this.pressImage;
+ this.changed();
+ }
+};
+
+ToggleButtonMorph.prototype.mouseClickLeft = function () {
+ if (!this.state) {
+ this.image = this.highlightImage;
+ this.changed();
+ }
+ this.trigger(); // allow me to be triggered again to force-update others
+};
+
+// ToggleButtonMorph action
+
+ToggleButtonMorph.prototype.trigger = function () {
+ ToggleButtonMorph.uber.trigger.call(this);
+ this.refresh();
+};
+
+ToggleButtonMorph.prototype.refresh = function () {
+/*
+ if query is a function:
+ execute the query with target as environment (can be null)
+ for lambdafied (inline) actions
+
+ else if query is a String:
+ treat it as function property of target and execute it
+ for selector-like queries
+*/
+ if (typeof this.query === 'function') {
+ this.state = this.query.call(this.target);
+ } else { // assume it's a String
+ this.state = this.target[this.query]();
+ }
+ if (this.state) {
+ this.image = this.pressImage;
+ if (this.trueStateLabel) {
+ this.label.hide();
+ this.trueStateLabel.show();
+ }
+ } else {
+ this.image = this.normalImage;
+ if (this.trueStateLabel) {
+ this.label.show();
+ this.trueStateLabel.hide();
+ }
+ }
+ this.changed();
+};
+
+// ToggleButtonMorph layout:
+
+ToggleButtonMorph.prototype.fixLayout = function () {
+ if (this.label !== null) {
+ var lw = Math.max(this.label.width(), this.labelMinExtent.x),
+ padding = this.padding * 2 + this.outline * 2 + this.edge * 2;
+ this.setExtent(new Point(
+ (this.minWidth ?
+ Math.max(this.minWidth, lw) + padding
+ : lw + padding),
+ Math.max(this.label instanceof StringMorph ?
+ this.label.rawHeight() :
+ this.label.height(), this.labelMinExtent.y) + padding
+ ));
+ this.label.setCenter(this.center());
+ if (this.trueStateLabel) {
+ this.trueStateLabel.setCenter(this.center());
+ }
+ if (this.minWidth) { // left-align along my corner
+ this.label.setLeft(
+ this.left()
+ + this.outline
+ + this.edge
+ + this.corner
+ + this.padding
+ );
+ }
+ }
+};
+
+// ToggleButtonMorph drawing
+
+ToggleButtonMorph.prototype.createBackgrounds = function () {
+/*
+ basically the same as inherited from PushButtonMorph, except for
+ not inverting the pressImage 3D-ish border (because it stays that way),
+ and optionally coloring the left edge in the press-color, previewing
+ the selection color (e.g. in the case of Scratch palette-category
+ selector. the latter is done in the drawEdges() method.
+*/
+ var context,
+ ext = this.extent();
+
+ if (this.template) { // take the backgrounds images from the template
+ this.image = this.template.image;
+ this.normalImage = this.template.normalImage;
+ this.highlightImage = this.template.highlightImage;
+ this.pressImage = this.template.pressImage;
+ return null;
+ }
+
+ this.normalImage = newCanvas(ext);
+ context = this.normalImage.getContext('2d');
+ this.drawOutline(context);
+ this.drawBackground(context, this.color);
+ this.drawEdges(
+ context,
+ this.color,
+ this.color.lighter(this.contrast),
+ this.color.darker(this.contrast)
+ );
+
+ this.highlightImage = newCanvas(ext);
+ context = this.highlightImage.getContext('2d');
+ this.drawOutline(context);
+ this.drawBackground(context, this.highlightColor);
+ this.drawEdges(
+ context,
+ this.highlightColor,
+ this.highlightColor.lighter(this.contrast),
+ this.highlightColor.darker(this.contrast)
+ );
+
+ // note: don't invert the 3D-ish edges for pressedImage, because
+ // it will stay that way, and should not look inverted (or should it?)
+ this.pressImage = newCanvas(ext);
+ context = this.pressImage.getContext('2d');
+ this.drawOutline(context);
+ this.drawBackground(context, this.pressColor);
+ this.drawEdges(
+ context,
+ this.pressColor,
+ this.pressColor.lighter(40),
+ this.pressColor.darker(40)
+ );
+
+ this.image = this.normalImage;
+};
+
+ToggleButtonMorph.prototype.drawEdges = function (
+ context,
+ color,
+ topColor,
+ bottomColor
+) {
+ var gradient;
+
+ ToggleButtonMorph.uber.drawEdges.call(
+ this,
+ context,
+ color,
+ topColor,
+ bottomColor
+ );
+
+ if (this.hasPreview) { // indicate the possible selection color
+ gradient = context.createLinearGradient(
+ 0,
+ 0,
+ this.corner,
+ 0
+ );
+ gradient.addColorStop(0, this.pressColor.lighter(40).toString());
+ gradient.addColorStop(1, this.pressColor.darker(40).toString());
+ context.fillStyle = gradient; // this.pressColor.toString();
+ context.beginPath();
+ this.previewPath(
+ context,
+ Math.max(this.corner - this.outline, 0),
+ this.outline
+ );
+ context.closePath();
+ context.fill();
+ }
+};
+
+ToggleButtonMorph.prototype.previewPath = function (context, radius, inset) {
+ var offset = radius + inset,
+ h = this.height();
+
+ // top left:
+ context.arc(
+ offset,
+ offset,
+ radius,
+ radians(-180),
+ radians(-90),
+ false
+ );
+ // bottom left:
+ context.arc(
+ offset,
+ h - offset,
+ radius,
+ radians(90),
+ radians(180),
+ false
+ );
+};
+
+ToggleButtonMorph.prototype.createLabel = function () {
+ if (this.label !== null) {
+ this.label.destroy();
+ }
+ if (this.trueStateLabel !== null) {
+ this.trueStateLabel.destroy();
+ }
+ if (this.labelString instanceof Array && this.labelString.length === 2) {
+ if (this.labelString[0] instanceof SymbolMorph) {
+ this.label = this.labelString[0].fullCopy();
+ this.trueStateLabel = this.labelString[1].fullCopy();
+ if (!this.isPicture) {
+ this.label.shadowOffset = this.labelShadowOffset;
+ this.label.shadowColor = this.labelShadowColor;
+ this.label.color = this.labelColor;
+ this.label.drawNew();
+
+ this.trueStateLabel.shadowOffset = this.labelShadowOffset;
+ this.trueStateLabel.shadowColor = this.labelShadowColor;
+ this.trueStateLabel.color = this.labelColor;
+ this.trueStateLabel.drawNew();
+ }
+ } else if (this.labelString[0] instanceof Morph) {
+ this.label = this.labelString[0].fullCopy();
+ this.trueStateLabel = this.labelString[1].fullCopy();
+ } else {
+ this.label = new StringMorph(
+ localize(this.labelString[0]),
+ this.fontSize,
+ this.fontStyle,
+ true,
+ false,
+ false,
+ this.labelShadowOffset,
+ this.labelShadowColor,
+ this.labelColor
+ );
+ this.trueStateLabel = new StringMorph(
+ localize(this.labelString[1]),
+ this.fontSize,
+ this.fontStyle,
+ true,
+ false,
+ false,
+ this.labelShadowOffset,
+ this.labelShadowColor,
+ this.labelColor
+ );
+ }
+ } else {
+ if (this.labelString instanceof SymbolMorph) {
+ this.label = this.labelString.fullCopy();
+ if (!this.isPicture) {
+ this.label.shadowOffset = this.labelShadowOffset;
+ this.label.shadowColor = this.labelShadowColor;
+ this.label.color = this.labelColor;
+ this.label.drawNew();
+ }
+ } else if (this.labelString instanceof Morph) {
+ this.label = this.labelString.fullCopy();
+ } else {
+ this.label = new StringMorph(
+ localize(this.labelString),
+ this.fontSize,
+ this.fontStyle,
+ true,
+ false,
+ false,
+ this.labelShadowOffset,
+ this.labelShadowColor,
+ this.labelColor
+ );
+ }
+ }
+ this.add(this.label);
+ if (this.trueStateLabel) {
+ this.add(this.trueStateLabel);
+ }
+};
+
+// ToggleButtonMorph hiding and showing:
+
+/*
+ override the inherited behavior to recursively hide/show all
+ children, so that my instances get restored correctly when
+ hiding/showing my parent.
+*/
+
+ToggleButtonMorph.prototype.hide = function () {
+ this.isVisible = false;
+ this.changed();
+};
+
+ToggleButtonMorph.prototype.show = function () {
+ this.isVisible = true;
+ this.changed();
+};
+
+// TabMorph ///////////////////////////////////////////////////////
+
+// TabMorph inherits from ToggleButtonMorph:
+
+TabMorph.prototype = new ToggleButtonMorph();
+TabMorph.prototype.constructor = TabMorph;
+TabMorph.uber = ToggleButtonMorph.prototype;
+
+// TabMorph instance creation:
+
+function TabMorph(
+ colors, // color overrides, <array>: [normal, highlight, pressed]
+ target,
+ action, // a toggle function
+ labelString,
+ query, // predicate/selector
+ environment,
+ hint
+) {
+ this.init(
+ colors,
+ target,
+ action,
+ labelString,
+ query,
+ environment,
+ hint
+ );
+}
+
+// TabMorph layout:
+
+TabMorph.prototype.fixLayout = function () {
+ if (this.label !== null) {
+ this.setExtent(new Point(
+ this.label.width()
+ + this.padding * 2
+ + this.corner * 3
+ + this.edge * 2,
+ (this.label instanceof StringMorph ?
+ this.label.rawHeight() : this.label.height())
+ + this.padding * 2
+ + this.edge
+ ));
+ this.label.setCenter(this.center());
+ }
+};
+
+// TabMorph action:
+
+TabMorph.prototype.refresh = function () {
+ if (this.state) { // bring to front
+ if (this.parent) {
+ this.parent.add(this);
+ }
+ }
+ TabMorph.uber.refresh.call(this);
+};
+
+// TabMorph drawing:
+
+TabMorph.prototype.drawBackground = function (context, color) {
+ var w = this.width(),
+ h = this.height(),
+ c = this.corner;
+
+ context.fillStyle = color.toString();
+ context.beginPath();
+ context.moveTo(0, h);
+ context.bezierCurveTo(c, h, c, 0, c * 2, 0);
+ context.lineTo(w - c * 2, 0);
+ context.bezierCurveTo(w - c, 0, w - c, h, w, h);
+ context.closePath();
+ context.fill();
+};
+
+TabMorph.prototype.drawOutline = function () {
+ nop();
+};
+
+TabMorph.prototype.drawEdges = function (
+ context,
+ color,
+ topColor,
+ bottomColor
+) {
+ var w = this.width(),
+ h = this.height(),
+ c = this.corner,
+ e = this.edge,
+ eh = e / 2,
+ gradient;
+
+ nop(color); // argument not needed here
+
+ gradient = context.createLinearGradient(0, 0, w, 0);
+ gradient.addColorStop(0, topColor.toString());
+ gradient.addColorStop(1, bottomColor.toString());
+
+ context.strokeStyle = gradient;
+ context.lineCap = 'round';
+ context.lineWidth = e;
+
+ context.beginPath();
+ context.moveTo(0, h + eh);
+ context.bezierCurveTo(c, h, c, 0, c * 2, eh);
+ context.lineTo(w - c * 2, eh);
+ context.bezierCurveTo(w - c, 0, w - c, h, w, h + eh);
+ context.stroke();
+};
+
+// ToggleMorph ///////////////////////////////////////////////////////
+
+/*
+ I am a PushButton which toggles a check mark ( becoming check box)
+ or a bullet (becoming a radio button). I can have both or either an
+ additional label and an additional pictogram, whereas the pictogram
+ can be either an instance of (any) Morph, in which case the pictogram
+ will be an interactive toggle itself or a Canvas, in which case it
+ is just going to be a picture.
+*/
+
+// ToggleMorph inherits from PushButtonMorph:
+
+ToggleMorph.prototype = new PushButtonMorph();
+ToggleMorph.prototype.constructor = ToggleMorph;
+ToggleMorph.uber = PushButtonMorph.prototype;
+
+// ToggleMorph instance creation:
+
+function ToggleMorph(
+ style, // 'checkbox' or 'radiobutton'
+ target,
+ action, // a toggle function
+ labelString,
+ query, // predicate/selector
+ environment,
+ hint,
+ template,
+ element, // optional Morph or Canvas to display
+ builder // method which constructs the element (only for Morphs)
+) {
+ this.init(
+ style,
+ target,
+ action,
+ labelString,
+ query,
+ environment,
+ hint,
+ template,
+ element,
+ builder
+ );
+}
+
+ToggleMorph.prototype.init = function (
+ style,
+ target,
+ action,
+ labelString,
+ query,
+ environment,
+ hint,
+ template,
+ element,
+ builder
+) {
+ // additional properties:
+ this.padding = 1;
+ style = style || 'checkbox';
+ this.corner = (style === 'checkbox' ?
+ 0 : fontHeight(this.fontSize) / 2 + this.outline + this.padding);
+ this.state = false;
+ this.query = query || function () {return true; };
+ this.tick = null;
+ this.captionString = labelString || null;
+ this.labelAlignment = 'right';
+ this.element = element || null;
+ this.builder = builder || null;
+ this.toggleElement = null;
+
+ // initialize inherited properties:
+ ToggleMorph.uber.init.call(
+ this,
+ target,
+ action,
+ (style === 'checkbox' ? '\u2713' : '\u25CF'),
+ environment,
+ hint,
+ template
+ );
+ this.refresh();
+ this.drawNew();
+};
+
+// ToggleMorph layout:
+
+ToggleMorph.prototype.fixLayout = function () {
+ var padding = this.padding * 2 + this.outline * 2,
+ y;
+ if (this.tick !== null) {
+ this.silentSetHeight(this.tick.rawHeight() + padding);
+ this.silentSetWidth(this.tick.width() + padding);
+
+ this.setExtent(new Point(
+ Math.max(this.width(), this.height()),
+ Math.max(this.width(), this.height())
+ ));
+ this.tick.setCenter(this.center());
+ }
+ if (this.state) {
+ this.tick.show();
+ } else {
+ this.tick.hide();
+ }
+ if (this.toggleElement && (this.labelAlignment === 'right')) {
+ y = this.top() + (this.height() - this.toggleElement.height()) / 2;
+ this.toggleElement.setPosition(new Point(
+ this.right() + padding,
+ y
+ ));
+ }
+ if (this.label !== null) {
+ y = this.top() + (this.height() - this.label.height()) / 2;
+ if (this.labelAlignment === 'right') {
+ this.label.setPosition(new Point(
+ this.toggleElement ?
+ this.toggleElement instanceof ToggleElementMorph ?
+ this.toggleElement.right()
+ : this.toggleElement.right() + padding
+ : this.right() + padding,
+ y
+ ));
+ } else {
+ this.label.setPosition(new Point(
+ this.left() - this.label.width() - padding,
+ y
+ ));
+ }
+ }
+};
+
+ToggleMorph.prototype.createLabel = function () {
+ if (this.label === null) {
+ if (this.captionString) {
+ this.label = new TextMorph(
+ localize(this.captionString),
+ this.fontSize,
+ this.fontStyle,
+ true
+ );
+ this.add(this.label);
+ }
+ }
+ if (this.tick === null) {
+ this.tick = new StringMorph(
+ localize(this.labelString),
+ this.fontSize,
+ this.fontStyle,
+ true,
+ false,
+ false,
+ new Point(1, 1),
+ new Color(240, 240, 240)
+ );
+ this.add(this.tick);
+ }
+ if (this.toggleElement === null) {
+ if (this.element) {
+ if (this.element instanceof Morph) {
+ this.toggleElement = new ToggleElementMorph(
+ this.target,
+ this.action,
+ this.element,
+ this.query,
+ this.environment,
+ this.hint,
+ this.builder
+ );
+ } else if (this.element instanceof HTMLCanvasElement) {
+ this.toggleElement = new Morph();
+ this.toggleElement.silentSetExtent(new Point(
+ this.element.width,
+ this.element.height
+ ));
+ this.toggleElement.image = this.element;
+ }
+ this.add(this.toggleElement);
+ }
+ }
+};
+
+// ToggleMorph action:
+
+ToggleMorph.prototype.trigger = function () {
+ ToggleMorph.uber.trigger.call(this);
+ this.refresh();
+};
+
+ToggleMorph.prototype.refresh = function () {
+ /*
+ if query is a function:
+ execute the query with target as environment (can be null)
+ for lambdafied (inline) actions
+
+ else if query is a String:
+ treat it as function property of target and execute it
+ for selector-like queries
+ */
+ if (typeof this.query === 'function') {
+ this.state = this.query.call(this.target);
+ } else { // assume it's a String
+ this.state = this.target[this.query]();
+ }
+ if (this.state) {
+ this.tick.show();
+ } else {
+ this.tick.hide();
+ }
+ if (this.toggleElement && this.toggleElement.refresh) {
+ this.toggleElement.refresh();
+ }
+};
+
+// ToggleMorph events
+
+ToggleMorph.prototype.mouseDownLeft = function () {
+ PushButtonMorph.uber.mouseDownLeft.call(this);
+ if (this.tick) {
+ this.tick.setCenter(this.center().add(1));
+ }
+};
+
+ToggleMorph.prototype.mouseClickLeft = function () {
+ PushButtonMorph.uber.mouseClickLeft.call(this);
+ if (this.tick) {
+ this.tick.setCenter(this.center());
+ }
+};
+
+ToggleMorph.prototype.mouseLeave = function () {
+ PushButtonMorph.uber.mouseLeave.call(this);
+ if (this.tick) {
+ this.tick.setCenter(this.center());
+ }
+};
+
+// ToggleMorph hiding and showing:
+
+/*
+ override the inherited behavior to recursively hide/show all
+ children, so that my instances get restored correctly when
+ hiding/showing my parent.
+*/
+
+ToggleMorph.prototype.hide = ToggleButtonMorph.prototype.hide;
+
+ToggleMorph.prototype.show = ToggleButtonMorph.prototype.show;
+
+// ToggleElementMorph /////////////////////////////////////////////////////
+/*
+ I am a picture of a Morph ("element") which acts as a toggle button.
+ I am different from ToggleButton in that I neither create a label nor
+ draw button outlines. Instead I display my element morph in specified
+ contrasts of a given color, symbolizing whether it is selected or not
+*/
+
+// ToggleElementMorph inherits from TriggerMorph:
+
+ToggleElementMorph.prototype = new TriggerMorph();
+ToggleElementMorph.prototype.constructor = ToggleElementMorph;
+ToggleElementMorph.uber = TriggerMorph.prototype;
+
+// ToggleElementMorph preferences settings
+
+ToggleElementMorph.prototype.contrast = 50;
+ToggleElementMorph.prototype.shadowOffset = new Point(2, 2);
+ToggleElementMorph.prototype.shadowAlpha = 0.6;
+ToggleElementMorph.prototype.fontSize = 10; // only for (optional) labels
+ToggleElementMorph.prototype.inactiveColor = new Color(180, 180, 180);
+
+// ToggleElementMorph instance creation:
+
+function ToggleElementMorph(
+ target,
+ action,
+ element,
+ query,
+ environment,
+ hint,
+ builder,
+ labelString
+) {
+ this.init(
+ target,
+ action,
+ element,
+ query,
+ environment,
+ hint,
+ builder,
+ labelString
+ );
+}
+
+ToggleElementMorph.prototype.init = function (
+ target,
+ action,
+ element, // mandatory
+ query,
+ environment,
+ hint,
+ builder, // optional function name that rebuilds the element
+ labelString
+) {
+ // additional properties:
+ this.target = target || null;
+ this.action = action || null;
+ this.element = element;
+ this.query = query || function () {return true; };
+ this.environment = environment || null;
+ this.hint = hint || null;
+ this.builder = builder || 'nop';
+ this.captionString = labelString || null;
+ this.labelAlignment = 'right';
+ this.state = false;
+
+ // initialize inherited properties:
+ TriggerMorph.uber.init.call(this);
+
+ // override inherited properties:
+ this.color = element.color;
+ this.createLabel();
+};
+
+// ToggleElementMorph drawing:
+
+ToggleElementMorph.prototype.createBackgrounds = function () {
+ this.color = this.element.color;
+ this.element.removeShadow();
+ this.element[this.builder]();
+ this.element.addShadow(this.shadowOffset, this.shadowAlpha);
+ this.silentSetExtent(this.element.fullBounds().extent()); // w/ shadow
+ this.pressImage = this.element.fullImage();
+
+ this.element.removeShadow();
+ this.element.setColor(this.inactiveColor);
+ this.element[this.builder](this.contrast);
+ this.element.addShadow(this.shadowOffset, 0);
+ this.normalImage = this.element.fullImage();
+
+ this.element.removeShadow();
+ this.element.setColor(this.color.lighter(this.contrast));
+ this.element[this.builder](this.contrast);
+ this.element.addShadow(this.shadowOffset, this.shadowAlpha);
+ this.highlightImage = this.element.fullImage();
+
+ this.element.removeShadow();
+ this.element.setColor(this.color);
+ this.element[this.builder]();
+ this.image = this.normalImage;
+};
+
+ToggleElementMorph.prototype.setColor = function (aColor) {
+ this.element.setColor(aColor);
+ this.createBackgrounds();
+ this.refresh();
+};
+
+// ToggleElementMorph layout:
+
+ToggleElementMorph.prototype.createLabel = function () {
+ var y;
+ if (this.captionString) {
+ this.label = new StringMorph(
+ this.captionString,
+ this.fontSize,
+ this.fontStyle,
+ true
+ );
+ this.add(this.label);
+ y = this.top() + (this.height() - this.label.height()) / 2;
+ if (this.labelAlignment === 'right') {
+ this.label.setPosition(new Point(
+ this.right(),
+ y
+ ));
+ } else {
+ this.label.setPosition(new Point(
+ this.left() - this.label.width(),
+ y
+ ));
+ }
+ }
+};
+
+// ToggleElementMorph action
+
+ToggleElementMorph.prototype.trigger
+ = ToggleButtonMorph.prototype.trigger;
+
+ToggleElementMorph.prototype.refresh
+ = ToggleButtonMorph.prototype.refresh;
+
+// ToggleElementMorph events
+
+ToggleElementMorph.prototype.mouseEnter
+ = ToggleButtonMorph.prototype.mouseEnter;
+
+ToggleElementMorph.prototype.mouseLeave
+ = ToggleButtonMorph.prototype.mouseLeave;
+
+ToggleElementMorph.prototype.mouseDownLeft
+ = ToggleButtonMorph.prototype.mouseDownLeft;
+
+ToggleElementMorph.prototype.mouseClickLeft
+ = ToggleButtonMorph.prototype.mouseClickLeft;
+
+// DialogBoxMorph /////////////////////////////////////////////////////
+
+// I am a DialogBox frame
+
+// DialogBoxMorph inherits from Morph:
+
+DialogBoxMorph.prototype = new Morph();
+DialogBoxMorph.prototype.constructor = DialogBoxMorph;
+DialogBoxMorph.uber = Morph.prototype;
+
+// DialogBoxMorph preferences settings:
+
+DialogBoxMorph.prototype.fontSize = 12;
+DialogBoxMorph.prototype.titleFontSize = 14;
+DialogBoxMorph.prototype.fontStyle = 'sans-serif';
+
+DialogBoxMorph.prototype.color = PushButtonMorph.prototype.color;
+DialogBoxMorph.prototype.titleTextColor = new Color(255, 255, 255);
+DialogBoxMorph.prototype.titleBarColor
+ = PushButtonMorph.prototype.pressColor;
+
+DialogBoxMorph.prototype.contrast = 40;
+
+DialogBoxMorph.prototype.corner = 12;
+DialogBoxMorph.prototype.padding = 14;
+DialogBoxMorph.prototype.titlePadding = 6;
+
+DialogBoxMorph.prototype.buttonContrast = 50;
+DialogBoxMorph.prototype.buttonFontSize = 12;
+DialogBoxMorph.prototype.buttonCorner = 12;
+DialogBoxMorph.prototype.buttonEdge = 6;
+DialogBoxMorph.prototype.buttonPadding = 0;
+DialogBoxMorph.prototype.buttonOutline = 3;
+DialogBoxMorph.prototype.buttonOutlineColor
+ = PushButtonMorph.prototype.color;
+DialogBoxMorph.prototype.buttonOutlineGradient = true;
+
+
+// DialogBoxMorph instance creation:
+
+function DialogBoxMorph(target, action, environment) {
+ this.init(target, action, environment);
+}
+
+DialogBoxMorph.prototype.init = function (target, action, environment) {
+ // additional properties:
+ this.target = target || null;
+ this.action = action || null;
+ this.environment = environment || null;
+
+ this.labelString = null;
+ this.label = null;
+ this.head = null;
+ this.body = null;
+ this.buttons = null;
+
+ // initialize inherited properties:
+ DialogBoxMorph.uber.init.call(this);
+
+ // override inherited properites:
+ this.isDraggable = true;
+ this.color = PushButtonMorph.prototype.color;
+ this.createLabel();
+ this.createButtons();
+ this.setExtent(new Point(300, 150));
+};
+
+// DialogBoxMorph ops
+DialogBoxMorph.prototype.inform = function (
+ title,
+ textString,
+ world,
+ pic
+) {
+ var txt = new TextMorph(
+ textString,
+ this.fontSize,
+ this.fontStyle,
+ true,
+ false,
+ 'center',
+ null,
+ null,
+ new Point(1, 1),
+ new Color(255, 255, 255)
+ );
+ this.labelString = title;
+ this.createLabel();
+ if (pic) {this.setPicture(pic); }
+ if (textString) {
+ this.addBody(txt);
+ }
+ this.addButton('ok', 'OK');
+ this.drawNew();
+ this.fixLayout();
+ if (world) {
+ world.add(this);
+ world.keyboardReceiver = this;
+ this.setCenter(world.center());
+ }
+};
+
+DialogBoxMorph.prototype.askYesNo = function (
+ title,
+ textString,
+ world,
+ pic
+) {
+ var txt = new TextMorph(
+ textString,
+ this.fontSize,
+ this.fontStyle,
+ true,
+ false,
+ 'center',
+ null,
+ null,
+ new Point(1, 1),
+ new Color(255, 255, 255)
+ );
+ this.labelString = title;
+ this.createLabel();
+ if (pic) {this.setPicture(pic); }
+ this.addBody(txt);
+ this.addButton('ok', 'Yes');
+ this.addButton('cancel', 'No');
+ this.fixLayout();
+ this.drawNew();
+ this.fixLayout();
+ if (world) {
+ world.add(this);
+ world.keyboardReceiver = this;
+ this.setCenter(world.center());
+ }
+};
+
+DialogBoxMorph.prototype.prompt = function (
+ title,
+ defaultString,
+ world,
+ pic,
+ choices, // optional dictionary for drop-down of choices
+ isReadOnly // optional when using choices
+) {
+ var txt = new InputFieldMorph(
+ defaultString,
+ false, // numeric?
+ choices || null, // drop-down dict, optional
+ choices ? isReadOnly || false : false
+ );
+ txt.setWidth(250);
+ this.labelString = title;
+ this.createLabel();
+ if (pic) {this.setPicture(pic); }
+ this.addBody(txt);
+ txt.drawNew();
+ this.addButton('ok', 'OK');
+ this.addButton('cancel', 'Cancel');
+ this.fixLayout();
+ this.drawNew();
+ this.fixLayout();
+ if (world) {
+ world.add(this);
+ this.setCenter(world.center());
+ this.edit();
+ }
+};
+
+DialogBoxMorph.prototype.promptCredentials = function (
+ title,
+ purpose,
+ tosURL,
+ tosLabel,
+ prvURL,
+ prvLabel,
+ checkBoxLabel,
+ world,
+ pic,
+ msg
+) {
+ var usr = new InputFieldMorph(),
+ bmn,
+ byr,
+ emlLabel,
+ eml = new InputFieldMorph(),
+ pw1 = new InputFieldMorph(),
+ pw2 = new InputFieldMorph(),
+ opw = new InputFieldMorph(),
+ agree = false,
+ chk,
+ dof = new AlignmentMorph('row', 4),
+ mCol = new AlignmentMorph('column', 2),
+ yCol = new AlignmentMorph('column', 2),
+ inp = new AlignmentMorph('column', 2),
+ lnk = new AlignmentMorph('row', 4),
+ bdy = new AlignmentMorph('column', this.padding),
+ years = {},
+ currentYear = new Date().getFullYear(),
+ firstYear = currentYear - 20,
+ myself = this;
+
+ function labelText(string) {
+ return new TextMorph(
+ string,
+ 10,
+ null, // style
+ false, // bold
+ null, // italic
+ null, // alignment
+ null, // width
+ null, // font name
+ new Point(1, 1), // shadow offset
+ new Color(255, 255, 255) // shadowColor
+ );
+ }
+
+ function linkButton(label, url) {
+ var btn = new PushButtonMorph(
+ myself,
+ function () {
+ window.open(url);
+ },
+ ' ' + localize(label) + ' '
+ );
+ btn.fontSize = 10;
+ btn.corner = myself.buttonCorner;
+ btn.edge = myself.buttonEdge;
+ btn.outline = myself.buttonOutline;
+ btn.outlineColor = myself.buttonOutlineColor;
+ btn.outlineGradient = myself.buttonOutlineGradient;
+ btn.padding = myself.buttonPadding;
+ btn.contrast = myself.buttonContrast;
+ btn.drawNew();
+ btn.fixLayout();
+ return btn;
+ }
+
+ function age() {
+ var today = new Date().getFullYear() + new Date().getMonth() / 12,
+ year = +byr.getValue() || 0,
+ monthName = bmn.getValue(),
+ month,
+ birthday;
+ if (monthName instanceof Array) { // translatable
+ monthName = monthName[0];
+ }
+ if (isNaN(year)) {
+ year = 0;
+ }
+ month = [
+ 'January',
+ 'February',
+ 'March',
+ 'April',
+ 'May',
+ 'June',
+ 'July',
+ 'August',
+ 'September',
+ 'October',
+ 'November',
+ 'December'
+ ].indexOf(monthName);
+ if (isNaN(month)) {
+ month = 0;
+ }
+ birthday = year + month / 12;
+ return today - birthday;
+ }
+
+ bmn = new InputFieldMorph(
+ null, // text
+ false, // numeric?
+ {
+ 'January' : ['January'],
+ 'February' : ['February'],
+ 'March' : ['March'],
+ 'April' : ['April'],
+ 'May' : ['May'],
+ 'June' : ['June'],
+ 'July' : ['July'],
+ 'August' : ['August'],
+ 'September' : ['September'],
+ 'October' : ['October'],
+ 'November' : ['November'],
+ 'December' : ['December']
+ },
+ true // read-only
+ );
+ for (currentYear; currentYear > firstYear; currentYear -= 1) {
+ years[currentYear.toString() + ' '] = currentYear;
+ }
+ years[firstYear + ' or before'] = '< ' + currentYear;
+ byr = new InputFieldMorph(
+ null, // text
+ false, // numeric?
+ years,
+ true // read-only
+ );
+
+ inp.alignment = 'left';
+ inp.setColor(this.color);
+ bdy.setColor(this.color);
+
+ mCol.alignment = 'left';
+ mCol.setColor(this.color);
+ yCol.alignment = 'left';
+ yCol.setColor(this.color);
+
+ usr.setWidth(200);
+ bmn.setWidth(100);
+ byr.contents().minWidth = 80;
+ byr.setWidth(80);
+ eml.setWidth(200);
+ pw1.setWidth(200);
+ pw2.setWidth(200);
+ opw.setWidth(200);
+ pw1.contents().text.toggleIsPassword();
+ pw2.contents().text.toggleIsPassword();
+ opw.contents().text.toggleIsPassword();
+
+ if (purpose === 'login') {
+ inp.add(labelText('User name:'));
+ inp.add(usr);
+ }
+
+ if (purpose === 'signup') {
+ inp.add(labelText('User name:'));
+ inp.add(usr);
+ mCol.add(labelText('Birth date:'));
+ mCol.add(bmn);
+ yCol.add(labelText('year:'));
+ yCol.add(byr);
+ dof.add(mCol);
+ dof.add(yCol);
+ inp.add(dof);
+ inp.add(emlLabel = labelText('foo'));
+ inp.add(eml);
+ }
+
+ if (purpose === 'login') {
+ inp.add(labelText('Password:'));
+ inp.add(pw1);
+ }
+
+ if (purpose === 'changePassword') {
+ inp.add(labelText('Old password:'));
+ inp.add(opw);
+ inp.add(labelText('New password:'));
+ inp.add(pw1);
+ inp.add(labelText('Repeat new password:'));
+ inp.add(pw2);
+ }
+
+ if (msg) {
+ bdy.add(labelText(msg));
+ }
+
+ bdy.add(inp);
+
+ if (tosURL || prvURL) {
+ bdy.add(lnk);
+ }
+ if (tosURL) {
+ lnk.add(linkButton(tosLabel, tosURL));
+ }
+ if (prvURL) {
+ lnk.add(linkButton(prvLabel, prvURL));
+ }
+
+ if (checkBoxLabel) {
+ chk = new ToggleMorph(
+ 'checkbox',
+ this,
+ function () {agree = !agree; }, // action,
+ checkBoxLabel,
+ function () {return agree; } //query
+ );
+ chk.edge = this.buttonEdge / 2;
+ chk.outline = this.buttonOutline / 2;
+ chk.outlineColor = this.buttonOutlineColor;
+ chk.outlineGradient = this.buttonOutlineGradient;
+ chk.contrast = this.buttonContrast;
+ chk.drawNew();
+ chk.fixLayout();
+ bdy.add(chk);
+ }
+
+ dof.fixLayout();
+ mCol.fixLayout();
+ yCol.fixLayout();
+ inp.fixLayout();
+ lnk.fixLayout();
+ bdy.fixLayout();
+
+ this.labelString = title;
+ this.createLabel();
+ if (pic) {this.setPicture(pic); }
+
+ this.addBody(bdy);
+
+ usr.drawNew();
+ dof.drawNew();
+ mCol.drawNew();
+ bmn.drawNew();
+ yCol.drawNew();
+ byr.drawNew();
+ pw1.drawNew();
+ pw2.drawNew();
+ opw.drawNew();
+ eml.drawNew();
+ bdy.fixLayout();
+
+ this.addButton('ok', 'OK');
+ this.addButton('cancel', 'Cancel');
+ this.fixLayout();
+ this.drawNew();
+ this.fixLayout();
+
+ function validInputs() {
+ var checklist,
+ empty,
+ em = eml.getValue();
+
+ function indicate(morph, string) {
+ var bubble = new SpeechBubbleMorph(string);
+ bubble.isPointingRight = false;
+ bubble.drawNew();
+ bubble.popUp(
+ world,
+ morph.leftCenter().subtract(new Point(bubble.width() + 2, 0))
+ );
+ if (morph.edit) {
+ morph.edit();
+ }
+ }
+
+ if (purpose === 'login') {
+ checklist = [usr, pw1];
+ } else if (purpose === 'signup') {
+ checklist = [usr, bmn, byr, eml];
+ } else if (purpose === 'changePassword') {
+ checklist = [opw, pw1, pw2];
+ }
+
+ empty = detect(
+ checklist,
+ function (inp) {
+ return !inp.getValue();
+ }
+ );
+ if (empty) {
+ indicate(empty, 'please fill out\nthis field');
+ return false;
+ }
+ if (purpose === 'signup') {
+ if (usr.getValue().length < 4) {
+ indicate(usr, 'User name must be four\ncharacters or longer');
+ return false;
+ }
+ if (em.indexOf(' ') > -1 || em.indexOf('@') === -1
+ || em.indexOf('.') === -1) {
+ indicate(eml, 'please provide a valid\nemail address');
+ return false;
+ }
+ }
+ if (purpose === 'changePassword') {
+ if (pw1.getValue().length < 6) {
+ indicate(pw1, 'password must be six\ncharacters or longer');
+ return false;
+ }
+ if (pw1.getValue() !== pw2.getValue()) {
+ indicate(pw2, 'passwords do\nnot match');
+ return false;
+ }
+ }
+ if (purpose === 'signup') {
+ if (!agree) {
+ indicate(chk, 'please agree to\nthe TOS');
+ return false;
+ }
+ }
+ return true;
+ }
+
+ this.accept = function () {
+ if (validInputs()) {
+ DialogBoxMorph.prototype.accept.call(myself);
+ }
+ };
+
+ this.edit = function () {
+ if (purpose === 'changePassword') {
+ opw.edit();
+ } else { // 'signup', 'login'
+ usr.edit();
+ }
+ };
+
+ this.getInput = function () {
+ return {
+ username: usr.getValue(),
+ email: eml.getValue(),
+ oldpassword: opw.getValue(),
+ password: pw1.getValue(),
+ choice: agree
+ };
+ };
+
+ this.reactToChoice = function () {
+ if (purpose === 'signup') {
+ emlLabel.changed();
+ emlLabel.text = age() <= 13 ?
+ 'E-mail address of parent or guardian:'
+ : 'E-mail address:';
+ emlLabel.drawNew();
+ emlLabel.changed();
+ }
+ };
+
+ this.reactToChoice(); // initialize e-mail label
+
+ if (world) {
+ world.add(this);
+ this.setCenter(world.center());
+ this.edit();
+ }
+};
+
+DialogBoxMorph.prototype.accept = function () {
+ /*
+ if target is a function, use it as callback:
+ execute target as callback function with action as argument
+ in the environment as optionally specified.
+ Note: if action is also a function, instead of becoming
+ the argument itself it will be called to answer the argument.
+ for selections, Yes/No Choices etc:
+
+ else (if target is not a function):
+
+ if action is a function:
+ execute the action with target as environment (can be null)
+ for lambdafied (inline) actions
+
+ else if action is a String:
+ treat it as function property of target and execute it
+ for selector-like actions
+ */
+ if (this.action) {
+ if (typeof this.target === 'function') {
+ if (typeof this.action === 'function') {
+ this.target.call(this.environment, this.action.call());
+ } else {
+ this.target.call(this.environment, this.action);
+ }
+ } else {
+ if (typeof this.action === 'function') {
+ this.action.call(this.target, this.getInput());
+ } else { // assume it's a String
+ this.target[this.action](this.getInput());
+ }
+ }
+ }
+ this.destroy();
+};
+
+DialogBoxMorph.prototype.ok = function () {
+ this.accept();
+};
+
+DialogBoxMorph.prototype.cancel = function () {
+ this.destroy();
+};
+
+DialogBoxMorph.prototype.edit = function () {
+ this.children.forEach(function (c) {
+ if (c.edit) {
+ return c.edit();
+ }
+ });
+};
+
+DialogBoxMorph.prototype.getInput = function () {
+ if (this.body instanceof InputFieldMorph) {
+ return this.body.getValue();
+ }
+ return null;
+};
+
+DialogBoxMorph.prototype.justDropped = function (hand) {
+ hand.world.keyboardReceiver = this;
+ this.edit();
+};
+
+DialogBoxMorph.prototype.destroy = function () {
+ var world = this.world();
+ world.keyboardReceiver = null;
+ world.hand.destroyTemporaries();
+ DialogBoxMorph.uber.destroy.call(this);
+};
+
+DialogBoxMorph.prototype.normalizeSpaces = function (string) {
+ var ans = '', i, c, flag = false;
+
+ for (i = 0; i < string.length; i += 1) {
+ c = string[i];
+ if (c === ' ') {
+ if (flag) {
+ ans += c;
+ flag = false;
+ }
+ } else {
+ ans += c;
+ flag = true;
+ }
+ }
+ return ans.trim();
+};
+
+// DialogBoxMorph submorph construction
+
+DialogBoxMorph.prototype.createLabel = function () {
+ if (this.label) {
+ this.label.destroy();
+ }
+ if (this.labelString) {
+ this.label = new StringMorph(
+ localize(this.labelString),
+ this.titleFontSize,
+ this.fontStyle,
+ true,
+ false,
+ false,
+ new Point(2, 1),
+ this.titleBarColor.darker(this.contrast)
+ );
+ this.label.color = this.titleTextColor;
+ this.label.drawNew();
+ this.add(this.label);
+ }
+};
+
+DialogBoxMorph.prototype.createButtons = function () {
+ if (this.buttons) {
+ this.buttons.destroy();
+ }
+ this.buttons = new AlignmentMorph('row', this.padding);
+ this.add(this.buttons);
+};
+
+DialogBoxMorph.prototype.addButton = function (action, label) {
+ var button = new PushButtonMorph(
+ this,
+ action || 'ok',
+ ' ' + localize((label || 'OK')) + ' '
+ );
+ button.fontSize = this.buttonFontSize;
+ button.corner = this.buttonCorner;
+ button.edge = this.buttonEdge;
+ button.outline = this.buttonOutline;
+ button.outlineColor = this.buttonOutlineColor;
+ button.outlineGradient = this.buttonOutlineGradient;
+ button.padding = this.buttonPadding;
+ button.contrast = this.buttonContrast;
+ button.drawNew();
+ button.fixLayout();
+ this.buttons.add(button);
+ return button;
+};
+
+DialogBoxMorph.prototype.setPicture = function (aMorphOrCanvas) {
+ var morph;
+ if (aMorphOrCanvas instanceof Morph) {
+ morph = aMorphOrCanvas;
+ } else {
+ morph = new Morph();
+ morph.image = aMorphOrCanvas;
+ morph.silentSetWidth(aMorphOrCanvas.width);
+ morph.silentSetHeight(aMorphOrCanvas.height);
+ }
+ this.addHead(morph);
+};
+
+DialogBoxMorph.prototype.addHead = function (aMorph) {
+ if (this.head) {
+ this.head.destroy();
+ }
+ this.head = aMorph;
+ this.add(this.head);
+};
+
+DialogBoxMorph.prototype.addBody = function (aMorph) {
+ if (this.body) {
+ this.body.destroy();
+ }
+ this.body = aMorph;
+ this.add(this.body);
+};
+
+// DialogBoxMorph layout
+
+DialogBoxMorph.prototype.addShadow = function () {nop(); };
+DialogBoxMorph.prototype.removeShadow = function () {nop(); };
+
+DialogBoxMorph.prototype.fixLayout = function () {
+ var th = fontHeight(this.titleFontSize) + this.titlePadding * 2, w;
+
+ if (this.head) {
+ this.head.setPosition(this.position().add(new Point(
+ this.padding,
+ th + this.padding
+ )));
+ this.silentSetWidth(this.head.width() + this.padding * 2);
+ this.silentSetHeight(
+ this.head.height()
+ + this.padding * 2
+ + th
+ );
+ }
+
+ if (this.body) {
+ if (this.head) {
+ this.body.setPosition(this.head.bottomLeft().add(new Point(
+ 0,
+ this.padding
+ )));
+ this.silentSetWidth(Math.max(
+ this.width(),
+ this.body.width() + this.padding * 2
+ ));
+ this.silentSetHeight(
+ this.height()
+ + this.body.height()
+ + this.padding
+ );
+ w = this.width();
+ this.head.setLeft(
+ this.left()
+ + Math.round((w - this.head.width()) / 2)
+ );
+ this.body.setLeft(
+ this.left()
+ + Math.round((w - this.body.width()) / 2)
+ );
+ } else {
+ this.body.setPosition(this.position().add(new Point(
+ this.padding,
+ th + this.padding
+ )));
+ this.silentSetWidth(this.body.width() + this.padding * 2);
+ this.silentSetHeight(
+ this.body.height()
+ + this.padding * 2
+ + th
+ );
+ }
+ }
+
+ if (this.label) {
+ this.label.setCenter(this.center());
+ this.label.setTop(this.top() + (th - this.label.height()) / 2);
+ }
+
+ if (this.buttons && (this.buttons.children.length > 0)) {
+ this.buttons.fixLayout();
+ this.silentSetHeight(
+ this.height()
+ + this.buttons.height()
+ + this.padding
+ );
+ this.buttons.setCenter(this.center());
+ this.buttons.setBottom(this.bottom() - this.padding);
+ }
+};
+
+// DialogBoxMorph shadow
+
+/*
+ only take the 'plain' image, so the box rounding doesn't become
+ conflicted by the scrolling scripts pane
+*/
+
+DialogBoxMorph.prototype.shadowImage = function (off, color) {
+ // fallback for Windows Chrome-Shadow bug
+ var fb, img, outline, sha, ctx,
+ offset = off || new Point(7, 7),
+ clr = color || new Color(0, 0, 0);
+ fb = this.extent();
+ img = this.image;
+ outline = newCanvas(fb);
+ ctx = outline.getContext('2d');
+ ctx.drawImage(img, 0, 0);
+ ctx.globalCompositeOperation = 'destination-out';
+ ctx.drawImage(
+ img,
+ -offset.x,
+ -offset.y
+ );
+ sha = newCanvas(fb);
+ ctx = sha.getContext('2d');
+ ctx.drawImage(outline, 0, 0);
+ ctx.globalCompositeOperation = 'source-atop';
+ ctx.fillStyle = clr.toString();
+ ctx.fillRect(0, 0, fb.x, fb.y);
+ return sha;
+};
+
+DialogBoxMorph.prototype.shadowImageBlurred = function (off, color) {
+ var fb, img, sha, ctx,
+ offset = off || new Point(7, 7),
+ blur = this.shadowBlur,
+ clr = color || new Color(0, 0, 0);
+ fb = this.extent().add(blur * 2);
+ img = this.image;
+ sha = newCanvas(fb);
+ ctx = sha.getContext('2d');
+ ctx.shadowOffsetX = offset.x;
+ ctx.shadowOffsetY = offset.y;
+ ctx.shadowBlur = blur;
+ ctx.shadowColor = clr.toString();
+ ctx.drawImage(
+ img,
+ blur - offset.x,
+ blur - offset.y
+ );
+ ctx.shadowOffsetX = 0;
+ ctx.shadowOffsetY = 0;
+ ctx.shadowBlur = 0;
+ ctx.globalCompositeOperation = 'destination-out';
+ ctx.drawImage(
+ img,
+ blur - offset.x,
+ blur - offset.y
+ );
+ return sha;
+};
+
+// DialogBoxMorph keyboard events
+
+DialogBoxMorph.prototype.processKeyPress = function () {nop(); };
+
+DialogBoxMorph.prototype.processKeyDown = function (event) {
+ // this.inspectKeyEvent(event);
+ switch (event.keyCode) {
+ case 13:
+ this.ok();
+ break;
+ case 27:
+ this.cancel();
+ break;
+ default:
+ // this.inspectKeyEvent(event);
+ }
+};
+
+// DialogBoxMorph drawing
+
+DialogBoxMorph.prototype.drawNew = function () {
+ this.fullChanged();
+ Morph.prototype.trackChanges = false;
+ DialogBoxMorph.uber.removeShadow.call(this);
+ this.fixLayout();
+
+ var context,
+ gradient,
+ w = this.width(),
+ h = this.height(),
+ th = Math.floor(
+ fontHeight(this.titleFontSize) + this.titlePadding * 2
+ ),
+ shift = this.corner / 2,
+ x,
+ y;
+
+ this.image = newCanvas(this.extent());
+ context = this.image.getContext('2d');
+
+ // title bar
+ gradient = context.createLinearGradient(0, 0, 0, th);
+ gradient.addColorStop(
+ 0,
+ this.titleBarColor.lighter(this.contrast / 2).toString()
+ );
+ gradient.addColorStop(
+ 1,
+ this.titleBarColor.darker(this.contrast).toString()
+ );
+ context.fillStyle = gradient;
+ context.beginPath();
+ this.outlinePathTitle(
+ context,
+ this.corner
+ );
+ context.closePath();
+ context.fill();
+
+ // flat shape
+ // body
+ context.fillStyle = this.color.toString();
+ context.beginPath();
+ this.outlinePathBody(
+ context,
+ this.corner
+ );
+ context.closePath();
+ context.fill();
+
+ // 3D-effect
+ // bottom left corner
+ gradient = context.createLinearGradient(
+ 0,
+ h - this.corner,
+ 0,
+ h
+ );
+ gradient.addColorStop(0, this.color.toString());
+ gradient.addColorStop(1, this.color.darker(this.contrast.toString()));
+
+ context.lineWidth = this.corner;
+ context.lineCap = 'round';
+ context.strokeStyle = gradient;
+
+ context.beginPath();
+ context.moveTo(this.corner, h - shift);
+ context.lineTo(this.corner + 1, h - shift);
+ context.stroke();
+
+ // bottom edge
+ gradient = context.createLinearGradient(
+ 0,
+ h - this.corner,
+ 0,
+ h
+ );
+ gradient.addColorStop(0, this.color.toString());
+ gradient.addColorStop(1, this.color.darker(this.contrast.toString()));
+
+ context.lineWidth = this.corner;
+ context.lineCap = 'butt';
+ context.strokeStyle = gradient;
+
+ context.beginPath();
+ context.moveTo(this.corner, h - shift);
+ context.lineTo(w - this.corner, h - shift);
+ context.stroke();
+
+ // right body edge
+ gradient = context.createLinearGradient(
+ w - this.corner,
+ 0,
+ w,
+ 0
+ );
+ gradient.addColorStop(0, this.color.toString());
+ gradient.addColorStop(1, this.color.darker(this.contrast).toString());
+
+ context.lineWidth = this.corner;
+ context.lineCap = 'butt';
+ context.strokeStyle = gradient;
+
+ context.beginPath();
+ context.moveTo(w - shift, th);
+ context.lineTo(w - shift, h - this.corner);
+ context.stroke();
+
+ // bottom right corner
+ x = w - this.corner;
+ y = h - this.corner;
+
+ gradient = context.createRadialGradient(
+ x,
+ y,
+ 0,
+ x,
+ y,
+ this.corner
+ );
+ gradient.addColorStop(0, this.color.toString());
+ gradient.addColorStop(1, this.color.darker(this.contrast.toString()));
+
+ context.lineCap = 'butt';
+
+ context.strokeStyle = gradient;
+
+ context.beginPath();
+ context.arc(
+ x,
+ y,
+ shift,
+ radians(90),
+ radians(0),
+ true
+ );
+ context.stroke();
+
+ // left body edge
+ gradient = context.createLinearGradient(
+ 0,
+ 0,
+ this.corner,
+ 0
+ );
+ gradient.addColorStop(1, this.color.toString());
+ gradient.addColorStop(
+ 0,
+ this.color.lighter(this.contrast).toString()
+ );
+
+ context.lineCap = 'butt';
+ context.strokeStyle = gradient;
+
+ context.beginPath();
+ context.moveTo(shift, th);
+ context.lineTo(shift, h - this.corner * 2);
+ context.stroke();
+
+ // left vertical bottom corner
+ gradient = context.createLinearGradient(
+ 0,
+ 0,
+ this.corner,
+ 0
+ );
+ gradient.addColorStop(1, this.color.toString());
+ gradient.addColorStop(
+ 0,
+ this.color.lighter(this.contrast).toString()
+ );
+
+ context.lineCap = 'round';
+ context.strokeStyle = gradient;
+
+ context.beginPath();
+ context.moveTo(shift, h - this.corner * 2);
+ context.lineTo(shift, h - this.corner - shift);
+ context.stroke();
+
+ DialogBoxMorph.uber.addShadow.call(this);
+ Morph.prototype.trackChanges = true;
+ this.fullChanged();
+};
+
+DialogBoxMorph.prototype.outlinePathTitle = function (context, radius) {
+ var w = this.width(),
+ h = Math.ceil(fontHeight(this.titleFontSize)) + this.titlePadding * 2;
+
+ // top left:
+ context.arc(
+ radius,
+ radius,
+ radius,
+ radians(-180),
+ radians(-90),
+ false
+ );
+ // top right:
+ context.arc(
+ w - radius,
+ radius,
+ radius,
+ radians(-90),
+ radians(-0),
+ false
+ );
+ // bottom right:
+ context.lineTo(w, h);
+
+ // bottom left:
+ context.lineTo(0, h);
+};
+
+DialogBoxMorph.prototype.outlinePathBody = function (context, radius) {
+ var w = this.width(),
+ h = this.height(),
+ th = Math.floor(fontHeight(this.titleFontSize)) +
+ this.titlePadding * 2;
+
+ // top left:
+ context.moveTo(0, th);
+
+ // top right:
+ context.lineTo(w, th);
+
+ // bottom right:
+ context.arc(
+ w - radius,
+ h - radius,
+ radius,
+ radians(0),
+ radians(90),
+ false
+ );
+ // bottom left:
+ context.arc(
+ radius,
+ h - radius,
+ radius,
+ radians(90),
+ radians(180),
+ false
+ );
+};
+
+// AlignmentMorph /////////////////////////////////////////////////////
+
+// I am a reified layout, either a row or a column of submorphs
+
+// AlignmentMorph inherits from Morph:
+
+AlignmentMorph.prototype = new Morph();
+AlignmentMorph.prototype.constructor = AlignmentMorph;
+AlignmentMorph.uber = Morph.prototype;
+
+// AlignmentMorph instance creation:
+
+function AlignmentMorph(orientation, padding) {
+ this.init(orientation, padding);
+}
+
+AlignmentMorph.prototype.init = function (orientation, padding) {
+ // additional properties:
+ this.orientation = orientation || 'row'; // or 'column'
+ this.alignment = 'center'; // or 'left' in a column
+ this.padding = padding || 0;
+ this.respectHiddens = false;
+
+ // initialize inherited properties:
+ AlignmentMorph.uber.init.call(this);
+
+ // override inherited properites:
+};
+
+// AlignmentMorph displaying and layout
+
+AlignmentMorph.prototype.drawNew = function () {
+ this.image = newCanvas(new Point(1, 1));
+ this.fixLayout();
+};
+
+AlignmentMorph.prototype.fixLayout = function () {
+ var myself = this,
+ last = null,
+ newBounds;
+ if (this.children.length === 0) {
+ return null;
+ }
+ this.children.forEach(function (c) {
+ var cfb = c.fullBounds(),
+ lfb;
+ if (c.isVisible || myself.respectHiddens) {
+ if (last) {
+ lfb = last.fullBounds();
+ if (myself.orientation === 'row') {
+ c.setPosition(
+ lfb.topRight().add(new Point(
+ myself.padding,
+ (lfb.height() - cfb.height()) / 2
+ ))
+ );
+ } else { // orientation === 'column'
+ c.setPosition(
+ lfb.bottomLeft().add(new Point(
+ myself.alignment === 'center' ?
+ (lfb.width() - cfb.width()) / 2
+ : 0,
+ myself.padding
+ ))
+ );
+ }
+ newBounds = newBounds.merge(cfb);
+ } else {
+ newBounds = cfb;
+ }
+ last = c;
+ }
+ });
+ this.bounds = newBounds;
+};
+
+// InputFieldMorph //////////////////////////////////////////////////////
+
+// InputFieldMorph inherits from Morph:
+
+InputFieldMorph.prototype = new Morph();
+InputFieldMorph.prototype.constructor = InputFieldMorph;
+InputFieldMorph.uber = Morph.prototype;
+
+// InputFieldMorph settings
+
+InputFieldMorph.prototype.edge = 2;
+InputFieldMorph.prototype.fontSize = 12;
+InputFieldMorph.prototype.typeInPadding = 2;
+InputFieldMorph.prototype.contrast = 65;
+
+// InputFieldMorph instance creation:
+
+function InputFieldMorph(text, isNumeric, choiceDict, isReadOnly) {
+ this.init(text, isNumeric, choiceDict, isReadOnly);
+}
+
+InputFieldMorph.prototype.init = function (
+ text,
+ isNumeric,
+ choiceDict,
+ isReadOnly
+) {
+ var contents = new StringFieldMorph(text || ''),
+ arrow = new ArrowMorph(
+ 'down',
+ 0,
+ Math.max(Math.floor(this.fontSize / 6), 1)
+ );
+
+ this.choices = choiceDict || null; // object, function or selector
+ this.isReadOnly = isReadOnly || false;
+ this.isNumeric = isNumeric || false;
+
+ contents.alpha = 0;
+ contents.fontSize = this.fontSize;
+ contents.drawNew();
+
+ this.oldContentsExtent = contents.extent();
+ this.isNumeric = isNumeric || false;
+
+ InputFieldMorph.uber.init.call(this);
+ this.color = new Color(255, 255, 255);
+ this.add(contents);
+ this.add(arrow);
+ contents.isDraggable = false;
+ this.drawNew();
+};
+
+// InputFieldMorph accessing:
+
+InputFieldMorph.prototype.contents = function () {
+ return detect(
+ this.children,
+ function (child) {
+ return (child instanceof StringFieldMorph);
+ }
+ );
+};
+
+InputFieldMorph.prototype.arrow = function () {
+ return detect(
+ this.children,
+ function (child) {
+ return (child instanceof ArrowMorph);
+ }
+ );
+};
+
+InputFieldMorph.prototype.setChoice = function (aStringOrFloat) {
+ this.setContents(aStringOrFloat);
+ this.escalateEvent('reactToChoice', aStringOrFloat);
+};
+
+InputFieldMorph.prototype.setContents = function (aStringOrFloat) {
+ var cnts = this.contents();
+ cnts.text.text = aStringOrFloat;
+ if (aStringOrFloat === undefined) {
+ return null;
+ }
+ if (aStringOrFloat === null) {
+ cnts.text.text = '';
+ } else if (aStringOrFloat.toString) {
+ cnts.text.text = aStringOrFloat.toString();
+ }
+ cnts.drawNew();
+ cnts.changed();
+};
+
+InputFieldMorph.prototype.edit = function () {
+ var c = this.contents();
+ c.text.edit();
+ c.text.selectAll();
+};
+
+InputFieldMorph.prototype.setIsNumeric = function (bool) {
+ var value;
+
+ this.isNumeric = bool;
+ this.contents().isNumeric = bool;
+ this.contents().text.isNumeric = bool;
+
+ // adjust my shown value to conform with the numeric flag
+ value = this.getValue();
+ if (this.isNumeric) {
+ value = parseFloat(value);
+ if (isNaN(value)) {
+ value = null;
+ }
+ }
+ this.setContents(value);
+};
+
+// InputFieldMorph drop-down menu:
+
+InputFieldMorph.prototype.dropDownMenu = function () {
+ var choices = this.choices,
+ key,
+ menu = new MenuMorph(
+ this.setChoice,
+ null,
+ this,
+ this.fontSize
+ );
+
+ if (choices instanceof Function) {
+ choices = choices.call(this);
+ } else if (isString(choices)) {
+ choices = this[choices]();
+ }
+ if (!choices) {
+ return null;
+ }
+ menu.addItem(' ', null);
+ for (key in choices) {
+ if (choices.hasOwnProperty(key)) {
+ if (key[0] === '~') {
+ menu.addLine();
+ } else {
+ menu.addItem(key, choices[key]);
+ }
+ }
+ }
+ if (menu.items.length > 0) {
+ menu.popUpAtHand(this.world());
+ } else {
+ return null;
+ }
+};
+
+// InputFieldMorph layout:
+
+InputFieldMorph.prototype.fixLayout = function () {
+ var contents = this.contents(),
+ arrow = this.arrow();
+
+ if (!contents) {return null; }
+ contents.isNumeric = this.isNumeric;
+ contents.isEditable = (!this.isReadOnly);
+ if (this.choices) {
+ arrow.setSize(this.fontSize);
+ arrow.show();
+ } else {
+ arrow.setSize(0);
+ arrow.hide();
+ }
+ this.silentSetHeight(
+ contents.height()
+ + this.edge * 2
+ + this.typeInPadding * 2
+ );
+ this.silentSetWidth(Math.max(
+ contents.minWidth
+ + this.edge * 2
+ + this.typeInPadding * 2,
+ this.width()
+ ));
+
+ contents.setWidth(
+ this.width() - this.edge - this.typeInPadding -
+ (this.choices ? arrow.width() + this.typeInPadding : 0)
+ );
+
+ contents.silentSetPosition(new Point(
+ this.edge,
+ this.edge
+ ).add(this.typeInPadding).add(this.position()));
+
+ arrow.silentSetPosition(new Point(
+ this.right() - arrow.width() - this.edge,
+ contents.top()
+ ));
+
+};
+
+// InputFieldMorph events:
+
+InputFieldMorph.prototype.mouseClickLeft = function (pos) {
+ if (this.arrow().bounds.containsPoint(pos)) {
+ this.dropDownMenu();
+ } else if (this.isReadOnly) {
+ this.dropDownMenu();
+ } else {
+ this.escalateEvent('mouseClickLeft', pos);
+ }
+};
+
+// InputFieldMorph retrieving:
+
+InputFieldMorph.prototype.getValue = function () {
+/*
+ answer my content's text string. If I am numerical convert that
+ string to a number. If the conversion fails answer the string
+ otherwise the numerical value.
+*/
+ var num,
+ contents = this.contents();
+ if (this.isNumeric) {
+ num = parseFloat(contents.text);
+ if (!isNaN(num)) {
+ return num;
+ }
+ }
+ return this.normalizeSpaces(contents.string());
+};
+
+InputFieldMorph.prototype.normalizeSpaces
+ = DialogBoxMorph.prototype.normalizeSpaces;
+
+// InputFieldMorph drawing:
+
+InputFieldMorph.prototype.drawNew = function () {
+ var context, borderColor;
+
+ this.fixLayout();
+
+ // initialize my surface property
+ this.image = newCanvas(this.extent());
+ context = this.image.getContext('2d');
+ if (this.parent) {
+ this.color = this.parent.color.lighter(this.contrast * 0.75);
+ borderColor = this.parent.color;
+ } else {
+ borderColor = new Color(120, 120, 120);
+ }
+ context.fillStyle = this.color.toString();
+
+ // cache my border colors
+ this.cachedClr = borderColor.toString();
+ this.cachedClrBright = borderColor.lighter(this.contrast)
+ .toString();
+ this.cachedClrDark = borderColor.darker(this.contrast).toString();
+
+ context.fillRect(
+ this.edge,
+ this.edge,
+ this.width() - this.edge * 2,
+ this.height() - this.edge * 2
+ );
+
+ this.drawRectBorder(context);
+};
+
+InputFieldMorph.prototype.drawRectBorder = function (context) {
+ var shift = this.edge * 0.5,
+ gradient;
+
+ context.lineWidth = this.edge;
+ context.lineJoin = 'round';
+ context.lineCap = 'round';
+
+ context.shadowOffsetY = shift;
+ context.shadowBlur = this.edge * 4;
+ context.shadowColor = this.cachedClrDark;
+
+ gradient = context.createLinearGradient(
+ 0,
+ 0,
+ 0,
+ this.edge
+ );
+
+ gradient.addColorStop(0, this.cachedClr);
+ gradient.addColorStop(1, this.cachedClrDark);
+ context.strokeStyle = gradient;
+ context.beginPath();
+ context.moveTo(this.edge, shift);
+ context.lineTo(this.width() - this.edge - shift, shift);
+ context.stroke();
+
+ context.shadowOffsetY = 0;
+
+ gradient = context.createLinearGradient(
+ 0,
+ 0,
+ this.edge,
+ 0
+ );
+ gradient.addColorStop(0, this.cachedClr);
+ gradient.addColorStop(1, this.cachedClrDark);
+ context.strokeStyle = gradient;
+ context.beginPath();
+ context.moveTo(shift, this.edge);
+ context.lineTo(shift, this.height() - this.edge - shift);
+ context.stroke();
+
+ context.shadowOffsetX = 0;
+ context.shadowOffsetY = 0;
+ context.shadowBlur = 0;
+
+ gradient = context.createLinearGradient(
+ 0,
+ this.height() - this.edge,
+ 0,
+ this.height()
+ );
+ gradient.addColorStop(0, this.cachedClrBright);
+ gradient.addColorStop(1, this.cachedClr);
+ context.strokeStyle = gradient;
+ context.beginPath();
+ context.moveTo(this.edge, this.height() - shift);
+ context.lineTo(this.width() - this.edge, this.height() - shift);
+ context.stroke();
+
+ gradient = context.createLinearGradient(
+ this.width() - this.edge,
+ 0,
+ this.width(),
+ 0
+ );
+ gradient.addColorStop(0, this.cachedClrBright);
+ gradient.addColorStop(1, this.cachedClr);
+ context.strokeStyle = gradient;
+ context.beginPath();
+ context.moveTo(this.width() - shift, this.edge);
+ context.lineTo(this.width() - shift, this.height() - this.edge);
+ context.stroke();
+}; \ No newline at end of file