kopia lustrzana https://github.com/backface/turtlestitch
3184 wiersze
84 KiB
JavaScript
3184 wiersze
84 KiB
JavaScript
/*
|
|
|
|
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, isNil, SliderMorph, MorphicPreferences,
|
|
ScrollFrameMorph*/
|
|
|
|
modules.widgets = '2013-October-25';
|
|
|
|
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.is3D = false; // for "flat" design exceptions
|
|
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,
|
|
isFlat = MorphicPreferences.isFlat && !this.is3D;
|
|
|
|
if (!this.outline || isFlat) {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,
|
|
isFlat ? 0 : this.corner,
|
|
0
|
|
);
|
|
context.closePath();
|
|
context.fill();
|
|
};
|
|
|
|
PushButtonMorph.prototype.drawBackground = function (context, color) {
|
|
var isFlat = MorphicPreferences.isFlat && !this.is3D;
|
|
|
|
context.fillStyle = color.toString();
|
|
context.beginPath();
|
|
this.outlinePath(
|
|
context,
|
|
isFlat ? 0 : 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
|
|
) {
|
|
if (MorphicPreferences.isFlat && !this.is3D) {return; }
|
|
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 () {
|
|
var shading = !MorphicPreferences.isFlat || this.is3D;
|
|
|
|
if (this.label !== null) {
|
|
this.label.destroy();
|
|
}
|
|
if (this.labelString instanceof SymbolMorph) {
|
|
this.label = this.labelString.fullCopy();
|
|
if (shading) {
|
|
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,
|
|
shading ? this.labelShadowOffset : null,
|
|
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
|
|
if (MorphicPreferences.isFlat && !this.is3D) {
|
|
context.fillStyle = this.pressColor.toString();
|
|
context.fillRect(
|
|
this.outline,
|
|
this.outline,
|
|
this.corner,
|
|
this.height() - this.outline * 2
|
|
);
|
|
return;
|
|
}
|
|
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 () {
|
|
var shading = !MorphicPreferences.isFlat || this.is3D,
|
|
none = new Point();
|
|
|
|
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 = shading ?
|
|
this.labelShadowOffset : none;
|
|
this.label.shadowColor = this.labelShadowColor;
|
|
this.label.color = this.labelColor;
|
|
this.label.drawNew();
|
|
|
|
this.trueStateLabel.shadowOffset = shading ?
|
|
this.labelShadowOffset : none;
|
|
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,
|
|
shading ? this.labelShadowOffset : null,
|
|
this.labelShadowColor,
|
|
this.labelColor
|
|
);
|
|
this.trueStateLabel = new StringMorph(
|
|
localize(this.labelString[1]),
|
|
this.fontSize,
|
|
this.fontStyle,
|
|
true,
|
|
false,
|
|
false,
|
|
shading ? this.labelShadowOffset : null,
|
|
this.labelShadowColor,
|
|
this.labelColor
|
|
);
|
|
}
|
|
} else {
|
|
if (this.labelString instanceof SymbolMorph) {
|
|
this.label = this.labelString.fullCopy();
|
|
if (!this.isPicture) {
|
|
this.label.shadowOffset = shading ?
|
|
this.labelShadowOffset : none;
|
|
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,
|
|
shading ? this.labelShadowOffset : none,
|
|
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
|
|
) {
|
|
if (MorphicPreferences.isFlat && !this.is3D) {return; }
|
|
|
|
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 () {
|
|
var shading = !MorphicPreferences.isFlat || this.is3D;
|
|
|
|
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,
|
|
shading ? new Point(1, 1) : null,
|
|
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 () {
|
|
var shading = !MorphicPreferences.isFlat || this.is3D;
|
|
|
|
this.color = this.element.color;
|
|
this.element.removeShadow();
|
|
this.element[this.builder]();
|
|
if (shading) {
|
|
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);
|
|
if (shading) {
|
|
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);
|
|
if (shading) {
|
|
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.
|
|
|
|
Note:
|
|
-----
|
|
my key property keeps track of my purpose to prevent multiple instances
|
|
on the same or similar objects
|
|
*/
|
|
|
|
// 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.prototype.instances = {}; // prevent multiple instances
|
|
|
|
// DialogBoxMorph instance creation:
|
|
|
|
function DialogBoxMorph(target, action, environment) {
|
|
this.init(target, action, environment);
|
|
}
|
|
|
|
DialogBoxMorph.prototype.init = function (target, action, environment) {
|
|
// additional properties:
|
|
this.is3D = false; // for "flat" design exceptions
|
|
this.target = target || null;
|
|
this.action = action || null;
|
|
this.environment = environment || null;
|
|
this.key = null; // keep track of my purpose to prevent mulitple instances
|
|
|
|
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,
|
|
MorphicPreferences.isFlat ? null : new Point(1, 1),
|
|
new Color(255, 255, 255)
|
|
);
|
|
|
|
if (!this.key) {
|
|
this.key = 'inform' + title + textString;
|
|
}
|
|
|
|
this.labelString = title;
|
|
this.createLabel();
|
|
if (pic) {this.setPicture(pic); }
|
|
if (textString) {
|
|
this.addBody(txt);
|
|
}
|
|
this.addButton('ok', 'OK');
|
|
this.drawNew();
|
|
this.fixLayout();
|
|
this.popUp(world);
|
|
};
|
|
|
|
DialogBoxMorph.prototype.askYesNo = function (
|
|
title,
|
|
textString,
|
|
world,
|
|
pic
|
|
) {
|
|
var txt = new TextMorph(
|
|
textString,
|
|
this.fontSize,
|
|
this.fontStyle,
|
|
true,
|
|
false,
|
|
'center',
|
|
null,
|
|
null,
|
|
MorphicPreferences.isFlat ? null : new Point(1, 1),
|
|
new Color(255, 255, 255)
|
|
);
|
|
|
|
if (!this.key) {
|
|
this.key = 'decide' + title + textString;
|
|
}
|
|
|
|
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();
|
|
this.popUp(world);
|
|
};
|
|
|
|
DialogBoxMorph.prototype.prompt = function (
|
|
title,
|
|
defaultString,
|
|
world,
|
|
pic,
|
|
choices, // optional dictionary for drop-down of choices
|
|
isReadOnly, // optional when using choices
|
|
isNumeric, // optional
|
|
sliderMin, // optional for numeric sliders
|
|
sliderMax, // optional for numeric sliders
|
|
sliderAction // optional single-arg function for numeric slider
|
|
) {
|
|
var sld,
|
|
head,
|
|
txt = new InputFieldMorph(
|
|
defaultString,
|
|
isNumeric || false, // numeric?
|
|
choices || null, // drop-down dict, optional
|
|
choices ? isReadOnly || false : false
|
|
);
|
|
txt.setWidth(250);
|
|
if (isNumeric) {
|
|
if (pic) {
|
|
head = new AlignmentMorph('column', this.padding);
|
|
pic.setPosition(head.position());
|
|
head.add(pic);
|
|
}
|
|
if (!isNil(sliderMin) && !isNil(sliderMax)) {
|
|
sld = new SliderMorph(
|
|
sliderMin * 100,
|
|
sliderMax * 100,
|
|
parseFloat(defaultString) * 100,
|
|
(sliderMax - sliderMin) / 10 * 100,
|
|
'horizontal'
|
|
);
|
|
sld.alpha = 1;
|
|
sld.color = this.color.lighter(50);
|
|
sld.setHeight(txt.height() * 0.7);
|
|
sld.setWidth(txt.width());
|
|
sld.action = function (num) {
|
|
if (sliderAction) {
|
|
sliderAction(num / 100);
|
|
}
|
|
txt.setContents(num / 100);
|
|
txt.edit();
|
|
};
|
|
if (!head) {
|
|
head = new AlignmentMorph('column', this.padding);
|
|
}
|
|
head.add(sld);
|
|
}
|
|
if (head) {
|
|
head.fixLayout();
|
|
this.setPicture(head);
|
|
head.fixLayout();
|
|
}
|
|
} else {
|
|
if (pic) {this.setPicture(pic); }
|
|
}
|
|
|
|
this.reactToChoice = function (inp) {
|
|
if (sld) {
|
|
sld.value = inp * 100;
|
|
sld.drawNew();
|
|
sld.changed();
|
|
}
|
|
if (sliderAction) {
|
|
sliderAction(inp);
|
|
}
|
|
};
|
|
|
|
txt.reactToKeystroke = function () {
|
|
var inp = txt.getValue();
|
|
if (sld) {
|
|
inp = Math.max(inp, sliderMin);
|
|
sld.value = inp * 100;
|
|
sld.drawNew();
|
|
sld.changed();
|
|
}
|
|
if (sliderAction) {
|
|
sliderAction(inp);
|
|
}
|
|
};
|
|
|
|
this.labelString = title;
|
|
this.createLabel();
|
|
|
|
if (!this.key) {
|
|
this.key = 'prompt' + title + defaultString;
|
|
}
|
|
|
|
this.addBody(txt);
|
|
txt.drawNew();
|
|
this.addButton('ok', 'OK');
|
|
this.addButton('cancel', 'Cancel');
|
|
this.fixLayout();
|
|
this.drawNew();
|
|
this.fixLayout();
|
|
this.popUp(world);
|
|
};
|
|
|
|
DialogBoxMorph.prototype.promptCode = function (
|
|
title,
|
|
defaultString,
|
|
world,
|
|
pic,
|
|
instructions
|
|
) {
|
|
var frame = new ScrollFrameMorph(),
|
|
text = new TextMorph(defaultString || ''),
|
|
bdy = new AlignmentMorph('column', this.padding),
|
|
size = pic ? Math.max(pic.width, 400) : 400;
|
|
|
|
this.getInput = function () {
|
|
return text.text;
|
|
};
|
|
|
|
function remarkText(string) {
|
|
return new TextMorph(
|
|
string,
|
|
10,
|
|
null, // style
|
|
false, // bold
|
|
null, // italic
|
|
null, // alignment
|
|
null, // width
|
|
null, // font name
|
|
MorphicPreferences.isFlat ? null : new Point(1, 1),
|
|
new Color(255, 255, 255) // shadowColor
|
|
);
|
|
}
|
|
|
|
frame.padding = 6;
|
|
frame.setWidth(size);
|
|
frame.acceptsDrops = false;
|
|
frame.contents.acceptsDrops = false;
|
|
|
|
text.fontName = 'monospace';
|
|
text.fontStyle = 'monospace';
|
|
text.fontSize = 11;
|
|
text.setPosition(frame.topLeft().add(frame.padding));
|
|
text.enableSelecting();
|
|
text.isEditable = true;
|
|
|
|
frame.setHeight(size / 4);
|
|
frame.fixLayout = nop;
|
|
frame.edge = InputFieldMorph.prototype.edge;
|
|
frame.fontSize = InputFieldMorph.prototype.fontSize;
|
|
frame.typeInPadding = InputFieldMorph.prototype.typeInPadding;
|
|
frame.contrast = InputFieldMorph.prototype.contrast;
|
|
frame.drawNew = InputFieldMorph.prototype.drawNew;
|
|
frame.drawRectBorder = InputFieldMorph.prototype.drawRectBorder;
|
|
|
|
frame.addContents(text);
|
|
text.drawNew();
|
|
|
|
if (pic) {this.setPicture(pic); }
|
|
|
|
this.labelString = title;
|
|
this.createLabel();
|
|
|
|
if (!this.key) {
|
|
this.key = 'promptCode' + title + defaultString;
|
|
}
|
|
|
|
bdy.setColor(this.color);
|
|
bdy.add(frame);
|
|
if (instructions) {
|
|
bdy.add(remarkText(instructions));
|
|
}
|
|
bdy.fixLayout();
|
|
|
|
this.addBody(bdy);
|
|
frame.drawNew();
|
|
bdy.drawNew();
|
|
|
|
this.addButton('ok', 'OK');
|
|
this.addButton('cancel', 'Cancel');
|
|
this.fixLayout();
|
|
this.drawNew();
|
|
this.fixLayout();
|
|
this.popUp(world);
|
|
text.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
|
|
MorphicPreferences.isFlat ? null : new Point(1, 1),
|
|
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);
|
|
emlLabel = labelText('foo');
|
|
inp.add(emlLabel);
|
|
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 (purpose === 'resetPassword') {
|
|
inp.add(labelText('User name:'));
|
|
inp.add(usr);
|
|
}
|
|
|
|
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];
|
|
} else if (purpose === 'resetPassword') {
|
|
checklist = [usr];
|
|
}
|
|
|
|
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', 'resetPassword'
|
|
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 (!this.key) {
|
|
this.key = 'credentials' + title + purpose;
|
|
}
|
|
|
|
this.popUp(world);
|
|
};
|
|
|
|
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.withKey = function (key) {
|
|
this.key = key;
|
|
return this;
|
|
};
|
|
|
|
DialogBoxMorph.prototype.popUp = function (world) {
|
|
if (world) {
|
|
if (this.key) {
|
|
if (this.instances[world.stamp]) {
|
|
if (this.instances[world.stamp][this.key]) {
|
|
this.instances[world.stamp][this.key].destroy();
|
|
}
|
|
this.instances[world.stamp][this.key] = this;
|
|
} else {
|
|
this.instances[world.stamp] = {};
|
|
this.instances[world.stamp][this.key] = this;
|
|
}
|
|
}
|
|
world.add(this);
|
|
world.keyboardReceiver = this;
|
|
this.setCenter(world.center());
|
|
this.edit();
|
|
}
|
|
};
|
|
|
|
DialogBoxMorph.prototype.destroy = function () {
|
|
DialogBoxMorph.uber.destroy.call(this);
|
|
if (this.key) {
|
|
delete this.instances[this.key];
|
|
}
|
|
};
|
|
|
|
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 () {
|
|
var shading = !MorphicPreferences.isFlat || this.is3D;
|
|
|
|
if (this.label) {
|
|
this.label.destroy();
|
|
}
|
|
if (this.labelString) {
|
|
this.label = new StringMorph(
|
|
localize(this.labelString),
|
|
this.titleFontSize,
|
|
this.fontStyle,
|
|
true,
|
|
false,
|
|
false,
|
|
shading ? new Point(2, 1) : null,
|
|
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,
|
|
isFlat = MorphicPreferences.isFlat && !this.is3D;
|
|
|
|
// this.alpha = isFlat ? 0.9 : 1;
|
|
|
|
this.image = newCanvas(this.extent());
|
|
context = this.image.getContext('2d');
|
|
|
|
// title bar
|
|
if (isFlat) {
|
|
context.fillStyle = this.titleBarColor.toString();
|
|
} else {
|
|
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,
|
|
isFlat ? 0 : this.corner
|
|
);
|
|
context.closePath();
|
|
context.fill();
|
|
|
|
// flat shape
|
|
// body
|
|
context.fillStyle = this.color.toString();
|
|
context.beginPath();
|
|
this.outlinePathBody(
|
|
context,
|
|
isFlat ? 0 : this.corner
|
|
);
|
|
context.closePath();
|
|
context.fill();
|
|
|
|
if (isFlat) {
|
|
DialogBoxMorph.uber.addShadow.call(this);
|
|
Morph.prototype.trackChanges = true;
|
|
this.fullChanged();
|
|
return;
|
|
}
|
|
|
|
// 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);
|
|
if (choices instanceof Array) {
|
|
choices.forEach(function (choice) {
|
|
menu.addItem(choice[0], choice[1]);
|
|
});
|
|
} else { // assuming a dictionary
|
|
for (key in choices) {
|
|
if (Object.prototype.hasOwnProperty.call(choices, 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) {
|
|
if (this.parent.color.eq(new Color(255, 255, 255))) {
|
|
this.color = this.parent.color.darker(this.contrast * 0.1);
|
|
} else {
|
|
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;
|
|
|
|
if (MorphicPreferences.isFlat && !this.is3D) {return; }
|
|
|
|
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();
|
|
};
|