]> git.lyx.org Git - lyx.git/blob - src/mathed/InsetMathScript.cpp
Add \makeat switches to babel settings if necessary.
[lyx.git] / src / mathed / InsetMathScript.cpp
1 /**
2  * \file InsetMathScript.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author André Pönitz
7  *
8  * Full author contact details are available in file CREDITS.
9  */
10
11 #include <config.h>
12
13 #include "BufferView.h"
14 #include "Cursor.h"
15 #include "DispatchResult.h"
16 #include "FuncRequest.h"
17 #include "FuncStatus.h"
18 #include "InsetMathFont.h"
19 #include "InsetMathScript.h"
20 #include "InsetMathSymbol.h"
21 #include "LaTeXFeatures.h"
22 #include "MathData.h"
23 #include "MathStream.h"
24 #include "MathSupport.h"
25
26 #include "support/debug.h"
27
28 #include "support/lassert.h"
29
30 #include <ostream>
31
32 using namespace std;
33
34 namespace lyx {
35
36
37 InsetMathScript::InsetMathScript(Buffer * buf)
38         : InsetMathNest(buf, 1), cell_1_is_up_(false), limits_(0)
39 {}
40
41
42 InsetMathScript::InsetMathScript(Buffer * buf, bool up)
43         : InsetMathNest(buf, 2), cell_1_is_up_(up), limits_(0)
44 {}
45
46
47 InsetMathScript::InsetMathScript(Buffer * buf, MathAtom const & at, bool up)
48         : InsetMathNest(buf, 2), cell_1_is_up_(up), limits_(0)
49 {
50         LASSERT(nargs() >= 1, /**/);
51         cell(0).push_back(at);
52 }
53
54
55 Inset * InsetMathScript::clone() const
56 {
57         return new InsetMathScript(*this);
58 }
59
60
61 InsetMathScript const * InsetMathScript::asScriptInset() const
62 {
63         return this;
64 }
65
66
67 InsetMathScript * InsetMathScript::asScriptInset()
68 {
69         return this;
70 }
71
72
73 bool InsetMathScript::idxFirst(Cursor & cur) const
74 {
75         cur.idx() = 0;
76         cur.pos() = 0;
77         return true;
78 }
79
80
81 bool InsetMathScript::idxLast(Cursor & cur) const
82 {
83         cur.idx() = 0;
84         cur.pos() = nuc().size();
85         return true;
86 }
87
88
89 MathData const & InsetMathScript::down() const
90 {
91         if (nargs() == 3)
92                 return cell(2);
93         LASSERT(nargs() > 1, /**/);
94         return cell(1);
95 }
96
97
98 MathData & InsetMathScript::down()
99 {
100         if (nargs() == 3)
101                 return cell(2);
102         LASSERT(nargs() > 1, /**/);
103         return cell(1);
104 }
105
106
107 MathData const & InsetMathScript::up() const
108 {
109         LASSERT(nargs() > 1, /**/);
110         return cell(1);
111 }
112
113
114 MathData & InsetMathScript::up()
115 {
116         LASSERT(nargs() > 1, /**/);
117         return cell(1);
118 }
119
120
121 void InsetMathScript::ensure(bool up)
122 {
123         if (nargs() == 1) {
124                 // just nucleus so far
125                 cells_.push_back(MathData());
126                 cell_1_is_up_ = up;
127         } else if (nargs() == 2 && !has(up)) {
128                 if (up) {
129                         cells_.push_back(cell(1));
130                         cell(1).clear();
131                 } else {
132                         cells_.push_back(MathData());
133                 }
134         }
135 }
136
137
138 MathData const & InsetMathScript::nuc() const
139 {
140         return cell(0);
141 }
142
143
144 MathData & InsetMathScript::nuc()
145 {
146         return cell(0);
147 }
148
149
150 namespace {
151
152 bool isAlphaSymbol(MathAtom const & at)
153 {
154         if (at->asCharInset() ||
155                         (at->asSymbolInset() &&
156                          at->asSymbolInset()->isOrdAlpha()))
157                 return true;
158
159         if (at->asFontInset()) {
160                 MathData const & ar = at->asFontInset()->cell(0);
161                 for (size_t i = 0; i < ar.size(); ++i) {
162                         if (!(ar[i]->asCharInset() ||
163                                         (ar[i]->asSymbolInset() &&
164                                          ar[i]->asSymbolInset()->isOrdAlpha())))
165                                 return false;
166                 }
167                 return true;
168         }
169         return false;
170 }
171
172 } // namespace anon
173
174
175 int InsetMathScript::dy01(BufferView const & bv, int asc, int des, int what) const
176 {
177         int dasc = 0;
178         int slevel = 0;
179         bool isCharBox = nuc().size() ? isAlphaSymbol(nuc().back()) : false;
180         if (hasDown()) {
181                 Dimension const & dimdown = down().dimension(bv);
182                 dasc = dimdown.ascent();
183                 slevel = nuc().slevel();
184                 int ascdrop = dasc - slevel;
185                 int desdrop = isCharBox ? 0 : des + nuc().sshift();
186                 int mindes = nuc().mindes();
187                 des = max(desdrop, ascdrop);
188                 des = max(mindes, des);
189         }
190         if (hasUp()) {
191                 Dimension const & dimup = up().dimension(bv);
192                 int minasc = nuc().minasc();
193                 int ascdrop = isCharBox ? 0 : asc - up().mindes();
194                 int udes = dimup.descent();
195                 asc = udes + nuc().sshift();
196                 asc = max(ascdrop, asc);
197                 asc = max(minasc, asc);
198                 if (hasDown()) {
199                         int del = asc - udes - dasc;
200                         if (del + des <= 2) {
201                                 int newdes = 2 - del;
202                                 del = slevel - asc + udes;
203                                 if (del > 0) {
204                                         asc += del;
205                                         newdes -= del;
206                                 }
207                                 des = max(des, newdes);
208                         }
209                 }
210         }
211         return what ? asc : des;
212 }
213
214
215 int InsetMathScript::dy0(BufferView const & bv) const
216 {
217         int nd = ndes(bv);
218         if (!hasDown())
219                 return nd;
220         int des = down().dimension(bv).ascent();
221         if (hasLimits())
222                 des += nd + 2;
223         else {
224                 int na = nasc(bv);
225                 des = dy01(bv, na, nd, 0);
226         }
227         return des;
228 }
229
230
231 int InsetMathScript::dy1(BufferView const & bv) const
232 {
233         int na = nasc(bv);
234         if (!hasUp())
235                 return na;
236         int asc = up().dimension(bv).descent();
237         if (hasLimits())
238                 asc += na + 2;
239         else {
240                 int nd = ndes(bv);
241                 asc = dy01(bv, na, nd, 1);
242         }
243         asc = max(asc, 5);
244         return asc;
245 }
246
247
248 int InsetMathScript::dx0(BufferView const & bv) const
249 {
250         LASSERT(hasDown(), /**/);
251         Dimension const dim = dimension(bv);
252         return hasLimits() ? (dim.wid - down().dimension(bv).width()) / 2 : nwid(bv);
253 }
254
255
256 int InsetMathScript::dx1(BufferView const & bv) const
257 {
258         LASSERT(hasUp(), /**/);
259         Dimension const dim = dimension(bv);
260         return hasLimits() ? (dim.wid - up().dimension(bv).width()) / 2 : nwid(bv) + nker(&bv);
261 }
262
263
264 int InsetMathScript::dxx(BufferView const & bv) const
265 {
266         Dimension const dim = dimension(bv);
267         return hasLimits() ? (dim.wid - nwid(bv)) / 2  :  0;
268 }
269
270
271 int InsetMathScript::nwid(BufferView const & bv) const
272 {
273         return nuc().size() ? nuc().dimension(bv).width() : 2;
274 }
275
276
277 int InsetMathScript::nasc(BufferView const & bv) const
278 {
279         return nuc().size() ? nuc().dimension(bv).ascent() : 5;
280 }
281
282
283 int InsetMathScript::ndes(BufferView const & bv) const
284 {
285         return nuc().size() ? nuc().dimension(bv).descent() : 0;
286 }
287
288
289 int InsetMathScript::nker(BufferView const * bv) const
290 {
291         if (nuc().size()) {
292                 int kerning = nuc().kerning(bv);
293                 return kerning > 0 ? kerning : 0;
294         }
295         return 0;
296 }
297
298
299 void InsetMathScript::metrics(MetricsInfo & mi, Dimension & dim) const
300 {
301         Dimension dim0;
302         Dimension dim1;
303         Dimension dim2;
304         cell(0).metrics(mi, dim0);
305         ScriptChanger dummy(mi.base);
306         if (nargs() > 1)
307                 cell(1).metrics(mi, dim1);
308         if (nargs() > 2)
309                 cell(2).metrics(mi, dim2);
310
311         dim.wid = 0;
312         BufferView & bv = *mi.base.bv;
313         // FIXME: data copying... not very efficient.
314         Dimension dimup;
315         Dimension dimdown;
316         if (hasUp())
317                 dimup = up().dimension(bv);
318         if (hasDown())
319                 dimdown = down().dimension(bv);
320
321         if (hasLimits()) {
322                 dim.wid = nwid(bv);
323                 if (hasUp())
324                         dim.wid = max(dim.wid, dimup.width());
325                 if (hasDown())
326                         dim.wid = max(dim.wid, dimdown.width());
327         } else {
328                 if (hasUp())
329                         dim.wid = max(dim.wid, nker(mi.base.bv) + dimup.width());
330                 if (hasDown())
331                         dim.wid = max(dim.wid, dimdown.width());
332                 dim.wid += nwid(bv);
333         }
334         int na = nasc(bv);
335         if (hasUp()) {
336                 int asc = dy1(bv) + dimup.ascent();
337                 dim.asc = max(na, asc);
338         } else
339                 dim.asc = na;
340         int nd = ndes(bv);
341         if (hasDown()) {
342                 int des = dy0(bv) + dimdown.descent();
343                 dim.des = max(nd, des);
344         } else
345                 dim.des = nd;
346         metricsMarkers(dim);
347 }
348
349
350 void InsetMathScript::draw(PainterInfo & pi, int x, int y) const
351 {
352         BufferView & bv = *pi.base.bv;
353         if (nuc().size())
354                 nuc().draw(pi, x + dxx(bv), y);
355         else {
356                 nuc().setXY(bv, x + dxx(bv), y);
357                 if (editing(&bv))
358                         pi.draw(x + dxx(bv), y, char_type('.'));
359         }
360         ScriptChanger dummy(pi.base);
361         if (hasUp())
362                 up().draw(pi, x + dx1(bv), y - dy1(bv));
363         if (hasDown())
364                 down().draw(pi, x + dx0(bv), y + dy0(bv));
365         drawMarkers(pi, x, y);
366 }
367
368
369 void InsetMathScript::metricsT(TextMetricsInfo const & mi, Dimension & dim) const
370 {
371         if (hasUp())
372                 up().metricsT(mi, dim);
373         if (hasDown())
374                 down().metricsT(mi, dim);
375         nuc().metricsT(mi, dim);
376 }
377
378
379 void InsetMathScript::drawT(TextPainter & pain, int x, int y) const
380 {
381         // FIXME: BROKEN
382         if (nuc().size())
383                 nuc().drawT(pain, x + 1, y);
384         if (hasUp())
385                 up().drawT(pain, x + 1, y - 1 /*dy1()*/);
386         if (hasDown())
387                 down().drawT(pain, x + 1, y + 1 /*dy0()*/);
388 }
389
390
391
392 bool InsetMathScript::hasLimits() const
393 {
394         // obvious cases
395         if (limits_ == 1)
396                 return true;
397         if (limits_ == -1)
398                 return false;
399
400         // we can only display limits if the nucleus wants some
401         if (!nuc().size())
402                 return false;
403         if (!nuc().back()->isScriptable())
404                 return false;
405
406         if (nuc().back()->asSymbolInset()) {
407                 // \intop is an alias for \int\limits, \ointop == \oint\limits
408                 if (nuc().back()->asSymbolInset()->name().find(from_ascii("intop")) != string::npos)
409                         return true;
410                 // per default \int has limits beside the \int even in displayed formulas
411                 if (nuc().back()->asSymbolInset()->name().find(from_ascii("int")) != string::npos)
412                         return false;
413         }
414
415         // assume "real" limits for everything else
416         return true;
417 }
418
419
420 void InsetMathScript::removeScript(bool up)
421 {
422         if (nargs() == 2) {
423                 if (up == cell_1_is_up_)
424                         cells_.pop_back();
425         } else if (nargs() == 3) {
426                 if (up == true) {
427                         swap(cells_[1], cells_[2]);
428                         cell_1_is_up_ = false;
429                 } else {
430                         cell_1_is_up_ = true;
431                 }
432                 cells_.pop_back();
433         }
434 }
435
436
437 bool InsetMathScript::has(bool up) const
438 {
439         return idxOfScript(up);
440 }
441
442
443 bool InsetMathScript::hasUp() const
444 {
445         //lyxerr << "1up: " << bool(cell_1_is_up_));
446         //lyxerr << "hasUp: " << bool(idxOfScript(true)));
447         return idxOfScript(true);
448 }
449
450
451 bool InsetMathScript::hasDown() const
452 {
453         //LYXERR0("1up: " << bool(cell_1_is_up_));
454         //LYXERR0("hasDown: " << bool(idxOfScript(false)));
455         return idxOfScript(false);
456 }
457
458
459 Inset::idx_type InsetMathScript::idxOfScript(bool up) const
460 {
461         if (nargs() == 1)
462                 return 0;
463         if (nargs() == 2)
464                 return (cell_1_is_up_ == up) ? 1 : 0;
465         if (nargs() == 3)
466                 return up ? 1 : 2;
467         LASSERT(false, /**/);
468         // Silence compiler
469         return 0;
470 }
471
472
473 bool InsetMathScript::idxForward(Cursor &) const
474 {
475         return false;
476 }
477
478
479 bool InsetMathScript::idxBackward(Cursor &) const
480 {
481         return false;
482 }
483
484
485 bool InsetMathScript::idxUpDown(Cursor & cur, bool up) const
486 {
487         // in nucleus?
488         if (cur.idx() == 0) {
489                 // don't go up/down if there is no cell in this direction
490                 if (!has(up))
491                         return false;
492                 // go up/down only if in the last position
493                 // or in the first position of something with displayed limits
494                 if (cur.pos() == cur.lastpos() || (cur.pos() == 0 && hasLimits())) {
495                         cur.idx() = idxOfScript(up);
496                         cur.pos() = 0;
497                         return true;
498                 }
499                 return false;
500         }
501
502         // Are we 'up'?
503         if (cur.idx() == idxOfScript(true)) {
504                 // can't go further up
505                 if (up)
506                         return false;
507                 // otherwise go to last position in the nucleus
508                 cur.idx() = 0;
509                 cur.pos() = cur.lastpos();
510                 return true;
511         }
512
513         // Are we 'down'?
514         if (cur.idx() == idxOfScript(false)) {
515                 // can't go further down
516                 if (!up)
517                         return false;
518                 // otherwise go to last position in the nucleus
519                 cur.idx() = 0;
520                 cur.pos() = cur.lastpos();
521                 return true;
522         }
523
524         return false;
525 }
526
527
528 void InsetMathScript::write(WriteStream & os) const
529 {
530         MathEnsurer ensurer(os);
531
532         if (nuc().size()) {
533                 os << nuc();
534                 //if (nuc().back()->takesLimits()) {
535                         if (limits_ == -1)
536                                 os << "\\nolimits ";
537                         if (limits_ == 1)
538                                 os << "\\limits ";
539                 //}
540         } else {
541                 if (os.firstitem())
542                         LYXERR(Debug::MATHED, "suppressing {} when writing");
543                 else
544                         os << "{}";
545         }
546
547         if (hasDown() /*&& down().size()*/)
548                 os << "_{" << down() << '}';
549
550         if (hasUp() /*&& up().size()*/)
551                 os << "^{" << up() << '}';
552
553         if (lock_ && !os.latex())
554                 os << "\\lyxlock ";
555 }
556
557
558 void InsetMathScript::normalize(NormalStream & os) const
559 {
560         bool d = hasDown() && down().size();
561         bool u = hasUp() && up().size();
562
563         if (u && d)
564                 os << "[subsup ";
565         else if (u)
566                 os << "[sup ";
567         else if (d)
568                 os << "[sub ";
569
570         if (nuc().size())
571                 os << nuc() << ' ';
572         else
573                 os << "[par]";
574
575         if (u && d)
576                 os << down() << ' ' << up() << ']';
577         else if (d)
578                 os << down() << ']';
579         else if (u)
580                 os << up() << ']';
581 }
582
583
584 void InsetMathScript::maple(MapleStream & os) const
585 {
586         if (nuc().size())
587                 os << nuc();
588         if (hasDown() && down().size())
589                 os << '[' << down() << ']';
590         if (hasUp() && up().size())
591                 os << "^(" << up() << ')';
592 }
593
594
595 void InsetMathScript::mathematica(MathematicaStream & os) const
596 {
597         bool d = hasDown() && down().size();
598         bool u = hasUp() && up().size();
599
600         if (nuc().size()) {
601                 if (d)
602                         os << "Subscript[" << nuc();
603                 else
604                         os << nuc();
605         }
606
607         if (u)
608                 os << "^(" << up() << ')';
609
610         if (nuc().size()) {
611                 if (d)
612                         os << ',' << down() << ']';
613         }
614 }
615
616
617 // FIXME XHTML
618 // It may be worth trying to output munder, mover, and munderover
619 // in certain cases, e.g., for display formulas. But then we would
620 // need to know if we're in a display formula.
621 void InsetMathScript::mathmlize(MathStream & os) const
622 {
623         bool d = hasDown() && down().size();
624         bool u = hasUp() && up().size();
625
626         if (u && d)
627                 os << MTag("msubsup");
628         else if (u)
629                 os << MTag("msup");
630         else if (d)
631                 os << MTag("msub");
632
633         if (nuc().size())
634                 os << MTag("mrow") << nuc() << ETag("mrow");
635         else
636                 os << "<mrow />";
637
638         if (u && d)
639                 os << MTag("mrow") << down() << ETag("mrow") 
640                    << MTag("mrow") << up() << ETag("mrow") 
641                    << ETag("msubsup");
642         else if (u)
643                 os << MTag("mrow") << up() << ETag("mrow") << ETag("msup");
644         else if (d)
645                 os << MTag("mrow") << down() << ETag("mrow") << ETag("msub");
646 }
647
648
649 void InsetMathScript::htmlize(HtmlStream & os) const
650 {
651         bool d = hasDown() && down().size();
652         bool u = hasUp() && up().size();
653
654         if (nuc().size())
655                 os << nuc();
656
657         if (u && d)
658                 os << MTag("span", "class='scripts'")
659                          << MTag("span") << up() << ETag("span")
660                          << MTag("span") << down() << ETag("span")
661                          << ETag("span");
662         else if (u)
663                 os << MTag("sup", "class='math'") << up() << ETag("sup");
664         else if (d)
665                 os << MTag("sub", "class='math'") << down() << ETag("sub");
666 }
667
668
669 void InsetMathScript::octave(OctaveStream & os) const
670 {
671         if (nuc().size())
672                 os << nuc();
673         if (hasDown() && down().size())
674                 os << '[' << down() << ']';
675         if (hasUp() && up().size())
676                 os << "^(" << up() << ')';
677 }
678
679
680 void InsetMathScript::infoize(odocstream & os) const
681 {
682         os << "Scripts";
683 }
684
685
686 void InsetMathScript::infoize2(odocstream & os) const
687 {
688         if (limits_)
689                 os << from_ascii(limits_ == 1 ? ", Displayed limits" : ", Inlined limits");
690 }
691
692
693 bool InsetMathScript::notifyCursorLeaves(Cursor const & old, Cursor & cur)
694 {
695         InsetMathNest::notifyCursorLeaves(old, cur);
696
697         //LYXERR0("InsetMathScript::notifyCursorLeaves: 1 " << cur);
698
699         // Remove empty scripts if possible:
700
701         // The case of two scripts, but only one got empty (1 = super, 2 = sub).
702         // We keep the script inset, but remove the empty script.
703         if (nargs() > 2 && (!cell(1).empty() || !cell(2).empty())) {
704                 if (cell(2).empty()) {
705                         // must be a subscript...
706                         old.recordUndoInset();
707                         removeScript(false);
708                         cur.screenUpdateFlags(cur.result().screenUpdate() | Update::SinglePar);
709                         return true;
710                 } else if (cell(1).empty()) {
711                         // must be a superscript...
712                         old.recordUndoInset();
713                         removeScript(true);
714                         cur.screenUpdateFlags(cur.result().screenUpdate() | Update::SinglePar);
715                         return true;
716                 }
717         }
718         // Now the two suicide cases:
719         // * we have only one script which is empty
720         // * we have two scripts which are both empty.
721         // The script inset is removed completely.
722         if ((nargs() == 2 && cell(1).empty())
723             || (nargs() == 3 && cell(1).empty() && cell(2).empty())) {
724                 // Make undo step. We cannot use cur for this because
725                 // it does not necessarily point to us anymore. But we
726                 // should be on top of the cursor old.
727                 Cursor insetCur = old;
728                 int scriptSlice = insetCur.find(this);
729                 LASSERT(scriptSlice != -1, /**/);
730                 insetCur.cutOff(scriptSlice);
731                 insetCur.recordUndoInset();
732
733                 // Let the script inset commit suicide. This is
734                 // modelled on Cursor.pullArg(), but tries not to
735                 // invoke notifyCursorLeaves again and does not touch
736                 // cur (since the top slice will be deleted
737                 // afterwards)
738                 MathData ar = cell(0);
739                 insetCur.pop();
740                 insetCur.cell().erase(insetCur.pos());
741                 insetCur.cell().insert(insetCur.pos(), ar);
742
743                 // redraw
744                 cur.screenUpdateFlags(cur.result().screenUpdate() | Update::SinglePar);
745                 return true;
746         }
747
748         //LYXERR0("InsetMathScript::notifyCursorLeaves: 2 " << cur);
749         return false;
750 }
751
752
753 void InsetMathScript::doDispatch(Cursor & cur, FuncRequest & cmd)
754 {
755         //LYXERR("InsetMathScript: request: " << cmd);
756
757         if (cmd.action() == LFUN_MATH_LIMITS) {
758                 cur.recordUndoInset();
759                 if (!cmd.argument().empty()) {
760                         if (cmd.argument() == "limits")
761                                 limits_ = 1;
762                         else if (cmd.argument() == "nolimits")
763                                 limits_ = -1;
764                         else
765                                 limits_ = 0;
766                 } else if (limits_ == 0)
767                         limits_ = hasLimits() ? -1 : 1;
768                 else
769                         limits_ = 0;
770                 return;
771         }
772
773         InsetMathNest::doDispatch(cur, cmd);
774 }
775
776
777 bool InsetMathScript::getStatus(Cursor & cur, FuncRequest const & cmd,
778                                 FuncStatus & flag) const
779 {
780         if (cmd.action() == LFUN_MATH_LIMITS) {
781                 if (!cmd.argument().empty()) {
782                         if (cmd.argument() == "limits")
783                                 flag.setOnOff(limits_ == 1);
784                         else if (cmd.argument() == "nolimits")
785                                 flag.setOnOff(limits_ == -1);
786                         else
787                                 flag.setOnOff(limits_ == 0);
788                 } 
789                 flag.setEnabled(true);
790                 return true;
791         }
792
793         return InsetMathNest::getStatus(cur, cmd, flag);
794 }
795
796
797 // the idea for dual scripts came from the eLyXer code
798 void InsetMathScript::validate(LaTeXFeatures & features) const
799 {
800         if (features.runparams().math_flavor == OutputParams::MathAsHTML)
801                 features.addCSSSnippet(
802                         "span.scripts{display: inline-block; vertical-align: middle; text-align:center; font-size: 75%;}\n"
803                         "span.scripts span {display: block;}\n"
804                         "sub.math{font-size: 75%;}\n"
805                         "sup.math{font-size: 75%;}");
806         InsetMathNest::validate(features);
807 }
808
809 } // namespace lyx