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