]> git.lyx.org Git - lyx.git/blob - src/mathed/MathMacroTemplate.cpp
dbd2918904b65bc0cf8bb732622fc1107187e3ba
[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  *
8  * Full author contact details are available in file CREDITS.
9  */
10
11 #include <config.h>
12
13 #include "MathMacroTemplate.h"
14
15 #include "DocIterator.h"
16 #include "LaTeXFeatures.h"
17 #include "InsetMathBrace.h"
18 #include "InsetMathChar.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 "support/debug.h"
32 #include "DispatchResult.h"
33 #include "FuncRequest.h"
34 #include "FuncStatus.h"
35 #include "support/gettext.h"
36 #include "Lexer.h"
37 #include "Undo.h"
38
39 #include "frontends/FontMetrics.h"
40 #include "frontends/Painter.h"
41
42 #include "support/convert.h"
43 #include "support/docstream.h"
44 #include "support/lstrings.h"
45
46 #include "support/debug.h"
47
48 #include <sstream>
49
50 using namespace std;
51
52 namespace lyx {
53
54 using support::bformat;
55
56 //////////////////////////////////////////////////////////////////////
57
58 class InsetLabelBox : public InsetMathNest {
59 public:
60         ///
61         InsetLabelBox(MathAtom const & atom, docstring label,
62                       MathMacroTemplate const & parent, bool frame = false);
63         InsetLabelBox(docstring label, MathMacroTemplate const & parent,
64                       bool frame = false);
65         ///
66         void metrics(MetricsInfo & mi, Dimension & dim) const;
67         ///
68         void draw(PainterInfo &, int x, int y) const;
69
70 protected:
71         ///
72         MathMacroTemplate const & parent_;
73         ///
74         Inset * clone() const;
75         ///
76         docstring const label_;
77         ///
78         bool frame_;
79 };
80
81
82 InsetLabelBox::InsetLabelBox(MathAtom const & atom, docstring label,
83                              MathMacroTemplate const & parent, bool frame)
84 :       InsetMathNest(1), parent_(parent), label_(label), frame_(frame)
85 {
86         cell(0).insert(0, atom);
87 }
88
89
90 InsetLabelBox::InsetLabelBox(docstring label,
91                              MathMacroTemplate const & parent, bool frame)
92 :       InsetMathNest(1), parent_(parent), label_(label), frame_(frame)
93 {
94 }
95
96
97 Inset * InsetLabelBox::clone() const
98 {
99         return new InsetLabelBox(*this);
100 }
101
102
103 void InsetLabelBox::metrics(MetricsInfo & mi, Dimension & dim) const
104 {
105         // kernel
106         cell(0).metrics(mi, dim);
107
108         // frame
109         if (frame_) {
110                 dim.wid += 6;
111                 dim.asc += 5;
112                 dim.des += 5;
113         }
114
115         // adjust to common height in main metrics phase
116         if (!parent_.premetrics()) {
117                 dim.asc = max(dim.asc, parent_.commonLabelBoxAscent());
118                 dim.des = max(dim.des, parent_.commonLabelBoxDescent());
119         }
120
121         // label
122         if (parent_.editing(mi.base.bv) && label_.length() > 0) {
123                 // grey
124                 FontInfo font = sane_font;
125                 font.setSize(FONT_SIZE_TINY);
126                 font.setColor(Color_mathmacrolabel);
127
128                 // make space for label and box
129                 int lwid = mathed_string_width(font, label_);
130                 int maxasc;
131                 int maxdes;
132                 math_font_max_dim(font, maxasc, maxdes);
133
134                 dim.wid = max(dim.wid, lwid + 2);
135
136                 // space for the label
137                 if (!parent_.premetrics())
138                         dim.des += maxasc + maxdes + 1;
139         }
140 }
141
142
143 void InsetLabelBox::draw(PainterInfo & pi, int x, int y) const
144 {
145         Dimension const dim = dimension(*pi.base.bv);
146         Dimension const cdim = cell(0).dimension(*pi.base.bv);
147
148         // kernel
149         cell(0).draw(pi, x + (dim.wid - cdim.wid) / 2, y);
150
151         // label
152         if (parent_.editing(pi.base.bv) && label_.length() > 0) {
153                 // grey
154                 FontInfo font = sane_font;
155                 font.setSize(FONT_SIZE_TINY);
156                 font.setColor(Color_mathmacrolabel);
157
158                 // make space for label and box
159                 int lwid = mathed_string_width(font, label_);
160                 int maxasc;
161                 int maxdes;
162                 math_font_max_dim(font, maxasc, maxdes);
163
164                 if (lwid < dim.wid)
165                         pi.pain.text(x + (dim.wid - lwid) / 2, y + dim.des - maxdes, label_, font);
166                 else
167                         pi.pain.text(x, y + dim.des - maxdes, label_, font);
168         }
169
170         // draw frame
171         int boxHeight = parent_.commonLabelBoxAscent() + parent_.commonLabelBoxDescent();
172         if (frame_) {
173                 pi.pain.rectangle(x + 1, y - dim.ascent() + 1,
174                                   dim.wid - 2, boxHeight - 2,
175                                   Color_mathline);
176         }
177 }
178
179
180 //////////////////////////////////////////////////////////////////////
181
182 class DisplayLabelBox : public InsetLabelBox {
183 public:
184         ///
185         DisplayLabelBox(MathAtom const & atom, docstring label,
186                         MathMacroTemplate const & parent);
187
188         ///
189         void metrics(MetricsInfo & mi, Dimension & dim) const;
190         ///
191         void draw(PainterInfo &, int x, int y) const;
192
193 protected:
194         ///
195         Inset * clone() const;
196 };
197
198
199 DisplayLabelBox::DisplayLabelBox(MathAtom const & atom,
200                                  docstring label,
201                                  MathMacroTemplate const & parent)
202         : InsetLabelBox(atom, label, parent, true)
203 {
204 }
205
206
207
208 Inset * DisplayLabelBox::clone() const
209 {
210         return new DisplayLabelBox(*this);
211 }
212
213
214 void DisplayLabelBox::metrics(MetricsInfo & mi, Dimension & dim) const
215 {
216         InsetLabelBox::metrics(mi, dim);
217         if (!parent_.editing(mi.base.bv)
218             && parent_.cell(parent_.displayIdx()).empty()) {
219                 dim.wid = 0;
220                 dim.asc = 0;
221                 dim.des = 0;
222         }
223 }
224
225
226 void DisplayLabelBox::draw(PainterInfo & pi, int x, int y) const
227 {
228         if (parent_.editing(pi.base.bv)
229             || !parent_.cell(parent_.displayIdx()).empty()) {
230                 InsetLabelBox::draw(pi, x, y);
231         } else {
232                 bool enabled = pi.pain.isDrawingEnabled();
233                 pi.pain.setDrawingEnabled(false);
234                 InsetLabelBox::draw(pi, x, y);
235                 pi.pain.setDrawingEnabled(enabled);
236         }
237 }
238
239
240 //////////////////////////////////////////////////////////////////////
241
242 class InsetMathWrapper : public InsetMath {
243 public:
244         ///
245         InsetMathWrapper(MathData const * value) : value_(value) {}
246         ///
247         void metrics(MetricsInfo & mi, Dimension & dim) const;
248         ///
249         void draw(PainterInfo &, int x, int y) const;
250
251 private:
252         ///
253         Inset * clone() const;
254         ///
255         MathData const * value_;
256 };
257
258
259 Inset * InsetMathWrapper::clone() const
260 {
261         return new InsetMathWrapper(*this);
262 }
263
264
265 void InsetMathWrapper::metrics(MetricsInfo & mi, Dimension & dim) const
266 {
267         value_->metrics(mi, dim);
268         //metricsMarkers2(dim);
269 }
270
271
272 void InsetMathWrapper::draw(PainterInfo & pi, int x, int y) const
273 {
274         value_->draw(pi, x, y);
275         //drawMarkers(pi, x, y);
276 }
277
278
279 ///////////////////////////////////////////////////////////////////////
280
281 class InsetNameWrapper : public InsetMathWrapper {
282 public:
283         ///
284         InsetNameWrapper(MathData const * value, MathMacroTemplate const & parent);
285         ///
286         void metrics(MetricsInfo & mi, Dimension & dim) const;
287         ///
288         void draw(PainterInfo &, int x, int y) const;
289
290 private:
291         ///
292         MathMacroTemplate const & parent_;
293         ///
294         Inset * clone() const;
295 };
296
297
298 InsetNameWrapper::InsetNameWrapper(MathData const * value,
299                                    MathMacroTemplate const & parent)
300         : InsetMathWrapper(value), parent_(parent)
301 {
302 }
303
304
305 Inset * InsetNameWrapper::clone() const
306 {
307         return new InsetNameWrapper(*this);
308 }
309
310
311 void InsetNameWrapper::metrics(MetricsInfo & mi, Dimension & dim) const
312 {
313         InsetMathWrapper::metrics(mi, dim);
314         dim.wid += mathed_string_width(mi.base.font, from_ascii("\\"));
315 }
316
317
318 void InsetNameWrapper::draw(PainterInfo & pi, int x, int y) const
319 {
320         // create fonts
321         PainterInfo namepi = pi;
322         if (parent_.validMacro())
323                 namepi.base.font.setColor(Color_latex);
324         else
325                 namepi.base.font.setColor(Color_error);
326
327         // draw backslash
328         pi.pain.text(x, y, from_ascii("\\"), namepi.base.font);
329         x += mathed_string_width(namepi.base.font, from_ascii("\\"));
330
331         // draw name
332         InsetMathWrapper::draw(namepi, x, y);
333 }
334
335
336 ///////////////////////////////////////////////////////////////////////
337
338
339 MathMacroTemplate::MathMacroTemplate()
340         : InsetMathNest(3), numargs_(0), optionals_(0),
341           type_(MacroTypeNewcommand), lookOutdated_(true)
342 {
343         initMath();
344 }
345
346
347 MathMacroTemplate::MathMacroTemplate(docstring const & name, int numargs,
348         int optionals, MacroType type,
349         vector<MathData> const & optionalValues,
350         MathData const & def, MathData const & display)
351         : InsetMathNest(optionals + 3), numargs_(numargs),
352           optionals_(optionals), optionalValues_(optionalValues),
353           type_(type), lookOutdated_(true)
354 {
355         initMath();
356
357         if (numargs_ > 9)
358                 lyxerr << "MathMacroTemplate::MathMacroTemplate: wrong # of arguments: "
359                         << numargs_ << endl;
360
361         asArray(name, cell(0));
362         optionalValues_.resize(9);
363         for (int i = 0; i < optionals_; ++i)
364                 cell(optIdx(i)) = optionalValues_[i];
365         cell(defIdx()) = def;
366         cell(displayIdx()) = display;
367
368         updateLook();
369 }
370
371
372 MathMacroTemplate::MathMacroTemplate(docstring const & str)
373         : InsetMathNest(3), numargs_(0), optionals_(0),
374         type_(MacroTypeNewcommand), lookOutdated_(true)
375 {
376         initMath();
377
378         MathData ar;
379         mathed_parse_cell(ar, str);
380         if (ar.size() != 1 || !ar[0]->asMacroTemplate()) {
381                 lyxerr << "Cannot read macro from '" << ar << "'" << endl;
382                 asArray(from_ascii("invalidmacro"), cell(0));
383                 // FIXME: The macro template does not make sense after this.
384                 // The whole parsing should not be in a constructor which
385                 // has no chance to report failure.
386                 return;
387         }
388         operator=( *(ar[0]->asMacroTemplate()) );
389
390         updateLook();
391 }
392
393
394 Inset * MathMacroTemplate::clone() const
395 {
396         MathMacroTemplate * inset = new MathMacroTemplate(*this);
397         // the parent pointers of the proxy insets above will point to
398         // to the old template. Hence, the look must be updated.
399         inset->updateLook();
400         return inset;
401 }
402
403
404 docstring MathMacroTemplate::name() const
405 {
406         return asString(cell(0));
407 }
408
409
410 void MathMacroTemplate::updateToContext(MacroContext const & mc) const
411 {
412         redefinition_ = mc.get(name()) != 0;
413 }
414
415
416 void MathMacroTemplate::updateLook() const
417 {
418         lookOutdated_ = true;
419 }
420
421
422 void MathMacroTemplate::createLook() const
423 {
424         look_.clear();
425
426         // \foo
427         look_.push_back(MathAtom(new InsetLabelBox(_("Name"), *this, false)));
428         MathData & nameData = look_[look_.size() - 1].nucleus()->cell(0);
429         nameData.push_back(MathAtom(new InsetNameWrapper(&cell(0), *this)));
430
431         // [#1][#2]
432         int i = 0;
433         if (optionals_ > 0) {
434                 look_.push_back(MathAtom(new InsetLabelBox(_("optional"), *this, false)));
435                 MathData & optData = look_[look_.size() - 1].nucleus()->cell(0);
436
437                 for (; i < optionals_; ++i) {
438                         optData.push_back(MathAtom(new InsetMathChar('[')));
439                         optData.push_back(MathAtom(new InsetMathWrapper(&cell(1 + i))));
440                         optData.push_back(MathAtom(new InsetMathChar(']')));
441                 }
442         }
443
444         // {#3}{#4}
445         for (; i < numargs_; ++i) {
446                 MathData arg;
447                 arg.push_back(MathAtom(new MathMacroArgument(i + 1)));
448                 look_.push_back(MathAtom(new InsetMathBrace(arg)));
449         }
450
451         // :=
452         look_.push_back(MathAtom(new InsetMathChar(':')));
453         look_.push_back(MathAtom(new InsetMathChar('=')));
454
455         // definition
456         look_.push_back(MathAtom(
457                 new InsetLabelBox(MathAtom(
458                         new InsetMathWrapper(&cell(defIdx()))), _("TeX"), *this,        true)));
459
460         // display
461         look_.push_back(MathAtom(
462                 new DisplayLabelBox(MathAtom(
463                         new InsetMathWrapper(&cell(displayIdx()))), _("LyX"), *this)));
464 }
465
466
467 void MathMacroTemplate::metrics(MetricsInfo & mi, Dimension & dim) const
468 {
469         FontSetChanger dummy1(mi.base, from_ascii("mathnormal"));
470         StyleChanger dummy2(mi.base, LM_ST_TEXT);
471
472         // valid macro?
473         MacroData const * macro = 0;
474         if (validName()) {
475                 macro = mi.macrocontext.get(name());
476
477                 // updateToContext() - avoids another lookup
478                 redefinition_ = macro != 0;
479         }
480
481         // update look?
482         if (lookOutdated_) {
483                 lookOutdated_ = false;
484                 createLook();
485         }
486
487         /// metrics for inset contents
488         if (macro)
489                 macro->lock();
490
491         // first phase, premetric:
492         premetrics_ = true;
493         look_.metrics(mi, dim);
494         labelBoxAscent_ = dim.asc;
495         labelBoxDescent_ = dim.des;
496
497         // second phase, main metric:
498         premetrics_ = false;
499         look_.metrics(mi, dim);
500
501         if (macro)
502                 macro->unlock();
503
504         dim.wid += 6;
505         dim.des += 2;
506         dim.asc += 2;
507
508         setDimCache(mi, dim);
509 }
510
511
512 void MathMacroTemplate::draw(PainterInfo & pi, int x, int y) const
513 {
514         FontSetChanger dummy1(pi.base, from_ascii("mathnormal"));
515         StyleChanger dummy2(pi.base, LM_ST_TEXT);
516
517         setPosCache(pi, x, y);
518         Dimension const dim = dimension(*pi.base.bv);
519
520         // draw outer frame
521         int const a = y - dim.asc + 1;
522         int const w = dim.wid - 2;
523         int const h = dim.height() - 2;
524         pi.pain.rectangle(x, a, w, h, Color_mathframe);
525
526         // just to be sure: set some dummy values for coord cache
527         for (idx_type i = 0; i < nargs(); ++i) {
528                 cell(i).setXY(*pi.base.bv, x, y);
529         }
530
531         // draw contents
532         look_.draw(pi, x + 3, y);
533 }
534
535
536 void MathMacroTemplate::edit(Cursor & cur, bool front, EntryDirectionType entry_from)
537 {
538         updateLook();
539         cur.updateFlags(Update::Force);
540         InsetMathNest::edit(cur, front, entry_from);
541 }
542
543
544 bool MathMacroTemplate::notifyCursorLeaves(Cursor & cur)
545 {
546         updateLook();
547         cur.updateFlags(Update::Force);
548         return InsetMathNest::notifyCursorLeaves(cur);
549 }
550
551
552 void MathMacroTemplate::removeArguments(Cursor & cur, int from, int to) {
553         for (DocIterator it = doc_iterator_begin(*this); it; it.forwardChar()) {
554                 if (!it.nextInset())
555                         continue;
556                 if (it.nextInset()->lyxCode() != MATHMACROARG_CODE)
557                         continue;
558                 MathMacroArgument * arg = static_cast<MathMacroArgument*>(it.nextInset());
559                 int n = arg->number() - 1;
560                 if (from <= n && n <= to) {
561                         int cellSlice = cur.find(it.cell());
562                         if (cellSlice != -1 && cur[cellSlice].pos() > it.pos())
563                                 --cur[cellSlice].pos();
564
565                         it.cell().erase(it.pos());
566                 }
567         }
568
569         updateLook();
570 }
571
572
573 void MathMacroTemplate::shiftArguments(size_t from, int by) {
574         for (DocIterator it = doc_iterator_begin(*this); it; it.forwardChar()) {
575                 if (!it.nextInset())
576                         continue;
577                 if (it.nextInset()->lyxCode() != MATHMACROARG_CODE)
578                         continue;
579                 MathMacroArgument * arg = static_cast<MathMacroArgument*>(it.nextInset());
580                 if (arg->number() >= from + 1)
581                         arg->setNumber(arg->number() + by);
582         }
583
584         updateLook();
585 }
586
587
588 // FIXME: factorize those functions here with a functional style, maybe using Boost's function
589 // objects?
590
591 void fixMacroInstancesAddRemove(Cursor const & from, docstring const & name, int n, bool insert) {
592         Cursor dit = from;
593
594         for (; dit; dit.forwardPos()) {
595                 // only until a macro is redefined
596                 if (dit.inset().lyxCode() == MATHMACRO_CODE) {
597                         MathMacroTemplate const & macroTemplate
598                         = static_cast<MathMacroTemplate const &>(dit.inset());
599                         if (macroTemplate.name() == name)
600                                 break;
601                 }
602
603                 // in front of macro instance?
604                 Inset * inset = dit.nextInset();
605                 if (!inset)
606                         continue;
607                 InsetMath * insetMath = inset->asInsetMath();
608                 if (!insetMath)
609                         continue;
610
611                 MathMacro * macro = insetMath->asMacro();
612                 if (macro && macro->name() == name && macro->folded()) {
613                         // found macro instance
614                         if (insert)
615                                 macro->insertArgument(n);
616                         else
617                                 macro->removeArgument(n);
618                 }
619         }
620 }
621
622
623 void fixMacroInstancesOptional(Cursor const & from, docstring const & name, int optionals) {
624         Cursor dit = from;
625
626         for (; dit; dit.forwardPos()) {
627                 // only until a macro is redefined
628                 if (dit.inset().lyxCode() == MATHMACRO_CODE) {
629                         MathMacroTemplate const & macroTemplate
630                         = static_cast<MathMacroTemplate const &>(dit.inset());
631                         if (macroTemplate.name() == name)
632                                 break;
633                 }
634
635                 // in front of macro instance?
636                 Inset * inset = dit.nextInset();
637                 if (!inset)
638                         continue;
639                 InsetMath * insetMath = inset->asInsetMath();
640                 if (!insetMath)
641                         continue;
642                 MathMacro * macro = insetMath->asMacro();
643                 if (macro && macro->name() == name && macro->folded()) {
644                         // found macro instance
645                         macro->setOptionals(optionals);
646                 }
647         }
648 }
649
650
651 template<class F>
652 void fixMacroInstancesFunctional(Cursor const & from,
653         docstring const & name, F & fix) {
654         Cursor dit = from;
655
656         for (; dit; dit.forwardPos()) {
657                 // only until a macro is redefined
658                 if (dit.inset().lyxCode() == MATHMACRO_CODE) {
659                         MathMacroTemplate const & macroTemplate
660                         = static_cast<MathMacroTemplate const &>(dit.inset());
661                         if (macroTemplate.name() == name)
662                                 break;
663                 }
664
665                 // in front of macro instance?
666                 Inset * inset = dit.nextInset();
667                 if (!inset)
668                         continue;
669                 InsetMath * insetMath = inset->asInsetMath();
670                 if (!insetMath)
671                         continue;
672                 MathMacro * macro = insetMath->asMacro();
673                 if (macro && macro->name() == name && macro->folded())
674                         F(macro);
675         }
676 }
677
678
679 void MathMacroTemplate::insertParameter(Cursor & cur, int pos, bool greedy)
680 {
681         if (pos <= numargs_ && pos >= optionals_ && numargs_ < 9) {
682                 ++numargs_;
683                 shiftArguments(pos, 1);
684
685                 // append example #n
686                 cell(defIdx()).push_back(MathAtom(new MathMacroArgument(pos + 1)));
687                 if (!cell(displayIdx()).empty())
688                         cell(displayIdx()).push_back(MathAtom(new MathMacroArgument(pos + 1)));
689
690                 if (!greedy) {
691                         Cursor dit = cur;
692                         dit.leaveInset(*this);
693                         // TODO: this was dit.forwardPosNoDescend before. Check that this is the same
694                         dit.top().forwardPos();
695
696                         // fix macro instances
697                         fixMacroInstancesAddRemove(dit, name(), pos, true);
698                 }
699         }
700
701         updateLook();
702 }
703
704
705 void MathMacroTemplate::removeParameter(Cursor & cur, int pos, bool greedy)
706 {
707         if (pos < numargs_ && pos >= 0) {
708                 --numargs_;
709                 removeArguments(cur, pos, pos);
710                 shiftArguments(pos + 1, -1);
711
712                 // removed optional parameter?
713                 if (pos < optionals_) {
714                         --optionals_;
715                         optionalValues_[pos] = cell(optIdx(pos));
716                         cells_.erase(cells_.begin() + optIdx(pos));
717
718                         // fix cursor
719                         int macroSlice = cur.find(this);
720                         if (macroSlice != -1) {
721                                 if (cur[macroSlice].idx() == optIdx(pos)) {
722                                         cur.cutOff(macroSlice);
723                                         cur[macroSlice].idx() = 1;
724                                         cur[macroSlice].pos() = 0;
725                                 } else if (cur[macroSlice].idx() > optIdx(pos))
726                                         --cur[macroSlice].idx();
727                         }
728                 }
729
730                 if (!greedy) {
731                         // fix macro instances
732                         //boost::function<void(MathMacro *)> fix = _1->insertArgument(n);
733                         //fixMacroInstancesFunctional(dit, name(), fix);
734                         Cursor dit = cur;
735                         dit.leaveInset(*this);
736                         // TODO: this was dit.forwardPosNoDescend before. Check that this is the same
737                         dit.top().forwardPos();
738                         fixMacroInstancesAddRemove(dit, name(), pos, false);
739                 }
740         }
741
742         updateLook();
743 }
744
745
746 void MathMacroTemplate::makeOptional(Cursor & cur) {
747         if (numargs_ > 0 && optionals_ < numargs_) {
748                 ++optionals_;
749                 cells_.insert(cells_.begin() + optIdx(optionals_ - 1), optionalValues_[optionals_ - 1]);
750                 // fix cursor
751                 int macroSlice = cur.find(this);
752                 if (macroSlice != -1 && cur[macroSlice].idx() >= optIdx(optionals_ - 1))
753                         ++cur[macroSlice].idx();
754
755                 // fix macro instances
756                 Cursor dit = cur;
757                 dit.leaveInset(*this);
758                 // TODO: this was dit.forwardPosNoDescend before. Check that this is the same
759                 dit.top().forwardPos();
760                 fixMacroInstancesOptional(dit, name(), optionals_);
761         }
762
763         updateLook();
764 }
765
766
767 void MathMacroTemplate::makeNonOptional(Cursor & cur) {
768         if (numargs_ > 0 && optionals_ > 0) {
769                 --optionals_;
770
771                 // store default value for later if the use changes his mind
772                 optionalValues_[optionals_] = cell(optIdx(optionals_));
773                 cells_.erase(cells_.begin() + optIdx(optionals_));
774
775                 // fix cursor
776                 int macroSlice = cur.find(this);
777                 if (macroSlice != -1) {
778                         if (cur[macroSlice].idx() > optIdx(optionals_))
779                                 --cur[macroSlice].idx();
780                         else if (cur[macroSlice].idx() == optIdx(optionals_)) {
781                                 cur.cutOff(macroSlice);
782                                 cur[macroSlice].idx() = optIdx(optionals_);
783                                 cur[macroSlice].pos() = 0;
784                         }
785                 }
786
787                 // fix macro instances
788                 Cursor dit = cur;
789                 dit.leaveInset(*this);
790                 // TODO: this was dit.forwardPosNoDescend before. Check that this is the same
791                 dit.top().forwardPos();
792                 fixMacroInstancesOptional(dit, name(), optionals_);
793         }
794
795         updateLook();
796 }
797
798
799 void MathMacroTemplate::doDispatch(Cursor & cur, FuncRequest & cmd)
800 {
801         string const arg = to_utf8(cmd.argument());
802         switch (cmd.action) {
803
804         case LFUN_MATH_MACRO_ADD_PARAM:
805                 if (numargs_ < 9) {
806                         cur.recordUndoFullDocument();
807                         size_t pos = numargs_;
808                         if (arg.size() != 0)
809                                 pos = (size_t)convert<int>(arg) - 1; // it is checked for >=0 in getStatus
810                         insertParameter(cur, pos);
811                 }
812                 break;
813
814
815         case LFUN_MATH_MACRO_REMOVE_PARAM:
816                 if (numargs_ > 0) {
817                         cur.recordUndoFullDocument();
818                         size_t pos = numargs_ - 1;
819                         if (arg.size() != 0)
820                                 pos = (size_t)convert<int>(arg) - 1; // it is checked for >=0 in getStatus
821                         removeParameter(cur, pos);
822                 }
823                 break;
824
825         case LFUN_MATH_MACRO_APPEND_GREEDY_PARAM:
826                 if (numargs_ < 9) {
827                         cur.recordUndoFullDocument();
828                         insertParameter(cur, numargs_, true);
829                 }
830                 break;
831
832         case LFUN_MATH_MACRO_REMOVE_GREEDY_PARAM:
833                 if (numargs_ > 0) {
834                         cur.recordUndoFullDocument();
835                         removeParameter(cur, numargs_ - 1, true);
836                 }
837                 break;
838
839         case LFUN_MATH_MACRO_MAKE_OPTIONAL:
840                 cur.recordUndoFullDocument();
841                 makeOptional(cur);
842                 break;
843
844         case LFUN_MATH_MACRO_MAKE_NONOPTIONAL:
845                 cur.recordUndoFullDocument();
846                 makeNonOptional(cur);
847                 break;
848
849         case LFUN_MATH_MACRO_ADD_OPTIONAL_PARAM:
850                 if (numargs_ < 9) {
851                         cur.recordUndoFullDocument();
852                         insertParameter(cur, optionals_);
853                         makeOptional(cur);
854                 }
855                 break;
856
857         case LFUN_MATH_MACRO_REMOVE_OPTIONAL_PARAM:
858                 if (optionals_ > 0) {
859                         cur.recordUndoFullDocument();
860                         removeParameter(cur, optionals_ - 1);
861                 } break;
862
863         case LFUN_MATH_MACRO_ADD_GREEDY_OPTIONAL_PARAM:
864                 if (numargs_ == optionals_) {
865                         cur.recordUndoFullDocument();
866                         insertParameter(cur, 0, true);
867                         makeOptional(cur);
868                 }
869                 break;
870
871         default:
872                 InsetMathNest::doDispatch(cur, cmd);
873                 break;
874         }
875 }
876
877
878 bool MathMacroTemplate::getStatus(Cursor & /*cur*/, FuncRequest const & cmd,
879         FuncStatus & flag) const
880 {
881         bool ret = true;
882         string const arg = to_utf8(cmd.argument());
883         switch (cmd.action) {
884                 case LFUN_MATH_MACRO_ADD_PARAM: {
885                         int num = numargs_ + 1;
886                         if (arg.size() != 0)
887                                 num = convert<int>(arg);
888                         bool on = (num >= optionals_
889                                    && numargs_ < 9 && num <= numargs_ + 1);
890                         flag.enabled(on);
891                         break;
892                 }
893
894                 case LFUN_MATH_MACRO_APPEND_GREEDY_PARAM:
895                         flag.enabled(numargs_ < 9);
896                         break;
897
898                 case LFUN_MATH_MACRO_REMOVE_PARAM: {
899                         int num = numargs_;
900                         if (arg.size() != 0)
901                                 num = convert<int>(arg);
902                         flag.enabled(num >= 1 && num <= numargs_);
903                         break;
904                 }
905
906                 case LFUN_MATH_MACRO_MAKE_OPTIONAL:
907                         flag.enabled(numargs_ > 0
908                                      && optionals_ < numargs_
909                                      && type_ != MacroTypeDef);
910                         break;
911
912                 case LFUN_MATH_MACRO_MAKE_NONOPTIONAL:
913                         flag.enabled(optionals_ > 0
914                                      && type_ != MacroTypeDef);
915                         break;
916
917                 case LFUN_MATH_MACRO_ADD_OPTIONAL_PARAM:
918                         flag.enabled(numargs_ < 9);
919                         break;
920
921                 case LFUN_MATH_MACRO_REMOVE_OPTIONAL_PARAM:
922                         flag.enabled(optionals_ > 0);
923                         break;
924
925                 case LFUN_MATH_MACRO_ADD_GREEDY_OPTIONAL_PARAM:
926                         flag.enabled(numargs_ == 0
927                                      && type_ != MacroTypeDef);
928                         break;
929
930                 case LFUN_IN_MATHMACROTEMPLATE:
931                         flag.enabled();
932                         break;
933
934                 default:
935                         ret = false;
936                         break;
937         }
938         return ret;
939 }
940
941
942 void MathMacroTemplate::read(Buffer const &, Lexer & lex)
943 {
944         MathData ar;
945         mathed_parse_cell(ar, lex.getStream());
946         if (ar.size() != 1 || !ar[0]->asMacroTemplate()) {
947                 lyxerr << "Cannot read macro from '" << ar << "'" << endl;
948                 lyxerr << "Read: " << to_utf8(asString(ar)) << endl;
949                 return;
950         }
951         operator=( *(ar[0]->asMacroTemplate()) );
952
953         updateLook();
954 }
955
956
957 void MathMacroTemplate::write(Buffer const &, ostream & os) const
958 {
959         odocstringstream oss;
960         WriteStream wi(oss, false, false);
961         oss << "FormulaMacro\n";
962         write(wi);
963         os << to_utf8(oss.str());
964 }
965
966
967 void MathMacroTemplate::write(WriteStream & os) const
968 {
969         write(os, false);
970 }
971
972
973 void MathMacroTemplate::write(WriteStream & os, bool overwriteRedefinition) const
974 {
975         // newcommand or renewcommand
976         if (os.latex() && optionals_ > 1)
977                 os << "\\newlyxcommand";
978         else {
979                 if (redefinition_ && !overwriteRedefinition)
980                         os << "\\renewcommand";
981                 else
982                         os << "\\newcommand";
983         }
984         os << "{\\" << name().c_str() << '}';
985         if (numargs_ > 0)
986                 os << '[' << numargs_ << ']';
987
988         // optional values
989         for (int i = 0; i < optionals_; ++i) {
990                 docstring optValue = asString(cell(optIdx(i)));
991                 if (optValue.find(']') != docstring::npos)
992                         os << "[{" << cell(optIdx(i)) << "}]";
993                 else
994                         os << "[" << cell(optIdx(i)) << "]";
995         }
996
997         os << "{" << cell(defIdx()) << "}";
998
999         if (os.latex()) {
1000                 // writing .tex. done.
1001                 os << "\n";
1002         } else {
1003                 // writing .lyx, write special .tex export only if necessary
1004                 if (!cell(displayIdx()).empty())
1005                         os << "\n{" << cell(displayIdx()) << '}';
1006         }
1007 }
1008
1009
1010 int MathMacroTemplate::plaintext(Buffer const & buf, odocstream & os,
1011                                  OutputParams const &) const
1012 {
1013         static docstring const str = '[' + buf.B_("math macro") + ']';
1014
1015         os << str;
1016         return str.size();
1017 }
1018
1019
1020 bool MathMacroTemplate::validName() const
1021 {
1022         docstring n = name();
1023
1024         // empty name?
1025         if (n.size() == 0)
1026                 return false;
1027
1028         // converting back and force doesn't swallow anything?
1029         /*MathData ma;
1030         asArray(n, ma);
1031         if (asString(ma) != n)
1032                 return false;*/
1033
1034         // valid characters?
1035         for (size_t i = 0; i < n.size(); ++i) {
1036                 if (!(n[i] >= 'a' && n[i] <= 'z') &&
1037                                 !(n[i] >= 'A' && n[i] <= 'Z'))
1038                         return false;
1039         }
1040
1041         return true;
1042 }
1043
1044
1045 bool MathMacroTemplate::validMacro() const
1046 {
1047         return validName();
1048 }
1049
1050
1051 bool MathMacroTemplate::fixNameAndCheckIfValid()
1052 {
1053         // check all the characters/insets in the name cell
1054         size_t i = 0;
1055         MathData & data = cell(0);
1056         while (i < data.size()) {
1057                 InsetMathChar const * cinset = data[i]->asCharInset();
1058                 if (cinset) {
1059                         // valid character in [a-zA-Z]?
1060                         char_type c = cinset->getChar();
1061                         if ((c >= 'a' && c <= 'z')
1062                             || (c >= 'A' && c <= 'Z')) {
1063                                 ++i;
1064                                 continue;
1065                         }
1066                 }
1067
1068                 // throw cell away
1069                 data.erase(i);
1070         }
1071
1072         // now it should be valid if anything in the name survived
1073         return data.size() > 0;
1074 }
1075
1076         
1077 void MathMacroTemplate::validate(LaTeXFeatures & features) const
1078 {
1079         if (optionals_ > 1) {
1080                 features.require("newlyxcommand");
1081         }
1082 }
1083
1084 void MathMacroTemplate::getDefaults(vector<docstring> & defaults) const
1085 {
1086         defaults.resize(numargs_);
1087         for (int i = 0; i < optionals_; ++i)
1088                 defaults[i] = asString(cell(optIdx(i)));
1089 }
1090
1091
1092 docstring MathMacroTemplate::definition() const
1093 {
1094         return asString(cell(defIdx()));
1095 }
1096
1097
1098 docstring MathMacroTemplate::displayDefinition() const
1099 {
1100         return asString(cell(displayIdx()));
1101 }
1102
1103
1104 size_t MathMacroTemplate::numArgs() const
1105 {
1106         return numargs_;
1107 }
1108
1109
1110 size_t MathMacroTemplate::numOptionals() const
1111 {
1112         return optionals_;
1113 }
1114
1115
1116 void MathMacroTemplate::infoize(odocstream & os) const
1117 {
1118         os << "Math Macro: \\" << name();
1119 }
1120
1121 } // namespace lyx