]> git.lyx.org Git - lyx.git/blob - src/mathed/formula.C
Fix retrun value of InsetFormula::latex
[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 "math_arrayinset.h"
28 #include "math_deliminset.h"
29 #include "lyx_main.h"
30 #include "BufferView.h"
31 #include "gettext.h"
32 #include "debug.h"
33 #include "lyx_gui_misc.h"
34 #include "support/LOstream.h"
35 #include "support/LAssert.h"
36 #include "support/lyxlib.h"
37 #include "support/syscall.h"
38 #include "support/lstrings.h"
39 #include "support/filetools.h" // LibFileSearch
40 #include "LyXView.h"
41 #include "Painter.h"
42 #include "lyxrc.h"
43 #include "math_hullinset.h"
44 #include "math_support.h"
45 #include "math_mathmlstream.h"
46
47 using std::ostream;
48 using std::ifstream;
49 using std::istream;
50 using std::pair;
51 using std::endl;
52 using std::vector;
53
54
55 namespace {
56
57         string captureOutput(string const & cmd, string const & data)
58         {
59                 string outfile = lyx::tempName(string(), "mathextern");
60                 string full =  "echo '" + data + "' | (" + cmd + ") > " + outfile;
61                 lyxerr << "calling: " << full << "\n";
62                 Systemcalls dummy(Systemcalls::System, full, 0);
63                 string out = GetFileContents(outfile);
64                 lyx::unlink(outfile);
65                 lyxerr << "result: '" << out << "'\n";
66                 return out;
67         }
68
69
70         MathArray pipeThroughMaple(string const & extra, MathArray const & ar)
71         {
72                 string header = "readlib(latex):\n";
73
74                 // remove the \\it for variable names
75                 //"#`latex/csname_font` := `\\it `:"
76                 header +=
77                         "`latex/csname_font` := ``:\n";
78
79                 // export matrices in (...) instead of [...]
80                 header +=
81                         "`latex/latex/matrix` := "
82                                 "subs(`[`=`(`, `]`=`)`,"
83                                         "eval(`latex/latex/matrix`)):\n";
84
85                 // replace \\cdots with proper '*'
86                 header +=
87                         "`latex/latex/*` := "
88                                 "subs(`\\,`=`\\cdot `,"
89                                         "eval(`latex/latex/*`)):\n";
90
91                 // remove spurious \\noalign{\\medskip} in matrix output
92                 header += 
93                         "`latex/latex/matrix`:= "
94                                 "subs(`\\\\\\\\\\\\noalign{\\\\medskip}` = `\\\\\\\\`,"
95                                         "eval(`latex/latex/matrix`)):\n";
96
97                 //"#`latex/latex/symbol` "
98                 //      " := subs((\\'_\\' = \\'`\\_`\\',eval(`latex/latex/symbol`)): ";
99
100                 string trailer = "quit;";
101                 ostringstream os;
102                 MapleStream ms(os);
103                 ms << ar;
104                 string expr = os.str().c_str();
105
106                 for (int i = 0; i < 100; ++i) { // at most 100 attempts
107                         // try to fix missing '*' the hard way by using mint
108                         //
109                         // ... > echo "1A;" | mint -i 1 -S -s -q
110                         // on line     1: 1A;
111                         //                 ^ syntax error - 
112                         //                   Probably missing an operator such as * p
113                         //
114                         lyxerr << "checking expr: '" << expr << "'\n";
115                         string out = captureOutput("mint -i 1 -S -s -q -q", expr + ";");
116                         if (out.empty())
117                                 break; // expression syntax is ok
118                         istringstream is(out.c_str());
119                         string line;
120                         getline(is, line);
121                         if (line.find("on line") != 0)
122                                 break; // error message not identified
123                         getline(is, line);
124                         string::size_type pos = line.find('^');
125                         if (pos == string::npos || pos < 15)
126                                 break; // caret position not found
127                         pos -= 15; // skip the "on line ..." part
128                         if (expr[pos] == '*' || (pos > 0 && expr[pos - 1] == '*'))
129                                 break; // two '*' in a row are definitely bad
130                         expr.insert(pos,  "*");
131                 }
132
133                 string full = "latex(" +  extra + '(' + expr + "));";
134                 string out = captureOutput("maple -q", header + full + trailer);
135
136                 // change \_ into _
137
138                 //
139                 MathArray res;
140                 mathed_parse_cell(res, out);
141                 return res;
142         }
143                 
144         
145         MathArray pipeThroughOctave(string const &, MathArray const & ar)
146         {
147                 ostringstream os;
148                 OctaveStream vs(os);
149                 vs << ar;
150                 string expr = os.str().c_str();
151                 string out;
152
153                 for (int i = 0; i < 100; ++i) { // at most 100 attempts
154                         //
155                         // try to fix missing '*' the hard way 
156                         // parse error:
157                         // >>> ([[1 2 3 ];[2 3 1 ];[3 1 2 ]])([[1 2 3 ];[2 3 1 ];[3 1 2 ]])
158                         //                                   ^
159                         //
160                         lyxerr << "checking expr: '" << expr << "'\n";
161                         out = captureOutput("octave -q 2>&1", expr);
162                         lyxerr << "checking out: '" << out << "'\n";
163
164                         // leave loop if expression syntax is probably ok
165                         if (out.find("parse error:") == string::npos)
166                                 break;
167
168                         // search line with single caret
169                         istringstream is(out.c_str());
170                         string line;
171                         while (is) {
172                                 getline(is, line);
173                                 lyxerr << "skipping line: '" << line << "'\n";
174                                 if (line.find(">>> ") != string::npos)
175                                         break;
176                         }
177
178                         // found line with error, next line is the one with caret
179                         getline(is, line);
180                         string::size_type pos = line.find('^');
181                         lyxerr << "caret line: '" << line << "'\n";
182                         lyxerr << "found caret at pos: '" << pos << "'\n";
183                         if (pos == string::npos || pos < 4)
184                                 break; // caret position not found
185                         pos -= 4; // skip the ">>> " part
186                         if (expr[pos] == '*')
187                                 break; // two '*' in a row are definitely bad
188                         expr.insert(pos,  "*");
189                 }
190
191                 if (out.size() < 6)
192                         return MathArray();
193
194                 // remove 'ans = '
195                 out = out.substr(6);
196
197                 // parse output as matrix or single number
198                 MathAtom at(new MathArrayInset(out));
199                 MathArrayInset const * mat = at.nucleus()->asArrayInset();
200                 MathArray res;
201                 if (mat->ncols() == 1 && mat->nrows() == 1)
202                         res.push_back(mat->cell(0));
203                 else {
204                         res.push_back(MathAtom(new MathDelimInset("(", ")")));
205                         res.back()->cell(0).push_back(at);
206                 }
207                 return res;
208         }
209
210
211         MathArray pipeThroughExtern(string const & lang, string const & extra,
212                 MathArray const & ar)
213         {
214                 if (lang == "octave")
215                         return pipeThroughOctave(extra, ar);
216
217                 if (lang == "maple")
218                         return pipeThroughMaple(extra, ar);
219
220                 // create normalized expression
221                 ostringstream os;
222                 NormalStream ns(os);
223                 os << "[" << extra << ' ';
224                 ns << ar;
225                 os << "]";
226                 string data = os.str().c_str();
227
228                 // search external script
229                 string file = LibFileSearch("mathed", "extern_" + lang);
230                 if (file.empty()) {
231                         lyxerr << "converter to '" << lang << "' not found\n";
232                         return MathArray();
233                 }
234                 
235                 // run external sript
236                 string out = captureOutput(file, data);
237                 MathArray res;
238                 mathed_parse_cell(res, out);
239                 return res;
240         }
241
242 }
243
244
245 InsetFormula::InsetFormula()
246         : par_(MathAtom(new MathHullInset))
247 {}
248
249
250 InsetFormula::InsetFormula(MathInsetTypes t)
251         : par_(MathAtom(new MathHullInset(t)))
252 {}
253
254
255 InsetFormula::InsetFormula(string const & s) 
256 {
257         if (s.size()) {
258                 bool res = mathed_parse_normal(par_, s);
259
260                 if (!res)
261                         res = mathed_parse_normal(par_, "$" + s + "$");
262
263                 if (!res) {
264                         lyxerr << "cannot interpret '" << s << "' as math\n";
265                         par_ = MathAtom(new MathHullInset(LM_OT_SIMPLE));
266                 }
267         }
268         metrics();
269 }
270
271
272 Inset * InsetFormula::clone(Buffer const &, bool) const
273 {
274         return new InsetFormula(*this);
275 }
276
277
278 void InsetFormula::write(Buffer const * buf, ostream & os) const
279 {
280         os << "Formula ";
281         latex(buf, os, false, false);
282 }
283
284
285 int InsetFormula::latex(Buffer const * buf, ostream & os, bool fragil, bool)
286         const
287 {
288         WriteStream wi(buf, os, fragil);
289         par_->write(wi);
290         return wi.line_;
291 }
292
293
294 int InsetFormula::ascii(Buffer const * buf, ostream & os, int) const
295 {
296         WriteStream wi(buf, os, false);
297         par_->write(wi);
298         return wi.line_;
299 }
300
301
302 int InsetFormula::linuxdoc(Buffer const * buf, ostream & os) const
303 {
304         return docbook(buf, os);
305 }
306
307
308 int InsetFormula::docbook(Buffer const * buf, ostream & os) const
309 {
310         MathMLStream ms(os);
311         ms << MTag("equation") << MTag("alt");
312         int res = ascii(buf, ms.os_, 0);
313         ms << ETag("alt") << MTag("math");
314         ms << par_.nucleus();
315         ms << ETag("math") << ETag("equation");
316         return ms.line_ + res;
317 }
318
319
320 void InsetFormula::read(Buffer const *, LyXLex & lex)
321 {
322         mathed_parse_normal(par_, lex);
323         metrics();
324 }
325
326
327 void InsetFormula::draw(BufferView * bv, LyXFont const & font,
328                         int y, float & xx, bool) const
329 {
330         int x = int(xx) - 1;
331         y -= 2;
332
333         Painter & pain = bv->painter();
334
335         metrics(bv, font);
336         int w = par_->width();
337         int h = par_->height();
338         int a = par_->ascent();
339         pain.fillRectangle(x, y - a, w, h, LColor::mathbg);
340
341         if (mathcursor && mathcursor->formula() == this) {
342                 mathcursor->drawSelection(pain);
343                 pain.rectangle(x, y - a, w, h, LColor::mathframe);
344         }
345
346         par_->draw(pain, x, y);
347         xx += par_->width();
348         xo_ = x;
349         yo_ = y;
350
351         setCursorVisible(false);
352 }
353
354
355 vector<string> const InsetFormula::getLabelList() const
356 {
357         return mat()->getLabelList();
358 }
359
360
361 UpdatableInset::RESULT
362 InsetFormula::localDispatch(BufferView * bv, kb_action action,
363          string const & arg)
364 {
365         RESULT result = DISPATCHED;
366
367         switch (action) {
368
369                 case LFUN_BREAKLINE: 
370                         bv->lockedInsetStoreUndo(Undo::INSERT);
371                         mathcursor->breakLine();
372                         mathcursor->normalize();
373                         updateLocal(bv, true);
374                         break;
375
376                 case LFUN_MATH_NUMBER:
377                 {
378                         //lyxerr << "toggling all numbers\n";
379                         if (display()) {
380                                 bv->lockedInsetStoreUndo(Undo::INSERT);
381                                 bool old = mat()->numberedType();
382                                 for (MathInset::row_type row = 0; row < par_->nrows(); ++row)
383                                         mat()->numbered(row, !old);
384                                 bv->owner()->message(old ? _("No number") : _("Number"));
385                                 updateLocal(bv, true);
386                         }
387                         break;
388                 }
389
390                 case LFUN_MATH_NONUMBER:
391                 {
392                         //lyxerr << "toggling line number\n";
393                         if (display()) {
394                                 bv->lockedInsetStoreUndo(Undo::INSERT);
395                                 MathCursor::row_type row = mathcursor->row();
396                                 bool old = mat()->numbered(row);
397                                 bv->owner()->message(old ? _("No number") : _("Number"));
398                                 mat()->numbered(row, !old);
399                                 updateLocal(bv, true);
400                         }
401                         break;
402                 }
403
404                 case LFUN_INSERT_LABEL:
405                 {
406                         bv->lockedInsetStoreUndo(Undo::INSERT);
407
408                         MathCursor::row_type row = mathcursor->row();
409                         string old_label = mat()->label(row);
410                         string new_label = arg;
411
412                         if (new_label.empty()) {
413                                 string const default_label =
414                                         (lyxrc.label_init_length >= 0) ? "eq:" : "";
415                                 pair<bool, string> const res = old_label.empty()
416                                         ? askForText(_("Enter new label to insert:"), default_label)
417                                         : askForText(_("Enter label:"), old_label);
418                                 
419                                 lyxerr << "res: " << res.first << " - '" << res.second << "'\n";
420                                 if (!res.first)
421                                         break;
422                                 new_label = frontStrip(strip(res.second));
423                         }
424
425                         //if (new_label == old_label)
426                         //      break;  // Nothing to do
427
428                         if (!new_label.empty()) {
429                                 lyxerr << "setting label to '" << new_label << "'\n";
430                                 mat()->numbered(row, true);
431                         }
432
433                         if (!new_label.empty() && bv->ChangeRefsIfUnique(old_label, new_label))
434                                 bv->redraw();
435
436                         mat()->label(row, new_label);
437
438                         updateLocal(bv, true);
439                         break;
440                 }
441
442                 case LFUN_MATH_MUTATE:
443                 {
444                         bv->lockedInsetStoreUndo(Undo::EDIT);
445                         int x;
446                         int y;
447                         mathcursor->getPos(x, y);
448                         mat()->mutate(arg);
449                         mathcursor->setPos(x, y);
450                         mathcursor->normalize();
451                         updateLocal(bv, true);
452                         break;
453                 }
454
455                 case LFUN_MATH_EXTERN:
456                 {
457                         bv->lockedInsetStoreUndo(Undo::EDIT);
458                         handleExtern(arg);
459                         // re-compute inset dimension
460                         metrics(bv);
461                         updateLocal(bv, true);
462                         break;
463                 }
464
465                 case LFUN_MATH_DISPLAY:
466                 {
467                         int x = 0;
468                         int y = 0;
469                         mathcursor->getPos(x, y);
470                         if (mat()->getType() == LM_OT_SIMPLE)
471                                 mat()->mutate(LM_OT_EQUATION);
472                         else
473                                 mat()->mutate(LM_OT_SIMPLE);
474                         mathcursor->setPos(x, y);
475                         mathcursor->normalize();
476                         updateLocal(bv, true);
477                         break;
478                 }
479                 
480                 case LFUN_PASTESELECTION:
481                 {
482                         string const clip = bv->getClipboard();
483                 if (!clip.empty())
484                                 mathed_parse_normal(par_, clip);
485                         break;
486                 }
487
488                 case LFUN_MATH_COLUMN_INSERT:
489                 {
490                         if (mat()->getType() == LM_OT_ALIGN)
491                                 mat()->mutate(LM_OT_ALIGNAT);
492                         mat()->addCol(mat()->ncols());
493                         mathcursor->normalize();
494                         updateLocal(bv, true);
495                         break;
496                 }
497
498                 default:
499                         result = InsetFormulaBase::localDispatch(bv, action, arg);
500         }
501
502         return result;
503 }
504
505
506 bool needEqnArray(string const & extra)
507 {
508         return false;
509         return extra == "dsolve";
510 }
511
512
513 void InsetFormula::handleExtern(const string & arg)
514 {
515         // where are we?
516         if (!mathcursor)
517                 return; 
518
519         string lang;
520         string extra;
521         istringstream iss(arg.c_str());
522         iss >> lang >> extra;
523         if (extra.empty())
524                 extra = "noextra";      
525
526         bool selected = mathcursor->selection();
527
528         MathArray ar;
529         if (needEqnArray(extra)) {
530                 mathcursor->last();
531                 mathcursor->readLine(ar);
532                 mathcursor->breakLine();
533         } else if (selected) {
534                 mathcursor->selGet(ar);
535                 //lyxerr << "use selection: " << ar << "\n";
536         } else {
537                 mathcursor->last();
538                 mathcursor->stripFromLastEqualSign();
539                 ar = mathcursor->cursor().cell();
540                 mathcursor->insert(MathAtom(new MathCharInset('=', LM_TC_VAR)));
541                 //lyxerr << "use whole cell: " << ar << "\n";
542         }
543
544         mathcursor->insert(pipeThroughExtern(lang, extra, ar));
545 }
546
547
548 bool InsetFormula::display() const
549 {
550         return mat()->getType() != LM_OT_SIMPLE;
551 }
552
553
554 MathHullInset const * InsetFormula::mat() const
555 {
556         lyx::Assert(par_->asHullInset());
557         return par_->asHullInset();
558 }
559
560
561 MathHullInset * InsetFormula::mat()
562 {
563         lyx::Assert(par_->asHullInset());
564         return par_->asHullInset();
565 }
566
567
568 Inset::Code InsetFormula::lyxCode() const
569 {
570         return Inset::MATH_CODE;
571 }
572
573
574 void InsetFormula::validate(LaTeXFeatures & features) const
575 {
576         par_->validate(features);
577 }
578
579
580 bool InsetFormula::insetAllowed(Inset::Code code) const
581 {
582         return 
583                 (code == Inset::LABEL_CODE && display())
584                 || code == Inset::ERT_CODE; 
585 }
586
587
588 int InsetFormula::ascent(BufferView *, LyXFont const &) const
589 {
590         return par_->ascent() + 2;
591 }
592
593
594 int InsetFormula::descent(BufferView *, LyXFont const &) const
595 {
596         return par_->descent() - 2;
597 }
598
599
600 int InsetFormula::width(BufferView * bv, LyXFont const & font) const
601 {
602         metrics(bv, font);
603         return par_->width();
604 }
605
606
607 MathInsetTypes InsetFormula::getType() const
608 {
609         return mat()->getType();
610 }