turtlestitch/lists.js

855 wiersze
22 KiB
JavaScript

2013-03-16 08:02:16 +00:00
/*
lists.js
list data structure and GUI for SNAP!
written by Jens Mönig and Brian Harvey
2013-03-16 08:02:16 +00:00
jens@moenig.org, bh@cs.berkeley.edu
2016-02-24 10:35:18 +00:00
Copyright (C) 2016 by Jens Mönig and Brian Harvey
2013-03-16 08:02:16 +00:00
This file is part of Snap!.
2013-03-16 08:02:16 +00:00
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 morphic.js, widgets.js and gui.js
I. hierarchy
-------------
the following tree lists all constructors hierarchically,
indentation indicating inheritance. Refer to this list to get a
contextual overview:
List
BoxMorph*
ListWatcherMorph
* 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:
List
ListWatcherMorph
*/
// Global settings /////////////////////////////////////////////////////
2016-02-24 10:35:18 +00:00
/*global modules, BoxMorph, HandleMorph, PushButtonMorph, SyntaxElementMorph,
Color, Point, WatcherMorph, StringMorph, SpriteMorph, ScrollFrameMorph,
CellMorph, ArrowMorph, MenuMorph, snapEquals, Morph, isNil, localize,
MorphicPreferences, TableDialogMorph, SpriteBubbleMorph, SpeechBubbleMorph,
2016-05-02 10:52:58 +00:00
TableFrameMorph, TableMorph, Variable, isSnapObject*/
2013-03-16 08:02:16 +00:00
2016-07-14 10:18:23 +00:00
modules.lists = '2016-July-14';
2013-03-16 08:02:16 +00:00
var List;
var ListWatcherMorph;
// List ////////////////////////////////////////////////////////////////
/*
I am a dynamic array data structure for SNAP!
My index starts with 1
I am a "smart" hybrid list, because I can be used as both a linked
list and as a dynamic array
public interface:
setters (linked):
-----------------
cons - answer a new list with the given item in front
cdr - answer all but the first element
setters (arrayed):
------------------
add(element, index) - insert the element before the given slot,
put(element, index) - overwrite the element at the given slot
remove(index) - remove the given slot, shortening the list
clear() - remove all elements
getters (all hybrid):
---------------------
length() - number of slots
at(index) - element present in specified slot
contains(element) - <bool>
conversion:
-----------
asArray() - answer me as JavaScript array
asText() - answer my elements (recursively) concatenated
*/
// List instance creation:
function List(array) {
this.contents = array || [];
this.first = null;
this.rest = null;
this.isLinked = false;
this.lastChanged = Date.now();
}
2016-02-24 10:35:18 +00:00
// List global preferences
List.prototype.enableTables = false; // default, to not confuse NYC teachers
// List printing
2013-03-16 08:02:16 +00:00
List.prototype.toString = function () {
return 'a List [' + this.length + ' elements]';
2013-03-16 08:02:16 +00:00
};
// List updating:
List.prototype.changed = function () {
this.lastChanged = Date.now();
};
// Linked List ops:
List.prototype.cons = function (car, cdr) {
var answer = new List();
if (!(cdr instanceof List || isNil(cdr))) {
throw new Error("cdr isn't a list: " + cdr);
}
2013-03-16 08:02:16 +00:00
answer.first = isNil(car) ? null : car;
answer.rest = cdr || null;
answer.isLinked = true;
return answer;
};
List.prototype.cdr = function () {
var result, i;
2013-03-16 08:02:16 +00:00
if (this.isLinked) {
return this.rest || new List();
}
if (this.contents.length < 2) {
return new List();
}
result = new List();
for (i = this.contents.length; i > 1; i -= 1) {
result = this.cons(this.at(i), result);
}
return result;
2013-03-16 08:02:16 +00:00
};
// List array setters:
List.prototype.add = function (element, index) {
/*
insert the element before the given slot index,
if no index is specifed, append the element
*/
var idx = index || this.length() + 1,
obj = isNil(element) ? null : element;
2013-03-16 08:02:16 +00:00
this.becomeArray();
this.contents.splice(idx - 1, 0, obj);
this.changed();
};
List.prototype.put = function (element, index) {
// exchange the element at the given slot for another
var data = element === 0 ? 0
: element === false ? false
: element || null;
this.becomeArray();
this.contents[index - 1] = data;
this.changed();
};
List.prototype.remove = function (index) {
// remove the given slot, shortening the list
this.becomeArray();
this.contents.splice(index - 1, 1);
this.changed();
};
List.prototype.clear = function () {
this.contents = [];
this.first = null;
this.rest = null;
this.isLinked = false;
this.changed();
};
// List getters (all hybrid):
List.prototype.length = function () {
if (this.isLinked) {
var pair = this,
result = 0;
while (pair && pair.isLinked) {
result += 1;
pair = pair.rest;
}
return result + (pair ? pair.contents.length : 0);
2013-03-16 08:02:16 +00:00
}
return this.contents.length;
};
List.prototype.at = function (index) {
var value, idx = +index, pair = this;
while (pair.isLinked) {
if (idx > 1) {
pair = pair.rest;
idx -= 1;
} else {
return pair.first;
}
2013-03-16 08:02:16 +00:00
}
value = pair.contents[idx - 1];
2013-03-16 08:02:16 +00:00
return isNil(value) ? '' : value;
};
List.prototype.contains = function (element) {
var pair = this;
while (pair.isLinked) {
2014-07-18 05:44:26 +00:00
if (snapEquals(pair.first, element)) {
2013-03-16 08:02:16 +00:00
return true;
}
pair = pair.rest;
2013-03-16 08:02:16 +00:00
}
// in case I'm arrayed
return pair.contents.some(function (any) {
return snapEquals(any, element);
});
2013-03-16 08:02:16 +00:00
};
2016-02-24 10:35:18 +00:00
// List table (2D) accessing (for table morph widget):
List.prototype.isTable = function () {
return this.enableTables && (this.length() > 100 || this.cols() > 1);
};
List.prototype.get = function (col, row) {
var r, len, cols;
if (!col) {
if (!row) {return [this.length()]; }
if (row > this.rows()) {return null; }
return this.rowName(row);
} else if (!row) {
if (this.cols() === 1) {return localize('items'); }
return this.colName(col);
}
r = this.at(row);
// encode "orphaned" as arrays and overshooting ones as Variables
if (r instanceof List) {
len = r.length();
cols = this.cols();
if (col > len) {
return null;
} else if (cols === 1 && len > 1) {
return [r];
} else if (col >= cols && len > cols) { // overshooting
return new Variable(r.at(col));
}
return r.at(col);
}
if (col === 1 && row <= this.rows()) {
return [r];
}
return null;
};
List.prototype.rows = function () {
return this.length();
};
List.prototype.cols = function () {
var r = (this.at(1));
return r instanceof List ? r.length() : 1;
};
List.prototype.colName = function (col) {
if (col > this.cols()) {return null; }
return String.fromCharCode(64 + ((col % 26) || 26)).repeat(
Math.floor((col - 1) / 26) + 1
);
};
List.prototype.rowName = function (row) {
return row;
};
List.prototype.columnNames = function () {
return [];
};
List.prototype.version = function (startRow, rows) {
var l = Math.min(startRow + rows, this.length()),
v = this.lastChanged,
r,
i;
for (i = startRow; i <= l; i += 1) {
r = this.at(i);
v = Math.max(v, r.lastChanged ? r.lastChanged : 0);
}
return v;
};
2013-03-16 08:02:16 +00:00
// List conversion:
List.prototype.asArray = function () {
// for use in the evaluator
this.becomeArray();
return this.contents;
};
2016-05-02 10:52:58 +00:00
List.prototype.itemsArray = function () {
// answer an array containing my elements
// don't convert linked lists to arrays
if (this.isLinked) {
var next = this,
result = [],
i;
while (next && next.isLinked) {
result.push(next.first);
next = next.rest;
}
if (next) {
for (i = 1; i <= next.contents.length; i += 1) {
result.push(next.at(i));
}
}
return result;
}
return this.contents;
};
2013-03-16 08:02:16 +00:00
List.prototype.asText = function () {
var result = '',
length,
2013-03-16 08:02:16 +00:00
element,
pair = this,
2013-03-16 08:02:16 +00:00
i;
while (pair.isLinked) {
2014-07-18 05:44:26 +00:00
element = pair.first;
if (element instanceof List) {
result = result.concat(element.asText());
} else {
element = isNil(element) ? '' : element.toString();
result = result.concat(element);
}
pair = pair.rest;
}
length = pair.length();
2013-03-16 08:02:16 +00:00
for (i = 1; i <= length; i += 1) {
element = pair.at(i);
2013-03-16 08:02:16 +00:00
if (element instanceof List) {
result = result.concat(element.asText());
} else {
element = isNil(element) ? '' : element.toString();
result = result.concat(element);
}
}
return result;
};
List.prototype.becomeArray = function () {
if (this.isLinked) {
2016-05-02 10:52:58 +00:00
this.contents = this.itemsArray();
2013-03-16 08:02:16 +00:00
this.isLinked = false;
this.first = null;
this.rest = null;
2013-03-16 08:02:16 +00:00
}
};
List.prototype.becomeLinked = function () {
var i, stop, tail = this;
if (!this.isLinked) {
stop = this.length();
for (i = 0; i < stop; i += 1) {
tail.first = this.contents[i];
2016-06-10 08:09:22 +00:00
if (i < (stop - 1)) {
tail.rest = new List();
tail.isLinked = true;
tail = tail.rest;
}
2013-03-16 08:02:16 +00:00
}
this.contents = [];
this.isLinked = true;
}
};
// List testing
List.prototype.equalTo = function (other) {
var myself = this, it = other, i, j, loopcount;
2013-03-16 08:02:16 +00:00
if (!(other instanceof List)) {
return false;
}
while (myself.isLinked && it.isLinked) {
if (!snapEquals(myself.first, it.first)) {
2013-03-16 08:02:16 +00:00
return false;
}
myself = myself.rest;
it = it.rest;
2013-03-16 08:02:16 +00:00
}
if (it.isLinked) {
i = it;
it = myself;
myself = i;
}
j = 0;
while (myself.isLinked) {
if (!snapEquals(myself.first, it.contents[j])) {
return false;
2013-03-16 08:02:16 +00:00
}
myself = myself.rest;
j += 1;
2013-03-16 08:02:16 +00:00
}
i = 0;
if (myself.contents.length !== (it.contents.length - j)) {
2013-03-16 08:02:16 +00:00
return false;
}
loopcount = myself.contents.length;
while (loopcount > 0) {
loopcount -= 1;
if (!snapEquals(myself.contents[i], it.contents[j])) {
2013-03-16 08:02:16 +00:00
return false;
}
i += 1;
j += 1;
2013-03-16 08:02:16 +00:00
}
return true;
};
// ListWatcherMorph ////////////////////////////////////////////////////
/*
I am a little window which observes a list and continuously
updates itself accordingly
*/
// ListWatcherMorph inherits from BoxMorph:
ListWatcherMorph.prototype = new BoxMorph();
2013-03-16 08:02:16 +00:00
ListWatcherMorph.prototype.constructor = ListWatcherMorph;
ListWatcherMorph.uber = BoxMorph.prototype;
// ListWatcherMorph default settings
ListWatcherMorph.prototype.cellColor =
SpriteMorph.prototype.blockColor.lists;
// ListWatcherMorph instance creation:
function ListWatcherMorph(list, parentCell) {
this.init(list, parentCell);
2013-03-16 08:02:16 +00:00
}
ListWatcherMorph.prototype.init = function (list, parentCell) {
2013-03-16 08:02:16 +00:00
var myself = this;
this.list = list || new List();
this.start = 1;
this.range = 100;
this.lastUpdated = Date.now();
this.lastCell = null;
this.parentCell = parentCell || null; // for circularity detection
2013-03-16 08:02:16 +00:00
// elements declarations
this.label = new StringMorph(
localize('length: ') + this.list.length(),
SyntaxElementMorph.prototype.fontSize,
null,
false,
false,
false,
MorphicPreferences.isFlat ? new Point() : new Point(1, 1),
2013-03-16 08:02:16 +00:00
new Color(255, 255, 255)
);
this.label.mouseClickLeft = function () {myself.startIndexMenu(); };
this.frame = new ScrollFrameMorph(null, 10);
this.frame.alpha = 0;
this.frame.acceptsDrops = false;
this.frame.contents.acceptsDrops = false;
this.handle = new HandleMorph(
this,
80,
70,
3,
3
);
this.handle.setExtent(new Point(13, 13));
this.arrow = new ArrowMorph(
'down',
SyntaxElementMorph.prototype.fontSize
);
this.arrow.mouseClickLeft = function () {myself.startIndexMenu(); };
this.arrow.setRight(this.handle.right());
this.arrow.setBottom(this.handle.top());
this.handle.add(this.arrow);
this.plusButton = new PushButtonMorph(
this.list,
'add',
'+'
);
this.plusButton.padding = 0;
this.plusButton.edge = 0;
this.plusButton.outlineColor = this.color;
this.plusButton.drawNew();
this.plusButton.fixLayout();
ListWatcherMorph.uber.init.call(
this,
SyntaxElementMorph.prototype.rounding,
1.000001, // shadow bug in Chrome,
new Color(120, 120, 120)
);
2013-03-16 08:02:16 +00:00
this.color = new Color(220, 220, 220);
2016-02-24 10:35:18 +00:00
this.isDraggable = false;
this.setExtent(new Point(80, 70).multiplyBy(
SyntaxElementMorph.prototype.scale
));
2013-03-16 08:02:16 +00:00
this.add(this.label);
this.add(this.frame);
this.add(this.plusButton);
this.add(this.handle);
this.handle.drawNew();
this.update();
this.fixLayout();
};
// ListWatcherMorph updating:
ListWatcherMorph.prototype.update = function (anyway) {
var i, idx, ceil, morphs, cell, cnts, label, button, max,
starttime, maxtime = 1000;
this.frame.contents.children.forEach(function (m) {
2016-05-02 10:52:58 +00:00
if (m instanceof CellMorph) {
if (m.contentsMorph instanceof ListWatcherMorph) {
m.contentsMorph.update();
} else if (isSnapObject(m.contents)) {
m.update();
}
2013-03-16 08:02:16 +00:00
}
});
if (this.lastUpdated === this.list.lastChanged && !anyway) {
return null;
}
this.updateLength(true);
// adjust start index to current list length
this.start = Math.max(
Math.min(
this.start,
Math.floor((this.list.length() - 1) / this.range)
* this.range + 1
),
1
);
// refresh existing cells
// highest index shown:
max = Math.min(
this.start + this.range - 1,
this.list.length()
);
// number of morphs available for refreshing
ceil = Math.min(
(max - this.start + 1) * 3,
this.frame.contents.children.length
);
for (i = 0; i < ceil; i += 3) {
idx = this.start + (i / 3);
cell = this.frame.contents.children[i];
label = this.frame.contents.children[i + 1];
button = this.frame.contents.children[i + 2];
cnts = this.list.at(idx);
if (cell.contents !== cnts) {
cell.contents = cnts;
cell.drawNew();
if (this.lastCell) {
cell.setLeft(this.lastCell.left());
}
}
this.lastCell = cell;
if (label.text !== idx.toString()) {
label.text = idx.toString();
label.drawNew();
}
button.action = idx;
}
// remove excess cells
// number of morphs to be shown
morphs = (max - this.start + 1) * 3;
while (this.frame.contents.children.length > morphs) {
this.frame.contents.children[morphs].destroy();
}
// add additional cells
ceil = morphs; //max * 3;
i = this.frame.contents.children.length;
starttime = Date.now();
if (ceil > i + 1) {
for (i; i < ceil; i += 3) {
if (Date.now() - starttime > maxtime) {
this.fixLayout();
this.frame.contents.adjustBounds();
this.frame.contents.setLeft(this.frame.left());
return null;
}
idx = this.start + (i / 3);
label = new StringMorph(
idx.toString(),
SyntaxElementMorph.prototype.fontSize,
null,
false,
false,
false,
MorphicPreferences.isFlat ? new Point() : new Point(1, 1),
2013-03-16 08:02:16 +00:00
new Color(255, 255, 255)
);
cell = new CellMorph(
this.list.at(idx),
this.cellColor,
idx,
this.parentCell
2013-03-16 08:02:16 +00:00
);
button = new PushButtonMorph(
this.list.remove,
idx,
'-',
this.list
);
button.padding = 1;
button.edge = 0;
button.corner = 1;
button.outlineColor = this.color.darker();
button.drawNew();
button.fixLayout();
this.frame.contents.add(cell);
if (this.lastCell) {
cell.setPosition(this.lastCell.bottomLeft());
} else {
cell.setTop(this.frame.contents.top());
}
this.lastCell = cell;
label.setCenter(cell.center());
label.setRight(cell.left() - 2);
this.frame.contents.add(label);
this.frame.contents.add(button);
}
}
this.lastCell = null;
this.fixLayout();
this.frame.contents.adjustBounds();
this.frame.contents.setLeft(this.frame.left());
this.updateLength();
this.lastUpdated = this.list.lastChanged;
};
ListWatcherMorph.prototype.updateLength = function (notDone) {
this.label.text = localize('length: ') + this.list.length();
if (notDone) {
this.label.color = new Color(0, 0, 100);
} else {
this.label.color = new Color(0, 0, 0);
}
this.label.drawNew();
this.label.setCenter(this.center());
this.label.setBottom(this.bottom() - 3);
};
ListWatcherMorph.prototype.startIndexMenu = function () {
var i,
range,
myself = this,
items = Math.ceil(this.list.length() / this.range),
menu = new MenuMorph(
function (idx) {myself.setStartIndex(idx); },
null,
myself
);
menu.addItem('1...', 1);
for (i = 1; i < items; i += 1) {
range = i * 100 + 1;
menu.addItem(range + '...', range);
}
menu.popUpAtHand(this.world());
};
ListWatcherMorph.prototype.setStartIndex = function (index) {
this.start = index;
this.list.changed();
};
ListWatcherMorph.prototype.fixLayout = function () {
if (!this.label) {return; }
2013-03-16 08:02:16 +00:00
Morph.prototype.trackChanges = false;
if (this.frame) {
this.arrangeCells();
this.frame.silentSetPosition(this.position().add(3));
this.frame.bounds.corner = this.bounds.corner.subtract(new Point(
3,
17
));
this.frame.drawNew();
this.frame.contents.adjustBounds();
}
this.label.setCenter(this.center());
this.label.setBottom(this.bottom() - 3);
this.plusButton.setLeft(this.left() + 3);
this.plusButton.setBottom(this.bottom() - 3);
Morph.prototype.trackChanges = true;
this.changed();
if (this.parent && this.parent.fixLayout) {
this.parent.fixLayout();
}
};
ListWatcherMorph.prototype.arrangeCells = function () {
var i, cell, label, button, lastCell,
end = this.frame.contents.children.length;
for (i = 0; i < end; i += 3) {
cell = this.frame.contents.children[i];
label = this.frame.contents.children[i + 1];
button = this.frame.contents.children[i + 2];
if (lastCell) {
cell.setTop(lastCell.bottom());
}
if (label) {
label.setTop(cell.center().y - label.height() / 2);
label.setRight(cell.left() - 2);
}
if (button) {
button.setCenter(cell.center());
button.setLeft(cell.right() + 2);
}
lastCell = cell;
}
this.frame.contents.adjustBounds();
};
Prepare for v4.0.3 * fixed occasional horizontal rendering artifacts in block “holes” (C-shaped slots and Ring holes) * improved user editing of input slots and fixed cursor behavior (note: the text cursor inside input slots also blinks again, as it should have) * always export comments attached to prototype hat blocks in the block editor along with script pic * when first opening a block editor on a custom block definition make it big enough to show everything (as much as fits on the screen) * remember block editor position and dimensions for each edited custom block definition when acknowledging (pressing OK or APPLY) for the session * speed up stacking of commands and loading of projects by suppressing redundant block redraws * introducing a “grab threshold” preference to suppress accidental grabbing through micro-movements of the hand. This addresses the “cannot-click-on-drop-downs-in-Chrome-under-Windows” set of bug reports, and also the issues that arise out of accidentally dragging off a copy of a parameter blob when trying to just click on it to rename it * new hidden (shift-click) option to adjust the grab threshold in the settings menu for the current session * expand list watchers inside result bubbles and speech/thought balloons to show everything (as much of the list’s first level as fist into either the scripting area for result bubbles or the stage for speech balloons - note resizing the stage affects the size of the list watchers inside speech balloons, i.e. making the stage bigger increases the number of visible elements even while the balloon is showing) * fixed a bug that make an occasional gray rectangle appear at the mouse pointer * added a way to invoke blocks synchronously in JavaScript - under construction (possibly to be used for generic “When” hat blocks and user-customizable drop-downs) * added methods to cache morphs’ fullImage and fullBounds while dragging * Reporters (also nested reporters) and sprite icons (in the corral) are now semi-transparent when being dragged, so you can see possible drop target halos /through/ them * in “prefer empty slot drops” mode (default) it is now much harder to drop reporters onto non-static C-slots inside custom blocks (e.g. in FOR loops) and onto variadic input arrowheads (to replace the whole input with an input list) * ScriptsMorphs are now noticing transparent clicks (addresses #997) * %interaction slots are now static, fixed #982 (it is no longer possible to drop reporters into the input slot of a “When I am…” hat block (never was intended that it should be possible) * fixed ctrl-f for the block editor in all situations (thanks, Brian, for the bug report)
2015-11-16 11:18:40 +00:00
ListWatcherMorph.prototype.expand = function (maxExtent) {
// make sure to show all (first 100) cells
var fe = this.frame.contents.extent(),
Prepare for v4.0.3 * fixed occasional horizontal rendering artifacts in block “holes” (C-shaped slots and Ring holes) * improved user editing of input slots and fixed cursor behavior (note: the text cursor inside input slots also blinks again, as it should have) * always export comments attached to prototype hat blocks in the block editor along with script pic * when first opening a block editor on a custom block definition make it big enough to show everything (as much as fits on the screen) * remember block editor position and dimensions for each edited custom block definition when acknowledging (pressing OK or APPLY) for the session * speed up stacking of commands and loading of projects by suppressing redundant block redraws * introducing a “grab threshold” preference to suppress accidental grabbing through micro-movements of the hand. This addresses the “cannot-click-on-drop-downs-in-Chrome-under-Windows” set of bug reports, and also the issues that arise out of accidentally dragging off a copy of a parameter blob when trying to just click on it to rename it * new hidden (shift-click) option to adjust the grab threshold in the settings menu for the current session * expand list watchers inside result bubbles and speech/thought balloons to show everything (as much of the list’s first level as fist into either the scripting area for result bubbles or the stage for speech balloons - note resizing the stage affects the size of the list watchers inside speech balloons, i.e. making the stage bigger increases the number of visible elements even while the balloon is showing) * fixed a bug that make an occasional gray rectangle appear at the mouse pointer * added a way to invoke blocks synchronously in JavaScript - under construction (possibly to be used for generic “When” hat blocks and user-customizable drop-downs) * added methods to cache morphs’ fullImage and fullBounds while dragging * Reporters (also nested reporters) and sprite icons (in the corral) are now semi-transparent when being dragged, so you can see possible drop target halos /through/ them * in “prefer empty slot drops” mode (default) it is now much harder to drop reporters onto non-static C-slots inside custom blocks (e.g. in FOR loops) and onto variadic input arrowheads (to replace the whole input with an input list) * ScriptsMorphs are now noticing transparent clicks (addresses #997) * %interaction slots are now static, fixed #982 (it is no longer possible to drop reporters into the input slot of a “When I am…” hat block (never was intended that it should be possible) * fixed ctrl-f for the block editor in all situations (thanks, Brian, for the bug report)
2015-11-16 11:18:40 +00:00
ext = new Point(fe.x + 6, fe.y + this.label.height() + 6);
if (maxExtent) {
ext = ext.min(maxExtent);
}
this.setExtent(ext);
this.handle.setRight(this.right() - 3);
this.handle.setBottom(this.bottom() - 3);
};
2016-02-24 10:35:18 +00:00
// ListWatcherMorph context menu
ListWatcherMorph.prototype.userMenu = function () {
if (!List.prototype.enableTables) {
return this.escalateEvent('userMenu');
}
var menu = new MenuMorph(this),
myself = this;
menu.addItem('table view...', 'showTableView');
menu.addLine();
menu.addItem(
'open in dialog...',
function () {
new TableDialogMorph(myself.list).popUp(myself.world());
}
);
return menu;
};
ListWatcherMorph.prototype.showTableView = function () {
var view = this.parentThatIsAnyOf([
SpriteBubbleMorph,
SpeechBubbleMorph,
CellMorph
]);
if (!view) {return; }
if (view instanceof SpriteBubbleMorph) {
view.changed();
2016-05-02 10:52:58 +00:00
view.drawNew(true);
2016-02-24 10:35:18 +00:00
} else if (view instanceof SpeechBubbleMorph) {
view.contents = new TableFrameMorph(new TableMorph(this.list, 10));
view.contents.expand(this.extent());
view.drawNew(true);
} else { // watcher cell
view.drawNew(true, 'table');
view.contentsMorph.expand(this.extent());
}
view.fixLayout();
};
// ListWatcherMorph events:
ListWatcherMorph.prototype.mouseDoubleClick = function (pos) {
if (List.prototype.enableTables) {
new TableDialogMorph(this.list).popUp(this.world());
} else {
this.escalateEvent('mouseDoubleClick', pos);
}
};
2013-03-16 08:02:16 +00:00
// ListWatcherMorph hiding/showing:
ListWatcherMorph.prototype.show = function () {
ListWatcherMorph.uber.show.call(this);
this.frame.contents.adjustBounds();
};
// ListWatcherMorph drawing:
ListWatcherMorph.prototype.drawNew = function () {
WatcherMorph.prototype.drawNew.call(this);
this.fixLayout();
};