]> git.lyx.org Git - lyx.git/blob - src/mathed/formula.C
3b0f22fc8645372a5016327a352192b3e149975e
[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 "support/LOstream.h"
34 #include "support/LAssert.h"
35 #include "support/lyxlib.h"
36 #include "support/systemcall.h"
37 #include "support/filetools.h"
38 #include "frontends/Alert.h"
39 #include "frontends/LyXView.h"
40 #include "frontends/Painter.h"
41 #include "graphics/GraphicsImage.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 #include <fstream>
49 #include <boost/bind.hpp>
50 #include <boost/utility.hpp>
51
52 using std::ostream;
53 using std::ifstream;
54 using std::istream;
55 using std::pair;
56 using std::endl;
57 using std::vector;
58 using std::getline;
59
60
61
62 InsetFormula::InsetFormula()
63         : par_(MathAtom(new MathHullInset)), loader_(0)
64 {}
65
66 /*
67
68 InsetFormula::InsetFormula(string const & type)
69         : par_(MathAtom(new MathHullInset(type))), loader_(0)
70 {}
71
72
73 */
74
75
76 Inset * InsetFormula::clone(Buffer const &, bool) const
77 {
78         return new InsetFormula(*this);
79 }
80
81
82 void InsetFormula::write(Buffer const *, ostream & os) const
83 {
84         os << "Formula ";
85         WriteStream wi(os, false, false);
86         par_->write(wi);
87 }
88
89
90 int InsetFormula::latex(Buffer const *, ostream & os, bool fragile, bool) const
91 {
92         WriteStream wi(os, fragile, true);
93         par_->write(wi);
94         return wi.line();
95 }
96
97
98 void InsetFormula::read(string const & s)
99 {
100         if (s.size()) {
101                 bool res = mathed_parse_normal(par_, s);
102
103                 if (!res)
104                         res = mathed_parse_normal(par_, "$" + s + "$");
105
106                 if (!res) {
107                         lyxerr << "cannot interpret '" << s << "' as math\n";
108                         par_ = MathAtom(new MathHullInset("simple"));
109                 }
110         }
111         metrics();
112         updatePreview();
113 }
114
115
116 int InsetFormula::ascii(Buffer const *, ostream & os, int) const
117 {
118 #if 0
119         TextMetricsInfo mi;
120         par()->metricsT(mi);
121         TextPainter tpain(par()->width(), par()->height());
122         par()->drawT(tpain, 0, par()->ascent());
123         tpain.show(os);
124         // reset metrics cache to "real" values
125         metrics();
126         return tpain.textheight();
127 #else
128         WriteStream wi(os, false, true);
129         par_->write(wi);
130         return wi.line();
131 #endif
132 }
133
134
135 int InsetFormula::linuxdoc(Buffer const * buf, ostream & os) const
136 {
137         return docbook(buf, os, false);
138 }
139
140
141 int InsetFormula::docbook(Buffer const * buf, ostream & os, bool) const
142 {
143         MathMLStream ms(os);
144         ms << MTag("equation");
145         ms <<   MTag("alt");
146         ms <<    "<[CDATA[";
147         int res = ascii(buf, ms.os(), 0);
148         ms <<    "]]>";
149         ms <<   ETag("alt");
150         ms <<   MTag("math");
151         ms <<    par_.nucleus();
152         ms <<   ETag("math");
153         ms << ETag("equation");
154         return ms.line() + res;
155 }
156
157
158 void InsetFormula::read(Buffer const *, LyXLex & lex)
159 {
160         mathed_parse_normal(par_, lex);
161         metrics();
162         updatePreview();
163 }
164
165
166 //ostream & operator<<(ostream & os, LyXCursor const & c)
167 //{
168 //      os << '[' << c.x() << ' ' << c.y() << ' ' << c.pos() << ']';
169 //      return os;
170 //}
171
172
173 void InsetFormula::draw(BufferView * bv, LyXFont const & font,
174                         int y, float & xx, bool) const
175 {
176         int const x = int(xx);
177         int const w = width(bv, font);
178         int const d = descent(bv, font);
179         int const a = ascent(bv, font);
180         int const h = a + d;
181
182         MathPainterInfo pi(bv->painter());
183
184         if (canPreview()) {
185                 pi.pain.image(x + 1, y - a + 1, w - 2, h - 2, *(loader_->image()));
186         } else {
187                 pi.base.style = display() ? LM_ST_DISPLAY : LM_ST_TEXT;
188                 pi.base.font  = font;
189                 pi.base.font.setColor(LColor::math);
190                 if (lcolor.getX11Name(LColor::mathbg)
191                             != lcolor.getX11Name(LColor::background))
192                         pi.pain.fillRectangle(x, y - a, w, h, LColor::mathbg);
193
194                 if (mathcursor &&
195                                 const_cast<InsetFormulaBase const *>(mathcursor->formula()) == this)
196                 {
197                         mathcursor->drawSelection(pi);
198                         pi.pain.rectangle(x, y - a, w, h, LColor::mathframe);
199                 }
200
201                 par_->draw(pi, x, y);
202         }
203
204         xx += w;
205         xo_ = x;
206         yo_ = y;
207
208         setCursorVisible(false);
209 }
210
211
212 vector<string> const InsetFormula::getLabelList() const
213 {
214         return hull()->getLabelList();
215 }
216
217
218 UpdatableInset::RESULT
219 InsetFormula::localDispatch(BufferView * bv, kb_action action,
220          string const & arg)
221 {
222         RESULT result = DISPATCHED;
223
224         switch (action) {
225
226                 case LFUN_BREAKLINE:
227                         bv->lockedInsetStoreUndo(Undo::INSERT);
228                         mathcursor->breakLine();
229                         mathcursor->normalize();
230                         updateLocal(bv, true);
231                         break;
232
233                 case LFUN_MATH_NUMBER:
234                 {
235                         //lyxerr << "toggling all numbers\n";
236                         if (display()) {
237                                 bv->lockedInsetStoreUndo(Undo::INSERT);
238                                 bool old = hull()->numberedType();
239                                 for (MathInset::row_type row = 0; row < par_->nrows(); ++row)
240                                         hull()->numbered(row, !old);
241                                 bv->owner()->message(old ? _("No number") : _("Number"));
242                                 updateLocal(bv, true);
243                         }
244                         break;
245                 }
246
247                 case LFUN_MATH_NONUMBER:
248                 {
249                         //lyxerr << "toggling line number\n";
250                         if (display()) {
251                                 bv->lockedInsetStoreUndo(Undo::INSERT);
252                                 MathCursor::row_type row = mathcursor->hullRow();
253                                 bool old = hull()->numbered(row);
254                                 bv->owner()->message(old ? _("No number") : _("Number"));
255                                 hull()->numbered(row, !old);
256                                 updateLocal(bv, true);
257                         }
258                         break;
259                 }
260
261                 case LFUN_INSERT_LABEL:
262                 {
263                         bv->lockedInsetStoreUndo(Undo::INSERT);
264
265                         MathCursor::row_type row = mathcursor->hullRow();
266                         string old_label = hull()->label(row);
267                         string new_label = arg;
268
269                         if (new_label.empty()) {
270                                 string const default_label =
271                                         (lyxrc.label_init_length >= 0) ? "eq:" : "";
272                                 pair<bool, string> const res = old_label.empty()
273                                         ? Alert::askForText(_("Enter new label to insert:"), default_label)
274                                         : Alert::askForText(_("Enter label:"), old_label);
275                                 if (!res.first)
276                                         break;
277                                 new_label = frontStrip(strip(res.second));
278                         }
279
280                         //if (new_label == old_label)
281                         //      break;  // Nothing to do
282
283                         if (!new_label.empty()) {
284                                 lyxerr << "setting label to '" << new_label << "'\n";
285                                 hull()->numbered(row, true);
286                         }
287
288 #warning FIXME: please check you really mean repaint() ... is it needed,
289 #warning and if so, should it be update() instead ? 
290                         if (!new_label.empty() && bv->ChangeRefsIfUnique(old_label, new_label))
291                                 bv->repaint();
292
293                         hull()->label(row, new_label);
294
295                         updateLocal(bv, true);
296                         break;
297                 }
298
299                 case LFUN_MATH_MUTATE:
300                 {
301                         bv->lockedInsetStoreUndo(Undo::EDIT);
302                         int x;
303                         int y;
304                         mathcursor->getPos(x, y);
305                         hull()->mutate(arg);
306                         mathcursor->setPos(x, y);
307                         mathcursor->normalize();
308                         updateLocal(bv, true);
309                         break;
310                 }
311
312                 case LFUN_MATH_EXTERN:
313                 {
314                         bv->lockedInsetStoreUndo(Undo::EDIT);
315                         if (mathcursor)
316                                 mathcursor->handleExtern(arg);
317                         // re-compute inset dimension
318                         metrics(bv);
319                         updateLocal(bv, true);
320                         break;
321                 }
322
323                 case LFUN_MATH_DISPLAY:
324                 {
325                         int x = 0;
326                         int y = 0;
327                         mathcursor->getPos(x, y);
328                         if (hullType() == "simple")
329                                 hull()->mutate("equation");
330                         else
331                                 hull()->mutate("simple");
332                         mathcursor->setPos(x, y);
333                         mathcursor->normalize();
334                         updateLocal(bv, true);
335                         break;
336                 }
337
338                 case LFUN_PASTESELECTION:
339                 {
340                         string const clip = bv->getClipboard();
341                 if (!clip.empty())
342                                 mathed_parse_normal(par_, clip);
343                         break;
344                 }
345
346                 default:
347                         result = InsetFormulaBase::localDispatch(bv, action, arg);
348         }
349
350         //updatePreview();
351
352         return result;
353 }
354
355
356 bool InsetFormula::display() const
357 {
358         return hullType() != "simple" && hullType() != "none";
359 }
360
361
362 MathHullInset const * InsetFormula::hull() const
363 {
364         lyx::Assert(par_->asHullInset());
365         return par_->asHullInset();
366 }
367
368
369 MathHullInset * InsetFormula::hull()
370 {
371         lyx::Assert(par_->asHullInset());
372         return par_->asHullInset();
373 }
374
375
376 Inset::Code InsetFormula::lyxCode() const
377 {
378         return Inset::MATH_CODE;
379 }
380
381
382 void InsetFormula::validate(LaTeXFeatures & features) const
383 {
384         par_->validate(features);
385 }
386
387
388 bool InsetFormula::insetAllowed(Inset::Code code) const
389 {
390         return
391                 (code == Inset::LABEL_CODE && display())
392                 || code == Inset::REF_CODE      
393                 || code == Inset::ERT_CODE;
394 }
395
396
397 int InsetFormula::ascent(BufferView *, LyXFont const &) const
398 {
399         const int a = par_->ascent();
400         if (!canPreview())
401                 return a + 1;
402         return a + 1 - (par_->height() - loader_->image()->getHeight()) / 2;
403 }
404
405
406 int InsetFormula::descent(BufferView *, LyXFont const &) const
407 {
408         const int d = par_->descent();
409         if (!canPreview())
410                 return d + 1;
411         return d + 1 - (par_->height() - loader_->image()->getHeight()) / 2;
412 }
413
414
415 int InsetFormula::width(BufferView * bv, LyXFont const & font) const
416 {
417         metrics(bv, font);
418         return canPreview() ? loader_->image()->getWidth() : par_->width();
419 }
420
421
422 string const & InsetFormula::hullType() const
423 {
424         return hull()->getType();
425 }
426
427
428 void InsetFormula::mutate(string const & type )
429 {
430         hull()->mutate(type);
431 }
432
433
434 //
435 // preview stuff
436 //
437
438 bool InsetFormula::canPreview() const
439 {
440         return lyxrc.preview && loader_ && !par_->asNestInset()->editing()
441                 && loader_->status() == grfx::Ready;
442 }
443
444
445 void InsetFormula::statusChanged()
446 {
447         lyxerr << "### InsetFormula::statusChanged called!, status: "
448                 << loader_->status() << "\n";
449         if (loader_->status() == grfx::Ready) 
450                 view()->updateInset(this, false);
451         else if (loader_->status() == grfx::WaitingToLoad)
452                 loader_->startLoading();
453 }
454
455
456 void InsetFormula::updatePreview()
457 {
458         // nothing to be done if no preview requested
459         lyxerr << "### updatePreview() called\n";
460         if (!lyxrc.preview)
461                 return;
462
463         // get LaTeX 
464         ostringstream ls;
465         WriteStream wi(ls, false, false);
466         par_->write(wi);
467         string const data = ls.str();
468
469         // the preview cache, maps contents to image loaders
470         typedef std::map<string, boost::shared_ptr<grfx::Loader> > cache_type;
471         static cache_type theCache;
472         static int theCounter = 0;
473
474         // set our loader corresponding to our current data
475         cache_type::const_iterator it = theCache.find(data);
476
477         // is this old data?
478         if (it != theCache.end()) {
479                 // we have already a loader, connect to it anyway
480                 //lyxerr << "### updatePreview(), old loader: " << loader_ << "\n";
481                 loader_ = it->second.get();
482                 loader_->statusChanged.connect
483                         (boost::bind(&InsetFormula::statusChanged, this));
484                 return;
485         }
486
487         // construct new file name
488         static string const dir = OnlyPath(lyx::tempName());
489         ostringstream os;
490         os << dir << theCounter++ << ".lyxpreview";
491         string file = os.str();
492
493         // the real work starts
494         //lyxerr << "### updatePreview(), new file " << file << "\n";
495         std::ofstream of(file.c_str());
496         of << "\\batchmode"
497                  << "\\documentclass{article}"
498                  << "\\usepackage{amssymb}"
499                  << "\\thispagestyle{empty}"
500                  << "\\pdfoutput=0"
501                  << "\\begin{document}"
502                  << data
503                  << "\\end{document}\n";
504         of.close();
505
506         // now we are done, start actual loading we will get called back via
507         // InsetFormula::statusChanged() if this is finished
508         theCache[data].reset(new grfx::Loader(file));
509         //lyxerr << "### updatePreview(), new loader: " << loader_ << "\n";
510         loader_ = theCache.find(data)->second.get();
511         loader_->startLoading();
512         loader_->statusChanged.connect(boost::bind(&InsetFormula::statusChanged, this));
513 }
514