]> git.lyx.org Git - lyx.git/blob - src/mathed/math_scriptinset.C
Fix math cursor positioning bug
[lyx.git] / src / mathed / math_scriptinset.C
1 /**
2  * \file math_scriptinset.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 "math_scriptinset.h"
14 #include "math_data.h"
15 #include "math_mathmlstream.h"
16 #include "math_support.h"
17 #include "math_symbolinset.h"
18 #include "dispatchresult.h"
19 #include "cursor.h"
20 #include "debug.h"
21 #include "funcrequest.h"
22
23 #include <boost/assert.hpp>
24
25 using std::string;
26 using std::max;
27 using std::auto_ptr;
28 using std::endl;
29
30
31
32 MathScriptInset::MathScriptInset()
33         : MathNestInset(1), cell_1_is_up_(false), limits_(0)
34 {}
35
36
37 MathScriptInset::MathScriptInset(bool up)
38         : MathNestInset(2), cell_1_is_up_(up), limits_(0)
39 {}
40
41
42 MathScriptInset::MathScriptInset(MathAtom const & at, bool up)
43         : MathNestInset(2), cell_1_is_up_(up), limits_(0)
44 {
45         BOOST_ASSERT(nargs() >= 1);
46         cell(0).push_back(at);
47 }
48
49
50 auto_ptr<InsetBase> MathScriptInset::doClone() const
51 {
52         return auto_ptr<InsetBase>(new MathScriptInset(*this));
53 }
54
55
56 MathScriptInset const * MathScriptInset::asScriptInset() const
57 {
58         return this;
59 }
60
61
62 MathScriptInset * MathScriptInset::asScriptInset()
63 {
64         return this;
65 }
66
67
68 bool MathScriptInset::idxFirst(LCursor & cur) const
69 {
70         cur.idx() = 0;
71         cur.pos() = 0;
72         return true;
73 }
74
75
76 bool MathScriptInset::idxLast(LCursor & cur) const
77 {
78         cur.idx() = 0;
79         cur.pos() = nuc().size();
80         return true;
81 }
82
83
84 MathArray const & MathScriptInset::down() const
85 {
86         if (nargs() == 3)
87                 return cell(2);
88         BOOST_ASSERT(nargs() > 1);
89         return cell(1);
90 }
91
92
93 MathArray & MathScriptInset::down()
94 {
95         if (nargs() == 3)
96                 return cell(2);
97         BOOST_ASSERT(nargs() > 1);
98         return cell(1);
99 }
100
101
102 MathArray const & MathScriptInset::up() const
103 {
104         BOOST_ASSERT(nargs() > 1);
105         return cell(1);
106 }
107
108
109 MathArray & MathScriptInset::up()
110 {
111         BOOST_ASSERT(nargs() > 1);
112         return cell(1);
113 }
114
115
116 void MathScriptInset::ensure(bool up)
117 {
118         if (nargs() == 1) {
119                 // just nucleus so far
120                 cells_.push_back(MathArray());
121                 cell_1_is_up_ = up;
122         } else if (nargs() == 2 && !has(up)) {
123                 if (up) {
124                         cells_.push_back(cell(1));
125                         cell(1).clear();
126                 } else {
127                         cells_.push_back(MathArray());
128                 }
129         }
130 }
131
132
133 MathArray const & MathScriptInset::nuc() const
134 {
135         return cell(0);
136 }
137
138
139 MathArray & MathScriptInset::nuc()
140 {
141         return cell(0);
142 }
143
144
145 int MathScriptInset::dy0() const
146 {
147         int nd = ndes();
148         if (!hasDown())
149                 return nd;
150         int des = down().ascent();
151         if (hasLimits())
152                 des += nd + 2;
153         else
154                 des = max(des, nd);
155         return des;
156 }
157
158
159 int MathScriptInset::dy1() const
160 {
161         int na = nasc();
162         if (!hasUp())
163                 return na;
164         int asc = up().descent();
165         if (hasLimits())
166                 asc += na + 2;
167         else
168                 asc = max(asc, na);
169         asc = max(asc, 5);
170         return asc;
171 }
172
173
174 int MathScriptInset::dx0() const
175 {
176         BOOST_ASSERT(hasDown());
177         return hasLimits() ? (dim_.wid - down().width()) / 2 : nwid();
178 }
179
180
181 int MathScriptInset::dx1() const
182 {
183         BOOST_ASSERT(hasUp());
184         return hasLimits() ? (dim_.wid - up().width()) / 2 : nwid();
185 }
186
187
188 int MathScriptInset::dxx() const
189 {
190         return hasLimits() ? (dim_.wid - nwid()) / 2  :  0;
191 }
192
193
194 int MathScriptInset::nwid() const
195 {
196         return nuc().size() ? nuc().width() : 2;
197 }
198
199
200 int MathScriptInset::nasc() const
201 {
202         return nuc().size() ? nuc().ascent() : 5;
203 }
204
205
206 int MathScriptInset::ndes() const
207 {
208         return nuc().size() ? nuc().descent() : 0;
209 }
210
211
212 void MathScriptInset::metrics(MetricsInfo & mi, Dimension & dim) const
213 {
214         cell(0).metrics(mi);
215         ScriptChanger dummy(mi.base);
216         if (nargs() > 1)
217                 cell(1).metrics(mi);
218         if (nargs() > 2)
219                 cell(2).metrics(mi);
220         dim.wid = 0;
221         if (hasLimits()) {
222                 dim.wid = nwid();
223                 if (hasUp())
224                         dim.wid = max(dim.wid, up().width());
225                 if (hasDown())
226                         dim.wid = max(dim.wid, down().width());
227         } else {
228                 if (hasUp())
229                         dim.wid = max(dim.wid, up().width());
230                 if (hasDown())
231                         dim.wid = max(dim.wid, down().width());
232                 dim.wid += nwid();
233         }
234         dim.asc = dy1() + (hasUp() ? up().ascent() : 0);
235         dim.des = dy0() + (hasDown() ? down().descent() : 0);
236         metricsMarkers(dim);
237         dim_ = dim;
238 }
239
240
241 void MathScriptInset::draw(PainterInfo & pi, int x, int y) const
242 {
243         if (nuc().size())
244                 nuc().draw(pi, x + dxx(), y);
245         else {
246                 nuc().setXY(x + dxx(), y);
247                 if (editing(pi.base.bv))
248                         drawStr(pi, pi.base.font, x + dxx(), y, ".");
249         }
250         ScriptChanger dummy(pi.base);
251         if (hasUp())
252                 up().draw(pi, x + dx1(), y - dy1());
253         if (hasDown())
254                 down().draw(pi, x + dx0(), y + dy0());
255         drawMarkers(pi, x, y);
256 }
257
258
259 void MathScriptInset::metricsT(TextMetricsInfo const & mi, Dimension & dim) const
260 {
261         if (hasUp())
262                 up().metricsT(mi, dim);
263         if (hasDown())
264                 down().metricsT(mi, dim);
265         nuc().metricsT(mi, dim);
266 }
267
268
269 void MathScriptInset::drawT(TextPainter & pain, int x, int y) const
270 {
271         if (nuc().size())
272                 nuc().drawT(pain, x + dxx(), y);
273         if (hasUp())
274                 up().drawT(pain, x + dx1(), y - dy1());
275         if (hasDown())
276                 down().drawT(pain, x + dx0(), y + dy0());
277 }
278
279
280
281 bool MathScriptInset::hasLimits() const
282 {
283         // obvious cases
284         if (limits_ == 1)
285                 return true;
286         if (limits_ == -1)
287                 return false;
288
289         // we can only display limits if the nucleus wants some
290         if (!nuc().size())
291                 return false;
292         if (!nuc().back()->isScriptable())
293                 return false;
294
295         // per default \int has limits beside the \int even in displayed formulas
296         if (nuc().back()->asSymbolInset())
297                 if (nuc().back()->asSymbolInset()->name().find("int") != string::npos)
298                         return false;
299
300         // assume "real" limits for everything else
301         return true;
302 }
303
304
305 void MathScriptInset::removeScript(bool up)
306 {
307         lyxerr << "MathScriptInset::removeScript: 1 up: " << up << endl;
308         if (nargs() == 2) {
309                 lyxerr << "MathScriptInset::removeScript: a up: " << up << endl;
310                 if (up == cell_1_is_up_)
311                         cells_.pop_back();
312                 lyxerr << "MathScriptInset::removeScript: b up: " << up << endl;
313         } else if (nargs() == 3) {
314                 if (up == true) {
315                         swap(cells_[1], cells_[2]);
316                         cell_1_is_up_ = false;
317                 } else {
318                         cell_1_is_up_ = true;
319                 }
320                 cells_.pop_back();
321         }
322         lyxerr << "MathScriptInset::removeScript: 2 up: " << up << endl;
323 }
324
325
326 bool MathScriptInset::has(bool up) const
327 {
328         return idxOfScript(up);
329 }
330
331
332 bool MathScriptInset::hasUp() const
333 {
334         //lyxerr << "1up: " << bool(cell_1_is_up_) << endl;
335         //lyxerr << "hasUp: " << bool(idxOfScript(true)) << endl;
336         return idxOfScript(true);
337 }
338
339
340 bool MathScriptInset::hasDown() const
341 {
342         //lyxerr << "1up: " << bool(cell_1_is_up_) << endl;
343         //lyxerr << "hasDown: " << bool(idxOfScript(false)) << endl;
344         return idxOfScript(false);
345 }
346
347
348 InsetBase::idx_type MathScriptInset::idxOfScript(bool up) const
349 {
350         if (nargs() == 1)
351                 return 0;
352         if (nargs() == 2)
353                 return (cell_1_is_up_ == up) ? 1 : 0;
354         if (nargs() == 3)
355                 return up ? 1 : 2;
356         BOOST_ASSERT(false);
357         // Silence compiler
358         return 0;
359 }
360
361
362 bool MathScriptInset::idxRight(LCursor &) const
363 {
364         return false;
365 }
366
367
368 bool MathScriptInset::idxLeft(LCursor &) const
369 {
370         return false;
371 }
372
373
374 bool MathScriptInset::idxUpDown(LCursor & cur, bool up) const
375 {
376         // in nucleus?
377         if (cur.idx() == 0) {
378                 // don't go up/down if there is no cell in this direction
379                 if (!has(up))
380                         return false;
381                 // go up/down only if in the last position
382                 // or in the first position of something with displayed limits
383                 if (cur.pos() == cur.lastpos() || (cur.pos() == 0 && hasLimits())) {
384                         cur.idx() = idxOfScript(up);
385                         cur.pos() = 0;
386                         return true;
387                 }
388                 return false;
389         }
390
391         // Are we 'up'?
392         if (has(up) && cur.idx() == idxOfScript(true)) {
393                 // can't go further up
394                 if (up)
395                         return false;
396                 // otherwise go to last position in the nucleus
397                 cur.idx() = 0;
398                 cur.pos() = cur.lastpos();
399                 return true;
400         }
401
402         // Are we 'down'?
403         if (has(up) && cur.idx() == idxOfScript(false)) {
404                 // can't go further down
405                 if (!up)
406                         return false;
407                 // otherwise go to last position in the nucleus
408                 cur.idx() = 0;
409                 cur.pos() = cur.lastpos();
410                 return true;
411         }
412
413         return false;
414 }
415
416
417 void MathScriptInset::write(WriteStream & os) const
418 {
419         if (nuc().size()) {
420                 os << nuc();
421                 //if (nuc().back()->takesLimits()) {
422                         if (limits_ == -1)
423                                 os << "\\nolimits ";
424                         if (limits_ == 1)
425                                 os << "\\limits ";
426                 //}
427         } else {
428                 if (os.firstitem())
429                         lyxerr[Debug::MATHED] << "suppressing {} when writing"
430                                               << endl;
431                 else
432                         os << "{}";
433         }
434
435         if (hasDown() && down().size())
436                 os << "_{" << down() << '}';
437
438         if (hasUp() && up().size())
439                 os << "^{" << up() << '}';
440
441         if (lock_ && !os.latex())
442                 os << "\\lyxlock ";
443 }
444
445
446 void MathScriptInset::normalize(NormalStream & os) const
447 {
448         bool d = hasDown() && down().size();
449         bool u = hasUp() && up().size();
450
451         if (u && d)
452                 os << "[subsup ";
453         else if (u)
454                 os << "[sup ";
455         else if (d)
456                 os << "[sub ";
457
458         if (nuc().size())
459                 os << nuc() << ' ';
460         else
461                 os << "[par]";
462
463         if (u && d)
464                 os << down() << ' ' << up() << ']';
465         else if (d)
466                 os << down() << ']';
467         else if (u)
468                 os << up() << ']';
469 }
470
471
472 void MathScriptInset::maple(MapleStream & os) const
473 {
474         if (nuc().size())
475                 os << nuc();
476         if (hasDown() && down().size())
477                 os << '[' << down() << ']';
478         if (hasUp() && up().size())
479                 os << "^(" << up() << ')';
480 }
481
482
483 void MathScriptInset::mathematica(MathematicaStream & os) const
484 {
485         bool d = hasDown() && down().size();
486         bool u = hasUp() && up().size();
487
488         if (nuc().size()) {
489                 if (d)
490                         os << "Subscript[" << nuc();
491                 else
492                         os << nuc();
493         }
494
495         if (u)
496                 os << "^(" << up() << ')';
497
498         if (nuc().size()) {
499                 if (d)
500                         os << ',' << down() << ']';
501         }
502 }
503
504
505 void MathScriptInset::mathmlize(MathMLStream & os) const
506 {
507         bool d = hasDown() && down().size();
508         bool u = hasUp() && up().size();
509
510         if (u && d)
511                 os << MTag("msubsup");
512         else if (u)
513                 os << MTag("msup");
514         else if (d)
515                 os << MTag("msub");
516
517         if (nuc().size())
518                 os << nuc();
519         else
520                 os << "<mrow/>";
521
522         if (u && d)
523                 os << down() << up() << ETag("msubsup");
524         else if (u)
525                 os << up() << ETag("msup");
526         else if (d)
527                 os << down() << ETag("msub");
528 }
529
530
531 void MathScriptInset::octave(OctaveStream & os) const
532 {
533         if (nuc().size())
534                 os << nuc();
535         if (hasDown() && down().size())
536                 os << '[' << down() << ']';
537         if (hasUp() && up().size())
538                 os << "^(" << up() << ')';
539 }
540
541
542 void MathScriptInset::infoize(std::ostream & os) const
543 {
544         os << "Scripts";
545 }
546
547
548 void MathScriptInset::infoize2(std::ostream & os) const
549 {
550         if (limits_)
551                 os << (limits_ == 1 ? ", Displayed limits" : ", Inlined limits");
552 }
553
554
555 void MathScriptInset::notifyCursorLeaves(LCursor & cur)
556 {
557         MathNestInset::notifyCursorLeaves(cur);
558
559         lyxerr << "MathScriptInset::notifyCursorLeaves: 1 " << cur << endl;
560
561         // remove empty scripts if possible
562 if (1) {
563         if (nargs() > 2 && cur.idx() == 2 && cell(2).empty()) {
564                 // must be a subscript...
565                 removeScript(false);
566         } else if (nargs() > 1 && cur.idx() == 1 && cell(1).empty()) {
567                 // could be either subscript or super script
568                 removeScript(cell_1_is_up_);
569         }
570 }
571
572         lyxerr << "MathScriptInset::notifyCursorLeaves: 2 " << cur << endl;
573 }
574
575
576 void MathScriptInset::doDispatch(LCursor & cur, FuncRequest & cmd)
577 {
578         //lyxerr << "MathScriptInset: request: " << cmd << std::endl;
579
580         if (cmd.action == LFUN_MATH_LIMITS) {
581                 if (!cmd.argument.empty()) {
582                         if (cmd.argument == "limits")
583                                 limits_ = 1;
584                         else if (cmd.argument == "nolimits")
585                                 limits_ = -1;
586                         else
587                                 limits_ = 0;
588                 } else if (limits_ == 0)
589                         limits_ = hasLimits() ? -1 : 1;
590                 else
591                         limits_ = 0;
592                 return;
593         }
594
595         MathNestInset::doDispatch(cur, cmd);
596 }