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