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