]> git.lyx.org Git - lyx.git/blob - src/mathed/InsetMathScript.C
Scons: update_po should now work (missing dependency though)
[lyx.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() + nker();
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 int InsetMathScript::nker() const
257 {
258         if (nuc().size()) {
259                 int kerning = nuc().kerning();
260                 return kerning > 0 ? kerning : 0;
261         }
262         return 0;
263 }
264
265
266 bool InsetMathScript::metrics(MetricsInfo & mi, Dimension & dim) const
267 {
268         cell(0).metrics(mi);
269         ScriptChanger dummy(mi.base);
270         if (nargs() > 1)
271                 cell(1).metrics(mi);
272         if (nargs() > 2)
273                 cell(2).metrics(mi);
274         dim.wid = 0;
275         if (hasLimits()) {
276                 dim.wid = nwid();
277                 if (hasUp())
278                         dim.wid = max(dim.wid, up().width());
279                 if (hasDown())
280                         dim.wid = max(dim.wid, down().width());
281         } else {
282                 if (hasUp())
283                         dim.wid = max(dim.wid, nker() + up().width());
284                 if (hasDown())
285                         dim.wid = max(dim.wid, down().width());
286                 dim.wid += nwid();
287         }
288         int na = nasc();
289         if (hasUp()) {
290                 int asc = dy1() + up().ascent();
291                 dim.asc = max(na, asc);
292         } else
293                 dim.asc = na;
294         int nd = ndes();
295         if (hasDown()) {
296                 int des = dy0() + down().descent();
297                 dim.des = max(nd, des);
298         } else
299                 dim.des = nd;
300         metricsMarkers(dim);
301         if (dim_ == dim)
302                 return false;
303         dim_ = dim;
304         return true;
305 }
306
307
308 void InsetMathScript::draw(PainterInfo & pi, int x, int y) const
309 {
310         if (nuc().size())
311                 nuc().draw(pi, x + dxx(), y);
312         else {
313                 nuc().setXY(*pi.base.bv, x + dxx(), y);
314                 if (editing(pi.base.bv))
315                         pi.draw(x + dxx(), y, char_type('.'));
316         }
317         ScriptChanger dummy(pi.base);
318         if (hasUp())
319                 up().draw(pi, x + dx1(), y - dy1());
320         if (hasDown())
321                 down().draw(pi, x + dx0(), y + dy0());
322         drawMarkers(pi, x, y);
323 }
324
325
326 void InsetMathScript::metricsT(TextMetricsInfo const & mi, Dimension & dim) const
327 {
328         if (hasUp())
329                 up().metricsT(mi, dim);
330         if (hasDown())
331                 down().metricsT(mi, dim);
332         nuc().metricsT(mi, dim);
333 }
334
335
336 void InsetMathScript::drawT(TextPainter & pain, int x, int y) const
337 {
338         if (nuc().size())
339                 nuc().drawT(pain, x + dxx(), y);
340         if (hasUp())
341                 up().drawT(pain, x + dx1(), y - dy1());
342         if (hasDown())
343                 down().drawT(pain, x + dx0(), y + dy0());
344 }
345
346
347
348 bool InsetMathScript::hasLimits() const
349 {
350         // obvious cases
351         if (limits_ == 1)
352                 return true;
353         if (limits_ == -1)
354                 return false;
355
356         // we can only display limits if the nucleus wants some
357         if (!nuc().size())
358                 return false;
359         if (!nuc().back()->isScriptable())
360                 return false;
361
362         if (nuc().back()->asSymbolInset()) {
363                 // \intop is an alias for \int\limits, \ointop == \oint\limits
364                 if (nuc().back()->asSymbolInset()->name().find(from_ascii("intop")) != string::npos)
365                         return true;
366                 // per default \int has limits beside the \int even in displayed formulas
367                 if (nuc().back()->asSymbolInset()->name().find(from_ascii("int")) != string::npos)
368                         return false;
369         }
370
371         // assume "real" limits for everything else
372         return true;
373 }
374
375
376 void InsetMathScript::removeScript(bool up)
377 {
378         if (nargs() == 2) {
379                 if (up == cell_1_is_up_)
380                         cells_.pop_back();
381         } else if (nargs() == 3) {
382                 if (up == true) {
383                         swap(cells_[1], cells_[2]);
384                         cell_1_is_up_ = false;
385                 } else {
386                         cell_1_is_up_ = true;
387                 }
388                 cells_.pop_back();
389         }
390 }
391
392
393 bool InsetMathScript::has(bool up) const
394 {
395         return idxOfScript(up);
396 }
397
398
399 bool InsetMathScript::hasUp() const
400 {
401         //lyxerr << "1up: " << bool(cell_1_is_up_) << endl;
402         //lyxerr << "hasUp: " << bool(idxOfScript(true)) << endl;
403         return idxOfScript(true);
404 }
405
406
407 bool InsetMathScript::hasDown() const
408 {
409         //lyxerr << "1up: " << bool(cell_1_is_up_) << endl;
410         //lyxerr << "hasDown: " << bool(idxOfScript(false)) << endl;
411         return idxOfScript(false);
412 }
413
414
415 InsetBase::idx_type InsetMathScript::idxOfScript(bool up) const
416 {
417         if (nargs() == 1)
418                 return 0;
419         if (nargs() == 2)
420                 return (cell_1_is_up_ == up) ? 1 : 0;
421         if (nargs() == 3)
422                 return up ? 1 : 2;
423         BOOST_ASSERT(false);
424         // Silence compiler
425         return 0;
426 }
427
428
429 bool InsetMathScript::idxRight(LCursor &) const
430 {
431         return false;
432 }
433
434
435 bool InsetMathScript::idxLeft(LCursor &) const
436 {
437         return false;
438 }
439
440
441 bool InsetMathScript::idxUpDown(LCursor & cur, bool up) const
442 {
443         // in nucleus?
444         if (cur.idx() == 0) {
445                 // don't go up/down if there is no cell in this direction
446                 if (!has(up))
447                         return false;
448                 // go up/down only if in the last position
449                 // or in the first position of something with displayed limits
450                 if (cur.pos() == cur.lastpos() || (cur.pos() == 0 && hasLimits())) {
451                         cur.idx() = idxOfScript(up);
452                         cur.pos() = 0;
453                         return true;
454                 }
455                 return false;
456         }
457
458         // Are we 'up'?
459         if (has(up) && cur.idx() == idxOfScript(true)) {
460                 // can't go further up
461                 if (up)
462                         return false;
463                 // otherwise go to last position in the nucleus
464                 cur.idx() = 0;
465                 cur.pos() = cur.lastpos();
466                 return true;
467         }
468
469         // Are we 'down'?
470         if (has(up) && cur.idx() == idxOfScript(false)) {
471                 // can't go further down
472                 if (!up)
473                         return false;
474                 // otherwise go to last position in the nucleus
475                 cur.idx() = 0;
476                 cur.pos() = cur.lastpos();
477                 return true;
478         }
479
480         return false;
481 }
482
483
484 void InsetMathScript::write(WriteStream & os) const
485 {
486         if (nuc().size()) {
487                 os << nuc();
488                 //if (nuc().back()->takesLimits()) {
489                         if (limits_ == -1)
490                                 os << "\\nolimits ";
491                         if (limits_ == 1)
492                                 os << "\\limits ";
493                 //}
494         } else {
495                 if (os.firstitem())
496                         lyxerr[Debug::MATHED] << "suppressing {} when writing"
497                                               << endl;
498                 else
499                         os << "{}";
500         }
501
502         if (hasDown() /*&& down().size()*/)
503                 os << "_{" << down() << '}';
504
505         if (hasUp() /*&& up().size()*/)
506                 os << "^{" << up() << '}';
507
508         if (lock_ && !os.latex())
509                 os << "\\lyxlock ";
510 }
511
512
513 void InsetMathScript::normalize(NormalStream & os) const
514 {
515         bool d = hasDown() && down().size();
516         bool u = hasUp() && up().size();
517
518         if (u && d)
519                 os << "[subsup ";
520         else if (u)
521                 os << "[sup ";
522         else if (d)
523                 os << "[sub ";
524
525         if (nuc().size())
526                 os << nuc() << ' ';
527         else
528                 os << "[par]";
529
530         if (u && d)
531                 os << down() << ' ' << up() << ']';
532         else if (d)
533                 os << down() << ']';
534         else if (u)
535                 os << up() << ']';
536 }
537
538
539 void InsetMathScript::maple(MapleStream & os) const
540 {
541         if (nuc().size())
542                 os << nuc();
543         if (hasDown() && down().size())
544                 os << '[' << down() << ']';
545         if (hasUp() && up().size())
546                 os << "^(" << up() << ')';
547 }
548
549
550 void InsetMathScript::mathematica(MathematicaStream & os) const
551 {
552         bool d = hasDown() && down().size();
553         bool u = hasUp() && up().size();
554
555         if (nuc().size()) {
556                 if (d)
557                         os << "Subscript[" << nuc();
558                 else
559                         os << nuc();
560         }
561
562         if (u)
563                 os << "^(" << up() << ')';
564
565         if (nuc().size()) {
566                 if (d)
567                         os << ',' << down() << ']';
568         }
569 }
570
571
572 void InsetMathScript::mathmlize(MathStream & os) const
573 {
574         bool d = hasDown() && down().size();
575         bool u = hasUp() && up().size();
576
577         if (u && d)
578                 os << MTag("msubsup");
579         else if (u)
580                 os << MTag("msup");
581         else if (d)
582                 os << MTag("msub");
583
584         if (nuc().size())
585                 os << nuc();
586         else
587                 os << "<mrow/>";
588
589         if (u && d)
590                 os << down() << up() << ETag("msubsup");
591         else if (u)
592                 os << up() << ETag("msup");
593         else if (d)
594                 os << down() << ETag("msub");
595 }
596
597
598 void InsetMathScript::octave(OctaveStream & os) const
599 {
600         if (nuc().size())
601                 os << nuc();
602         if (hasDown() && down().size())
603                 os << '[' << down() << ']';
604         if (hasUp() && up().size())
605                 os << "^(" << up() << ')';
606 }
607
608
609 void InsetMathScript::infoize(odocstream & os) const
610 {
611         os << "Scripts";
612 }
613
614
615 void InsetMathScript::infoize2(odocstream & os) const
616 {
617         if (limits_)
618                 os << (limits_ == 1 ? ", Displayed limits" : ", Inlined limits");
619 }
620
621
622 bool InsetMathScript::notifyCursorLeaves(LCursor & cur)
623 {
624         InsetMathNest::notifyCursorLeaves(cur);
625
626         //lyxerr << "InsetMathScript::notifyCursorLeaves: 1 " << cur << endl;
627
628         // remove empty scripts if possible
629         if (nargs() > 2) {
630                 // Case of two scripts. In this case, 1 = super, 2 = sub
631                 if (cur.idx() == 2 && cell(2).empty()) {
632                         // must be a subscript...
633                         recordUndoInset(cur);
634                         removeScript(false);
635                         return true;
636                 } else if (cur.idx() == 1 && cell(1).empty()) {
637                         // must be a superscript...
638                         recordUndoInset(cur);
639                         removeScript(true);
640                         return true;
641                 }
642         } else if (nargs() > 1 && cur.idx() == 1 && cell(1).empty()) {
643                 // could be either subscript or super script
644                 recordUndoInset(cur);
645                 removeScript(cell_1_is_up_);
646                 // Let the script inset commit suicide. This is
647                 // modelled on LCursor.pullArg(), but tries not to
648                 // invoke notifyCursorLeaves again and does not touch
649                 // cur (since the top slice will be deleted
650                 // afterwards))
651                 MathArray ar = cell(0);
652                 LCursor tmpcur = cur;
653                 tmpcur.pop();
654                 tmpcur.cell().erase(tmpcur.pos());
655                 tmpcur.cell().insert(tmpcur.pos(), ar);
656                 return true;
657         }
658
659         //lyxerr << "InsetMathScript::notifyCursorLeaves: 2 " << cur << endl;
660         return false;
661 }
662
663
664 void InsetMathScript::doDispatch(LCursor & cur, FuncRequest & cmd)
665 {
666         //lyxerr << "InsetMathScript: request: " << cmd << std::endl;
667
668         if (cmd.action == LFUN_MATH_LIMITS) {
669                 if (!cmd.argument().empty()) {
670                         if (cmd.argument() == "limits")
671                                 limits_ = 1;
672                         else if (cmd.argument() == "nolimits")
673                                 limits_ = -1;
674                         else
675                                 limits_ = 0;
676                 } else if (limits_ == 0)
677                         limits_ = hasLimits() ? -1 : 1;
678                 else
679                         limits_ = 0;
680                 return;
681         }
682
683         InsetMathNest::doDispatch(cur, cmd);
684 }
685
686
687 } // namespace lyx