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