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