/*
This file is part of LONCAPA-Daxe.
LONCAPA-Daxe is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
LONCAPA-Daxe 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with Daxe. If not, see <http://www.gnu.org/licenses/>.
*/
part of loncapa_daxe;
/**
* Display for the chem element.
* Jaxe display type: 'chem'.
*/
class Chem extends DNString {
bool preview;
StreamSubscription<h.MouseEvent> listener = null;
Chem.fromRef(x.Element elementRef) : super.fromRef(elementRef) {
preview = false;
}
Chem.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent) {
if (firstChild is DNText && firstChild.nextSibling == null)
firstChild.replaceWith(new ChemText(firstChild.nodeValue));
preview = previewIsPossible();
}
bool previewIsPossible() {
return (firstChild is DNText && !firstChild.nodeValue.trim().startsWith('\$'));
}
@override
h.Element html() {
if (!preview) {
if (listener == null)
listener = h.document.onClick.listen((h.MouseEvent e) => onClick(e));
return super.html();
}
h.SpanElement span = new h.SpanElement();
span.id = "$id";
span.classes.add('dn');
if (!valid)
span.classes.add('invalid');
String text = firstChild.nodeValue.trim();
span.setInnerHtml(Chem.textToHTML(text));
span.onClick.listen((h.MouseEvent e) {
preview = false;
updateHTML();
// prevent calling onClick(e):
// (it could switch back to preview mode if the element takes more
// space and moves to the next line after switching to non-preview).
e.stopPropagation();
e.preventDefault();
// but set cursor inside:
page.moveCursorTo(new Position(this, this.offsetLength));
});
setupDrag(span);
return(span);
}
void onClick(h.MouseEvent e) {
if (!previewIsPossible())
return; // NOTE: that will generate a lot of events for nothing when variables are used...
h.Element node = getHTMLNode();
if (node == null) {
listener.cancel();
listener = null;
return;
}
h.Rectangle r = node.getBoundingClientRect(); // could be more precise with getClientRects
if (!r.containsPoint(e.client)) {
listener.cancel();
listener = null;
preview = true;
updateHTML();
e.stopPropagation();
e.preventDefault();
page.cursor.refresh();
}
}
@override
void updateHTML() {
if (preview && !previewIsPossible())
preview = false;
super.updateHTML();
}
@override
void updateHTMLAfterChildrenChange(List<DaxeNode> changed) {
if (preview)
updateHTML();
else
super.updateHTMLAfterChildrenChange(changed);
}
@override
h.Element getHTMLContentsNode() {
if (!preview)
return super.getHTMLContentsNode();
return(getHTMLNode());
}
@override
Position firstCursorPositionInside() {
if (!preview)
return super.firstCursorPositionInside();
return(null);
}
@override
Position lastCursorPositionInside() {
if (!preview)
return super.lastCursorPositionInside();
return(null);
}
@override
bool get needsSpecialDNText {
return true;
}
@override
DNText specialDNTextConstructor(String text) {
return new ChemText(text);
}
static String textToHTML(String reaction) {
// this is doing the same thing as chemparse, except it uses UNICODE characters instead of LaTeX
//List<String> tokens = reaction.split(new RegExp(r"(\s\+|\->|<=>|<\-|\.)"));
// this did not work (delimiters are not preserved)
// using look-ahead/behind does not work either...
List<String> tokens = getTokensWithDelimiters(reaction, new RegExp(r"(\s\+|\->|<=>|<\-|\.)"));
String formula = '';
for (int i=0; i<tokens.length; i++) {
String token = tokens[i];
if (token == '->' ) {
formula += '→ ';
continue;
}
if (token == '<-' ) {
formula += '← ';
continue;
}
if (token == '<=>') {
formula += '⇌ ';
continue;
}
if (token == '.') {
formula = formula.replaceFirst(new RegExp(r" | "), '');
formula += '·';
continue;
}
Iterable<Match> matches = new RegExp(r"^\s*([\d|\/]*(?:&frac\d\d)?)(.*)").allMatches(token);
String molecule;
if (matches.length > 0) {
Match firstMatch = matches.first;
if (firstMatch.group(1) != null)
formula += firstMatch.group(1); // stoichiometric coefficient
if (firstMatch.group(2) != null)
molecule = firstMatch.group(2);
else
molecule = '';
} else
molecule = '';
// subscripts
// $molecule =~ s|(?<=[a-zA-Z\)\]\s])(\d+)|<sub>$1</sub>|g;
// Javascript does not support look-behind like Perl
molecule = molecule.replaceAllMapped(new RegExp(r"([a-zA-Z\)\]\s])(\d+)"), (Match m) => "${m[1]}<sub>${m[2]}</sub>");
// superscripts
molecule = molecule.replaceAllMapped(new RegExp(r"\^(\d*[+\-]*)"), (Match m) => "<sup>${m[1]}</sup>");
// strip whitespace
molecule = molecule.replaceAll(new RegExp(r"\s*"), '');
// forced space
molecule = molecule.replaceAll('_', ' ');
molecule = molecule.replaceAll('-', '−');
formula += molecule + ' ';
}
// get rid of trailing space
formula = formula.replaceFirst(new RegExp(r"( | )$"), '');
return formula;
}
static List<String> getTokensWithDelimiters(String s, Pattern p) {
int start = 0;
int ind = s.indexOf(p, start);
List<String> list = new List<String>();
while (ind >= 0) {
if (ind > 0)
list.add(s.substring(start, ind));
Match m = p.matchAsPrefix(s, ind);
String delimiter = m.group(0);
list.add(delimiter);
start = ind + delimiter.length;
ind = s.indexOf(p, start);
}
if (start < s.length)
list.add(s.substring(start));
return list;
}
}
/**
* A DNText which triggers an update for the parent Chem when preview is true.
* This is needed to handle undo of a part of the text when in preview mode.
*/
class ChemText extends DNText {
ChemText(String s) : super(s) {
}
ChemText.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent) {
}
@override
void updateHTML() {
if (parent is Chem && (parent as Chem).preview)
parent.updateHTML();
else
super.updateHTML();
}
}
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>