]> git.lyx.org Git - lyx.git/blob - src/mathed/MathMacroTemplate.cpp
Account for old versions of Pygments
[lyx.git] / src / mathed / MathMacroTemplate.cpp
1 /**
2  * \file MathMacroTemplate.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 "MathMacroTemplate.h"
15
16 #include "InsetMathBrace.h"
17 #include "InsetMathChar.h"
18 #include "InsetMathHull.h"
19 #include "InsetMathSqrt.h"
20 #include "MathMacro.h"
21 #include "MathMacroArgument.h"
22 #include "MathStream.h"
23 #include "MathParser.h"
24 #include "MathSupport.h"
25 #include "MathMacroArgument.h"
26
27 #include "Buffer.h"
28 #include "BufferView.h"
29 #include "Color.h"
30 #include "Cursor.h"
31 #include "DispatchResult.h"
32 #include "DocIterator.h"
33 #include "FuncRequest.h"
34 #include "FuncStatus.h"
35 #include "LaTeXFeatures.h"
36 #include "Lexer.h"
37 #include "MetricsInfo.h"
38 #include "TocBackend.h"
39
40 #include "frontends/Painter.h"
41
42 #include "insets/RenderPreview.h"
43
44 #include "support/lassert.h"
45 #include "support/convert.h"
46 #include "support/debug.h"
47 #include "support/gettext.h"
48 #include "support/docstream.h"
49 #include "support/lstrings.h"
50
51 #include <set>
52 #include <sstream>
53
54 using namespace std;
55
56 namespace lyx {
57
58 using support::bformat;
59
60 //////////////////////////////////////////////////////////////////////
61
62 class InsetLabelBox : public InsetMathNest {
63 public:
64         ///
65         InsetLabelBox(Buffer * buf, MathAtom const & atom, docstring label,
66                       MathMacroTemplate const & parent, bool frame = false);
67         InsetLabelBox(Buffer * buf, docstring label, MathMacroTemplate const & parent,
68                       bool frame = false);
69         ///
70         void metrics(MetricsInfo & mi, Dimension & dim) const;
71         ///
72         void draw(PainterInfo &, int x, int y) const;
73
74 protected:
75         ///
76         MathMacroTemplate const & parent_;
77         ///
78         Inset * clone() const;
79         ///
80         docstring const label_;
81         ///
82         bool frame_;
83 };
84
85
86 InsetLabelBox::InsetLabelBox(Buffer * buf, MathAtom const & atom, docstring label,
87         MathMacroTemplate const & parent, bool frame)
88         : InsetMathNest(buf, 1), parent_(parent), label_(label), frame_(frame)
89 {
90         cell(0).insert(0, atom);
91 }
92
93
94 InsetLabelBox::InsetLabelBox(Buffer * buf, docstring label,
95                              MathMacroTemplate const & parent, bool frame)
96         : InsetMathNest(buf, 1), parent_(parent), label_(label), frame_(frame)
97 {
98 }
99
100
101 Inset * InsetLabelBox::clone() const
102 {
103         return new InsetLabelBox(*this);
104 }
105
106
107 void InsetLabelBox::metrics(MetricsInfo & mi, Dimension & dim) const
108 {
109         // kernel
110         cell(0).metrics(mi, dim);
111
112         // frame
113         if (frame_) {
114                 dim.wid += 6;
115                 dim.asc += 5;
116                 dim.des += 5;
117         }
118
119         // adjust to common height in main metrics phase
120         if (!parent_.premetrics()) {
121                 dim.asc = max(dim.asc, parent_.commonLabelBoxAscent());
122                 dim.des = max(dim.des, parent_.commonLabelBoxDescent());
123         }
124
125         // label
126         if (parent_.editing(mi.base.bv) && label_.length() > 0) {
127                 // grey
128                 FontInfo font = sane_font;
129                 font.setSize(FONT_SIZE_TINY);
130                 font.setColor(Color_mathmacrolabel);
131
132                 // make space for label and box
133                 int lwid = mathed_string_width(font, label_);
134                 int maxasc;
135                 int maxdes;
136                 math_font_max_dim(font, maxasc, maxdes);
137
138                 dim.wid = max(dim.wid, lwid + 2);
139
140                 // space for the label
141                 if (!parent_.premetrics())
142                         dim.des += maxasc + maxdes + 1;
143         }
144 }
145
146
147 void InsetLabelBox::draw(PainterInfo & pi, int x, int y) const
148 {
149         Dimension const dim = dimension(*pi.base.bv);
150         Dimension const cdim = cell(0).dimension(*pi.base.bv);
151
152         // kernel
153         cell(0).draw(pi, x + (dim.wid - cdim.wid) / 2, y);
154
155         // label
156         if (parent_.editing(pi.base.bv) && label_.length() > 0) {
157                 // grey
158                 FontInfo font = sane_font;
159                 font.setSize(FONT_SIZE_TINY);
160                 font.setColor(Color_mathmacrolabel);
161
162                 // make space for label and box
163                 int lwid = mathed_string_width(font, label_);
164                 int maxasc;
165                 int maxdes;
166                 math_font_max_dim(font, maxasc, maxdes);
167
168                 if (lwid < dim.wid)
169                         pi.pain.text(x + (dim.wid - lwid) / 2, y + dim.des - maxdes, label_, font);
170                 else
171                         pi.pain.text(x, y + dim.des - maxdes, label_, font);
172         }
173
174         // draw frame
175         int boxHeight = parent_.commonLabelBoxAscent() + parent_.commonLabelBoxDescent();
176         if (frame_) {
177                 pi.pain.rectangle(x + 1, y - dim.ascent() + 1,
178                                   dim.wid - 2, boxHeight - 2,
179                                   Color_mathline);
180         }
181 }
182
183
184 //////////////////////////////////////////////////////////////////////
185
186 class DisplayLabelBox : public InsetLabelBox {
187 public:
188         ///
189         DisplayLabelBox(Buffer * buf, MathAtom const & atom, docstring label,
190                         MathMacroTemplate const & parent);
191
192         ///
193         marker_type marker(BufferView const *) const;
194         ///
195         void metrics(MetricsInfo & mi, Dimension & dim) const;
196         ///
197         void draw(PainterInfo &, int x, int y) const;
198
199 protected:
200         ///
201         Inset * clone() const;
202 };
203
204
205 DisplayLabelBox::DisplayLabelBox(Buffer * buf, MathAtom const & atom,
206                                  docstring label,
207                                  MathMacroTemplate const & parent)
208         : InsetLabelBox(buf, atom, label, parent, true)
209 {
210 }
211
212
213
214 Inset * DisplayLabelBox::clone() const
215 {
216         return new DisplayLabelBox(*this);
217 }
218
219
220 InsetMath::marker_type DisplayLabelBox::marker(BufferView const * bv) const
221 {
222         if (parent_.editing(bv)
223             || !parent_.cell(parent_.displayIdx()).empty())
224                 return MARKER;
225         else
226                 return NO_MARKER;
227 }
228
229
230 void DisplayLabelBox::metrics(MetricsInfo & mi, Dimension & dim) const
231 {
232         InsetLabelBox::metrics(mi, dim);
233         if (!parent_.editing(mi.base.bv)
234             && parent_.cell(parent_.displayIdx()).empty()) {
235                 dim.wid = 0;
236                 dim.asc = 0;
237                 dim.des = 0;
238         }
239 }
240
241
242 void DisplayLabelBox::draw(PainterInfo & pi, int x, int y) const
243 {
244         if (parent_.editing(pi.base.bv)
245             || !parent_.cell(parent_.displayIdx()).empty()) {
246                 InsetLabelBox::draw(pi, x, y);
247         } else {
248                 bool enabled = pi.pain.isDrawingEnabled();
249                 pi.pain.setDrawingEnabled(false);
250                 InsetLabelBox::draw(pi, x, y);
251                 pi.pain.setDrawingEnabled(enabled);
252         }
253 }
254
255
256 //////////////////////////////////////////////////////////////////////
257
258 class InsetMathWrapper : public InsetMath {
259 public:
260         ///
261         InsetMathWrapper(MathData const * value) : value_(value) {}
262         ///
263         void metrics(MetricsInfo & mi, Dimension & dim) const;
264         ///
265         void draw(PainterInfo &, int x, int y) const;
266
267 private:
268         ///
269         Inset * clone() const;
270         ///
271         MathData const * value_;
272 };
273
274
275 Inset * InsetMathWrapper::clone() const
276 {
277         return new InsetMathWrapper(*this);
278 }
279
280
281 void InsetMathWrapper::metrics(MetricsInfo & mi, Dimension & dim) const
282 {
283         value_->metrics(mi, dim);
284 }
285
286
287 void InsetMathWrapper::draw(PainterInfo & pi, int x, int y) const
288 {
289         value_->draw(pi, x, y);
290 }
291
292
293 ///////////////////////////////////////////////////////////////////////
294 class InsetColoredCell : public InsetMathNest {
295 public:
296         ///
297         InsetColoredCell(Buffer * buf, ColorCode min, ColorCode max);
298         ///
299         InsetColoredCell(Buffer * buf, ColorCode min, ColorCode max, MathAtom const & atom);
300         ///
301         void draw(PainterInfo &, int x, int y) const;
302         ///
303         void metrics(MetricsInfo & mi, Dimension & dim) const;
304
305 protected:
306         ///
307         Inset * clone() const;
308         ///
309         ColorCode min_;
310         ///
311         ColorCode max_;
312 };
313
314
315 InsetColoredCell::InsetColoredCell(Buffer * buf, ColorCode min, ColorCode max)
316         : InsetMathNest(buf, 1), min_(min), max_(max)
317 {
318 }
319
320
321 InsetColoredCell::InsetColoredCell(Buffer * buf, ColorCode min, ColorCode max, MathAtom const & atom)
322         : InsetMathNest(buf, 1), min_(min), max_(max)
323 {
324         cell(0).insert(0, atom);
325 }
326
327
328 Inset * InsetColoredCell::clone() const
329 {
330         return new InsetColoredCell(*this);
331 }
332
333
334 void InsetColoredCell::metrics(MetricsInfo & mi, Dimension & dim) const
335 {
336         cell(0).metrics(mi, dim);
337 }
338
339
340 void InsetColoredCell::draw(PainterInfo & pi, int x, int y) const
341 {
342         pi.pain.enterMonochromeMode(min_, max_);
343         cell(0).draw(pi, x, y);
344         pi.pain.leaveMonochromeMode();
345 }
346
347
348 ///////////////////////////////////////////////////////////////////////
349
350 class InsetNameWrapper : public InsetMathWrapper {
351 public:
352         ///
353         InsetNameWrapper(MathData const * value, MathMacroTemplate const & parent);
354         ///
355         void metrics(MetricsInfo & mi, Dimension & dim) const;
356         ///
357         void draw(PainterInfo &, int x, int y) const;
358
359 private:
360         ///
361         MathMacroTemplate const & parent_;
362         ///
363         Inset * clone() const;
364 };
365
366
367 InsetNameWrapper::InsetNameWrapper(MathData const * value,
368                                    MathMacroTemplate const & parent)
369         : InsetMathWrapper(value), parent_(parent)
370 {
371 }
372
373
374 Inset * InsetNameWrapper::clone() const
375 {
376         return new InsetNameWrapper(*this);
377 }
378
379
380 void InsetNameWrapper::metrics(MetricsInfo & mi, Dimension & dim) const
381 {
382         Changer dummy = mi.base.changeFontSet("textit");
383         InsetMathWrapper::metrics(mi, dim);
384         dim.wid += mathed_string_width(mi.base.font, from_ascii("\\"));
385 }
386
387
388 void InsetNameWrapper::draw(PainterInfo & pi, int x, int y) const
389 {
390         ColorCode const color = parent_.validMacro() ? Color_latex : Color_error;
391
392         Changer dummy = pi.base.changeFontSet("textit");
393         Changer dummy2 = pi.base.font.changeColor(color);
394
395         // draw backslash
396         pi.pain.text(x, y, from_ascii("\\"), pi.base.font);
397         x += mathed_string_width(pi.base.font, from_ascii("\\"));
398
399         // draw name
400         InsetMathWrapper::draw(pi, x, y);
401 }
402
403
404 ///////////////////////////////////////////////////////////////////////
405
406
407 MathMacroTemplate::MathMacroTemplate(Buffer * buf)
408         : InsetMathNest(buf, 3), numargs_(0), argsInLook_(0), optionals_(0),
409           type_(MacroTypeNewcommand), redefinition_(false), lookOutdated_(true),
410           premetrics_(false), labelBoxAscent_(0), labelBoxDescent_(0)
411 {
412         initMath();
413 }
414
415
416 MathMacroTemplate::MathMacroTemplate(Buffer * buf, docstring const & name, int numargs,
417         int optionals, MacroType type, vector<MathData> const & optionalValues,
418         MathData const & def, MathData const & display)
419         : InsetMathNest(buf, optionals + 3), numargs_(numargs), argsInLook_(numargs),
420           optionals_(optionals), optionalValues_(optionalValues),
421           type_(type), redefinition_(false), lookOutdated_(true),
422           premetrics_(false), labelBoxAscent_(0), labelBoxDescent_(0)
423 {
424         initMath();
425
426         if (numargs_ > 9)
427                 lyxerr << "MathMacroTemplate::MathMacroTemplate: wrong # of arguments: "
428                         << numargs_ << endl;
429
430         asArray(name, cell(0));
431         optionalValues_.resize(9);
432         for (int i = 0; i < optionals_; ++i)
433                 cell(optIdx(i)) = optionalValues_[i];
434         cell(defIdx()) = def;
435         cell(displayIdx()) = display;
436
437         updateLook();
438 }
439
440
441 bool MathMacroTemplate::fromString(docstring const & str)
442 {
443         MathData ar(buffer_);
444         mathed_parse_cell(ar, str, Parse::NORMAL);
445         if (ar.size() != 1 || !ar[0]->asMacroTemplate()) {
446                 lyxerr << "Cannot read macro from '" << ar << "'" << endl;
447                 asArray(from_ascii("invalidmacro"), cell(0));
448                 // The macro template does not make sense after this.
449                 return false;
450         }
451         operator=( *(ar[0]->asMacroTemplate()) );
452
453         updateLook();
454         return true;
455 }
456
457
458 Inset * MathMacroTemplate::clone() const
459 {
460         MathMacroTemplate * inset = new MathMacroTemplate(*this);
461         // the parent pointers of the proxy insets above will point to
462         // to the old template. Hence, the look must be updated.
463         inset->updateLook();
464         return inset;
465 }
466
467
468 docstring MathMacroTemplate::name() const
469 {
470         return asString(cell(0));
471 }
472
473
474 void MathMacroTemplate::updateToContext(MacroContext const & mc)
475 {
476         redefinition_ = mc.get(name()) != 0;
477 }
478
479
480 void MathMacroTemplate::updateLook() const
481 {
482         lookOutdated_ = true;
483 }
484
485
486 void MathMacroTemplate::createLook(int args) const
487 {
488         look_.clear();
489         argsInLook_ = args;
490
491         // \foo
492         look_.push_back(MathAtom(
493                 new InsetLabelBox(buffer_, _("Name"), *this, false)));
494         MathData & nameData = look_[look_.size() - 1].nucleus()->cell(0);
495         nameData.push_back(MathAtom(new InsetNameWrapper(&cell(0), *this)));
496
497         // [#1][#2]
498         int i = 0;
499         if (optionals_ > 0) {
500                 look_.push_back(MathAtom(
501                         new InsetLabelBox(buffer_, _("optional"), *this, false)));
502                 
503                 MathData * optData = &look_[look_.size() - 1].nucleus()->cell(0);
504                 for (; i < optionals_; ++i) {
505                         // color it light grey, if it is to be removed when the cursor leaves
506                         if (i == argsInLook_) {
507                                 optData->push_back(MathAtom(
508                                         new InsetColoredCell(buffer_, Color_mathbg, Color_mathmacrooldarg)));
509                                 optData = &(*optData)[optData->size() - 1].nucleus()->cell(0);
510                         }
511
512                         optData->push_back(MathAtom(new InsetMathChar('[')));
513                         optData->push_back(MathAtom(new InsetMathWrapper(&cell(1 + i))));
514                         optData->push_back(MathAtom(new InsetMathChar(']')));
515                 }
516         }
517
518         // {#3}{#4}
519         for (; i < numargs_; ++i) {
520                 MathData arg;
521                 arg.push_back(MathAtom(new MathMacroArgument(i + 1)));
522                 if (i >= argsInLook_) {
523                         look_.push_back(MathAtom(new InsetColoredCell(buffer_,
524                                 Color_mathbg, Color_mathmacrooldarg,
525                                 MathAtom(new InsetMathBrace(arg)))));
526                 } else
527                         look_.push_back(MathAtom(new InsetMathBrace(arg)));
528         }
529         for (; i < argsInLook_; ++i) {
530                 MathData arg;
531                 arg.push_back(MathAtom(new MathMacroArgument(i + 1)));
532                 look_.push_back(MathAtom(new InsetColoredCell(buffer_,
533                         Color_mathbg, Color_mathmacronewarg,
534                         MathAtom(new InsetMathBrace(arg)))));
535         }
536         
537         // :=
538         look_.push_back(MathAtom(new InsetMathChar(':')));
539         look_.push_back(MathAtom(new InsetMathChar('=')));
540
541         // definition
542         look_.push_back(MathAtom(
543                 new InsetLabelBox(buffer_, MathAtom(
544                         new InsetMathWrapper(&cell(defIdx()))), _("TeX"), *this,        true)));
545
546         // display
547         look_.push_back(MathAtom(
548                 new DisplayLabelBox(buffer_, MathAtom(
549                         new InsetMathWrapper(&cell(displayIdx()))), _("LyX"), *this)));
550 }
551
552
553 void MathMacroTemplate::metrics(MetricsInfo & mi, Dimension & dim) const
554 {
555         Changer dummy1 = mi.base.changeFontSet("mathnormal");
556         Changer dummy2 = mi.base.font.changeStyle(LM_ST_TEXT);
557
558         // valid macro?
559         MacroData const * macro = 0;
560         if (validName())
561                 macro = mi.macrocontext.get(name());
562
563         // update look?
564         int argsInDef = maxArgumentInDefinition();
565         if (lookOutdated_ || argsInDef != argsInLook_) {
566                 lookOutdated_ = false;
567                 createLook(argsInDef);
568         }
569
570         /// metrics for inset contents
571         if (macro)
572                 macro->lock();
573
574         // first phase, premetric:
575         premetrics_ = true;
576         look_.metrics(mi, dim);
577         labelBoxAscent_ = dim.asc;
578         labelBoxDescent_ = dim.des;
579
580         // second phase, main metric:
581         premetrics_ = false;
582         look_.metrics(mi, dim);
583
584         if (macro)
585                 macro->unlock();
586
587         dim.wid += 6;
588         dim.des += 2;
589         dim.asc += 2;
590 }
591
592
593 void MathMacroTemplate::draw(PainterInfo & pi, int x, int y) const
594 {
595         // FIXME: Calling Changer on the same object repeatedly is inefficient.
596         Changer dummy0 = pi.base.font.changeColor(Color_math);
597         Changer dummy1 = pi.base.changeFontSet("mathnormal");
598         Changer dummy2 = pi.base.font.changeStyle(LM_ST_TEXT);
599
600         Dimension const dim = dimension(*pi.base.bv);
601
602         // draw outer frame
603         int const a = y - dim.asc + 1;
604         int const w = dim.wid - 2;
605         int const h = dim.height() - 2;
606         pi.pain.rectangle(x + 1, a, w, h, Color_mathframe);
607
608         // just to be sure: set some dummy values for coord cache
609         for (idx_type i = 0; i < nargs(); ++i)
610                 cell(i).setXY(*pi.base.bv, x, y);
611
612         // draw contents
613         look_.draw(pi, x + 3, y);
614 }
615
616
617 void MathMacroTemplate::edit(Cursor & cur, bool front, EntryDirection entry_from)
618 {
619         updateLook();
620         cur.screenUpdateFlags(Update::SinglePar);
621         InsetMathNest::edit(cur, front, entry_from);
622 }
623
624
625 bool MathMacroTemplate::notifyCursorLeaves(Cursor const & old, Cursor & cur)
626 {
627         unsigned int const nargs_before = nargs();
628         commitEditChanges(cur, old);
629         updateLook();
630         cur.screenUpdateFlags(Update::Force);
631         // If we have removed a cell, we might have invalidated the cursor
632         return InsetMathNest::notifyCursorLeaves(old, cur)
633                 || nargs() < nargs_before;
634 }
635
636
637 void MathMacroTemplate::removeArguments(Cursor & cur,
638         DocIterator const & /*inset_pos*/, int from, int to)
639 {
640         DocIterator it = doc_iterator_begin(&buffer(), this);
641         for (; it; it.forwardChar()) {
642                 if (!it.nextInset())
643                         continue;
644                 if (it.nextInset()->lyxCode() != MATH_MACROARG_CODE)
645                         continue;
646                 MathMacroArgument * arg = static_cast<MathMacroArgument*>(it.nextInset());
647                 int n = arg->number() - 1;
648                 if (from <= n && n <= to) {
649                         int cellSlice = cur.find(it.cell());
650                         if (cellSlice != -1 && cur[cellSlice].pos() > it.pos())
651                                 --cur[cellSlice].pos();
652
653                         it.cell().erase(it.pos());
654                 }
655         }
656
657         updateLook();
658 }
659
660
661 void MathMacroTemplate::shiftArguments(size_t from, int by)
662 {
663         for (DocIterator it = doc_iterator_begin(&buffer(), this); it; it.forwardChar()) {
664                 if (!it.nextInset())
665                         continue;
666                 if (it.nextInset()->lyxCode() != MATH_MACROARG_CODE)
667                         continue;
668                 MathMacroArgument * arg = static_cast<MathMacroArgument*>(it.nextInset());
669                 if (arg->number() >= int(from) + 1)
670                         arg->setNumber(arg->number() + by);
671         }
672
673         updateLook();
674 }
675
676
677 int MathMacroTemplate::maxArgumentInDefinition() const
678 {
679         // We don't have a buffer when pasting from the clipboard (bug 6014).
680         Buffer const * macro_buffer = isBufferLoaded() ? &buffer() : 0;
681         int maxArg = 0;
682         DocIterator it = doc_iterator_begin(macro_buffer, this);
683         it.idx() = defIdx();
684         for (; it; it.forwardChar()) {
685                 if (!it.nextInset())
686                         continue;
687                 if (it.nextInset()->lyxCode() != MATH_MACROARG_CODE)
688                         continue;
689                 MathMacroArgument * arg = static_cast<MathMacroArgument*>(it.nextInset());
690                 maxArg = std::max(int(arg->number()), maxArg);
691         }
692         return maxArg;
693 }
694
695
696 void MathMacroTemplate::insertMissingArguments(int maxArg)
697 {
698         bool found[9] = { false, false, false, false, false, false, false, false, false };
699         idx_type idx = cell(displayIdx()).empty() ? defIdx() : displayIdx();
700
701         // search for #n macros arguments
702         DocIterator it = doc_iterator_begin(&buffer(), this);
703         it.idx() = idx;
704         for (; it && it[0].idx() == idx; it.forwardChar()) {
705                 if (!it.nextInset())
706                         continue;
707                 if (it.nextInset()->lyxCode() != MATH_MACROARG_CODE)
708                         continue;
709                 MathMacroArgument * arg = static_cast<MathMacroArgument*>(it.nextInset());
710                 found[arg->number() - 1] = true;
711         }
712
713         // add missing ones
714         for (int i = 0; i < maxArg; ++i) {
715                 if (found[i])
716                         continue;
717
718                 cell(idx).push_back(MathAtom(new MathMacroArgument(i + 1)));
719         }
720 }
721
722
723 void MathMacroTemplate::changeArity(Cursor & cur,
724         DocIterator const & inset_pos, int newNumArg)
725 {
726         // remove parameter which do not appear anymore in the definition
727         for (int i = numargs_; i > newNumArg; --i)
728                 removeParameter(cur, inset_pos, numargs_ - 1, true);
729         
730         // add missing parameter
731         for (int i = numargs_; i < newNumArg; ++i)
732                 insertParameter(cur, inset_pos, numargs_, true, false);
733 }
734
735
736 ///
737 class AddRemoveMacroInstanceFix
738 {
739 public:
740         ///
741         AddRemoveMacroInstanceFix(int n, bool insert) : n_(n), insert_(insert) {}
742         ///
743         void operator()(MathMacro * macro)
744         {
745                 if (macro->folded()) {
746                         if (insert_)
747                                 macro->insertArgument(n_);
748                         else
749                                 macro->removeArgument(n_);
750                 }
751         }
752
753 private:
754         ///
755         int n_;
756         ///
757         bool insert_;
758 };
759
760
761 ///
762 class OptionalsMacroInstanceFix
763 {
764 public:
765         ///
766         OptionalsMacroInstanceFix(int optionals) : optionals_(optionals) {}
767         ///
768         void operator()(MathMacro * macro)
769         {
770                 macro->setOptionals(optionals_);
771         }
772
773 private:
774         ///
775         int optionals_;
776 };
777
778
779 ///
780 class NullMacroInstanceFix
781 {
782 public:
783         ///
784         void operator()(MathMacro * ) {}
785 };
786
787
788 template<class F>
789 void fixMacroInstances(Cursor & cur, DocIterator const & inset_pos,
790         docstring const & name, F & fix)
791 {
792         // goto position behind macro template
793         DocIterator dit = inset_pos;
794         dit.pop_back();
795         dit.top().forwardPos();
796
797         // remember hull to trigger preview reload
798         DocIterator hull(dit.buffer());
799         bool preview_reload_needed = false;
800         set<DocIterator> preview_hulls;
801
802         // iterate over all positions until macro is redefined
803         for (; dit; dit.forwardPos()) {
804                 // left the outer hull?
805                 if (!hull.empty() && dit.depth() == hull.depth()) {
806                         // schedule reload of the preview if necessary 
807                         if (preview_reload_needed) {
808                                 preview_hulls.insert(hull);
809                                 preview_reload_needed = false;
810                         }
811                         hull.clear();
812                 }
813
814                 // only until a macro is redefined
815                 if (dit.inset().lyxCode() == MATHMACRO_CODE) {
816                         MathMacroTemplate const & macroTemplate
817                         = static_cast<MathMacroTemplate const &>(dit.inset());
818                         if (macroTemplate.name() == name)
819                                 break;
820                 }
821
822                 // in front of macro instance?
823                 Inset * inset = dit.nextInset();
824                 if (!inset)
825                         continue;
826                 InsetMath * insetMath = inset->asInsetMath();
827                 if (!insetMath)
828                         continue;
829
830                 // in front of outer hull?
831                 InsetMathHull * inset_hull = insetMath->asHullInset();
832                 if (inset_hull && hull.empty()) {
833                         // remember this for later preview reload
834                         hull = dit;
835                 }
836
837                 MathMacro * macro = insetMath->asMacro();
838                 if (macro && macro->name() == name && macro->folded()) {
839                         fix(macro);
840                         if (RenderPreview::previewMath())
841                                 preview_reload_needed = true;
842                 }
843         }
844
845         if (!preview_hulls.empty()) {
846                 // reload the scheduled previews
847                 set<DocIterator>::const_iterator sit = preview_hulls.begin();
848                 set<DocIterator>::const_iterator end = preview_hulls.end();
849                 for (; sit != end; ++sit) {
850                         InsetMathHull * inset_hull =
851                                 sit->nextInset()->asInsetMath()->asHullInset();
852                         LBUFERR(inset_hull);
853                         inset_hull->reloadPreview(*sit);
854                 }
855                 cur.screenUpdateFlags(Update::Force);
856         }
857 }
858
859
860 void MathMacroTemplate::commitEditChanges(Cursor & cur,
861         DocIterator const & inset_pos)
862 {
863         int args_in_def = maxArgumentInDefinition();
864         if (args_in_def != numargs_) {
865                 // FIXME: implement precise undo handling (only a few places
866                 //   need undo)
867                 cur.recordUndoFullBuffer();
868                 changeArity(cur, inset_pos, args_in_def);
869         }
870         insertMissingArguments(args_in_def);
871
872         // make sure the preview are up to date
873         NullMacroInstanceFix fix;
874         fixMacroInstances(cur, inset_pos, name(), fix);
875 }
876
877
878 void MathMacroTemplate::insertParameter(Cursor & cur,
879         DocIterator const & inset_pos, int pos, bool greedy, bool addarg)
880 {
881         if (pos <= numargs_ && pos >= optionals_ && numargs_ < 9) {
882                 ++numargs_;
883
884                 // append example #n
885                 if (addarg) {
886                         shiftArguments(pos, 1);
887
888                         cell(defIdx()).push_back(MathAtom(new MathMacroArgument(pos + 1)));
889                         if (!cell(displayIdx()).empty())
890                                 cell(displayIdx()).push_back(MathAtom(new MathMacroArgument(pos + 1)));
891                 }
892
893                 if (!greedy) {
894                         // fix macro instances
895                         AddRemoveMacroInstanceFix fix(pos, true);
896                         fixMacroInstances(cur, inset_pos, name(), fix);
897                 }
898         }
899
900         updateLook();
901 }
902
903
904 void MathMacroTemplate::removeParameter(Cursor & cur,
905         DocIterator const & inset_pos, int pos, bool greedy)
906 {
907         if (pos < numargs_ && pos >= 0) {
908                 --numargs_;
909                 removeArguments(cur, inset_pos, pos, pos);
910                 shiftArguments(pos + 1, -1);
911
912                 // removed optional parameter?
913                 if (pos < optionals_) {
914                         --optionals_;
915                         optionalValues_[pos] = cell(optIdx(pos));
916                         cells_.erase(cells_.begin() + optIdx(pos));
917
918                         // fix cursor
919                         int macroSlice = cur.find(this);
920                         if (macroSlice != -1) {
921                                 if (cur[macroSlice].idx() == optIdx(pos)) {
922                                         cur.cutOff(macroSlice);
923                                         cur[macroSlice].idx() = 1;
924                                         cur[macroSlice].pos() = 0;
925                                 } else if (cur[macroSlice].idx() > optIdx(pos))
926                                         --cur[macroSlice].idx();
927                         }
928                 }
929
930                 if (!greedy) {
931                         // fix macro instances
932                         AddRemoveMacroInstanceFix fix(pos, false);
933                         fixMacroInstances(cur, inset_pos, name(), fix);
934                 }
935         }
936
937         updateLook();
938 }
939
940
941 void MathMacroTemplate::makeOptional(Cursor & cur,
942         DocIterator const & inset_pos)
943 {
944         if (numargs_ > 0 && optionals_ < numargs_) {
945                 ++optionals_;
946                 cells_.insert(cells_.begin() + optIdx(optionals_ - 1), optionalValues_[optionals_ - 1]);
947                 // fix cursor
948                 int macroSlice = cur.find(this);
949                 if (macroSlice != -1 && cur[macroSlice].idx() >= optIdx(optionals_ - 1))
950                         ++cur[macroSlice].idx();
951
952                 // fix macro instances
953                 OptionalsMacroInstanceFix fix(optionals_);
954                 fixMacroInstances(cur, inset_pos, name(), fix);
955         }
956
957         updateLook();
958 }
959
960
961 void MathMacroTemplate::makeNonOptional(Cursor & cur,
962         DocIterator const & inset_pos)
963 {
964         if (numargs_ > 0 && optionals_ > 0) {
965                 --optionals_;
966
967                 // store default value for later if the user changes his mind
968                 optionalValues_[optionals_] = cell(optIdx(optionals_));
969                 cells_.erase(cells_.begin() + optIdx(optionals_));
970
971                 // fix cursor
972                 int macroSlice = cur.find(this);
973                 if (macroSlice != -1) {
974                         if (cur[macroSlice].idx() > optIdx(optionals_))
975                                 --cur[macroSlice].idx();
976                         else if (cur[macroSlice].idx() == optIdx(optionals_)) {
977                                 cur.cutOff(macroSlice);
978                                 cur[macroSlice].idx() = optIdx(optionals_);
979                                 cur[macroSlice].pos() = 0;
980                         }
981                 }
982
983                 // fix macro instances
984                 OptionalsMacroInstanceFix fix(optionals_);
985                 fixMacroInstances(cur, inset_pos, name(), fix);
986         }
987
988         updateLook();
989 }
990
991
992 void MathMacroTemplate::doDispatch(Cursor & cur, FuncRequest & cmd)
993 {
994         string const arg = to_utf8(cmd.argument());
995         switch (cmd.action()) {
996
997         case LFUN_MATH_MACRO_ADD_PARAM:
998                 if (numargs_ < 9) {
999                         commitEditChanges(cur, cur);
1000                         // FIXME: implement precise undo handling (only a few places
1001                         //   need undo)
1002                         cur.recordUndoFullBuffer();
1003                         size_t pos = numargs_;
1004                         if (!arg.empty())
1005                                 pos = (size_t)convert<int>(arg) - 1; // it is checked for >=0 in getStatus
1006                         insertParameter(cur, cur, pos);
1007                 }
1008                 break;
1009
1010
1011         case LFUN_MATH_MACRO_REMOVE_PARAM:
1012                 if (numargs_ > 0) {
1013                         commitEditChanges(cur, cur);
1014                         // FIXME: implement precise undo handling (only a few places
1015                         //   need undo)
1016                         cur.recordUndoFullBuffer();
1017                         size_t pos = numargs_ - 1;
1018                         if (!arg.empty())
1019                                 pos = (size_t)convert<int>(arg) - 1; // it is checked for >=0 in getStatus
1020                         removeParameter(cur, cur, pos);
1021                 }
1022                 break;
1023
1024         case LFUN_MATH_MACRO_APPEND_GREEDY_PARAM:
1025                 if (numargs_ < 9) {
1026                         commitEditChanges(cur, cur);
1027                         // FIXME: implement precise undo handling (only a few places
1028                         //   need undo)
1029                         cur.recordUndoFullBuffer();
1030                         insertParameter(cur, cur, numargs_, true);
1031                 }
1032                 break;
1033
1034         case LFUN_MATH_MACRO_REMOVE_GREEDY_PARAM:
1035                 if (numargs_ > 0) {
1036                         commitEditChanges(cur, cur);
1037                         // FIXME: implement precise undo handling (only a few places
1038                         //   need undo)
1039                         cur.recordUndoFullBuffer();
1040                         removeParameter(cur, cur, numargs_ - 1, true);
1041                 }
1042                 break;
1043
1044         case LFUN_MATH_MACRO_MAKE_OPTIONAL:
1045                 commitEditChanges(cur, cur);
1046                 // FIXME: implement precise undo handling (only a few places
1047                 //   need undo)
1048                 cur.recordUndoFullBuffer();
1049                 makeOptional(cur, cur);
1050                 break;
1051
1052         case LFUN_MATH_MACRO_MAKE_NONOPTIONAL:
1053                 commitEditChanges(cur, cur);
1054                 // FIXME: implement precise undo handling (only a few places
1055                 //   need undo)
1056                 cur.recordUndoFullBuffer();
1057                 makeNonOptional(cur, cur);
1058                 break;
1059
1060         case LFUN_MATH_MACRO_ADD_OPTIONAL_PARAM:
1061                 if (numargs_ < 9) {
1062                         commitEditChanges(cur, cur);
1063                         // FIXME: implement precise undo handling (only a few places
1064                         //   need undo)
1065                         cur.recordUndoFullBuffer();
1066                         insertParameter(cur, cur, optionals_);
1067                         makeOptional(cur, cur);
1068                 }
1069                 break;
1070
1071         case LFUN_MATH_MACRO_REMOVE_OPTIONAL_PARAM:
1072                 if (optionals_ > 0) {
1073                         commitEditChanges(cur, cur);
1074                         // FIXME: implement precise undo handling (only a few places
1075                         //   need undo)
1076                         cur.recordUndoFullBuffer();
1077                         removeParameter(cur, cur, optionals_ - 1);
1078                 } break;
1079
1080         case LFUN_MATH_MACRO_ADD_GREEDY_OPTIONAL_PARAM:
1081                 if (numargs_ == optionals_) {
1082                         commitEditChanges(cur, cur);
1083                         // FIXME: implement precise undo handling (only a few places
1084                         //   need undo)
1085                         cur.recordUndoFullBuffer();
1086                         insertParameter(cur, cur, 0, true);
1087                         makeOptional(cur, cur);
1088                 }
1089                 break;
1090
1091         default:
1092                 InsetMathNest::doDispatch(cur, cmd);
1093                 break;
1094         }
1095 }
1096
1097
1098 bool MathMacroTemplate::getStatus(Cursor & cur, FuncRequest const & cmd,
1099         FuncStatus & flag) const
1100 {
1101         bool ret = true;
1102         string const arg = to_utf8(cmd.argument());
1103         switch (cmd.action()) {
1104                 case LFUN_MATH_MACRO_ADD_PARAM: {
1105                         int num = numargs_ + 1;
1106                         if (!arg.empty())
1107                                 num = convert<int>(arg);
1108                         bool on = (num >= optionals_
1109                                    && numargs_ < 9 && num <= numargs_ + 1);
1110                         flag.setEnabled(on);
1111                         break;
1112                 }
1113
1114                 case LFUN_MATH_MACRO_APPEND_GREEDY_PARAM:
1115                         flag.setEnabled(numargs_ < 9);
1116                         break;
1117
1118                 case LFUN_MATH_MACRO_REMOVE_GREEDY_PARAM:
1119                 case LFUN_MATH_MACRO_REMOVE_PARAM: {
1120                         int num = numargs_;
1121                         if (!arg.empty())
1122                                 num = convert<int>(arg);
1123                         flag.setEnabled(num >= 1 && num <= numargs_);
1124                         break;
1125                 }
1126
1127                 case LFUN_MATH_MACRO_MAKE_OPTIONAL:
1128                         flag.setEnabled(numargs_ > 0
1129                                      && optionals_ < numargs_
1130                                      && type_ != MacroTypeDef);
1131                         break;
1132
1133                 case LFUN_MATH_MACRO_MAKE_NONOPTIONAL:
1134                         flag.setEnabled(optionals_ > 0
1135                                      && type_ != MacroTypeDef);
1136                         break;
1137
1138                 case LFUN_MATH_MACRO_ADD_OPTIONAL_PARAM:
1139                         flag.setEnabled(numargs_ < 9);
1140                         break;
1141
1142                 case LFUN_MATH_MACRO_REMOVE_OPTIONAL_PARAM:
1143                         flag.setEnabled(optionals_ > 0);
1144                         break;
1145
1146                 case LFUN_MATH_MACRO_ADD_GREEDY_OPTIONAL_PARAM:
1147                         flag.setEnabled(numargs_ == 0
1148                                      && type_ != MacroTypeDef);
1149                         break;
1150
1151                 case LFUN_IN_MATHMACROTEMPLATE:
1152                         flag.setEnabled(true);
1153                         break;
1154
1155                 default:
1156                         ret = InsetMathNest::getStatus(cur, cmd, flag);
1157                         break;
1158         }
1159         return ret;
1160 }
1161
1162
1163 void MathMacroTemplate::read(Lexer & lex)
1164 {
1165         MathData ar(buffer_);
1166         mathed_parse_cell(ar, lex.getStream(), Parse::TRACKMACRO);
1167         if (ar.size() != 1 || !ar[0]->asMacroTemplate()) {
1168                 lyxerr << "Cannot read macro from '" << ar << "'" << endl;
1169                 lyxerr << "Read: " << to_utf8(asString(ar)) << endl;
1170                 return;
1171         }
1172         operator=( *(ar[0]->asMacroTemplate()) );
1173
1174         updateLook();
1175 }
1176
1177
1178 void MathMacroTemplate::write(ostream & os) const
1179 {
1180         odocstringstream oss;
1181         otexrowstream ots(oss);
1182         WriteStream wi(ots, false, false, WriteStream::wsDefault);
1183         oss << "FormulaMacro\n";
1184         write(wi);
1185         os << to_utf8(oss.str());
1186 }
1187
1188
1189 void MathMacroTemplate::write(WriteStream & os) const
1190 {
1191         write(os, false);
1192 }
1193
1194
1195 int MathMacroTemplate::write(WriteStream & os, bool overwriteRedefinition) const
1196 {
1197         int num_lines = 0;
1198
1199         if (os.latex()) {
1200                 if (optionals_ > 0) {
1201                         // macros with optionals use the xargs package, e.g.:
1202                         // \newcommandx{\foo}[2][usedefault, addprefix=\global,1=default]{#1,#2}
1203                         // \long is implicit by xargs
1204                         if (redefinition_ && !overwriteRedefinition)
1205                                 os << "\\renewcommandx";
1206                         else
1207                                 os << "\\newcommandx";
1208
1209                         os << "\\" << name()
1210                            << "[" << numargs_ << "]"
1211                            << "[usedefault, addprefix=\\global";
1212                         for (int i = 0; i < optionals_; ++i) {
1213                                 docstring optValue = asString(cell(optIdx(i)));
1214                                 if (optValue.find(']') != docstring::npos
1215                                     || optValue.find(',') != docstring::npos)
1216                                         os << ", " << i + 1 << "="
1217                                         << "{" << cell(optIdx(i)) << "}";
1218                                 else
1219                                         os << ", " << i + 1 << "="
1220                                         << cell(optIdx(i));
1221                         }
1222                         os << "]";
1223                 } else {
1224                         // Macros without optionals use standard _global_ \def macros:
1225                         //   \global\def\long\foo#1#2{#1,#2}
1226                         // We use the \long prefix as this is the equivalent to \newcommand.
1227                         // We cannot use \newcommand directly because \global does not work with it.
1228                         os << "\\global\\long\\def\\" << name();
1229                         docstring param = from_ascii("#0");
1230                         for (int i = 1; i <= numargs_; ++i) { 
1231                                 param[1] = '0' + i;
1232                                 os << param;
1233                         }
1234                 }
1235         } else {
1236                 // in LyX output we use some pseudo syntax which is implementation
1237                 // independent, e.g.
1238                 // \newcommand{\foo}[2][default]{#1,#2}
1239                 if (redefinition_ && !overwriteRedefinition)
1240                         os << "\\renewcommand";
1241                 else
1242                         os << "\\newcommand";
1243                 os << "{\\" << name() << '}';
1244                 if (numargs_ > 0)
1245                         os << '[' << numargs_ << ']';
1246
1247                 for (int i = 0; i < optionals_; ++i) {
1248                         docstring optValue = asString(cell(optIdx(i)));
1249                         if (optValue.find(']') != docstring::npos)
1250                                 os << "[{" << cell(optIdx(i)) << "}]";
1251                         else
1252                                 os << "[" << cell(optIdx(i)) << "]";
1253                 }
1254         }
1255
1256         os << "{" << cell(defIdx()) << "}";
1257
1258         if (os.latex()) {
1259                 // writing .tex. done.
1260                 os << "\n";
1261                 ++num_lines;
1262         } else {
1263                 // writing .lyx, write special .tex export only if necessary
1264                 if (!cell(displayIdx()).empty()) {
1265                         os << "\n{" << cell(displayIdx()) << '}';
1266                         ++num_lines;
1267                 }
1268         }
1269
1270         return num_lines;
1271 }
1272
1273
1274 docstring MathMacroTemplate::xhtml(XHTMLStream &, OutputParams const &) const
1275 {
1276         return docstring();
1277 }
1278
1279 int MathMacroTemplate::plaintext(odocstringstream & os,
1280                                  OutputParams const &, size_t) const
1281 {
1282         docstring const str = '[' + buffer().B_("math macro") + ']';
1283
1284         os << str;
1285         return str.size();
1286 }
1287
1288
1289 bool MathMacroTemplate::validName() const
1290 {
1291         docstring n = name();
1292
1293         if (n.empty())
1294                 return false;
1295
1296         // converting back and force doesn't swallow anything?
1297         /*MathData ma;
1298         asArray(n, ma);
1299         if (asString(ma) != n)
1300                 return false;*/
1301
1302         // valid characters?
1303         for (size_t i = 0; i < n.size(); ++i) {
1304                 if (!(n[i] >= 'a' && n[i] <= 'z')
1305                     && !(n[i] >= 'A' && n[i] <= 'Z')
1306                     && n[i] != '*')
1307                         return false;
1308         }
1309
1310         return true;
1311 }
1312
1313
1314 bool MathMacroTemplate::validMacro() const
1315 {
1316         return validName();
1317 }
1318
1319
1320 bool MathMacroTemplate::fixNameAndCheckIfValid()
1321 {
1322         // check all the characters/insets in the name cell
1323         size_t i = 0;
1324         MathData & data = cell(0);
1325         while (i < data.size()) {
1326                 InsetMathChar const * cinset = data[i]->asCharInset();
1327                 if (cinset) {
1328                         // valid character in [a-zA-Z]?
1329                         char_type c = cinset->getChar();
1330                         if ((c >= 'a' && c <= 'z')
1331                             || (c >= 'A' && c <= 'Z')) {
1332                                 ++i;
1333                                 continue;
1334                         }
1335                 }
1336
1337                 // throw cell away
1338                 data.erase(i);
1339         }
1340
1341         // now it should be valid if anything in the name survived
1342         return !data.empty();
1343 }
1344
1345         
1346 void MathMacroTemplate::validate(LaTeXFeatures & features) const
1347 {
1348         // we need global optional macro arguments. They are not available 
1349         // with \def, and \newcommand does not support global macros. So we
1350         // are bound to xargs also for the single-optional-parameter case.
1351         if (optionals_ > 0)
1352                 features.require("xargs");
1353 }
1354
1355 void MathMacroTemplate::getDefaults(vector<docstring> & defaults) const
1356 {
1357         defaults.resize(numargs_);
1358         for (int i = 0; i < optionals_; ++i)
1359                 defaults[i] = asString(cell(optIdx(i)));
1360 }
1361
1362
1363 docstring MathMacroTemplate::definition() const
1364 {
1365         return asString(cell(defIdx()));
1366 }
1367
1368
1369 docstring MathMacroTemplate::displayDefinition() const
1370 {
1371         return asString(cell(displayIdx()));
1372 }
1373
1374
1375 size_t MathMacroTemplate::numArgs() const
1376 {
1377         return numargs_;
1378 }
1379
1380
1381 size_t MathMacroTemplate::numOptionals() const
1382 {
1383         return optionals_;
1384 }
1385
1386
1387 void MathMacroTemplate::infoize(odocstream & os) const
1388 {
1389         os << bformat(_("Math Macro: \\%1$s"), name());
1390 }
1391
1392
1393 string MathMacroTemplate::contextMenuName() const
1394 {
1395         return "context-math-macro-definition";
1396 }
1397
1398
1399 void MathMacroTemplate::addToToc(DocIterator const & pit, bool output_active,
1400                                                                  UpdateType, TocBackend & backend) const
1401 {
1402         docstring str;
1403         if (!validMacro())
1404                 str = bformat(_("Invalid macro! \\%1$s"), name());
1405         else
1406                 str = "\\" + name();
1407         TocBuilder & b = backend.builder("math-macro");
1408         b.pushItem(pit, str, output_active);
1409         b.pop();
1410 }
1411
1412
1413 } // namespace lyx