Annotation of modules/damieng/graphical_editor/loncapa_daxe/web/nodes/chem.dart, revision 1.4
1.1 damieng 1: /*
2: This file is part of LONCAPA-Daxe.
3:
4: LONCAPA-Daxe is free software: you can redistribute it and/or modify
5: it under the terms of the GNU General Public License as published by
6: the Free Software Foundation, either version 3 of the License, or
7: (at your option) any later version.
8:
9: LONCAPA-Daxe is distributed in the hope that it will be useful,
10: but WITHOUT ANY WARRANTY; without even the implied warranty of
11: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12: GNU General Public License for more details.
13:
14: You should have received a copy of the GNU General Public License
15: along with Daxe. If not, see <http://www.gnu.org/licenses/>.
16: */
17:
18: part of loncapa_daxe;
19:
20: /**
21: * Display for the chem element.
22: * Jaxe display type: 'chem'.
23: */
24: class Chem extends DNString {
25: bool preview;
26: StreamSubscription<h.MouseEvent> listener = null;
27:
28: Chem.fromRef(x.Element elementRef) : super.fromRef(elementRef) {
29: preview = false;
30: }
31:
32: Chem.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent) {
1.4 ! damieng 33: if (firstChild is DNText && firstChild.nextSibling == null)
! 34: firstChild.replaceWith(new ChemText(firstChild.nodeValue));
1.1 damieng 35: preview = previewIsPossible();
36: }
37:
38: bool previewIsPossible() {
39: return (firstChild is DNText && !firstChild.nodeValue.trim().startsWith('\$'));
40: }
41:
42: @override
43: h.Element html() {
44: if (!preview) {
45: if (listener == null)
46: listener = h.document.onClick.listen((h.MouseEvent e) => onClick(e));
47: return super.html();
48: }
49: h.SpanElement span = new h.SpanElement();
50: span.id = "$id";
51: span.classes.add('dn');
52: if (!valid)
53: span.classes.add('invalid');
54: String text = firstChild.nodeValue.trim();
55: span.setInnerHtml(Chem.textToHTML(text));
56: span.onClick.listen((h.MouseEvent e) {
57: preview = false;
58: updateHTML();
1.3 damieng 59: // prevent calling onClick(e):
60: // (it could switch back to preview mode if the element takes more
61: // space and moves to the next line after switching to non-preview).
62: e.stopPropagation();
63: e.preventDefault();
64: // but set cursor inside:
65: page.moveCursorTo(new Position(this, this.offsetLength));
1.1 damieng 66: });
1.2 damieng 67: setupDrag(span);
1.1 damieng 68: return(span);
69: }
70:
71: void onClick(h.MouseEvent e) {
72: if (!previewIsPossible())
73: return; // NOTE: that will generate a lot of events for nothing when variables are used...
74: h.Element node = getHTMLNode();
75: if (node == null) {
76: listener.cancel();
77: listener = null;
78: return;
79: }
80: h.Rectangle r = node.getBoundingClientRect(); // could be more precise with getClientRects
1.3 damieng 81: if (!r.containsPoint(e.client)) {
82: listener.cancel();
83: listener = null;
84: preview = true;
85: updateHTML();
86: e.stopPropagation();
87: e.preventDefault();
88: page.cursor.refresh();
89: }
1.1 damieng 90: }
91:
92: @override
1.4 ! damieng 93: void updateHTML() {
! 94: if (preview && !previewIsPossible())
! 95: preview = false;
! 96: super.updateHTML();
! 97: }
! 98:
! 99: @override
! 100: void updateHTMLAfterChildrenChange(List<DaxeNode> changed) {
! 101: if (preview)
! 102: updateHTML();
! 103: else
! 104: super.updateHTMLAfterChildrenChange(changed);
! 105: }
! 106:
! 107: @override
1.1 damieng 108: h.Element getHTMLContentsNode() {
109: if (!preview)
110: return super.getHTMLContentsNode();
111: return(getHTMLNode());
112: }
113:
114: @override
115: Position firstCursorPositionInside() {
116: if (!preview)
117: return super.firstCursorPositionInside();
118: return(null);
119: }
120:
121: @override
122: Position lastCursorPositionInside() {
123: if (!preview)
124: return super.lastCursorPositionInside();
125: return(null);
126: }
127:
1.4 ! damieng 128: @override
! 129: bool get needsSpecialDNText {
! 130: return true;
! 131: }
! 132:
! 133: @override
! 134: DNText specialDNTextConstructor(String text) {
! 135: return new ChemText(text);
! 136: }
! 137:
1.1 damieng 138: static String textToHTML(String reaction) {
139: // this is doing the same thing as chemparse, except it uses UNICODE characters instead of LaTeX
140: //List<String> tokens = reaction.split(new RegExp(r"(\s\+|\->|<=>|<\-|\.)"));
141: // this did not work (delimiters are not preserved)
142: // using look-ahead/behind does not work either...
143: List<String> tokens = getTokensWithDelimiters(reaction, new RegExp(r"(\s\+|\->|<=>|<\-|\.)"));
144: String formula = '';
145: for (int i=0; i<tokens.length; i++) {
146: String token = tokens[i];
147: if (token == '->' ) {
148: formula += '→ ';
149: continue;
150: }
151: if (token == '<-' ) {
152: formula += '← ';
153: continue;
154: }
155: if (token == '<=>') {
156: formula += '⇌ ';
157: continue;
158: }
159: if (token == '.') {
160: formula = formula.replaceFirst(new RegExp(r" | "), '');
161: formula += '·';
162: continue;
163: }
164: Iterable<Match> matches = new RegExp(r"^\s*([\d|\/]*(?:&frac\d\d)?)(.*)").allMatches(token);
165: String molecule;
166: if (matches.length > 0) {
167: Match firstMatch = matches.first;
168: if (firstMatch.group(1) != null)
169: formula += firstMatch.group(1); // stoichiometric coefficient
170: if (firstMatch.group(2) != null)
171: molecule = firstMatch.group(2);
172: else
173: molecule = '';
174: } else
175: molecule = '';
176: // subscripts
177: // $molecule =~ s|(?<=[a-zA-Z\)\]\s])(\d+)|<sub>$1</sub>|g;
178: // Javascript does not support look-behind like Perl
179: molecule = molecule.replaceAllMapped(new RegExp(r"([a-zA-Z\)\]\s])(\d+)"), (Match m) => "${m[1]}<sub>${m[2]}</sub>");
180: // superscripts
181: molecule = molecule.replaceAllMapped(new RegExp(r"\^(\d*[+\-]*)"), (Match m) => "<sup>${m[1]}</sup>");
182: // strip whitespace
183: molecule = molecule.replaceAll(new RegExp(r"\s*"), '');
184: // forced space
185: molecule = molecule.replaceAll('_', ' ');
186: molecule = molecule.replaceAll('-', '−');
187: formula += molecule + ' ';
188: }
189: // get rid of trailing space
190: formula = formula.replaceFirst(new RegExp(r"( | )$"), '');
191: return formula;
192: }
193:
194: static List<String> getTokensWithDelimiters(String s, Pattern p) {
195: int start = 0;
196: int ind = s.indexOf(p, start);
197: List<String> list = new List<String>();
198: while (ind >= 0) {
199: if (ind > 0)
200: list.add(s.substring(start, ind));
201: Match m = p.matchAsPrefix(s, ind);
202: String delimiter = m.group(0);
203: list.add(delimiter);
204: start = ind + delimiter.length;
205: ind = s.indexOf(p, start);
206: }
207: if (start < s.length)
208: list.add(s.substring(start));
209: return list;
210: }
211: }
1.4 ! damieng 212:
! 213: /**
! 214: * A DNText which triggers an update for the parent Chem when preview is true.
! 215: * This is needed to handle undo of a part of the text when in preview mode.
! 216: */
! 217: class ChemText extends DNText {
! 218:
! 219: ChemText(String s) : super(s) {
! 220: }
! 221:
! 222: ChemText.fromNode(x.Node node, DaxeNode parent) : super.fromNode(node, parent) {
! 223: }
! 224:
! 225: @override
! 226: void updateHTML() {
! 227: if (parent is Chem && (parent as Chem).preview)
! 228: parent.updateHTML();
! 229: else
! 230: super.updateHTML();
! 231: }
! 232: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>