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