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