]> git.lyx.org Git - lyx.git/blob - src/mathed/InsetMathMacroTemplate.cpp
Amend 6c3447c8: FindAdv: sometimes a space is added on some math symbols
[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 bool InsetMathMacroTemplate::notifyCursorLeaves(Cursor const & old, Cursor & cur)
629 {
630         unsigned int const nargs_before = nargs();
631         commitEditChanges(cur, old);
632         updateLook();
633         cur.screenUpdateFlags(Update::Force);
634         // If we have removed a cell, we might have invalidated the cursor
635         return InsetMathNest::notifyCursorLeaves(old, cur)
636                 || nargs() < nargs_before;
637 }
638
639
640 void InsetMathMacroTemplate::removeArguments(Cursor & cur,
641         DocIterator const & /*inset_pos*/, int from, int to)
642 {
643         DocIterator it = doc_iterator_begin(&buffer(), this);
644         for (; it; it.forwardChar()) {
645                 if (!it.nextInset())
646                         continue;
647                 if (it.nextInset()->lyxCode() != MATH_MACROARG_CODE)
648                         continue;
649                 InsetMathMacroArgument * arg = static_cast<InsetMathMacroArgument*>(it.nextInset());
650                 int n = arg->number() - 1;
651                 if (from <= n && n <= to) {
652                         int cellSlice = cur.find(it.cell());
653                         if (cellSlice != -1 && cur[cellSlice].pos() > it.pos())
654                                 --cur[cellSlice].pos();
655
656                         it.cell().erase(it.pos());
657                 }
658         }
659
660         updateLook();
661 }
662
663
664 void InsetMathMacroTemplate::shiftArguments(size_t from, int by)
665 {
666         for (DocIterator it = doc_iterator_begin(&buffer(), this); it; it.forwardChar()) {
667                 if (!it.nextInset())
668                         continue;
669                 if (it.nextInset()->lyxCode() != MATH_MACROARG_CODE)
670                         continue;
671                 InsetMathMacroArgument * arg = static_cast<InsetMathMacroArgument*>(it.nextInset());
672                 if (arg->number() >= int(from) + 1)
673                         arg->setNumber(arg->number() + by);
674         }
675
676         updateLook();
677 }
678
679
680 int InsetMathMacroTemplate::maxArgumentInDefinition() const
681 {
682         // We don't have a buffer when pasting from the clipboard (bug 6014).
683         Buffer const * macro_buffer = isBufferLoaded() ? &buffer() : 0;
684         int maxArg = 0;
685         DocIterator it = doc_iterator_begin(macro_buffer, this);
686         it.idx() = defIdx();
687         for (; it; it.forwardChar()) {
688                 if (!it.nextInset())
689                         continue;
690                 if (it.nextInset()->lyxCode() != MATH_MACROARG_CODE)
691                         continue;
692                 InsetMathMacroArgument * arg = static_cast<InsetMathMacroArgument*>(it.nextInset());
693                 maxArg = std::max(arg->number(), maxArg);
694         }
695         return maxArg;
696 }
697
698
699 void InsetMathMacroTemplate::insertMissingArguments(int maxArg)
700 {
701         bool found[9] = { false, false, false, false, false, false, false, false, false };
702         idx_type idx = cell(displayIdx()).empty() ? defIdx() : displayIdx();
703
704         // search for #n macros arguments
705         DocIterator it = doc_iterator_begin(&buffer(), this);
706         it.idx() = idx;
707         for (; it && it[0].idx() == idx; it.forwardChar()) {
708                 if (!it.nextInset())
709                         continue;
710                 if (it.nextInset()->lyxCode() != MATH_MACROARG_CODE)
711                         continue;
712                 InsetMathMacroArgument * arg = static_cast<InsetMathMacroArgument*>(it.nextInset());
713                 found[arg->number() - 1] = true;
714         }
715
716         // add missing ones
717         for (int i = 0; i < maxArg; ++i) {
718                 if (found[i])
719                         continue;
720
721                 cell(idx).push_back(MathAtom(new InsetMathMacroArgument(buffer_, i + 1)));
722         }
723 }
724
725
726 void InsetMathMacroTemplate::changeArity(Cursor & cur,
727         DocIterator const & inset_pos, int newNumArg)
728 {
729         // remove parameter which do not appear anymore in the definition
730         for (int i = numargs_; i > newNumArg; --i)
731                 removeParameter(cur, inset_pos, numargs_ - 1, true);
732
733         // add missing parameter
734         for (int i = numargs_; i < newNumArg; ++i)
735                 insertParameter(cur, inset_pos, numargs_, true, false);
736 }
737
738
739 ///
740 class AddRemoveMacroInstanceFix
741 {
742 public:
743         ///
744         AddRemoveMacroInstanceFix(int n, bool insert) : n_(n), insert_(insert) {}
745         ///
746         void operator()(InsetMathMacro * macro)
747         {
748                 if (macro->folded()) {
749                         if (insert_)
750                                 macro->insertArgument(n_);
751                         else
752                                 macro->removeArgument(n_);
753                 }
754         }
755
756 private:
757         ///
758         int n_;
759         ///
760         bool insert_;
761 };
762
763
764 ///
765 class OptionalsMacroInstanceFix
766 {
767 public:
768         ///
769         explicit OptionalsMacroInstanceFix(int optionals) : optionals_(optionals) {}
770         ///
771         void operator()(InsetMathMacro * macro)
772         {
773                 macro->setOptionals(optionals_);
774         }
775
776 private:
777         ///
778         int optionals_;
779 };
780
781
782 ///
783 class NullMacroInstanceFix
784 {
785 public:
786         ///
787         void operator()(InsetMathMacro * ) {}
788 };
789
790
791 template<class F>
792 void fixMacroInstances(Cursor & cur, DocIterator const & inset_pos,
793         docstring const & name, F & fix)
794 {
795         // goto position behind macro template
796         DocIterator dit = inset_pos;
797         dit.pop_back();
798         dit.top().forwardPos();
799
800         // remember hull to trigger preview reload
801         DocIterator hull(dit.buffer());
802         bool preview_reload_needed = false;
803         set<DocIterator> preview_hulls;
804
805         // iterate over all positions until macro is redefined
806         for (; dit; dit.forwardPos()) {
807                 // left the outer hull?
808                 if (!hull.empty() && dit.depth() == hull.depth()) {
809                         // schedule reload of the preview if necessary
810                         if (preview_reload_needed) {
811                                 preview_hulls.insert(hull);
812                                 preview_reload_needed = false;
813                         }
814                         hull.clear();
815                 }
816
817                 // only until a macro is redefined
818                 if (dit.inset().lyxCode() == MATHMACRO_CODE) {
819                         InsetMathMacroTemplate const & macroTemplate
820                         = static_cast<InsetMathMacroTemplate const &>(dit.inset());
821                         if (macroTemplate.name() == name)
822                                 break;
823                 }
824
825                 // in front of macro instance?
826                 Inset * inset = dit.nextInset();
827                 if (!inset)
828                         continue;
829                 InsetMath * insetMath = inset->asInsetMath();
830                 if (!insetMath)
831                         continue;
832
833                 // in front of outer hull?
834                 InsetMathHull * inset_hull = insetMath->asHullInset();
835                 if (inset_hull && hull.empty()) {
836                         // remember this for later preview reload
837                         hull = dit;
838                 }
839
840                 InsetMathMacro * macro = insetMath->asMacro();
841                 if (macro && macro->name() == name && macro->folded()) {
842                         fix(macro);
843                         if (RenderPreview::previewMath())
844                                 preview_reload_needed = true;
845                 }
846         }
847
848         if (!preview_hulls.empty()) {
849                 // reload the scheduled previews
850                 set<DocIterator>::const_iterator sit = preview_hulls.begin();
851                 set<DocIterator>::const_iterator end = preview_hulls.end();
852                 for (; sit != end; ++sit) {
853                         InsetMathHull * inset_hull =
854                                 sit->nextInset()->asInsetMath()->asHullInset();
855                         LBUFERR(inset_hull);
856                         inset_hull->reloadPreview(*sit);
857                 }
858                 cur.screenUpdateFlags(Update::Force);
859         }
860 }
861
862
863 void InsetMathMacroTemplate::commitEditChanges(Cursor & cur,
864         DocIterator const & inset_pos)
865 {
866         int args_in_def = maxArgumentInDefinition();
867         if (args_in_def != numargs_) {
868                 // FIXME: implement precise undo handling (only a few places
869                 //   need undo)
870                 cur.recordUndoFullBuffer();
871                 changeArity(cur, inset_pos, args_in_def);
872         }
873         insertMissingArguments(args_in_def);
874
875         // make sure the preview are up to date
876         NullMacroInstanceFix fix;
877         fixMacroInstances(cur, inset_pos, name(), fix);
878 }
879
880
881 void InsetMathMacroTemplate::insertParameter(Cursor & cur,
882         DocIterator const & inset_pos, int pos, bool greedy, bool addarg)
883 {
884         if (pos <= numargs_ && pos >= optionals_ && numargs_ < 9) {
885                 ++numargs_;
886
887                 // append example #n
888                 if (addarg) {
889                         shiftArguments(pos, 1);
890
891                         cell(defIdx()).push_back(MathAtom(new InsetMathMacroArgument(buffer_, pos + 1)));
892                         if (!cell(displayIdx()).empty())
893                                 cell(displayIdx()).push_back(MathAtom(new InsetMathMacroArgument(buffer_, pos + 1)));
894                 }
895
896                 if (!greedy) {
897                         // fix macro instances
898                         AddRemoveMacroInstanceFix fix(pos, true);
899                         fixMacroInstances(cur, inset_pos, name(), fix);
900                 }
901         }
902
903         updateLook();
904 }
905
906
907 void InsetMathMacroTemplate::removeParameter(Cursor & cur,
908         DocIterator const & inset_pos, int pos, bool greedy)
909 {
910         if (pos < numargs_ && pos >= 0) {
911                 --numargs_;
912                 removeArguments(cur, inset_pos, pos, pos);
913                 shiftArguments(pos + 1, -1);
914
915                 // removed optional parameter?
916                 if (pos < optionals_) {
917                         --optionals_;
918                         optionalValues_[pos] = cell(optIdx(pos));
919                         cells_.erase(cells_.begin() + optIdx(pos));
920
921                         // fix cursor
922                         int macroSlice = cur.find(this);
923                         if (macroSlice != -1) {
924                                 if (cur[macroSlice].idx() == optIdx(pos)) {
925                                         cur.cutOff(macroSlice);
926                                         cur[macroSlice].idx() = 1;
927                                         cur[macroSlice].pos() = 0;
928                                 } else if (cur[macroSlice].idx() > optIdx(pos))
929                                         --cur[macroSlice].idx();
930                         }
931                 }
932
933                 if (!greedy) {
934                         // fix macro instances
935                         AddRemoveMacroInstanceFix fix(pos, false);
936                         fixMacroInstances(cur, inset_pos, name(), fix);
937                 }
938         }
939
940         updateLook();
941 }
942
943
944 void InsetMathMacroTemplate::makeOptional(Cursor & cur,
945         DocIterator const & inset_pos)
946 {
947         if (numargs_ > 0 && optionals_ < numargs_) {
948                 ++optionals_;
949                 cells_.insert(cells_.begin() + optIdx(optionals_ - 1), optionalValues_[optionals_ - 1]);
950                 // fix cursor
951                 int macroSlice = cur.find(this);
952                 if (macroSlice != -1 && cur[macroSlice].idx() >= optIdx(optionals_ - 1))
953                         ++cur[macroSlice].idx();
954
955                 // fix macro instances
956                 OptionalsMacroInstanceFix fix(optionals_);
957                 fixMacroInstances(cur, inset_pos, name(), fix);
958         }
959
960         updateLook();
961 }
962
963
964 void InsetMathMacroTemplate::makeNonOptional(Cursor & cur,
965         DocIterator const & inset_pos)
966 {
967         if (numargs_ > 0 && optionals_ > 0) {
968                 --optionals_;
969
970                 // store default value for later if the user changes his mind
971                 optionalValues_[optionals_] = cell(optIdx(optionals_));
972                 cells_.erase(cells_.begin() + optIdx(optionals_));
973
974                 // fix cursor
975                 int macroSlice = cur.find(this);
976                 if (macroSlice != -1) {
977                         if (cur[macroSlice].idx() > optIdx(optionals_))
978                                 --cur[macroSlice].idx();
979                         else if (cur[macroSlice].idx() == optIdx(optionals_)) {
980                                 cur.cutOff(macroSlice);
981                                 cur[macroSlice].idx() = optIdx(optionals_);
982                                 cur[macroSlice].pos() = 0;
983                         }
984                 }
985
986                 // fix macro instances
987                 OptionalsMacroInstanceFix fix(optionals_);
988                 fixMacroInstances(cur, inset_pos, name(), fix);
989         }
990
991         updateLook();
992 }
993
994
995 void InsetMathMacroTemplate::doDispatch(Cursor & cur, FuncRequest & cmd)
996 {
997         string const arg = to_utf8(cmd.argument());
998         switch (cmd.action()) {
999
1000         case LFUN_MATH_MACRO_ADD_PARAM:
1001                 if (numargs_ < 9) {
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_;
1007                         if (!arg.empty())
1008                                 pos = (size_t)convert<int>(arg) - 1; // it is checked for >=0 in getStatus
1009                         insertParameter(cur, cur, pos);
1010                 }
1011                 break;
1012
1013
1014         case LFUN_MATH_MACRO_REMOVE_PARAM:
1015                 if (numargs_ > 0) {
1016                         commitEditChanges(cur, cur);
1017                         // FIXME: implement precise undo handling (only a few places
1018                         //   need undo)
1019                         cur.recordUndoFullBuffer();
1020                         size_t pos = numargs_ - 1;
1021                         if (!arg.empty())
1022                                 pos = (size_t)convert<int>(arg) - 1; // it is checked for >=0 in getStatus
1023                         removeParameter(cur, cur, pos);
1024                 }
1025                 break;
1026
1027         case LFUN_MATH_MACRO_APPEND_GREEDY_PARAM:
1028                 if (numargs_ < 9) {
1029                         commitEditChanges(cur, cur);
1030                         // FIXME: implement precise undo handling (only a few places
1031                         //   need undo)
1032                         cur.recordUndoFullBuffer();
1033                         insertParameter(cur, cur, numargs_, true);
1034                 }
1035                 break;
1036
1037         case LFUN_MATH_MACRO_REMOVE_GREEDY_PARAM:
1038                 if (numargs_ > 0) {
1039                         commitEditChanges(cur, cur);
1040                         // FIXME: implement precise undo handling (only a few places
1041                         //   need undo)
1042                         cur.recordUndoFullBuffer();
1043                         removeParameter(cur, cur, numargs_ - 1, true);
1044                 }
1045                 break;
1046
1047         case LFUN_MATH_MACRO_MAKE_OPTIONAL:
1048                 commitEditChanges(cur, cur);
1049                 // FIXME: implement precise undo handling (only a few places
1050                 //   need undo)
1051                 cur.recordUndoFullBuffer();
1052                 makeOptional(cur, cur);
1053                 break;
1054
1055         case LFUN_MATH_MACRO_MAKE_NONOPTIONAL:
1056                 commitEditChanges(cur, cur);
1057                 // FIXME: implement precise undo handling (only a few places
1058                 //   need undo)
1059                 cur.recordUndoFullBuffer();
1060                 makeNonOptional(cur, cur);
1061                 break;
1062
1063         case LFUN_MATH_MACRO_ADD_OPTIONAL_PARAM:
1064                 if (numargs_ < 9) {
1065                         commitEditChanges(cur, cur);
1066                         // FIXME: implement precise undo handling (only a few places
1067                         //   need undo)
1068                         cur.recordUndoFullBuffer();
1069                         insertParameter(cur, cur, optionals_);
1070                         makeOptional(cur, cur);
1071                 }
1072                 break;
1073
1074         case LFUN_MATH_MACRO_REMOVE_OPTIONAL_PARAM:
1075                 if (optionals_ > 0) {
1076                         commitEditChanges(cur, cur);
1077                         // FIXME: implement precise undo handling (only a few places
1078                         //   need undo)
1079                         cur.recordUndoFullBuffer();
1080                         removeParameter(cur, cur, optionals_ - 1);
1081                 } break;
1082
1083         case LFUN_MATH_MACRO_ADD_GREEDY_OPTIONAL_PARAM:
1084                 if (numargs_ == optionals_) {
1085                         commitEditChanges(cur, cur);
1086                         // FIXME: implement precise undo handling (only a few places
1087                         //   need undo)
1088                         cur.recordUndoFullBuffer();
1089                         insertParameter(cur, cur, 0, true);
1090                         makeOptional(cur, cur);
1091                 }
1092                 break;
1093
1094         default:
1095                 InsetMathNest::doDispatch(cur, cmd);
1096                 break;
1097         }
1098 }
1099
1100
1101 bool InsetMathMacroTemplate::getStatus(Cursor & cur, FuncRequest const & cmd,
1102         FuncStatus & status) const
1103 {
1104         bool ret = true;
1105         string const arg = to_utf8(cmd.argument());
1106         switch (cmd.action()) {
1107                 case LFUN_MATH_MACRO_ADD_PARAM: {
1108                         int num = numargs_ + 1;
1109                         if (!arg.empty())
1110                                 num = convert<int>(arg);
1111                         bool on = (num >= optionals_
1112                                    && numargs_ < 9 && num <= numargs_ + 1);
1113                         status.setEnabled(on);
1114                         break;
1115                 }
1116
1117                 case LFUN_MATH_MACRO_APPEND_GREEDY_PARAM:
1118                         status.setEnabled(numargs_ < 9);
1119                         break;
1120
1121                 case LFUN_MATH_MACRO_REMOVE_GREEDY_PARAM:
1122                 case LFUN_MATH_MACRO_REMOVE_PARAM: {
1123                         int num = numargs_;
1124                         if (!arg.empty())
1125                                 num = convert<int>(arg);
1126                         status.setEnabled(num >= 1 && num <= numargs_);
1127                         break;
1128                 }
1129
1130                 case LFUN_MATH_MACRO_MAKE_OPTIONAL:
1131                         status.setEnabled(numargs_ > 0
1132                                      && optionals_ < numargs_
1133                                      && type_ != MacroTypeDef);
1134                         break;
1135
1136                 case LFUN_MATH_MACRO_MAKE_NONOPTIONAL:
1137                         status.setEnabled(optionals_ > 0
1138                                      && type_ != MacroTypeDef);
1139                         break;
1140
1141                 case LFUN_MATH_MACRO_ADD_OPTIONAL_PARAM:
1142                         status.setEnabled(numargs_ < 9);
1143                         break;
1144
1145                 case LFUN_MATH_MACRO_REMOVE_OPTIONAL_PARAM:
1146                         status.setEnabled(optionals_ > 0);
1147                         break;
1148
1149                 case LFUN_MATH_MACRO_ADD_GREEDY_OPTIONAL_PARAM:
1150                         status.setEnabled(numargs_ == 0
1151                                      && type_ != MacroTypeDef);
1152                         break;
1153
1154                 case LFUN_IN_MATHMACROTEMPLATE:
1155                         status.setEnabled(true);
1156                         break;
1157
1158                 default:
1159                         ret = InsetMathNest::getStatus(cur, cmd, status);
1160                         break;
1161         }
1162         return ret;
1163 }
1164
1165
1166 void InsetMathMacroTemplate::read(Lexer & lex)
1167 {
1168         MathData ar(buffer_);
1169         mathed_parse_cell(ar, lex.getStream(), Parse::TRACKMACRO);
1170         if (ar.size() != 1 || !ar[0]->asMacroTemplate()) {
1171                 lyxerr << "Cannot read macro from '" << ar << "'" << endl;
1172                 lyxerr << "Read: " << to_utf8(asString(ar)) << endl;
1173                 return;
1174         }
1175         operator=( *(ar[0]->asMacroTemplate()) );
1176
1177         updateLook();
1178 }
1179
1180
1181 void InsetMathMacroTemplate::write(ostream & os) const
1182 {
1183         odocstringstream oss;
1184         otexrowstream ots(oss);
1185         TeXMathStream wi(ots, false, false, TeXMathStream::wsDefault);
1186         oss << "FormulaMacro\n";
1187         write(wi);
1188         os << to_utf8(oss.str());
1189 }
1190
1191
1192 void InsetMathMacroTemplate::write(TeXMathStream & os) const
1193 {
1194         write(os, false);
1195 }
1196
1197
1198 int InsetMathMacroTemplate::write(TeXMathStream & os, bool overwriteRedefinition) const
1199 {
1200         int num_lines = 0;
1201
1202         if (os.latex()) {
1203                 if (definition().empty())
1204                         return num_lines;
1205
1206                 if (optionals_ > 0) {
1207                         // macros with optionals use the xargs package, e.g.:
1208                         // \newcommandx{\foo}[2][usedefault, addprefix=\global,1=default]{#1,#2}
1209                         // \long is implicit by xargs
1210                         if (redefinition_ && !overwriteRedefinition)
1211                                 os << "\\renewcommandx";
1212                         else
1213                                 os << "\\newcommandx";
1214
1215                         os << "\\" << name()
1216                            << "[" << numargs_ << "]"
1217                            << "[usedefault, addprefix=\\global";
1218                         for (int i = 0; i < optionals_; ++i) {
1219                                 docstring optValue = asString(cell(optIdx(i)));
1220                                 if (optValue.find(']') != docstring::npos
1221                                     || optValue.find(',') != docstring::npos)
1222                                         os << ", " << i + 1 << "="
1223                                         << "{" << cell(optIdx(i)) << "}";
1224                                 else
1225                                         os << ", " << i + 1 << "="
1226                                         << cell(optIdx(i));
1227                         }
1228                         os << "]";
1229                 } else {
1230                         // Macros without optionals use standard _global_ \def macros:
1231                         //   \global\def\long\foo#1#2{#1,#2}
1232                         // We use the \long prefix as this is the equivalent to \newcommand.
1233                         // We cannot use \newcommand directly because \global does not work with it.
1234                         os << "\n\\global\\long\\def\\" << name();
1235                         docstring param = from_ascii("#0");
1236                         for (int i = 1; i <= numargs_; ++i) {
1237                                 param[1] = '0' + i;
1238                                 os << param;
1239                         }
1240                 }
1241         } else {
1242                 // in LyX output we use some pseudo syntax which is implementation
1243                 // independent, e.g.
1244                 // \newcommand{\foo}[2][default]{#1,#2}
1245                 if (redefinition_ && !overwriteRedefinition)
1246                         os << "\\renewcommand";
1247                 else
1248                         os << "\\newcommand";
1249                 os << "{\\" << name() << '}';
1250                 if (numargs_ > 0)
1251                         os << '[' << numargs_ << ']';
1252
1253                 for (int i = 0; i < optionals_; ++i) {
1254                         docstring optValue = asString(cell(optIdx(i)));
1255                         if (optValue.find(']') != docstring::npos)
1256                                 os << "[{" << cell(optIdx(i)) << "}]";
1257                         else
1258                                 os << "[" << cell(optIdx(i)) << "]";
1259                 }
1260         }
1261
1262         os << "{" << cell(defIdx()) << "}";
1263
1264         if (os.latex()) {
1265                 // writing .tex. done.
1266                 os << "%\n";
1267                 ++num_lines;
1268         } else {
1269                 // writing .lyx, write special .tex export only if necessary
1270                 if (!cell(displayIdx()).empty()) {
1271                         os << "\n{" << cell(displayIdx()) << '}';
1272                         ++num_lines;
1273                 }
1274         }
1275
1276         return num_lines;
1277 }
1278
1279
1280 docstring InsetMathMacroTemplate::xhtml(XMLStream &, OutputParams const &) const
1281 {
1282         return docstring();
1283 }
1284
1285 int InsetMathMacroTemplate::plaintext(odocstringstream & os,
1286                                  OutputParams const &, size_t) const
1287 {
1288         docstring const str = '[' + buffer().B_("math macro") + ']';
1289
1290         os << str;
1291         return str.size();
1292 }
1293
1294
1295 bool InsetMathMacroTemplate::validName() const
1296 {
1297         docstring n = name();
1298
1299         if (n.empty())
1300                 return false;
1301
1302         // converting back and force doesn't swallow anything?
1303         /*MathData ma;
1304         asArray(n, ma);
1305         if (asString(ma) != n)
1306                 return false;*/
1307
1308         // valid characters?
1309         if (n.size() > 1) {
1310                 for (char_type c : n) {
1311                         if (!(c >= 'a' && c <= 'z')
1312                             && !(c >= 'A' && c <= 'Z')
1313                             && c != '*')
1314                                 return false;
1315                 }
1316         }
1317
1318         return true;
1319 }
1320
1321
1322 bool InsetMathMacroTemplate::validMacro() const
1323 {
1324         return validName();
1325 }
1326
1327
1328 bool InsetMathMacroTemplate::fixNameAndCheckIfValid()
1329 {
1330         // check all the characters/insets in the name cell
1331         size_t i = 0;
1332         MathData & data = cell(0);
1333         while (i < data.size()) {
1334                 InsetMathChar const * cinset = data[i]->asCharInset();
1335                 if (cinset) {
1336                         // valid character in [a-zA-Z]?
1337                         char_type c = cinset->getChar();
1338                         if ((c >= 'a' && c <= 'z')
1339                             || (c >= 'A' && c <= 'Z')) {
1340                                 ++i;
1341                                 continue;
1342                         }
1343                 }
1344
1345                 // throw cell away
1346                 data.erase(i);
1347         }
1348
1349         // now it should be valid if anything in the name survived
1350         return !data.empty();
1351 }
1352
1353
1354 void InsetMathMacroTemplate::validate(LaTeXFeatures & features) const
1355 {
1356         // we need global optional macro arguments. They are not available
1357         // with \def, and \newcommand does not support global macros. So we
1358         // are bound to xargs also for the single-optional-parameter case.
1359         if (optionals_ > 0)
1360                 features.require("xargs");
1361 }
1362
1363 void InsetMathMacroTemplate::getDefaults(vector<docstring> & defaults) const
1364 {
1365         defaults.resize(numargs_);
1366         for (int i = 0; i < optionals_; ++i)
1367                 defaults[i] = asString(cell(optIdx(i)));
1368 }
1369
1370
1371 docstring InsetMathMacroTemplate::definition() const
1372 {
1373         return asString(cell(defIdx()));
1374 }
1375
1376
1377 docstring InsetMathMacroTemplate::displayDefinition() const
1378 {
1379         return asString(cell(displayIdx()));
1380 }
1381
1382
1383 size_t InsetMathMacroTemplate::numArgs() const
1384 {
1385         return numargs_;
1386 }
1387
1388
1389 size_t InsetMathMacroTemplate::numOptionals() const
1390 {
1391         return optionals_;
1392 }
1393
1394
1395 void InsetMathMacroTemplate::infoize(odocstream & os) const
1396 {
1397         os << bformat(_("Math Macro: \\%1$s"), name());
1398 }
1399
1400
1401 string InsetMathMacroTemplate::contextMenuName() const
1402 {
1403         return "context-math-macro-definition";
1404 }
1405
1406
1407 void InsetMathMacroTemplate::addToToc(DocIterator const & pit, bool output_active,
1408                                                                  UpdateType, TocBackend & backend) const
1409 {
1410         docstring str;
1411         if (!validMacro())
1412                 str = bformat(_("Invalid macro! \\%1$s"), name());
1413         else
1414                 str = "\\" + name();
1415         TocBuilder & b = backend.builder("math-macro");
1416         b.pushItem(pit, str, output_active);
1417         b.pop();
1418 }
1419
1420
1421 } // namespace lyx