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