]> git.lyx.org Git - lyx.git/blob - src/mathed/formula.C
start native C++ support for Octave, Maple and MathML
[lyx.git] / src / mathed / formula.C
1 /*
2 *  File:        formula.C
3 *  Purpose:     Implementation of formula inset
4 *  Author:      Alejandro Aguilar Sierra <asierra@servidor.unam.mx>
5 *  Created:     January 1996
6 *  Description: Allows the edition of math paragraphs inside Lyx.
7 *
8 *  Copyright: 1996-1998 Alejandro Aguilar Sierra
9 *
10 *  Version: 0.4, Lyx project.
11 *
12 *   You are free to use and modify this code under the terms of
13 *   the GNU General Public Licence version 2 or later.
14 */
15
16 #ifdef __GNUG__
17 #pragma implementation
18 #endif
19
20 #include <config.h>
21
22 #include "formula.h"
23 #include "commandtags.h"
24 #include "math_cursor.h"
25 #include "math_parser.h"
26 #include "math_charinset.h"
27 #include "lyx_main.h"
28 #include "BufferView.h"
29 #include "gettext.h"
30 #include "debug.h"
31 #include "lyx_gui_misc.h"
32 #include "support/LOstream.h"
33 #include "support/LAssert.h"
34 #include "support/lyxlib.h"
35 #include "support/syscall.h"
36 #include "support/lstrings.h"
37 #include "support/filetools.h" // LibFileSearch
38 #include "LyXView.h"
39 #include "Painter.h"
40 #include "lyxrc.h"
41 #include "math_matrixinset.h"
42 #include "mathed/support.h"
43
44 using std::ostream;
45 using std::ifstream;
46 using std::istream;
47 using std::pair;
48 using std::endl;
49 using std::vector;
50
51
52 namespace {
53
54         void stripFromLastEqualSign(MathArray & ar)
55         {
56                 // find position of last '=' in the array
57                 MathArray::size_type pos = ar.size();
58                 for (MathArray::const_iterator it = ar.begin(); it != ar.end(); ++it)
59                         if ((*it)->getChar() == '=')
60                                 pos = it - ar.begin();
61
62                 // delete everything behind this position
63                 ar.erase(pos, ar.size());
64         }
65
66
67         string captureOutput(string const & cmd, string const & data)
68         {
69                 string outfile = lyx::tempName(string(), "mathextern");
70                 string full =  "echo '" + data + "' | " + cmd + " > " + outfile;
71                 lyxerr << "calling: " << full << "\n";
72                 Systemcalls dummy(Systemcalls::System, full, 0);
73                 string out = GetFileContents(outfile);
74                 lyxerr << "result: " << out << "\n";
75                 return out;
76         }
77
78
79         string pipeThroughMaple(string const & extra, MathArray const & ar)
80         {
81                 string header = 
82                         "readlib(latex):\n"
83                         "`latex/csname_font` := ``:\n"
84                         "`latex/latex/*` := subs(`\\,`=`\\cdot `,eval(`latex/latex/*`)):\n";
85                 //"#`latex/csname_font` := `\\it `:"
86                 //"#`latex/latex/symbol` "
87                 //      " := subs((\\'_\\' = \\'`\\_`\\',eval(`latex/latex/symbol`)): ";
88
89                 string trailer = "quit;";
90                 string expr = ar.maplize();
91
92                 for (int i = 0; i < 100; ++i) { // at most 100 attempts
93                         // try to fix missing '*' the hard way by using mint
94                         //
95                         // ... > echo "1A;" | mint -i 1 -S -s -q
96                         // on line     1: 1A;
97                         //                 ^ syntax error - 
98                         //                   Probably missing an operator such as * p
99                         //
100                         lyxerr << "checking expr: '" << expr << "'\n";
101                         string out = captureOutput("mint -i 1 -S -s -q -q", expr + ";");
102                         if (out.empty())
103                                 break; // expression syntax is ok
104                         istringstream is(out);
105                         string line;
106                         getline(is, line);
107                         if (line.find("on line") != 0)
108                                 break; // error message not identified
109                         getline(is, line);
110                         string::size_type pos = line.find('^');
111                         if (pos == string::npos || pos < 15)
112                                 break; // caret position not found
113                         expr.insert(pos - 15,  "*");
114                 }
115
116                 string full = "latex(" +  extra + '(' + expr + "));";
117                 string res = captureOutput("maple -q", header + full + trailer);
118
119                 // change \_ into _
120                 return res;
121         }
122                 
123
124
125         MathArray pipeThroughExtern(string const & arg, MathArray const & ar)
126         {
127                 string lang;
128                 string extra;
129                 istringstream iss(arg.c_str());
130                 iss >> lang >> extra;
131                 if (extra.empty())
132                         extra = "noextra";      
133
134                 MathArray res;
135
136                 if (lang == "octave") {
137
138                         string out = captureOutput("octave -q", ar.octavize());
139                         if (out.size() > 6)
140                                 out = out.substr(6);
141                         mathed_parse_cell(res, out);
142
143                 } else if (lang == "maple") {
144
145                         mathed_parse_cell(res, pipeThroughMaple(extra, ar));
146
147                 } else {
148
149                         // create normalized expression
150                         ostringstream os;
151                         os << "[" << extra << ' ';
152                         ar.writeNormal(os); 
153                         os << "]";
154                         string data = os.str().c_str();
155
156                         // search external script
157                         string file = LibFileSearch("mathed", "extern_" + lang);
158                         if (file.empty()) {
159                                 lyxerr << "converter to '" << lang << "' not found\n";
160                                 return MathArray();
161                         }
162                 
163                         // run external sript
164                         string out = captureOutput(file, data);
165                         mathed_parse_cell(res, out);
166                 }
167
168                 return res;
169         }
170
171 }
172
173
174 InsetFormula::InsetFormula()
175         : par_(MathAtom(new MathMatrixInset))
176 {}
177
178
179 InsetFormula::InsetFormula(MathInsetTypes t)
180         : par_(MathAtom(new MathMatrixInset(t)))
181 {}
182
183
184 InsetFormula::InsetFormula(string const & s) 
185 {
186         if (s.size()) {
187                 bool res = mathed_parse_normal(par_, s);
188
189                 if (!res)
190                         res = mathed_parse_normal(par_, "$" + s + "$");
191
192                 if (!res) {
193                         lyxerr << "cannot interpret '" << s << "' as math\n";
194                         par_ = MathAtom(new MathMatrixInset(LM_OT_SIMPLE));
195                 }
196         }
197         metrics();
198 }
199
200
201 Inset * InsetFormula::clone(Buffer const &, bool) const
202 {
203         return new InsetFormula(*this);
204 }
205
206
207 void InsetFormula::write(Buffer const * buf, ostream & os) const
208 {
209         os << "Formula ";
210         latex(buf, os, false, false);
211 }
212
213
214 int InsetFormula::latex(Buffer const * buf, ostream & os, bool fragil, bool)
215         const
216 {
217         MathWriteInfo wi(buf, os, fragil);
218         par_->write(wi);
219         return 1;
220 }
221
222
223 int InsetFormula::ascii(Buffer const * buf, ostream & os, int) const
224 {
225         MathWriteInfo wi(buf, os, false);
226         par_->write(wi);
227         return 1;
228 }
229
230
231 int InsetFormula::linuxdoc(Buffer const * buf, ostream & os) const
232 {
233         return ascii(buf, os, 0);
234 }
235
236
237 int InsetFormula::docbook(Buffer const * buf, ostream & os) const
238 {
239         return ascii(buf, os, 0);
240 }
241
242
243 void InsetFormula::read(Buffer const *, LyXLex & lex)
244 {
245         mathed_parse_normal(par_, lex);
246         metrics();
247 }
248
249
250 void InsetFormula::draw(BufferView * bv, LyXFont const & font,
251                         int y, float & xx, bool) const
252 {
253         int x = int(xx) - 1;
254         y -= 2;
255
256         Painter & pain = bv->painter();
257
258         metrics(bv, font);
259         int w = par_->width();
260         int h = par_->height();
261         int a = par_->ascent();
262         pain.fillRectangle(x, y - a, w, h, LColor::mathbg);
263
264         if (mathcursor && mathcursor->formula() == this) {
265                 mathcursor->drawSelection(pain);
266                 pain.rectangle(x, y - a, w, h, LColor::mathframe);
267         }
268
269         par_->draw(pain, x, y);
270         xx += par_->width();
271         xo_ = x;
272         yo_ = y;
273
274         setCursorVisible(false);
275 }
276
277
278 vector<string> const InsetFormula::getLabelList() const
279 {
280         return mat()->getLabelList();
281 }
282
283
284 UpdatableInset::RESULT
285 InsetFormula::localDispatch(BufferView * bv, kb_action action,
286          string const & arg)
287 {
288         RESULT result = DISPATCHED;
289
290         switch (action) {
291
292                 case LFUN_BREAKLINE: 
293                         bv->lockedInsetStoreUndo(Undo::INSERT);
294                         mathcursor->breakLine();
295                         mathcursor->normalize();
296                         updateLocal(bv, true);
297                         break;
298
299                 case LFUN_MATH_NUMBER:
300                 {
301                         //lyxerr << "toggling all numbers\n";
302                         if (display()) {
303                                 bv->lockedInsetStoreUndo(Undo::INSERT);
304                                 bool old = mat()->numberedType();
305                                 for (MathInset::row_type row = 0; row < par_->nrows(); ++row)
306                                         mat()->numbered(row, !old);
307                                 bv->owner()->message(old ? _("No number") : _("Number"));
308                                 updateLocal(bv, true);
309                         }
310                         break;
311                 }
312
313                 case LFUN_MATH_NONUMBER:
314                 {
315                         //lyxerr << "toggling line number\n";
316                         if (display()) {
317                                 bv->lockedInsetStoreUndo(Undo::INSERT);
318                                 MathCursor::row_type row = mathcursor->row();
319                                 bool old = mat()->numbered(row);
320                                 bv->owner()->message(old ? _("No number") : _("Number"));
321                                 mat()->numbered(row, !old);
322                                 updateLocal(bv, true);
323                         }
324                         break;
325                 }
326
327                 case LFUN_INSERT_LABEL:
328                 {
329                         bv->lockedInsetStoreUndo(Undo::INSERT);
330
331                         MathCursor::row_type row = mathcursor->row();
332                         string old_label = mat()->label(row);
333                         string new_label = arg;
334
335                         if (new_label.empty()) {
336                                 string const default_label =
337                                         (lyxrc.label_init_length >= 0) ? "eq:" : "";
338                                 pair<bool, string> const res = old_label.empty()
339                                         ? askForText(_("Enter new label to insert:"), default_label)
340                                         : askForText(_("Enter label:"), old_label);
341                                 
342                                 lyxerr << "res: " << res.first << " - '" << res.second << "'\n";
343                                 if (!res.first)
344                                         break;
345                                 new_label = frontStrip(strip(res.second));
346                         }
347
348                         //if (new_label == old_label)
349                         //      break;  // Nothing to do
350
351                         if (!new_label.empty()) {
352                                 lyxerr << "setting label to '" << new_label << "'\n";
353                                 mat()->numbered(row, true);
354                         }
355
356                         if (!new_label.empty() && bv->ChangeRefsIfUnique(old_label, new_label))
357                                 bv->redraw();
358
359                         mat()->label(row, new_label);
360
361                         updateLocal(bv, true);
362                         break;
363                 }
364
365                 case LFUN_MATH_MUTATE:
366                 {
367                         bv->lockedInsetStoreUndo(Undo::EDIT);
368                         int x;
369                         int y;
370                         mathcursor->getPos(x, y);
371                         mat()->mutate(arg);
372                         mathcursor->setPos(x, y);
373                         mathcursor->normalize();
374                         updateLocal(bv, true);
375                         break;
376                 }
377
378                 case LFUN_MATH_EXTERN:
379                 {
380                         bv->lockedInsetStoreUndo(Undo::EDIT);
381                         handleExtern(arg);
382                         // re-compute inset dimension
383                         metrics(bv);
384                         updateLocal(bv, true);
385                         break;
386                 }
387
388                 case LFUN_MATH_DISPLAY:
389                 {
390                         int x;
391                         int y;
392                         mathcursor->getPos(x, y);
393                         if (mat()->getType() == LM_OT_SIMPLE)
394                                 mat()->mutate(LM_OT_EQUATION);
395                         else
396                                 mat()->mutate(LM_OT_SIMPLE);
397                         mathcursor->setPos(x, y);
398                         mathcursor->normalize();
399                         updateLocal(bv, true);
400                         break;
401                 }
402                 
403                 case LFUN_PASTESELECTION:
404                 {
405                         string const clip = bv->getClipboard();
406                 if (!clip.empty())
407                                 mathed_parse_normal(par_, clip);
408                         break;
409                 }
410
411                 case LFUN_MATH_COLUMN_INSERT:
412                 {
413                         if (mat()->getType() == LM_OT_ALIGN)
414                                 mat()->mutate(LM_OT_ALIGNAT);
415                         mat()->addCol(mat()->ncols());
416                         mathcursor->normalize();
417                         updateLocal(bv, true);
418                 }
419
420                 default:
421                         result = InsetFormulaBase::localDispatch(bv, action, arg);
422         }
423
424         return result;
425 }
426
427
428 void InsetFormula::handleExtern(const string & arg)
429 {
430         // where are we?
431         if (!mathcursor)
432                 return; 
433
434         bool selected = mathcursor->selection();
435
436         MathArray ar;
437         if (selected) {
438                 mathcursor->selGet(ar);
439                 lyxerr << "use selection: " << ar << "\n";
440         } else {
441                 mathcursor->end();
442                 ar = mathcursor->cursor().cell();
443                 stripFromLastEqualSign(ar);
444                 mathcursor->insert(MathAtom(new MathCharInset('=', LM_TC_VAR)));
445                 //lyxerr << "use whole cell: " << ar << "\n";
446         }
447
448         mathcursor->insert(pipeThroughExtern(arg, ar));
449 }
450
451
452 bool InsetFormula::display() const
453 {
454         return mat()->getType() != LM_OT_SIMPLE;
455 }
456
457
458 MathMatrixInset const * InsetFormula::mat() const
459 {
460         lyx::Assert(par_->asMatrixInset());
461         return par_->asMatrixInset();
462 }
463
464
465 MathMatrixInset * InsetFormula::mat()
466 {
467         lyx::Assert(par_->asMatrixInset());
468         return par_->asMatrixInset();
469 }
470
471
472 Inset::Code InsetFormula::lyxCode() const
473 {
474         return Inset::MATH_CODE;
475 }
476
477
478 void InsetFormula::validate(LaTeXFeatures & features) const
479 {
480         par_->validate(features);
481 }
482
483
484 bool InsetFormula::insetAllowed(Inset::Code code) const
485 {
486         return code == Inset::LABEL_CODE && display(); 
487 }
488
489
490 int InsetFormula::ascent(BufferView *, LyXFont const &) const
491 {
492         return par_->ascent() + 2;
493 }
494
495
496 int InsetFormula::descent(BufferView *, LyXFont const &) const
497 {
498         return par_->descent() - 2;
499 }
500
501
502 int InsetFormula::width(BufferView * bv, LyXFont const & font) const
503 {
504         metrics(bv, font);
505         return par_->width();
506 }
507
508
509 MathInsetTypes InsetFormula::getType() const
510 {
511         return mat()->getType();
512 }