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