]> git.lyx.org Git - lyx.git/blob - src/mathed/MathData.cpp
Fix build with GNU libstdc++ C++11 ABI
[lyx.git] / src / mathed / MathData.cpp
1 /**
2  * \file MathData.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  * \author Stefan Schimanski
8  *
9  * Full author contact details are available in file CREDITS.
10  */
11
12 #include <config.h>
13
14 #include "MathData.h"
15
16 #include "InsetMathBrace.h"
17 #include "InsetMathFont.h"
18 #include "InsetMathScript.h"
19 #include "MacroTable.h"
20 #include "MathMacro.h"
21 #include "MathStream.h"
22 #include "MathSupport.h"
23 #include "MetricsInfo.h"
24 #include "ReplaceData.h"
25
26 #include "Buffer.h"
27 #include "BufferView.h"
28 #include "CoordCache.h"
29 #include "Cursor.h"
30
31 #include "mathed/InsetMathUnknown.h"
32
33 #include "support/debug.h"
34 #include "support/docstream.h"
35
36 #include "frontends/FontMetrics.h"
37 #include "frontends/Painter.h"
38
39 #include "support/gettext.h"
40 #include "support/lassert.h"
41 #include <boost/next_prior.hpp>
42
43 #include <cstdlib>
44
45 using namespace std;
46
47 namespace lyx {
48
49
50 MathData::MathData(Buffer * buf, const_iterator from, const_iterator to)
51         : base_type(from, to), buffer_(buf)
52 {}
53
54
55 MathAtom & MathData::operator[](pos_type pos)
56 {
57         LBUFERR(pos < size());
58         return base_type::operator[](pos);
59 }
60
61
62 MathAtom const & MathData::operator[](pos_type pos) const
63 {
64         LBUFERR(pos < size());
65         return base_type::operator[](pos);
66 }
67
68
69 void MathData::insert(size_type pos, MathAtom const & t)
70 {
71         LBUFERR(pos <= size());
72         base_type::insert(begin() + pos, t);
73 }
74
75
76 void MathData::insert(size_type pos, MathData const & ar)
77 {
78         LBUFERR(pos <= size());
79         base_type::insert(begin() + pos, ar.begin(), ar.end());
80 }
81
82
83 void MathData::append(MathData const & ar)
84 {
85         insert(size(), ar);
86 }
87
88
89 void MathData::erase(size_type pos)
90 {
91         if (pos < size())
92                 erase(pos, pos + 1);
93 }
94
95
96 void MathData::erase(iterator pos1, iterator pos2)
97 {
98         base_type::erase(pos1, pos2);
99 }
100
101
102 void MathData::erase(iterator pos)
103 {
104         base_type::erase(pos);
105 }
106
107
108 void MathData::erase(size_type pos1, size_type pos2)
109 {
110         base_type::erase(begin() + pos1, begin() + pos2);
111 }
112
113
114 void MathData::dump2() const
115 {
116         odocstringstream os;
117         NormalStream ns(os);
118         for (const_iterator it = begin(); it != end(); ++it)
119                 ns << *it << ' ';
120         lyxerr << to_utf8(os.str());
121 }
122
123
124 void MathData::dump() const
125 {
126         odocstringstream os;
127         NormalStream ns(os);
128         for (const_iterator it = begin(); it != end(); ++it)
129                 ns << '<' << *it << '>';
130         lyxerr << to_utf8(os.str());
131 }
132
133
134 void MathData::validate(LaTeXFeatures & features) const
135 {
136         for (const_iterator it = begin(); it != end(); ++it)
137                 (*it)->validate(features);
138 }
139
140
141 bool MathData::match(MathData const & ar) const
142 {
143         return size() == ar.size() && matchpart(ar, 0);
144 }
145
146
147 bool MathData::matchpart(MathData const & ar, pos_type pos) const
148 {
149         if (size() < ar.size() + pos)
150                 return false;
151         const_iterator it = begin() + pos;
152         for (const_iterator jt = ar.begin(); jt != ar.end(); ++jt, ++it)
153                 if (asString(*it) != asString(*jt))
154                         return false;
155         return true;
156 }
157
158
159 void MathData::replace(ReplaceData & rep)
160 {
161         for (size_type i = 0; i < size(); ++i) {
162                 if (find1(rep.from, i)) {
163                         // match found
164                         lyxerr << "match found!" << endl;
165                         erase(i, i + rep.from.size());
166                         insert(i, rep.to);
167                 }
168         }
169
170         // FIXME: temporarily disabled
171         // for (const_iterator it = begin(); it != end(); ++it)
172         //      it->nucleus()->replace(rep);
173 }
174
175
176 bool MathData::find1(MathData const & ar, size_type pos) const
177 {
178         lyxerr << "finding '" << ar << "' in '" << *this << "'" << endl;
179         for (size_type i = 0, n = ar.size(); i < n; ++i)
180                 if (asString(operator[](pos + i)) != asString(ar[i]))
181                         return false;
182         return true;
183 }
184
185
186 MathData::size_type MathData::find(MathData const & ar) const
187 {
188         for (int i = 0, last = size() - ar.size(); i < last; ++i)
189                 if (find1(ar, i))
190                         return i;
191         return size();
192 }
193
194
195 MathData::size_type MathData::find_last(MathData const & ar) const
196 {
197         for (int i = size() - ar.size(); i >= 0; --i)
198                 if (find1(ar, i))
199                         return i;
200         return size();
201 }
202
203
204 bool MathData::contains(MathData const & ar) const
205 {
206         if (find(ar) != size())
207                 return true;
208         for (const_iterator it = begin(); it != end(); ++it)
209                 if ((*it)->contains(ar))
210                         return true;
211         return false;
212 }
213
214
215 void MathData::touch() const
216 {
217 }
218
219
220 #if 0
221 namespace {
222
223 bool isInside(DocIterator const & it, MathData const & ar,
224         pos_type p1, pos_type p2)
225 {
226         for (size_t i = 0; i != it.depth(); ++i) {
227                 CursorSlice const & sl = it[i];
228                 if (sl.inset().inMathed() && &sl.cell() == &ar)
229                         return p1 <= sl.pos() && sl.pos() < p2;
230         }
231         return false;
232 }
233
234 }
235 #endif
236
237
238 void MathData::metrics(MetricsInfo & mi, Dimension & dim) const
239 {
240         frontend::FontMetrics const & fm = theFontMetrics(mi.base.font);
241         dim = fm.dimension('I');
242         int xascent = fm.dimension('x').ascent();
243         if (xascent >= dim.asc)
244                 xascent = (2 * dim.asc) / 3;
245         minasc_ = xascent;
246         mindes_ = (3 * xascent) / 4;
247         slevel_ = (4 * xascent) / 5;
248         sshift_ = xascent / 4;
249         kerning_ = 0;
250
251         if (empty()) {
252                 // Cache the dimension.
253                 mi.base.bv->coordCache().arrays().add(this, dim);
254                 return;
255         }
256
257         Cursor & cur = mi.base.bv->cursor();
258         const_cast<MathData*>(this)->updateMacros(&cur, mi.macrocontext, InternalUpdate);
259
260         DocIterator const & inlineCompletionPos = mi.base.bv->inlineCompletionPos();
261         MathData const * inlineCompletionData = 0;
262         if (inlineCompletionPos.inMathed())
263                 inlineCompletionData = &inlineCompletionPos.cell();
264
265         dim.asc = 0;
266         dim.wid = 0;
267         Dimension d;
268         CoordCacheBase<Inset> & coords = mi.base.bv->coordCache().insets();
269         for (pos_type i = 0, n = size(); i != n; ++i) {
270                 MathAtom const & at = operator[](i);
271                 at->metrics(mi, d);
272                 coords.add(at.nucleus(), d);
273                 dim += d;
274                 if (i == n - 1)
275                         kerning_ = at->kerning(mi.base.bv);
276
277                 // HACK to draw completion suggestion inline
278                 if (inlineCompletionData != this
279                     || size_t(inlineCompletionPos.pos()) != i + 1)
280                         continue;
281
282                 docstring const & completion = mi.base.bv->inlineCompletion();
283                 if (completion.length() == 0)
284                         continue;
285
286                 FontInfo font = mi.base.font;
287                 augmentFont(font, from_ascii("mathnormal"));
288                 dim.wid += mathed_string_width(font, completion);
289         }
290         // Cache the dimension.
291         mi.base.bv->coordCache().arrays().add(this, dim);
292 }
293
294
295 void MathData::draw(PainterInfo & pi, int x, int y) const
296 {
297         //lyxerr << "MathData::draw: x: " << x << " y: " << y << endl;
298         BufferView & bv  = *pi.base.bv;
299         setXY(bv, x, y);
300
301         Dimension const & dim = bv.coordCache().getArrays().dim(this);
302
303         if (empty()) {
304                 pi.pain.rectangle(x, y - dim.ascent(), dim.width(), dim.height(), Color_mathline);
305                 return;
306         }
307
308         // don't draw outside the workarea
309         if (y + dim.descent() <= 0
310                 || y - dim.ascent() >= bv.workHeight()
311                 || x + dim.width() <= 0
312                 || x >= bv. workWidth())
313                 return;
314
315         DocIterator const & inlineCompletionPos = bv.inlineCompletionPos();
316         MathData const * inlineCompletionData = 0;
317         if (inlineCompletionPos.inMathed())
318                 inlineCompletionData = &inlineCompletionPos.cell();
319
320         CoordCacheBase<Inset> & coords = pi.base.bv->coordCache().insets();
321         for (size_t i = 0, n = size(); i != n; ++i) {
322                 MathAtom const & at = operator[](i);
323                 coords.add(at.nucleus(), x, y);
324                 at->drawSelection(pi, x, y);
325                 at->draw(pi, x, y);
326                 x += coords.dim(at.nucleus()).wid;
327
328                 // Is the inline completion here?
329                 if (inlineCompletionData != this
330                     || size_t(inlineCompletionPos.pos()) != i + 1)
331                         continue;
332                 docstring const & completion = bv.inlineCompletion();
333                 if (completion.length() == 0)
334                         continue;
335                 FontInfo f = pi.base.font;
336                 augmentFont(f, from_ascii("mathnormal"));
337
338                 // draw the unique and the non-unique completion part
339                 // Note: this is not time-critical as it is
340                 // only done once per screen.
341                 size_t uniqueTo = bv.inlineCompletionUniqueChars();
342                 docstring s1 = completion.substr(0, uniqueTo);
343                 docstring s2 = completion.substr(uniqueTo);
344
345                 if (!s1.empty()) {
346                         f.setColor(Color_inlinecompletion);
347                         pi.pain.text(x, y, s1, f);
348                         x += mathed_string_width(f, s1);
349                 }
350
351                 if (!s2.empty()) {
352                         f.setColor(Color_nonunique_inlinecompletion);
353                         pi.pain.text(x, y, s2, f);
354                         x += mathed_string_width(f, s2);
355                 }
356         }
357 }
358
359
360 void MathData::metricsT(TextMetricsInfo const & mi, Dimension & dim) const
361 {
362         dim.clear();
363         Dimension d;
364         for (const_iterator it = begin(); it != end(); ++it) {
365                 (*it)->metricsT(mi, d);
366                 dim += d;
367         }
368 }
369
370
371 void MathData::drawT(TextPainter & pain, int x, int y) const
372 {
373         //lyxerr << "x: " << x << " y: " << y << ' ' << pain.workAreaHeight() << endl;
374
375         // FIXME: Abdel 16/10/2006
376         // This drawT() method is never used, this is dead code.
377
378         for (const_iterator it = begin(), et = end(); it != et; ++it) {
379                 (*it)->drawT(pain, x, y);
380                 //x += (*it)->width_;
381                 x += 2;
382         }
383 }
384
385
386 void MathData::updateBuffer(ParIterator const & it, UpdateType utype)
387 {
388         // pass down
389         for (size_t i = 0, n = size(); i != n; ++i) {
390                 MathAtom & at = operator[](i);
391                 at.nucleus()->updateBuffer(it, utype);
392         }
393 }
394
395
396 void MathData::updateMacros(Cursor * cur, MacroContext const & mc,
397                 UpdateType utype)
398 {
399         // If we are editing a macro, we cannot update it immediately,
400         // otherwise wrong undo steps will be recorded (bug 6208).
401         InsetMath const * inmath = cur ? cur->inset().asInsetMath() : 0;
402         MathMacro const * inmacro = inmath ? inmath->asMacro() : 0;
403         docstring const edited_name = inmacro ? inmacro->name() : docstring();
404
405         // go over the array and look for macros
406         for (size_t i = 0; i < size(); ++i) {
407                 MathMacro * macroInset = operator[](i).nucleus()->asMacro();
408                 if (!macroInset || macroInset->macroName().empty()
409                                 || macroInset->macroName()[0] == '^'
410                                 || macroInset->macroName()[0] == '_'
411                                 || (macroInset->name() == edited_name
412                                     && macroInset->displayMode() ==
413                                                 MathMacro::DISPLAY_UNFOLDED))
414                         continue;
415
416                 // get macro
417                 macroInset->updateMacro(mc);
418                 size_t macroNumArgs = 0;
419                 size_t macroOptionals = 0;
420                 MacroData const * macro = macroInset->macro();
421                 if (macro) {
422                         macroNumArgs = macro->numargs();
423                         macroOptionals = macro->optionals();
424                 }
425
426                 // store old and compute new display mode
427                 MathMacro::DisplayMode newDisplayMode;
428                 MathMacro::DisplayMode oldDisplayMode = macroInset->displayMode();
429                 newDisplayMode = macroInset->computeDisplayMode();
430
431                 // arity changed or other reason to detach?
432                 if (oldDisplayMode == MathMacro::DISPLAY_NORMAL
433                     && (macroInset->arity() != macroNumArgs
434                         || macroInset->optionals() != macroOptionals
435                         || newDisplayMode == MathMacro::DISPLAY_UNFOLDED)) {
436
437                         detachMacroParameters(cur, i);
438                         // FIXME: proper anchor handling, this removes the selection
439                         if (cur)
440                                 cur->clearSelection();
441                 }
442
443                 // the macro could have been copied while resizing this
444                 macroInset = operator[](i).nucleus()->asMacro();
445
446                 // Cursor in \label?
447                 if (newDisplayMode != MathMacro::DISPLAY_UNFOLDED
448                     && oldDisplayMode == MathMacro::DISPLAY_UNFOLDED) {
449                         // put cursor in front of macro
450                         if (cur) {
451                                 int macroSlice = cur->find(macroInset);
452                                 if (macroSlice != -1)
453                                         cur->cutOff(macroSlice - 1);
454                         }
455                 }
456
457                 // update the display mode
458                 size_t appetite = macroInset->appetite();
459                 macroInset->setDisplayMode(newDisplayMode);
460
461                 // arity changed?
462                 if (newDisplayMode == MathMacro::DISPLAY_NORMAL
463                     && (macroInset->arity() != macroNumArgs
464                         || macroInset->optionals() != macroOptionals)) {
465                         // is it a virgin macro which was never attached to parameters?
466                         bool fromInitToNormalMode
467                         = (oldDisplayMode == MathMacro::DISPLAY_INIT
468                            || oldDisplayMode == MathMacro::DISPLAY_INTERACTIVE_INIT)
469                           && newDisplayMode == MathMacro::DISPLAY_NORMAL;
470
471                         // if the macro was entered interactively (i.e. not by paste or during
472                         // loading), it should not be greedy, but the cursor should
473                         // automatically jump into the macro when behind
474                         bool interactive = (oldDisplayMode == MathMacro::DISPLAY_INTERACTIVE_INIT);
475
476                         // attach parameters
477                         attachMacroParameters(cur, i, macroNumArgs, macroOptionals,
478                                 fromInitToNormalMode, interactive, appetite);
479
480                         if (cur) {
481                                 // FIXME: proper anchor handling, this removes the selection
482                                 cur->updateInsets(&cur->bottom().inset());
483                                 cur->clearSelection();
484                         }
485                 }
486
487                 // Give macro the chance to adapt to new situation.
488                 // The macroInset could be invalid now because it was put into a script
489                 // inset and therefore "deep" copied. So get it again from the MathData.
490                 InsetMath * inset = operator[](i).nucleus();
491                 if (inset->asScriptInset())
492                         inset = inset->asScriptInset()->nuc()[0].nucleus();
493                 LASSERT(inset->asMacro(), continue);
494                 inset->asMacro()->updateRepresentation(cur, mc, utype);
495         }
496 }
497
498
499 void MathData::detachMacroParameters(DocIterator * cur, const size_type macroPos)
500 {
501         MathMacro * macroInset = operator[](macroPos).nucleus()->asMacro();
502
503         // detach all arguments
504         vector<MathData> detachedArgs;
505         if (macroPos + 1 == size())
506                 // strip arguments if we are at the MathData end
507                 macroInset->detachArguments(detachedArgs, true);
508         else
509                 macroInset->detachArguments(detachedArgs, false);
510
511         // find cursor slice
512         int curMacroSlice = -1;
513         if (cur)
514                 curMacroSlice = cur->find(macroInset);
515         idx_type curMacroIdx = -1;
516         pos_type curMacroPos = -1;
517         vector<CursorSlice> argSlices;
518         if (curMacroSlice != -1) {
519                 curMacroPos = (*cur)[curMacroSlice].pos();
520                 curMacroIdx = (*cur)[curMacroSlice].idx();
521                 cur->cutOff(curMacroSlice, argSlices);
522                 cur->pop_back();
523         }
524
525         // only [] after the last non-empty argument can be dropped later
526         size_t lastNonEmptyOptional = 0;
527         for (size_t l = 0; l < detachedArgs.size() && l < macroInset->optionals(); ++l) {
528                 if (!detachedArgs[l].empty())
529                         lastNonEmptyOptional = l;
530         }
531
532         // optional arguments to be put back?
533         pos_type p = macroPos + 1;
534         size_t j = 0;
535         for (; j < detachedArgs.size() && j < macroInset->optionals(); ++j) {
536                 // another non-empty parameter follows?
537                 bool canDropEmptyOptional = j >= lastNonEmptyOptional;
538
539                 // then we can drop empty optional parameters
540                 if (detachedArgs[j].empty() && canDropEmptyOptional) {
541                         if (curMacroIdx == j)
542                                 (*cur)[curMacroSlice - 1].pos() = macroPos + 1;
543                         continue;
544                 }
545
546                 // Otherwise we don't drop an empty optional, put it back normally
547                 MathData optarg;
548                 asArray(from_ascii("[]"), optarg);
549                 MathData & arg = detachedArgs[j];
550
551                 // look for "]", i.e. put a brace around?
552                 InsetMathBrace * brace = 0;
553                 for (size_t q = 0; q < arg.size(); ++q) {
554                         if (arg[q]->getChar() == ']') {
555                                 // put brace
556                                 brace = new InsetMathBrace(buffer_);
557                                 break;
558                         }
559                 }
560
561                 // put arg between []
562                 if (brace) {
563                         brace->cell(0) = arg;
564                         optarg.insert(1, MathAtom(brace));
565                 } else
566                         optarg.insert(1, arg);
567
568                 // insert it into the array
569                 insert(p, optarg);
570                 p += optarg.size();
571
572                 // cursor in macro?
573                 if (curMacroSlice == -1)
574                         continue;
575
576                 // cursor in optional argument of macro?
577                 if (curMacroIdx == j) {
578                         if (brace) {
579                                 cur->append(0, curMacroPos);
580                                 (*cur)[curMacroSlice - 1].pos() = macroPos + 2;
581                         } else
582                                 (*cur)[curMacroSlice - 1].pos() = macroPos + 2 + curMacroPos;
583                         cur->append(argSlices);
584                 } else if ((*cur)[curMacroSlice - 1].pos() >= int(p))
585                         // cursor right of macro
586                         (*cur)[curMacroSlice - 1].pos() += optarg.size();
587         }
588
589         // put them back into the MathData
590         for (; j < detachedArgs.size(); ++j, ++p) {
591                 MathData const & arg = detachedArgs[j];
592                 if (arg.size() == 1
593                     && !arg[0]->asScriptInset()
594                     && !(arg[0]->asMacro() && arg[0]->asMacro()->arity() > 0))
595                         insert(p, arg[0]);
596                 else
597                         insert(p, MathAtom(new InsetMathBrace(arg)));
598
599                 // cursor in macro?
600                 if (curMacroSlice == -1)
601                         continue;
602
603                 // cursor in j-th argument of macro?
604                 if (curMacroIdx == j) {
605                         if (operator[](p).nucleus()->asBraceInset()) {
606                                 (*cur)[curMacroSlice - 1].pos() = p;
607                                 cur->append(0, curMacroPos);
608                                 cur->append(argSlices);
609                         } else {
610                                 (*cur)[curMacroSlice - 1].pos() = p; // + macroPos;
611                                 cur->append(argSlices);
612                         }
613                 } else if ((*cur)[curMacroSlice - 1].pos() >= int(p))
614                         ++(*cur)[curMacroSlice - 1].pos();
615         }
616
617         if (cur)
618                 cur->updateInsets(&cur->bottom().inset());
619 }
620
621
622 void MathData::attachMacroParameters(Cursor * cur,
623         const size_type macroPos, const size_type macroNumArgs,
624         const int macroOptionals, const bool fromInitToNormalMode,
625         const bool interactiveInit, const size_t appetite)
626 {
627         MathMacro * macroInset = operator[](macroPos).nucleus()->asMacro();
628
629         // start at atom behind the macro again, maybe with some new arguments
630         // from the detach phase above, to add them back into the macro inset
631         size_t p = macroPos + 1;
632         vector<MathData> detachedArgs;
633         MathAtom scriptToPutAround;
634
635         // find cursor slice again of this MathData
636         int thisSlice = -1;
637         if (cur)
638                 thisSlice = cur->find(*this);
639         int thisPos = -1;
640         if (thisSlice != -1)
641                 thisPos = (*cur)[thisSlice].pos();
642
643         // find arguments behind the macro
644         if (!interactiveInit) {
645                 collectOptionalParameters(cur, macroOptionals, detachedArgs, p,
646                         scriptToPutAround, macroPos, thisPos, thisSlice);
647         }
648         collectParameters(cur, macroNumArgs, detachedArgs, p,
649                 scriptToPutAround, macroPos, thisPos, thisSlice, appetite);
650
651         // attach arguments back to macro inset
652         macroInset->attachArguments(detachedArgs, macroNumArgs, macroOptionals);
653
654         // found tail script? E.g. \foo{a}b^x
655         if (scriptToPutAround.nucleus()) {
656                 InsetMathScript * scriptInset =
657                         scriptToPutAround.nucleus()->asScriptInset();
658                 // In the math parser we remove empty braces in the base
659                 // of a script inset, but we have to restore them here.
660                 if (scriptInset->nuc().empty()) {
661                         MathData ar;
662                         scriptInset->nuc().push_back(
663                                         MathAtom(new InsetMathBrace(ar)));
664                 }
665                 // put macro into a script inset
666                 scriptInset->nuc()[0] = operator[](macroPos);
667                 operator[](macroPos) = scriptToPutAround;
668
669                 // go into the script inset nucleus
670                 if (cur && thisPos == int(macroPos))
671                         cur->append(0, 0);
672
673                 // get pointer to "deep" copied macro inset
674                 scriptInset = operator[](macroPos).nucleus()->asScriptInset();
675                 macroInset = scriptInset->nuc()[0].nucleus()->asMacro();
676         }
677
678         // remove them from the MathData
679         erase(macroPos + 1, p);
680
681         // cursor outside this MathData?
682         if (thisSlice == -1)
683                 return;
684
685         // fix cursor if right of p
686         if (thisPos >= int(p))
687                 (*cur)[thisSlice].pos() -= p - (macroPos + 1);
688
689         // was the macro inset just inserted interactively and was now folded
690         // and the cursor is just behind?
691         if ((*cur)[thisSlice].pos() == int(macroPos + 1)
692             && interactiveInit
693             && fromInitToNormalMode
694             && macroInset->arity() > 0
695             && thisSlice + 1 == int(cur->depth())) {
696                 // then enter it if the cursor was just behind
697                 (*cur)[thisSlice].pos() = macroPos;
698                 cur->push_back(CursorSlice(*macroInset));
699                 macroInset->idxFirst(*cur);
700         }
701 }
702
703
704 void MathData::collectOptionalParameters(Cursor * cur,
705         const size_type numOptionalParams, vector<MathData> & params,
706         size_t & pos, MathAtom & scriptToPutAround,
707         const pos_type macroPos, const int thisPos, const int thisSlice)
708 {
709         Buffer * buf = cur ? cur->buffer() : 0;
710         // insert optional arguments?
711         while (params.size() < numOptionalParams
712                && pos < size()
713                && !scriptToPutAround.nucleus()) {
714                 // is a [] block following which could be an optional parameter?
715                 if (operator[](pos)->getChar() != '[')
716                         break;
717
718                 // found possible optional argument, look for "]"
719                 size_t right = pos + 1;
720                 for (; right < size(); ++right) {
721                         MathAtom & cell = operator[](right);
722
723                         if (cell->getChar() == ']')
724                                 // found right end
725                                 break;
726
727                         // maybe "]" with a script around?
728                         InsetMathScript * script = cell.nucleus()->asScriptInset();
729                         if (!script)
730                                 continue;
731                         if (script->nuc().size() != 1)
732                                 continue;
733                         if (script->nuc()[0]->getChar() == ']') {
734                                 // script will be put around the macro later
735                                 scriptToPutAround = cell;
736                                 break;
737                         }
738                 }
739
740                 // found?
741                 if (right >= size()) {
742                         // no ] found, so it's not an optional argument
743                         break;
744                 }
745
746                 // add everything between [ and ] as optional argument
747                 MathData optarg(buf, begin() + pos + 1, begin() + right);
748
749                 // a brace?
750                 bool brace = false;
751                 if (optarg.size() == 1 && optarg[0]->asBraceInset()) {
752                         brace = true;
753                         params.push_back(optarg[0]->asBraceInset()->cell(0));
754                 } else
755                         params.push_back(optarg);
756
757                 // place cursor in optional argument of macro
758                 if (thisSlice != -1
759                     && thisPos >= int(pos) && thisPos <= int(right)) {
760                         int paramPos = max(0, thisPos - int(pos) - 1);
761                         vector<CursorSlice> x;
762                         cur->cutOff(thisSlice, x);
763                         (*cur)[thisSlice].pos() = macroPos;
764                         if (brace) {
765                                 paramPos = x[0].pos();
766                                 x.erase(x.begin());
767                         }
768                         cur->append(0, paramPos);
769                         cur->append(x);
770                 }
771                 pos = right + 1;
772         }
773
774         // fill up empty optional parameters
775         while (params.size() < numOptionalParams)
776                 params.push_back(MathData());
777 }
778
779
780 void MathData::collectParameters(Cursor * cur,
781         const size_type numParams, vector<MathData> & params,
782         size_t & pos, MathAtom & scriptToPutAround,
783         const pos_type macroPos, const int thisPos, const int thisSlice,
784         const size_t appetite)
785 {
786         size_t startSize = params.size();
787
788         // insert normal arguments
789         while (params.size() < numParams
790                && params.size() - startSize < appetite
791                && pos < size()
792                && !scriptToPutAround.nucleus()) {
793                 MathAtom & cell = operator[](pos);
794
795                 // fix cursor
796                 vector<CursorSlice> argSlices;
797                 int argPos = 0;
798                 if (thisSlice != -1 && thisPos == int(pos))
799                         cur->cutOff(thisSlice, argSlices);
800
801                 // which kind of parameter is it? In {}? With index x^n?
802                 InsetMathBrace const * brace = cell->asBraceInset();
803                 if (brace) {
804                         // found brace, convert into argument
805                         params.push_back(brace->cell(0));
806
807                         // cursor inside of the brace or just in front of?
808                         if (thisPos == int(pos) && !argSlices.empty()) {
809                                 argPos = argSlices[0].pos();
810                                 argSlices.erase(argSlices.begin());
811                         }
812                 } else if (cell->asScriptInset() && params.size() + 1 == numParams) {
813                         // last inset with scripts without braces
814                         // -> they belong to the macro, not the argument
815                         InsetMathScript * script = cell.nucleus()->asScriptInset();
816                         if (script->nuc().size() == 1 && script->nuc()[0]->asBraceInset())
817                                 // nucleus in brace? Unpack!
818                                 params.push_back(script->nuc()[0]->asBraceInset()->cell(0));
819                         else
820                                 params.push_back(script->nuc());
821
822                         // script will be put around below
823                         scriptToPutAround = cell;
824
825                         // this should only happen after loading, so make cursor handling simple
826                         if (thisPos >= int(macroPos) && thisPos <= int(macroPos + numParams)) {
827                                 argSlices.clear();
828                                 if (cur)
829                                         cur->append(0, 0);
830                         }
831                 } else {
832                         // the simplest case: plain inset
833                         MathData array;
834                         array.insert(0, cell);
835                         params.push_back(array);
836                 }
837
838                 // put cursor in argument again
839                 if (thisSlice != - 1 && thisPos == int(pos)) {
840                         cur->append(params.size() - 1, argPos);
841                         cur->append(argSlices);
842                         (*cur)[thisSlice].pos() = macroPos;
843                 }
844
845                 ++pos;
846         }
847 }
848
849
850 int MathData::pos2x(BufferView const * bv, size_type pos) const
851 {
852         return pos2x(bv, pos, 0);
853 }
854
855
856 int MathData::pos2x(BufferView const * bv, size_type pos, int glue) const
857 {
858         int x = 0;
859         size_type target = min(pos, size());
860         CoordCacheBase<Inset> const & coords = bv->coordCache().getInsets();
861         for (size_type i = 0; i < target; ++i) {
862                 const_iterator it = begin() + i;
863                 if ((*it)->getChar() == ' ')
864                         x += glue;
865                 //lyxerr << "char: " << (*it)->getChar()
866                 //      << "width: " << (*it)->width() << endl;
867                 x += coords.dim((*it).nucleus()).wid;
868         }
869         return x;
870 }
871
872
873 MathData::size_type MathData::x2pos(BufferView const * bv, int targetx) const
874 {
875         return x2pos(bv, targetx, 0);
876 }
877
878
879 MathData::size_type MathData::x2pos(BufferView const * bv, int targetx, int glue) const
880 {
881         const_iterator it = begin();
882         int lastx = 0;
883         int currx = 0;
884         CoordCacheBase<Inset> const & coords = bv->coordCache().getInsets();
885         // find first position after targetx
886         for (; currx < targetx && it != end(); ++it) {
887                 lastx = currx;
888                 if ((*it)->getChar() == ' ')
889                         currx += glue;
890                 currx += coords.dim((*it).nucleus()).wid;
891         }
892
893         /**
894          * If we are not at the beginning of the array, go to the left
895          * of the inset if one of the following two condition holds:
896          * - the current inset is editable (so that the cursor tip is
897          *   deeper than us): in this case, we want all intermediate
898          *   cursor slices to be before insets;
899          * - the mouse is closer to the left side of the inset than to
900          *   the right one.
901          * See bug 1918 for details.
902          **/
903         if (it != begin() && currx >= targetx
904             && ((*boost::prior(it))->asNestInset()
905                 || abs(lastx - targetx) < abs(currx - targetx))) {
906                 --it;
907         }
908
909         return it - begin();
910 }
911
912
913 int MathData::dist(BufferView const & bv, int x, int y) const
914 {
915         return bv.coordCache().getArrays().squareDistance(this, x, y);
916 }
917
918
919 void MathData::setXY(BufferView & bv, int x, int y) const
920 {
921         //lyxerr << "setting position cache for MathData " << this << endl;
922         bv.coordCache().arrays().add(this, x, y);
923 }
924
925
926 Dimension const & MathData::dimension(BufferView const & bv) const
927 {
928         return bv.coordCache().getArrays().dim(this);
929 }
930
931
932 int MathData::xm(BufferView const & bv) const
933 {
934         Geometry const & g = bv.coordCache().getArrays().geometry(this);
935
936         return g.pos.x_ + g.dim.wid / 2;
937 }
938
939
940 int MathData::ym(BufferView const & bv) const
941 {
942         Geometry const & g = bv.coordCache().getArrays().geometry(this);
943
944         return g.pos.y_ + (g.dim.des - g.dim.asc) / 2;
945 }
946
947
948 int MathData::xo(BufferView const & bv) const
949 {
950         return bv.coordCache().getArrays().x(this);
951 }
952
953
954 int MathData::yo(BufferView const & bv) const
955 {
956         return bv.coordCache().getArrays().y(this);
957 }
958
959
960 ostream & operator<<(ostream & os, MathData const & ar)
961 {
962         odocstringstream oss;
963         NormalStream ns(oss);
964         ns << ar;
965         return os << to_utf8(oss.str());
966 }
967
968
969 odocstream & operator<<(odocstream & os, MathData const & ar)
970 {
971         NormalStream ns(os);
972         ns << ar;
973         return os;
974 }
975
976
977 } // namespace lyx