]> git.lyx.org Git - lyx.git/blob - src/mathed/InsetMathMacro.cpp
Revert "Set buffer when expanding macro"
[lyx.git] / src / mathed / InsetMathMacro.cpp
1 /**
2  * \file InsetMathMacro.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Alejandro Aguilar Sierra
7  * \author André Pönitz
8  * \author Stefan Schimanski
9  *
10  * Full author contact details are available in file CREDITS.
11  */
12
13 #include <config.h>
14
15 #include "InsetMathMacro.h"
16
17 #include "InsetMathChar.h"
18 #include "InsetMathScript.h"
19 #include "MathCompletionList.h"
20 #include "MathExtern.h"
21 #include "MathFactory.h"
22 #include "MathStream.h"
23 #include "MathSupport.h"
24
25 #include "Buffer.h"
26 #include "BufferView.h"
27 #include "CoordCache.h"
28 #include "Cursor.h"
29 #include "FuncStatus.h"
30 #include "FuncRequest.h"
31 #include "LaTeXFeatures.h"
32 #include "LyX.h"
33 #include "LyXRC.h"
34 #include "MetricsInfo.h"
35
36 #include "frontends/Painter.h"
37
38 #include "support/debug.h"
39 #include "support/gettext.h"
40 #include "support/lassert.h"
41 #include "support/lstrings.h"
42 #include "support/RefChanger.h"
43 #include "support/textutils.h"
44
45 #include <ostream>
46 #include <vector>
47
48 using namespace lyx::support;
49 using namespace std;
50
51 namespace lyx {
52
53
54 /// A proxy for the macro values
55 class InsetArgumentProxy : public InsetMath {
56 public:
57         ///
58         InsetArgumentProxy(InsetMathMacro * mathMacro, size_t idx)
59                 : mathMacro_(mathMacro), idx_(idx) {}
60         ///
61         InsetArgumentProxy(InsetMathMacro * mathMacro, size_t idx, docstring const & def)
62                 : mathMacro_(mathMacro), idx_(idx)
63         {
64                         asArray(def, def_);
65         }
66         ///
67         void setOwner(InsetMathMacro * mathMacro) { mathMacro_ = mathMacro; }
68         ///
69         InsetMathMacro const * owner() { return mathMacro_; }
70         ///
71         marker_type marker(BufferView const *) const { return NO_MARKER; }
72         ///
73         InsetCode lyxCode() const { return ARGUMENT_PROXY_CODE; }
74         /// The math data to use for display
75         MathData const & displayCell(BufferView const * bv) const
76         {
77                 // handle default macro arguments
78                 bool use_def_arg = !mathMacro_->editMetrics(bv)
79                         && mathMacro_->cell(idx_).empty();
80                 return use_def_arg ? def_ : mathMacro_->cell(idx_);
81         }
82         ///
83         bool addToMathRow(MathRow & mrow, MetricsInfo & mi) const
84         {
85                 // macro arguments are in macros
86                 LATTEST(mathMacro_->nesting() > 0);
87                 /// The macro nesting can change display of insets. Change it locally.
88                 Changer chg = make_change(mi.base.macro_nesting,
89                                           mathMacro_->nesting() == 1 ? 0 : mathMacro_->nesting());
90
91                 MathRow::Element e_beg(mi, MathRow::BEGIN);
92                 e_beg.inset = this;
93                 e_beg.ar = &mathMacro_->cell(idx_);
94                 mrow.push_back(e_beg);
95
96                 mathMacro_->macro()->unlock();
97                 bool has_contents = displayCell(mi.base.bv).addToMathRow(mrow, mi);
98                 mathMacro_->macro()->lock();
99
100                 // if there was no contents, and the contents is editable,
101                 // then we insert a box instead.
102                 if (!has_contents && mathMacro_->nesting() == 1) {
103                         // mathclass is ord because it should be spaced as a normal atom
104                         MathRow::Element e(mi, MathRow::BOX, MC_ORD);
105                         e.color = Color_mathline;
106                         mrow.push_back(e);
107                         has_contents = true;
108                 }
109
110                 MathRow::Element e_end(mi, MathRow::END);
111                 e_end.inset = this;
112                 e_end.ar = &mathMacro_->cell(idx_);
113                 mrow.push_back(e_end);
114
115                 return has_contents;
116         }
117         ///
118         void beforeMetrics() const
119         {
120                 mathMacro_->macro()->unlock();
121         }
122         ///
123         void afterMetrics() const
124         {
125                 mathMacro_->macro()->lock();
126         }
127         ///
128         void beforeDraw(PainterInfo const & pi) const
129         {
130                 // if the macro is being edited, then the painter is in
131                 // monochrome mode.
132                 if (mathMacro_->editMetrics(pi.base.bv))
133                         pi.pain.leaveMonochromeMode();
134         }
135         ///
136         void afterDraw(PainterInfo const & pi) const
137         {
138                 if (mathMacro_->editMetrics(pi.base.bv))
139                         pi.pain.enterMonochromeMode(Color_mathbg, Color_mathmacroblend);
140         }
141         ///
142         void metrics(MetricsInfo &, Dimension &) const {
143                 // This should never be invoked, since InsetArgumentProxy insets are linearized
144                 LATTEST(false);
145         }
146         ///
147         void draw(PainterInfo &, int, int) const {
148                 // This should never be invoked, since InsetArgumentProxy insets are linearized
149                 LATTEST(false);
150         }
151         ///
152         int kerning(BufferView const * bv) const
153         {
154                 return displayCell(bv).kerning(bv);
155         }
156         // write(), normalize(), infoize() and infoize2() are not needed since
157         // InsetMathMacro uses the definition and not the expanded cells.
158         ///
159         void maple(MapleStream & ms) const { ms << mathMacro_->cell(idx_); }
160         ///
161         void maxima(MaximaStream & ms) const { ms << mathMacro_->cell(idx_); }
162         ///
163         void mathematica(MathematicaStream & ms) const { ms << mathMacro_->cell(idx_); }
164         ///
165         void mathmlize(MathStream & ms) const { ms << mathMacro_->cell(idx_); }
166         ///
167         void htmlize(HtmlStream & ms) const { ms << mathMacro_->cell(idx_); }
168         ///
169         void octave(OctaveStream & os) const { os << mathMacro_->cell(idx_); }
170         ///
171         MathClass mathClass() const
172         {
173                 return MC_UNKNOWN;
174                 // This can be refined once the pointer issues are fixed. I did not
175                 // notice any immediate crash with the following code, but it is risky
176                 // nevertheless:
177                 //return mathMacro_->cell(idx_).mathClass();
178         }
179
180 private:
181         ///
182         Inset * clone() const
183         {
184                 return new InsetArgumentProxy(*this);
185         }
186         ///
187         InsetMathMacro * mathMacro_;
188         ///
189         size_t idx_;
190         ///
191         MathData def_;
192 };
193
194
195 /// Private implementation of InsetMathMacro
196 class InsetMathMacro::Private {
197 public:
198         Private(Buffer * buf, docstring const & name)
199                 : name_(name), displayMode_(DISPLAY_INIT),
200                   expanded_(buf), definition_(buf), attachedArgsNum_(0),
201                   optionals_(0), nextFoldMode_(true), macroBackup_(buf),
202                   macro_(0), needsUpdate_(false), isUpdating_(false),
203                   appetite_(9), nesting_(0)
204         {
205         }
206         /// Update the pointers to our owner of all expanded macros.
207         /// This needs to be called every time a copy of the owner is created
208         /// (bug 9418).
209         void updateChildren(InsetMathMacro * owner);
210         /// Recursively update the pointers of all expanded macros
211         /// appearing in the arguments of the current macro
212         void updateNestedChildren(InsetMathMacro * owner, InsetMathNest * ni);
213         /// name of macro
214         docstring name_;
215         /// current display mode
216         DisplayMode displayMode_;
217         /// expanded macro with ArgumentProxies
218         MathData expanded_;
219         /// macro definition with #1,#2,.. insets
220         MathData definition_;
221         /// number of arguments that were really attached
222         size_t attachedArgsNum_;
223         /// optional argument attached? (only in DISPLAY_NORMAL mode)
224         size_t optionals_;
225         /// fold mode to be set in next metrics call?
226         bool nextFoldMode_;
227         /// if macro_ == true, then here is a copy of the macro
228         /// don't use it for locking
229         MacroData macroBackup_;
230         /// if macroNotFound_ == false, then here is a reference to the macro
231         /// this might invalidate after metrics was called
232         MacroData const * macro_;
233         ///
234         mutable std::map<BufferView const *, bool> editing_;
235         ///
236         std::string requires_;
237         /// update macro representation
238         bool needsUpdate_;
239         ///
240         bool isUpdating_;
241         /// maximal number of arguments the macro is greedy for
242         size_t appetite_;
243         /// Level of nesting in macros (including this one)
244         int nesting_;
245 };
246
247
248 void InsetMathMacro::Private::updateChildren(InsetMathMacro * owner)
249 {
250         for (size_t i = 0; i < expanded_.size(); ++i) {
251                 InsetArgumentProxy * p = dynamic_cast<InsetArgumentProxy *>(expanded_[i].nucleus());
252                 if (p)
253                         p->setOwner(owner);
254
255                 InsetMathNest * ni = expanded_[i].nucleus()->asNestInset();
256                 if (ni)
257                         updateNestedChildren(owner, ni);
258         }
259
260         if (macro_) {
261                 // The macro_ pointer is updated when MathData::metrics() is
262                 // called. However, when instant preview is on or the macro is
263                 // not on screen, MathData::metrics() is not called and we may
264                 // have a dangling pointer. As a safety measure, when a macro
265                 // is copied, always let macro_ point to the backup copy of the
266                 // MacroData structure. This backup is updated every time the
267                 // macro is changed, so it will not become stale.
268                 macro_ = &macroBackup_;
269         }
270 }
271
272
273 void InsetMathMacro::Private::updateNestedChildren(InsetMathMacro * owner, InsetMathNest * ni)
274 {
275         for (size_t i = 0; i < ni->nargs(); ++i) {
276                 MathData & ar = ni->cell(i);
277                 for (size_t j = 0; j < ar.size(); ++j) {
278                         InsetArgumentProxy * ap = dynamic_cast
279                                 <InsetArgumentProxy *>(ar[j].nucleus());
280                         if (ap) {
281                                 InsetMathMacro::Private * md = ap->owner()->d;
282                                 if (md->macro_)
283                                         md->macro_ = &md->macroBackup_;
284                                 ap->setOwner(owner);
285                         }
286                         InsetMathNest * imn = ar[j].nucleus()->asNestInset();
287                         if (imn)
288                                 updateNestedChildren(owner, imn);
289                 }
290         }
291 }
292
293
294 InsetMathMacro::InsetMathMacro(Buffer * buf, docstring const & name)
295         : InsetMathNest(buf, 0), d(new Private(buf, name))
296 {}
297
298
299 InsetMathMacro::InsetMathMacro(InsetMathMacro const & that)
300         : InsetMathNest(that), d(new Private(*that.d))
301 {
302         setBuffer(*that.buffer_);
303         d->updateChildren(this);
304 }
305
306
307 InsetMathMacro & InsetMathMacro::operator=(InsetMathMacro const & that)
308 {
309         if (&that == this)
310                 return *this;
311         InsetMathNest::operator=(that);
312         *d = *that.d;
313         d->updateChildren(this);
314         return *this;
315 }
316
317
318 InsetMathMacro::~InsetMathMacro()
319 {
320         delete d;
321 }
322
323
324 bool InsetMathMacro::addToMathRow(MathRow & mrow, MetricsInfo & mi) const
325 {
326         // set edit mode for which we will have calculated row.
327         // This is the same as what is done in metrics().
328         d->editing_[mi.base.bv] = editMode(mi.base.bv);
329
330         // For now we do not linearize in the following cases (can be improved)
331         // - display mode different from normal
332         // - editing with parameter list
333         // - editing with box around macro
334         if (displayMode() != InsetMathMacro::DISPLAY_NORMAL
335                 || (d->editing_[mi.base.bv] && lyxrc.macro_edit_style == LyXRC::MACRO_EDIT_LIST))
336                 return InsetMath::addToMathRow(mrow, mi);
337
338         /// The macro nesting can change display of insets. Change it locally.
339         Changer chg = make_change(mi.base.macro_nesting, d->nesting_);
340
341         MathRow::Element e_beg(mi, MathRow::BEGIN);
342         e_beg.inset = this;
343         e_beg.marker = (d->nesting_ == 1) ? marker(mi.base.bv) : NO_MARKER;
344         mrow.push_back(e_beg);
345
346         d->macro_->lock();
347         bool has_contents = d->expanded_.addToMathRow(mrow, mi);
348         d->macro_->unlock();
349
350         // if there was no contents and the array is editable, then we
351         // insert a grey box instead.
352         if (!has_contents && mi.base.macro_nesting == 1) {
353                 // mathclass is unknown because it is irrelevant for spacing
354                 MathRow::Element e(mi, MathRow::BOX);
355                 e.color = Color_mathmacroblend;
356                 mrow.push_back(e);
357                 has_contents = true;
358         }
359
360         MathRow::Element e_end(mi, MathRow::END);
361         e_end.inset = this;
362         e_end.marker = (d->nesting_ == 1) ? marker(mi.base.bv) : NO_MARKER;
363         mrow.push_back(e_end);
364
365         return has_contents;
366 }
367
368 void InsetMathMacro::beforeMetrics() const
369 {
370         d->macro_->lock();
371 }
372
373
374 void InsetMathMacro::afterMetrics() const
375 {
376         d->macro_->unlock();
377 }
378
379
380 void InsetMathMacro::beforeDraw(PainterInfo const & pi) const
381 {
382         if (d->editing_[pi.base.bv])
383                 pi.pain.enterMonochromeMode(Color_mathbg, Color_mathmacroblend);
384 }
385
386
387 void InsetMathMacro::afterDraw(PainterInfo const & pi) const
388 {
389         if (d->editing_[pi.base.bv])
390                 pi.pain.leaveMonochromeMode();
391 }
392
393
394 Inset * InsetMathMacro::clone() const
395 {
396         InsetMathMacro * copy = new InsetMathMacro(*this);
397         copy->d->needsUpdate_ = true;
398         //copy->d->expanded_.clear();
399         return copy;
400 }
401
402
403 void InsetMathMacro::normalize(NormalStream & os) const
404 {
405         os << "[macro " << name();
406         for (size_t i = 0; i < nargs(); ++i)
407                 os << ' ' << cell(i);
408         os << ']';
409 }
410
411
412 InsetMathMacro::DisplayMode InsetMathMacro::displayMode() const
413 {
414         return d->displayMode_;
415 }
416
417
418 bool InsetMathMacro::extraBraces() const
419 {
420         return d->displayMode_ == DISPLAY_NORMAL && arity() > 0;
421 }
422
423
424 docstring InsetMathMacro::name() const
425 {
426         if (d->displayMode_ == DISPLAY_UNFOLDED)
427                 return asString(cell(0));
428
429         return d->name_;
430 }
431
432
433 docstring InsetMathMacro::macroName() const
434 {
435         return d->name_;
436 }
437
438
439 int InsetMathMacro::nesting() const
440 {
441         return d->nesting_;
442 }
443
444
445 void InsetMathMacro::cursorPos(BufferView const & bv,
446                 CursorSlice const & sl, bool boundary, int & x, int & y) const
447 {
448         // We may have 0 arguments, but InsetMathNest requires at least one.
449         if (nargs() > 0)
450                 InsetMathNest::cursorPos(bv, sl, boundary, x, y);
451 }
452
453
454 bool InsetMathMacro::editMode(BufferView const * bv) const {
455         // find this in cursor trace
456         Cursor const & cur = bv->cursor();
457         for (size_t i = 0; i != cur.depth(); ++i)
458                 if (&cur[i].inset() == this) {
459                         // look if there is no other macro in edit mode above
460                         ++i;
461                         for (; i != cur.depth(); ++i) {
462                                 InsetMath * im = cur[i].asInsetMath();
463                                 if (im) {
464                                         InsetMathMacro const * macro = im->asMacro();
465                                         if (macro && macro->displayMode() == DISPLAY_NORMAL)
466                                                 return false;
467                                 }
468                         }
469
470                         // ok, none found, I am the highest one
471                         return true;
472                 }
473
474         return false;
475 }
476
477
478 MacroData const * InsetMathMacro::macro() const
479 {
480         return d->macro_;
481 }
482
483
484 bool InsetMathMacro::editMetrics(BufferView const * bv) const
485 {
486         return d->editing_[bv];
487 }
488
489
490 InsetMath::marker_type InsetMathMacro::marker(BufferView const * bv) const
491 {
492         if (nargs() == 0)
493                 return NO_MARKER;
494
495         switch (d->displayMode_) {
496         case DISPLAY_INIT:
497         case DISPLAY_INTERACTIVE_INIT:
498                 return NO_MARKER;
499         case DISPLAY_UNFOLDED:
500                 return MARKER;
501         case DISPLAY_NORMAL:
502                 switch (lyxrc.macro_edit_style) {
503                 case LyXRC::MACRO_EDIT_INLINE:
504                         return MARKER2;
505                 case LyXRC::MACRO_EDIT_INLINE_BOX:
506                         return d->editing_[bv] ? BOX_MARKER : MARKER2;
507                 case LyXRC::MACRO_EDIT_LIST:
508                         return MARKER2;
509                 }
510         }
511         // please gcc 4.6
512         return NO_MARKER;
513 }
514
515
516 void InsetMathMacro::metrics(MetricsInfo & mi, Dimension & dim) const
517 {
518         /// The macro nesting can change display of insets. Change it locally.
519         Changer chg = make_change(mi.base.macro_nesting, d->nesting_);
520
521         // set edit mode for which we will have calculated metrics. But only
522         d->editing_[mi.base.bv] = editMode(mi.base.bv);
523
524         // calculate new metrics according to display mode
525         if (d->displayMode_ == DISPLAY_INIT || d->displayMode_ == DISPLAY_INTERACTIVE_INIT) {
526                 Changer dummy = mi.base.changeFontSet("lyxtex");
527                 mathed_string_dim(mi.base.font, from_ascii("\\") + name(), dim);
528         } else if (d->displayMode_ == DISPLAY_UNFOLDED) {
529                 Changer dummy = mi.base.changeFontSet("lyxtex");
530                 cell(0).metrics(mi, dim);
531                 Dimension bsdim;
532                 mathed_string_dim(mi.base.font, from_ascii("\\"), bsdim);
533                 dim.wid += bsdim.width() + 1;
534                 dim.asc = max(bsdim.ascent(), dim.ascent());
535                 dim.des = max(bsdim.descent(), dim.descent());
536         } else if (lyxrc.macro_edit_style == LyXRC::MACRO_EDIT_LIST
537                    && d->editing_[mi.base.bv]) {
538                 // Macro will be edited in a old-style list mode here:
539
540                 LBUFERR(d->macro_);
541                 Dimension fontDim;
542                 FontInfo labelFont = sane_font;
543                 math_font_max_dim(labelFont, fontDim.asc, fontDim.des);
544
545                 // get dimension of components of list view
546                 Dimension nameDim;
547                 nameDim.wid = mathed_string_width(mi.base.font, from_ascii("Macro \\") + name() + ": ");
548                 nameDim.asc = fontDim.asc;
549                 nameDim.des = fontDim.des;
550
551                 Dimension argDim;
552                 argDim.wid = mathed_string_width(labelFont, from_ascii("#9: "));
553                 argDim.asc = fontDim.asc;
554                 argDim.des = fontDim.des;
555
556                 Dimension defDim;
557                 d->definition_.metrics(mi, defDim);
558
559                 // add them up
560                 dim.wid = nameDim.wid + defDim.wid;
561                 dim.asc = max(nameDim.asc, defDim.asc);
562                 dim.des = max(nameDim.des, defDim.des);
563
564                 for (idx_type i = 0; i < nargs(); ++i) {
565                         Dimension cdim;
566                         cell(i).metrics(mi, cdim);
567                         dim.des += max(argDim.height(), cdim.height()) + 1;
568                         dim.wid = max(dim.wid, argDim.wid + cdim.wid);
569                 }
570
571                 // make space for box and markers, 2 pixels
572                 dim.asc += 1;
573                 dim.des += 1;
574                 dim.wid += 2;
575         } else {
576                 // We should not be here, since the macro is linearized in this case.
577                 LBUFERR(false);
578         }
579 }
580
581
582 int InsetMathMacro::kerning(BufferView const * bv) const {
583         if (d->displayMode_ == DISPLAY_NORMAL && !d->editing_[bv])
584                 return d->expanded_.kerning(bv);
585         else
586                 return 0;
587 }
588
589
590 void InsetMathMacro::updateMacro(MacroContext const & mc)
591 {
592         if (validName()) {
593                 d->macro_ = mc.get(name());
594                 if (d->macro_ && d->macroBackup_ != *d->macro_) {
595                         d->macroBackup_ = *d->macro_;
596                         d->needsUpdate_ = true;
597                 }
598         } else {
599                 d->macro_ = 0;
600         }
601 }
602
603
604 class InsetMathMacro::UpdateLocker
605 {
606 public:
607         explicit UpdateLocker(InsetMathMacro & mm) : mac(mm)
608         {
609                 mac.d->isUpdating_ = true;
610         }
611         ~UpdateLocker() { mac.d->isUpdating_ = false; }
612 private:
613         InsetMathMacro & mac;
614 };
615 /** Avoid wrong usage of UpdateLocker.
616     To avoid wrong usage:
617     UpdateLocker(...); // wrong
618     UpdateLocker locker(...); // right
619 */
620 #define UpdateLocker(x) unnamed_UpdateLocker;
621 // Tip gotten from Bobby Schmidt's column in C/C++ Users Journal
622
623
624 void InsetMathMacro::updateRepresentation(Cursor * cur, MacroContext const & mc,
625         UpdateType utype, int nesting)
626 {
627         // block recursive calls (bug 8999)
628         if (d->isUpdating_)
629                 return;
630
631         UpdateLocker locker(*this);
632
633         // known macro?
634         if (d->macro_ == 0)
635                 return;
636
637         // remember nesting level of this macro
638         d->nesting_ = nesting;
639
640         // update requires
641         d->requires_ = d->macro_->requires();
642
643         if (!d->needsUpdate_
644                 // non-normal mode? We are done!
645                 || (d->displayMode_ != DISPLAY_NORMAL))
646                 return;
647
648         d->needsUpdate_ = false;
649
650         // get default values of macro
651         vector<docstring> const & defaults = d->macro_->defaults();
652
653         // create MathMacroArgumentValue objects pointing to the cells of the macro
654         vector<MathData> values(nargs());
655         for (size_t i = 0; i < nargs(); ++i) {
656                 InsetArgumentProxy * proxy;
657                 if (i < defaults.size())
658                         proxy = new InsetArgumentProxy(this, i, defaults[i]);
659                 else
660                         proxy = new InsetArgumentProxy(this, i);
661                 values[i].insert(0, MathAtom(proxy));
662         }
663         // expanding macro with the values
664         // Only update the argument macros if anything was expanded or the LyX
665         // representation part does not contain the macro itself, otherwise we
666         // would get an endless loop (bugs 9140 and 11595). UpdateLocker does
667         // not work in this case, since MacroData::expand() creates new
668         // InsetMathMacro objects, so this would be a different recursion path
669         // than the one protected by UpdateLocker.
670         docstring const & display = d->macro_->display();
671         docstring const latexname = from_ascii("\\") + macroName();
672         if (d->macro_->expand(values, d->expanded_)
673             && !support::contains(display, latexname)) {
674                 if (utype == OutputUpdate && !d->expanded_.empty())
675                         d->expanded_.updateMacros(cur, mc, utype, nesting);
676         }
677         // get definition for list edit mode
678         asArray(display.empty() ? d->macro_->definition() : display,
679                 d->definition_, Parse::QUIET | Parse::MACRODEF);
680 }
681
682
683 void InsetMathMacro::draw(PainterInfo & pi, int x, int y) const
684 {
685         Dimension const dim = dimension(*pi.base.bv);
686
687         int expx = x;
688         int expy = y;
689
690         if (d->displayMode_ == DISPLAY_INIT || d->displayMode_ == DISPLAY_INTERACTIVE_INIT) {
691                 Changer dummy = pi.base.changeFontSet("lyxtex");
692                 pi.pain.text(x, y, from_ascii("\\") + name(), pi.base.font);
693         } else if (d->displayMode_ == DISPLAY_UNFOLDED) {
694                 Changer dummy = pi.base.changeFontSet("lyxtex");
695                 pi.pain.text(x, y, from_ascii("\\"), pi.base.font);
696                 x += mathed_string_width(pi.base.font, from_ascii("\\")) + 1;
697                 cell(0).draw(pi, x, y);
698         } else if (lyxrc.macro_edit_style == LyXRC::MACRO_EDIT_LIST
699                    && d->editing_[pi.base.bv]) {
700                 // Macro will be edited in a old-style list mode here:
701
702                 CoordCache const & coords = pi.base.bv->coordCache();
703                 FontInfo const & labelFont = sane_font;
704
705                 // box needs one pixel
706                 x += 1;
707
708                 // get maximal font height
709                 Dimension fontDim;
710                 math_font_max_dim(pi.base.font, fontDim.asc, fontDim.des);
711
712                 // draw label
713                 docstring label = from_ascii("Macro \\") + name() + from_ascii(": ");
714                 pi.pain.text(x, y, label, labelFont);
715                 x += mathed_string_width(labelFont, label);
716
717                 // draw definition
718                 d->definition_.draw(pi, x, y);
719                 Dimension const & defDim = coords.getArrays().dim(&d->definition_);
720                 y += max(fontDim.des, defDim.des);
721
722                 // draw parameters
723                 docstring str = from_ascii("#9");
724                 int strw1 = mathed_string_width(labelFont, from_ascii("#9"));
725                 int strw2 = mathed_string_width(labelFont, from_ascii(": "));
726
727                 for (idx_type i = 0; i < nargs(); ++i) {
728                         // position of label
729                         Dimension const & cdim = coords.getArrays().dim(&cell(i));
730                         x = expx + 1;
731                         y += max(fontDim.asc, cdim.asc) + 1;
732
733                         // draw label
734                         str[1] = '1' + i;
735                         pi.pain.text(x, y, str, labelFont);
736                         x += strw1;
737                         pi.pain.text(x, y, from_ascii(":"), labelFont);
738                         x += strw2;
739
740                         // draw paramter
741                         cell(i).draw(pi, x, y);
742
743                         // next line
744                         y += max(fontDim.des, cdim.des);
745                 }
746
747                 pi.pain.rectangle(expx, expy - dim.asc + 1, dim.wid - 1,
748                                   dim.height() - 2, Color_mathmacroframe);
749         } else {
750                 // We should not be here, since the macro is linearized in this case.
751                 LBUFERR(false);
752         }
753
754         // edit mode changed?
755         if (d->editing_[pi.base.bv] != editMode(pi.base.bv))
756                 pi.base.bv->cursor().screenUpdateFlags(Update::SinglePar);
757 }
758
759
760 void InsetMathMacro::setDisplayMode(InsetMathMacro::DisplayMode mode, int appetite)
761 {
762         if (d->displayMode_ != mode) {
763                 // transfer name if changing from or to DISPLAY_UNFOLDED
764                 if (mode == DISPLAY_UNFOLDED) {
765                         cells_.resize(1);
766                         asArray(d->name_, cell(0));
767                 } else if (d->displayMode_ == DISPLAY_UNFOLDED) {
768                         d->name_ = asString(cell(0));
769                         cells_.resize(0);
770                 }
771
772                 d->displayMode_ = mode;
773                 d->needsUpdate_ = true;
774         }
775
776         // the interactive init mode is non-greedy by default
777         if (appetite == -1)
778                 d->appetite_ = (mode == DISPLAY_INTERACTIVE_INIT) ? 0 : 9;
779         else
780                 d->appetite_ = size_t(appetite);
781 }
782
783
784 InsetMathMacro::DisplayMode InsetMathMacro::computeDisplayMode() const
785 {
786         if (d->nextFoldMode_ == true && d->macro_ && !d->macro_->locked())
787                 return DISPLAY_NORMAL;
788         else
789                 return DISPLAY_UNFOLDED;
790 }
791
792
793 bool InsetMathMacro::validName() const
794 {
795         docstring n = name();
796
797         if (n.empty())
798                 return false;
799
800         // converting back and force doesn't swallow anything?
801         /*MathData ma;
802         asArray(n, ma);
803         if (asString(ma) != n)
804                 return false;*/
805
806         // valid characters?
807         if (n.size() > 1) {
808                 for (size_t i = 0; i<n.size(); ++i) {
809                         if (!(n[i] >= 'a' && n[i] <= 'z')
810                             && !(n[i] >= 'A' && n[i] <= 'Z')
811                             && n[i] != '*')
812                                 return false;
813                 }
814         }
815
816         return true;
817 }
818
819
820 size_t InsetMathMacro::arity() const
821 {
822         if (d->displayMode_ == DISPLAY_NORMAL )
823                 return cells_.size();
824         else
825                 return 0;
826 }
827
828
829 size_t InsetMathMacro::optionals() const
830 {
831         return d->optionals_;
832 }
833
834
835 void InsetMathMacro::setOptionals(int n)
836 {
837         if (n <= int(nargs()))
838                 d->optionals_ = n;
839 }
840
841
842 size_t InsetMathMacro::appetite() const
843 {
844         return d->appetite_;
845 }
846
847
848 MathClass InsetMathMacro::mathClass() const
849 {
850         // This can be just a heuristic, since it is only considered for display
851         // when the macro is not linearised. Therefore it affects:
852         // * The spacing of the inset while being edited,
853         // * Intelligent splitting
854         // * Cursor word movement (Ctrl-Arrow).
855         if (MacroData const * m = macroBackup()) {
856                 // If it is a global macro and is defined explicitly
857                 if (m->symbol()) {
858                         MathClass mc = string_to_class(m->symbol()->extra);
859                         if (mc != MC_UNKNOWN)
860                                 return mc;
861                 }
862         }
863         // Otherwise guess from the expanded macro
864         return d->expanded_.mathClass();
865 }
866
867
868 InsetMath::mode_type InsetMathMacro::currentMode() const
869 {
870         // User defined macros are always assumed to be mathmode macros.
871         // Only the global macros defined in lib/symbols may be textmode.
872         if (MacroData const * m = macroBackup()) {
873                 if (m->symbol() && m->symbol()->extra == "textmode")
874                         return TEXT_MODE;
875                 else
876                         return MATH_MODE;
877         }
878         // Unknown macros are undecided.
879         return UNDECIDED_MODE;
880 }
881
882
883 MacroData const * InsetMathMacro::macroBackup() const
884 {
885         if (macro())
886                 return &d->macroBackup_;
887         if (MacroData const * data = MacroTable::globalMacros().get(name()))
888                 return data;
889         return nullptr;
890 }
891
892
893 void InsetMathMacro::validate(LaTeXFeatures & features) const
894 {
895         // Immediately after a document is loaded, in some cases the MacroData
896         // of the global macros defined in the lib/symbols file may still not
897         // be known to the macro machinery because it will be set only after
898         // the first call to updateMacros(). This is not a problem unless
899         // instant preview is on for math, in which case we will be missing
900         // the corresponding requirements.
901         // In this case, we get the required info from the global macro table.
902         if (!d->requires_.empty())
903                 features.require(d->requires_);
904         else if (!d->macro_) {
905                 // Update requires for known global macros.
906                 MacroData const * data = MacroTable::globalMacros().get(name());
907                 if (data && !data->requires().empty())
908                         features.require(data->requires());
909         }
910
911         if (name() == "binom")
912                 features.require("binom");
913
914         // validate the cells and the definition
915         if (displayMode() == DISPLAY_NORMAL) {
916                 d->definition_.validate(features);
917                 InsetMathNest::validate(features);
918         }
919 }
920
921
922 void InsetMathMacro::edit(Cursor & cur, bool front, EntryDirection entry_from)
923 {
924         cur.screenUpdateFlags(Update::SinglePar);
925         InsetMathNest::edit(cur, front, entry_from);
926 }
927
928
929 Inset * InsetMathMacro::editXY(Cursor & cur, int x, int y)
930 {
931         // We may have 0 arguments, but InsetMathNest requires at least one.
932         if (nargs() > 0) {
933                 cur.screenUpdateFlags(Update::SinglePar);
934                 return InsetMathNest::editXY(cur, x, y);
935         } else
936                 return this;
937 }
938
939
940 void InsetMathMacro::removeArgument(Inset::pos_type pos) {
941         if (d->displayMode_ == DISPLAY_NORMAL) {
942                 LASSERT(size_t(pos) < cells_.size(), return);
943                 cells_.erase(cells_.begin() + pos);
944                 if (size_t(pos) < d->attachedArgsNum_)
945                         --d->attachedArgsNum_;
946                 if (size_t(pos) < d->optionals_) {
947                         --d->optionals_;
948                 }
949
950                 d->needsUpdate_ = true;
951         }
952 }
953
954
955 void InsetMathMacro::insertArgument(Inset::pos_type pos) {
956         if (d->displayMode_ == DISPLAY_NORMAL) {
957                 LASSERT(size_t(pos) <= cells_.size(), return);
958                 cells_.insert(cells_.begin() + pos, MathData());
959                 if (size_t(pos) < d->attachedArgsNum_)
960                         ++d->attachedArgsNum_;
961                 if (size_t(pos) < d->optionals_)
962                         ++d->optionals_;
963
964                 d->needsUpdate_ = true;
965         }
966 }
967
968
969 void InsetMathMacro::detachArguments(vector<MathData> & args, bool strip)
970 {
971         LASSERT(d->displayMode_ == DISPLAY_NORMAL, return);
972         args = cells_;
973
974         // strip off empty cells, but not more than arity-attachedArgsNum_
975         if (strip) {
976                 size_t i;
977                 for (i = cells_.size(); i > d->attachedArgsNum_; --i)
978                         if (!cell(i - 1).empty()) break;
979                 args.resize(i);
980         }
981
982         d->attachedArgsNum_ = 0;
983         d->expanded_ = MathData();
984         cells_.resize(0);
985
986         d->needsUpdate_ = true;
987 }
988
989
990 void InsetMathMacro::attachArguments(vector<MathData> const & args, size_t arity, int optionals)
991 {
992         LASSERT(d->displayMode_ == DISPLAY_NORMAL, return);
993         cells_ = args;
994         d->attachedArgsNum_ = args.size();
995         cells_.resize(arity);
996         d->expanded_ = MathData();
997         d->optionals_ = optionals;
998
999         d->needsUpdate_ = true;
1000 }
1001
1002
1003 bool InsetMathMacro::idxFirst(Cursor & cur) const
1004 {
1005         cur.screenUpdateFlags(Update::SinglePar);
1006         return InsetMathNest::idxFirst(cur);
1007 }
1008
1009
1010 bool InsetMathMacro::idxLast(Cursor & cur) const
1011 {
1012         cur.screenUpdateFlags(Update::SinglePar);
1013         return InsetMathNest::idxLast(cur);
1014 }
1015
1016
1017 bool InsetMathMacro::notifyCursorLeaves(Cursor const & old, Cursor & cur)
1018 {
1019         if (d->displayMode_ == DISPLAY_UNFOLDED) {
1020                 docstring const & unfolded_name = name();
1021                 if (unfolded_name != d->name_) {
1022                         // The macro name was changed
1023                         Cursor inset_cursor = old;
1024                         int macroSlice = inset_cursor.find(this);
1025                         // returning true means the cursor is "now" invalid,
1026                         // which it was.
1027                         LASSERT(macroSlice != -1, return true);
1028                         inset_cursor.cutOff(macroSlice);
1029                         inset_cursor.recordUndoInset();
1030                         inset_cursor.pop();
1031                         inset_cursor.cell().erase(inset_cursor.pos());
1032                         inset_cursor.cell().insert(inset_cursor.pos(),
1033                                 createInsetMath(unfolded_name, cur.buffer()));
1034                         cur.resetAnchor();
1035                         cur.screenUpdateFlags(cur.result().screenUpdate() | Update::SinglePar);
1036                         return true;
1037                 }
1038         }
1039         cur.screenUpdateFlags(Update::Force);
1040         return InsetMathNest::notifyCursorLeaves(old, cur);
1041 }
1042
1043
1044 void InsetMathMacro::fold(Cursor & cur)
1045 {
1046         if (!d->nextFoldMode_) {
1047                 d->nextFoldMode_ = true;
1048                 cur.screenUpdateFlags(Update::SinglePar);
1049         }
1050 }
1051
1052
1053 void InsetMathMacro::unfold(Cursor & cur)
1054 {
1055         if (d->nextFoldMode_) {
1056                 d->nextFoldMode_ = false;
1057                 cur.screenUpdateFlags(Update::SinglePar);
1058         }
1059 }
1060
1061
1062 bool InsetMathMacro::folded() const
1063 {
1064         return d->nextFoldMode_;
1065 }
1066
1067
1068 void InsetMathMacro::write(WriteStream & os) const
1069 {
1070         mode_type mode = currentMode();
1071         MathEnsurer ensurer(os, mode == MATH_MODE, true, mode == TEXT_MODE);
1072
1073         // non-normal mode
1074         if (d->displayMode_ != DISPLAY_NORMAL) {
1075                 os << "\\" << name();
1076                 if (name().size() != 1 || isAlphaASCII(name()[0]))
1077                         os.pendingSpace(true);
1078                 return;
1079         }
1080
1081         // normal mode
1082         // we should be ok to continue even if this fails.
1083         LATTEST(d->macro_);
1084
1085         // Always protect macros in a fragile environment
1086         if (os.fragile())
1087                 os << "\\protect";
1088
1089         os << "\\" << name();
1090         bool first = true;
1091
1092         // Optional arguments:
1093         // First find last non-empty optional argument
1094         idx_type emptyOptFrom = 0;
1095         idx_type i = 0;
1096         for (; i < cells_.size() && i < d->optionals_; ++i) {
1097                 if (!cell(i).empty())
1098                         emptyOptFrom = i + 1;
1099         }
1100
1101         // print out optionals
1102         for (i=0; i < cells_.size() && i < emptyOptFrom; ++i) {
1103                 first = false;
1104                 // For correctly parsing it when a document is reloaded, we
1105                 // need to enclose an optional argument in braces if it starts
1106                 // with a script inset with empty nucleus or ends with a
1107                 // delimiter-size-modifier macro (see #10497 and #11346).
1108                 // We also need to do that when the optional argument
1109                 // contains macros with optionals.
1110                 bool braced = false;
1111                 size_type last = cell(i).size() - 1;
1112                 if (cell(i).size() && cell(i)[last]->asUnknownInset()) {
1113                         latexkeys const * l = in_word_set(cell(i)[last]->name());
1114                         braced = (l && l->inset == "big");
1115                 } else if (cell(i).size() && cell(i)[0]->asScriptInset()) {
1116                         braced = cell(i)[0]->asScriptInset()->nuc().empty();
1117                 } else {
1118                         for (size_type j = 0; j < cell(i).size(); ++j) {
1119                                 InsetMathMacro const * ma = cell(i)[j]->asMacro();
1120                                 if (ma && ma->optionals()) {
1121                                         braced = true;
1122                                         break;
1123                                 }
1124                         }
1125                 }
1126                 if (braced)
1127                         os << "[{" << cell(i) << "}]";
1128                 else
1129                         os << "[" << cell(i) << "]";
1130         }
1131
1132         // skip the tailing empty optionals
1133         i = d->optionals_;
1134
1135         // Print remaining arguments
1136         for (; i < cells_.size(); ++i) {
1137                 if (cell(i).size() == 1
1138                         && cell(i)[0].nucleus()->asCharInset()
1139                         && isASCII(cell(i)[0].nucleus()->asCharInset()->getChar())) {
1140                         if (first)
1141                                 os << " ";
1142                         os << cell(i);
1143                 } else
1144                         os << "{" << cell(i) << "}";
1145                 first = false;
1146         }
1147
1148         // add space if there was no argument
1149         if (first)
1150                 os.pendingSpace(true);
1151 }
1152
1153
1154 void InsetMathMacro::maple(MapleStream & os) const
1155 {
1156         lyx::maple(d->expanded_, os);
1157 }
1158
1159
1160 void InsetMathMacro::maxima(MaximaStream & os) const
1161 {
1162         lyx::maxima(d->expanded_, os);
1163 }
1164
1165
1166 void InsetMathMacro::mathematica(MathematicaStream & os) const
1167 {
1168         lyx::mathematica(d->expanded_, os);
1169 }
1170
1171
1172 void InsetMathMacro::mathmlize(MathStream & os) const
1173 {
1174         // macro_ is 0 if this is an unknown macro
1175         LATTEST(d->macro_ || d->displayMode_ != DISPLAY_NORMAL);
1176         if (d->macro_) {
1177                 docstring const xmlname = d->macro_->xmlname();
1178                 if (!xmlname.empty()) {
1179                         char const * type = d->macro_->MathMLtype();
1180                         os << '<' << type << "> " << xmlname << " </"
1181                            << type << '>';
1182                         return;
1183                 }
1184         }
1185         if (d->expanded_.empty()) {
1186                 // this means that we do not recognize the macro
1187                 throw MathExportException();
1188         }
1189         os << d->expanded_;
1190 }
1191
1192
1193 void InsetMathMacro::htmlize(HtmlStream & os) const
1194 {
1195         // macro_ is 0 if this is an unknown macro
1196         LATTEST(d->macro_ || d->displayMode_ != DISPLAY_NORMAL);
1197         if (d->macro_) {
1198                 docstring const xmlname = d->macro_->xmlname();
1199                 if (!xmlname.empty()) {
1200                         os << ' ' << xmlname << ' ';
1201                         return;
1202                 }
1203         }
1204         if (d->expanded_.empty()) {
1205                 // this means that we do not recognize the macro
1206                 throw MathExportException();
1207         }
1208         os << d->expanded_;
1209 }
1210
1211
1212 void InsetMathMacro::octave(OctaveStream & os) const
1213 {
1214         lyx::octave(d->expanded_, os);
1215 }
1216
1217
1218 void InsetMathMacro::infoize(odocstream & os) const
1219 {
1220         os << bformat(_("Macro: %1$s"), name());
1221 }
1222
1223
1224 void InsetMathMacro::infoize2(odocstream & os) const
1225 {
1226         os << bformat(_("Macro: %1$s"), name());
1227 }
1228
1229
1230 bool InsetMathMacro::completionSupported(Cursor const & cur) const
1231 {
1232         if (displayMode() != DISPLAY_UNFOLDED)
1233                 return InsetMathNest::completionSupported(cur);
1234
1235         return lyxrc.completion_popup_math
1236                 && displayMode() == DISPLAY_UNFOLDED
1237                 && cur.bv().cursor().pos() == int(name().size());
1238 }
1239
1240
1241 bool InsetMathMacro::inlineCompletionSupported(Cursor const & cur) const
1242 {
1243         if (displayMode() != DISPLAY_UNFOLDED)
1244                 return InsetMathNest::inlineCompletionSupported(cur);
1245
1246         return lyxrc.completion_inline_math
1247                 && displayMode() == DISPLAY_UNFOLDED
1248                 && cur.bv().cursor().pos() == int(name().size());
1249 }
1250
1251
1252 bool InsetMathMacro::automaticInlineCompletion() const
1253 {
1254         if (displayMode() != DISPLAY_UNFOLDED)
1255                 return InsetMathNest::automaticInlineCompletion();
1256
1257         return lyxrc.completion_inline_math;
1258 }
1259
1260
1261 bool InsetMathMacro::automaticPopupCompletion() const
1262 {
1263         if (displayMode() != DISPLAY_UNFOLDED)
1264                 return InsetMathNest::automaticPopupCompletion();
1265
1266         return lyxrc.completion_popup_math;
1267 }
1268
1269
1270 CompletionList const *
1271 InsetMathMacro::createCompletionList(Cursor const & cur) const
1272 {
1273         if (displayMode() != DISPLAY_UNFOLDED)
1274                 return InsetMathNest::createCompletionList(cur);
1275
1276         return new MathCompletionList(cur.bv().cursor());
1277 }
1278
1279
1280 docstring InsetMathMacro::completionPrefix(Cursor const & cur) const
1281 {
1282         if (displayMode() != DISPLAY_UNFOLDED)
1283                 return InsetMathNest::completionPrefix(cur);
1284
1285         if (!completionSupported(cur))
1286                 return docstring();
1287
1288         return "\\" + name();
1289 }
1290
1291
1292 bool InsetMathMacro::insertCompletion(Cursor & cur, docstring const & s,
1293                                         bool finished)
1294 {
1295         if (displayMode() != DISPLAY_UNFOLDED)
1296                 return InsetMathNest::insertCompletion(cur, s, finished);
1297
1298         if (!completionSupported(cur))
1299                 return false;
1300
1301         // append completion
1302         docstring newName = name() + s;
1303         asArray(newName, cell(0));
1304         cur.bv().cursor().pos() = name().size();
1305         cur.screenUpdateFlags(Update::SinglePar);
1306
1307         // finish macro
1308         if (finished) {
1309                 cur.bv().cursor().pop();
1310                 ++cur.bv().cursor().pos();
1311                 cur.screenUpdateFlags(Update::SinglePar);
1312         }
1313
1314         return true;
1315 }
1316
1317
1318 void InsetMathMacro::completionPosAndDim(Cursor const & cur, int & x, int & y,
1319         Dimension & dim) const
1320 {
1321         if (displayMode() != DISPLAY_UNFOLDED)
1322                 InsetMathNest::completionPosAndDim(cur, x, y, dim);
1323
1324         // get inset dimensions
1325         dim = cur.bv().coordCache().insets().dim(this);
1326         // FIXME: these 3 are no accurate, but should depend on the font.
1327         // Now the popup jumps down if you enter a char with descent > 0.
1328         dim.des += 3;
1329         dim.asc += 3;
1330
1331         // and position
1332         Point xy
1333         = cur.bv().coordCache().insets().xy(this);
1334         x = xy.x_;
1335         y = xy.y_;
1336 }
1337
1338
1339 void InsetMathMacro::setBuffer(Buffer & buffer)
1340 {
1341         d->definition_.setBuffer(buffer);
1342         InsetMathNest::setBuffer(buffer);
1343 }
1344
1345 } // namespace lyx