Annotation of modules/damieng/graphical_editor/loncapa_daxe/web/nodes/chem.dart, revision 1.3
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) {
33: preview = previewIsPossible();
34: }
35:
36: bool previewIsPossible() {
37: return (firstChild is DNText && !firstChild.nodeValue.trim().startsWith('\$'));
38: }
39:
40: @override
41: h.Element html() {
42: if (!preview) {
43: if (listener == null)
44: listener = h.document.onClick.listen((h.MouseEvent e) => onClick(e));
45: return super.html();
46: }
47: h.SpanElement span = new h.SpanElement();
48: span.id = "$id";
49: span.classes.add('dn');
50: if (!valid)
51: span.classes.add('invalid');
52: String text = firstChild.nodeValue.trim();
53: span.setInnerHtml(Chem.textToHTML(text));
54: span.onClick.listen((h.MouseEvent e) {
55: preview = false;
56: updateHTML();
1.3 ! damieng 57: // prevent calling onClick(e):
! 58: // (it could switch back to preview mode if the element takes more
! 59: // space and moves to the next line after switching to non-preview).
! 60: e.stopPropagation();
! 61: e.preventDefault();
! 62: // but set cursor inside:
! 63: page.moveCursorTo(new Position(this, this.offsetLength));
1.1 damieng 64: });
1.2 damieng 65: setupDrag(span);
1.1 damieng 66: return(span);
67: }
68:
69: void onClick(h.MouseEvent e) {
70: if (!previewIsPossible())
71: return; // NOTE: that will generate a lot of events for nothing when variables are used...
72: h.Element node = getHTMLNode();
73: if (node == null) {
74: listener.cancel();
75: listener = null;
76: return;
77: }
78: h.Rectangle r = node.getBoundingClientRect(); // could be more precise with getClientRects
1.3 ! damieng 79: if (!r.containsPoint(e.client)) {
! 80: listener.cancel();
! 81: listener = null;
! 82: preview = true;
! 83: updateHTML();
! 84: e.stopPropagation();
! 85: e.preventDefault();
! 86: page.cursor.refresh();
! 87: }
1.1 damieng 88: }
89:
90: @override
91: h.Element getHTMLContentsNode() {
92: if (!preview)
93: return super.getHTMLContentsNode();
94: return(getHTMLNode());
95: }
96:
97: @override
98: Position firstCursorPositionInside() {
99: if (!preview)
100: return super.firstCursorPositionInside();
101: return(null);
102: }
103:
104: @override
105: Position lastCursorPositionInside() {
106: if (!preview)
107: return super.lastCursorPositionInside();
108: return(null);
109: }
110:
111: static String textToHTML(String reaction) {
112: // this is doing the same thing as chemparse, except it uses UNICODE characters instead of LaTeX
113: //List<String> tokens = reaction.split(new RegExp(r"(\s\+|\->|<=>|<\-|\.)"));
114: // this did not work (delimiters are not preserved)
115: // using look-ahead/behind does not work either...
116: List<String> tokens = getTokensWithDelimiters(reaction, new RegExp(r"(\s\+|\->|<=>|<\-|\.)"));
117: String formula = '';
118: for (int i=0; i<tokens.length; i++) {
119: String token = tokens[i];
120: if (token == '->' ) {
121: formula += '→ ';
122: continue;
123: }
124: if (token == '<-' ) {
125: formula += '← ';
126: continue;
127: }
128: if (token == '<=>') {
129: formula += '⇌ ';
130: continue;
131: }
132: if (token == '.') {
133: formula = formula.replaceFirst(new RegExp(r" | "), '');
134: formula += '·';
135: continue;
136: }
137: Iterable<Match> matches = new RegExp(r"^\s*([\d|\/]*(?:&frac\d\d)?)(.*)").allMatches(token);
138: String molecule;
139: if (matches.length > 0) {
140: Match firstMatch = matches.first;
141: if (firstMatch.group(1) != null)
142: formula += firstMatch.group(1); // stoichiometric coefficient
143: if (firstMatch.group(2) != null)
144: molecule = firstMatch.group(2);
145: else
146: molecule = '';
147: } else
148: molecule = '';
149: // subscripts
150: // $molecule =~ s|(?<=[a-zA-Z\)\]\s])(\d+)|<sub>$1</sub>|g;
151: // Javascript does not support look-behind like Perl
152: molecule = molecule.replaceAllMapped(new RegExp(r"([a-zA-Z\)\]\s])(\d+)"), (Match m) => "${m[1]}<sub>${m[2]}</sub>");
153: // superscripts
154: molecule = molecule.replaceAllMapped(new RegExp(r"\^(\d*[+\-]*)"), (Match m) => "<sup>${m[1]}</sup>");
155: // strip whitespace
156: molecule = molecule.replaceAll(new RegExp(r"\s*"), '');
157: // forced space
158: molecule = molecule.replaceAll('_', ' ');
159: molecule = molecule.replaceAll('-', '−');
160: formula += molecule + ' ';
161: }
162: // get rid of trailing space
163: formula = formula.replaceFirst(new RegExp(r"( | )$"), '');
164: return formula;
165: }
166:
167: static List<String> getTokensWithDelimiters(String s, Pattern p) {
168: int start = 0;
169: int ind = s.indexOf(p, start);
170: List<String> list = new List<String>();
171: while (ind >= 0) {
172: if (ind > 0)
173: list.add(s.substring(start, ind));
174: Match m = p.matchAsPrefix(s, ind);
175: String delimiter = m.group(0);
176: list.add(delimiter);
177: start = ind + delimiter.length;
178: ind = s.indexOf(p, start);
179: }
180: if (start < s.length)
181: list.add(s.substring(start));
182: return list;
183: }
184: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>