]> git.lyx.org Git - lyx.git/blob - src/mathed/MathMacro.cpp
08f367cbfca00fdc34081f068427250c2598c686
[lyx.git] / src / mathed / MathMacro.cpp
1 /**
2  * \file MathMacro.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 "MathMacro.h"
16
17 #include "InsetMathChar.h"
18 #include "MathCompletionList.h"
19 #include "MathExtern.h"
20 #include "MathFactory.h"
21 #include "MathStream.h"
22 #include "MathSupport.h"
23
24 #include "Buffer.h"
25 #include "BufferList.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
35 #include "frontends/Painter.h"
36
37 #include "support/debug.h"
38 #include "support/gettext.h"
39 #include "support/lassert.h"
40 #include "support/lstrings.h"
41 #include "support/textutils.h"
42
43 #include <ostream>
44 #include <vector>
45
46 using namespace lyx::support;
47 using namespace std;
48
49 namespace lyx {
50
51
52 /// A proxy for the macro values
53 class ArgumentProxy : public InsetMath {
54 public:
55         ///
56         ArgumentProxy(MathMacro * mathMacro, size_t idx)
57                 : mathMacro_(mathMacro), idx_(idx) {}
58         ///
59         ArgumentProxy(MathMacro * mathMacro, size_t idx, docstring const & def)
60                 : mathMacro_(mathMacro), idx_(idx)
61         {
62                         asArray(def, def_);
63         }
64         ///
65         void setOwner(MathMacro * mathMacro) { mathMacro_ = mathMacro; }
66         ///
67         InsetCode lyxCode() const { return ARGUMENT_PROXY_CODE; }
68         ///
69         void metrics(MetricsInfo & mi, Dimension & dim) const {
70                 mathMacro_->macro()->unlock();
71                 mathMacro_->cell(idx_).metrics(mi, dim);
72
73                 if (!mathMacro_->editMetrics(mi.base.bv)
74                     && mathMacro_->cell(idx_).empty())
75                         def_.metrics(mi, dim);
76
77                 mathMacro_->macro()->lock();
78         }
79         // write(), normalize(), infoize() and infoize2() are not needed since
80         // MathMacro uses the definition and not the expanded cells.
81         ///
82         void maple(MapleStream & ms) const { ms << mathMacro_->cell(idx_); }
83         ///
84         void maxima(MaximaStream & ms) const { ms << mathMacro_->cell(idx_); }
85         ///
86         void mathematica(MathematicaStream & ms) const { ms << mathMacro_->cell(idx_); }
87         ///
88         void mathmlize(MathStream & ms) const { ms << mathMacro_->cell(idx_); }
89         ///
90         void htmlize(HtmlStream & ms) const { ms << mathMacro_->cell(idx_); }
91         ///
92         void octave(OctaveStream & os) const { os << mathMacro_->cell(idx_); }
93         ///
94         void draw(PainterInfo & pi, int x, int y) const {
95                 if (mathMacro_->editMetrics(pi.base.bv)) {
96                         // The only way a ArgumentProxy can appear is in a cell of the
97                         // MathMacro. Moreover the cells are only drawn in the DISPLAY_FOLDED
98                         // mode and then, if the macro is edited the monochrome
99                         // mode is entered by the MathMacro before calling the cells' draw
100                         // method. Then eventually this code is reached and the proxy leaves
101                         // monochrome mode temporarely. Hence, if it is not in monochrome
102                         // here (and the assert triggers in pain.leaveMonochromeMode())
103                         // it's a bug.
104                         pi.pain.leaveMonochromeMode();
105                         mathMacro_->cell(idx_).draw(pi, x, y);
106                         pi.pain.enterMonochromeMode(Color_mathbg, Color_mathmacroblend);
107                 } else if (mathMacro_->cell(idx_).empty()) {
108                         mathMacro_->cell(idx_).setXY(*pi.base.bv, x, y);
109                         def_.draw(pi, x, y);
110                 } else
111                         mathMacro_->cell(idx_).draw(pi, x, y);
112         }
113         ///
114         size_t idx() const { return idx_; }
115         ///
116         int kerning(BufferView const * bv) const
117         {
118                 if (mathMacro_->editMetrics(bv)
119                     || !mathMacro_->cell(idx_).empty())
120                         return mathMacro_->cell(idx_).kerning(bv);
121                 else
122                         return def_.kerning(bv);
123         }
124
125 private:
126         ///
127         Inset * clone() const
128         {
129                 return new ArgumentProxy(*this);
130         }
131         ///
132         MathMacro * mathMacro_;
133         ///
134         size_t idx_;
135         ///
136         MathData def_;
137 };
138
139
140 /// Private implementation of MathMacro
141 class MathMacro::Private {
142 public:
143         Private(Buffer * buf, docstring const & name)
144                 : name_(name), displayMode_(DISPLAY_INIT),
145                   expanded_(buf), definition_(buf), attachedArgsNum_(0),
146                   optionals_(0), nextFoldMode_(true), macroBackup_(buf),
147                   macro_(0), needsUpdate_(false), isUpdating_(false),
148                   appetite_(9)
149         {
150         }
151         /// Update the pointers to our owner of all expanded macros.
152         /// This needs to be called every time a copy of the owner is created
153         /// (bug 9418).
154         void updateChildren(MathMacro * owner);
155         /// name of macro
156         docstring name_;
157         /// current display mode
158         DisplayMode displayMode_;
159         /// expanded macro with ArgumentProxies
160         MathData expanded_;
161         /// macro definition with #1,#2,.. insets
162         MathData definition_;
163         /// number of arguments that were really attached
164         size_t attachedArgsNum_;
165         /// optional argument attached? (only in DISPLAY_NORMAL mode)
166         size_t optionals_;
167         /// fold mode to be set in next metrics call?
168         bool nextFoldMode_;
169         /// if macro_ == true, then here is a copy of the macro
170         /// don't use it for locking
171         MacroData macroBackup_;
172         /// if macroNotFound_ == false, then here is a reference to the macro
173         /// this might invalidate after metrics was called
174         MacroData const * macro_;
175         ///
176         mutable std::map<BufferView const *, bool> editing_;
177         ///
178         std::string requires_;
179         /// update macro representation
180         bool needsUpdate_;
181         ///
182         bool isUpdating_;
183         /// maximal number of arguments the macro is greedy for
184         size_t appetite_;
185 };
186
187
188 void MathMacro::Private::updateChildren(MathMacro * owner)
189 {
190         for (size_t i = 0; i < expanded_.size(); ++i) {
191                 ArgumentProxy * p = dynamic_cast<ArgumentProxy *>(expanded_[i].nucleus());
192                 if (p)
193                         p->setOwner(owner);
194         }
195
196         if (macro_ && lyxrc.preview == LyXRC::PREVIEW_ON) {
197                 // As MathData::metrics() is not called when instant preview is
198                 // on, we have to update macro_ by ourselves. In this case, we
199                 // simply let it point to the last known good copy of MacroData.
200                 macro_ = &macroBackup_;
201         }
202 }
203
204
205 MathMacro::MathMacro(Buffer * buf, docstring const & name)
206         : InsetMathNest(buf, 0), d(new Private(buf, name))
207 {}
208
209
210 MathMacro::MathMacro(MathMacro const & that)
211         : InsetMathNest(that), d(new Private(*that.d))
212 {
213         d->updateChildren(this);
214 }
215
216
217 MathMacro & MathMacro::operator=(MathMacro const & that)
218 {
219         if (&that == this)
220                 return *this;
221         InsetMathNest::operator=(that);
222         *d = *that.d;
223         d->updateChildren(this);
224         return *this;
225 }
226
227
228 MathMacro::~MathMacro()
229 {
230         delete d;
231 }
232
233
234 Inset * MathMacro::clone() const
235 {
236         MathMacro * copy = new MathMacro(*this);
237         copy->d->needsUpdate_ = true;
238         //copy->d->expanded_.clear();
239         return copy;
240 }
241
242
243 void MathMacro::normalize(NormalStream & os) const
244 {
245         os << "[macro " << name();
246         for (size_t i = 0; i < nargs(); ++i)
247                 os << ' ' << cell(i);
248         os << ']';
249 }
250
251
252 MathMacro::DisplayMode MathMacro::displayMode() const
253 {
254         return d->displayMode_;
255 }
256
257
258 bool MathMacro::extraBraces() const
259 {
260         return d->displayMode_ == DISPLAY_NORMAL && arity() > 0;
261 }
262
263
264 docstring MathMacro::name() const
265 {
266         if (d->displayMode_ == DISPLAY_UNFOLDED)
267                 return asString(cell(0));
268
269         return d->name_;
270 }
271
272
273 docstring MathMacro::macroName() const
274 {
275         return d->name_;
276 }
277
278
279 void MathMacro::cursorPos(BufferView const & bv,
280                 CursorSlice const & sl, bool boundary, int & x, int & y) const
281 {
282         // We may have 0 arguments, but InsetMathNest requires at least one.
283         if (nargs() > 0)
284                 InsetMathNest::cursorPos(bv, sl, boundary, x, y);
285 }
286
287
288 bool MathMacro::editMode(BufferView const * bv) const {
289         // find this in cursor trace
290         Cursor const & cur = bv->cursor();
291         for (size_t i = 0; i != cur.depth(); ++i)
292                 if (&cur[i].inset() == this) {
293                         // look if there is no other macro in edit mode above
294                         ++i;
295                         for (; i != cur.depth(); ++i) {
296                                 InsetMath * im = cur[i].asInsetMath();
297                                 if (im) {
298                                         MathMacro const * macro = im->asMacro();
299                                         if (macro && macro->displayMode() == DISPLAY_NORMAL)
300                                                 return false;
301                                 }
302                         }
303
304                         // ok, none found, I am the highest one
305                         return true;
306                 }
307
308         return false;
309 }
310
311
312 MacroData const * MathMacro::macro()
313 {
314         return d->macro_;
315 }
316
317
318 bool MathMacro::editMetrics(BufferView const * bv) const
319 {
320         return d->editing_[bv];
321 }
322
323
324 void MathMacro::metrics(MetricsInfo & mi, Dimension & dim) const
325 {
326         // set edit mode for which we will have calculated metrics. But only
327         d->editing_[mi.base.bv] = editMode(mi.base.bv);
328
329         // calculate new metrics according to display mode
330         if (d->displayMode_ == DISPLAY_INIT || d->displayMode_ == DISPLAY_INTERACTIVE_INIT) {
331                 mathed_string_dim(mi.base.font, from_ascii("\\") + name(), dim);
332         } else if (d->displayMode_ == DISPLAY_UNFOLDED) {
333                 cell(0).metrics(mi, dim);
334                 Dimension bsdim;
335                 mathed_string_dim(mi.base.font, from_ascii("\\"), bsdim);
336                 dim.wid += bsdim.width() + 1;
337                 dim.asc = max(bsdim.ascent(), dim.ascent());
338                 dim.des = max(bsdim.descent(), dim.descent());
339                 metricsMarkers(dim);
340         } else if (lyxrc.macro_edit_style == LyXRC::MACRO_EDIT_LIST
341                    && d->editing_[mi.base.bv]) {
342                 // Macro will be edited in a old-style list mode here:
343
344                 LBUFERR(d->macro_);
345                 Dimension fontDim;
346                 FontInfo labelFont = sane_font;
347                 math_font_max_dim(labelFont, fontDim.asc, fontDim.des);
348
349                 // get dimension of components of list view
350                 Dimension nameDim;
351                 nameDim.wid = mathed_string_width(mi.base.font, from_ascii("Macro \\") + name() + ": ");
352                 nameDim.asc = fontDim.asc;
353                 nameDim.des = fontDim.des;
354
355                 Dimension argDim;
356                 argDim.wid = mathed_string_width(labelFont, from_ascii("#9: "));
357                 argDim.asc = fontDim.asc;
358                 argDim.des = fontDim.des;
359
360                 Dimension defDim;
361                 d->definition_.metrics(mi, defDim);
362
363                 // add them up
364                 dim.wid = nameDim.wid + defDim.wid;
365                 dim.asc = max(nameDim.asc, defDim.asc);
366                 dim.des = max(nameDim.des, defDim.des);
367
368                 for (idx_type i = 0; i < nargs(); ++i) {
369                         Dimension cdim;
370                         cell(i).metrics(mi, cdim);
371                         dim.des += max(argDim.height(), cdim.height()) + 1;
372                         dim.wid = max(dim.wid, argDim.wid + cdim.wid);
373                 }
374
375                 // make space for box and markers, 2 pixels
376                 dim.asc += 1;
377                 dim.des += 1;
378                 dim.wid += 2;
379                 metricsMarkers2(dim);
380         } else {
381                 LBUFERR(d->macro_);
382
383                 // calculate metrics, hoping that all cells are seen
384                 d->macro_->lock();
385                 d->expanded_.metrics(mi, dim);
386
387                 // otherwise do a manual metrics call
388                 CoordCache & coords = mi.base.bv->coordCache();
389                 for (idx_type i = 0; i < nargs(); ++i) {
390                         if (!coords.getArrays().hasDim(&cell(i))) {
391                                 Dimension tdim;
392                                 cell(i).metrics(mi, tdim);
393                         }
394                 }
395                 d->macro_->unlock();
396
397                 // calculate dimension with label while editing
398                 if (lyxrc.macro_edit_style == LyXRC::MACRO_EDIT_INLINE_BOX
399                     && d->editing_[mi.base.bv]) {
400                         FontInfo font = mi.base.font;
401                         augmentFont(font, from_ascii("lyxtex"));
402                         Dimension namedim;
403                         mathed_string_dim(font, name(), namedim);
404 #if 0
405                         dim.wid += 2 + namedim.wid + 2 + 2;
406                         dim.asc = max(dim.asc, namedim.asc) + 2;
407                         dim.des = max(dim.des, namedim.des) + 2;
408 #endif
409                         dim.wid = max(1 + namedim.wid + 1, 2 + dim.wid + 2);
410                         dim.asc += 1 + namedim.height() + 1;
411                         dim.des += 2;
412                 }
413         }
414 }
415
416
417 int MathMacro::kerning(BufferView const * bv) const {
418         if (d->displayMode_ == DISPLAY_NORMAL && !d->editing_[bv])
419                 return d->expanded_.kerning(bv);
420         else
421                 return 0;
422 }
423
424
425 void MathMacro::updateMacro(MacroContext const & mc)
426 {
427         if (validName()) {
428                 d->macro_ = mc.get(name());
429                 if (d->macro_ && d->macroBackup_ != *d->macro_) {
430                         d->macroBackup_ = *d->macro_;
431                         d->needsUpdate_ = true;
432                 }
433         } else {
434                 d->macro_ = 0;
435         }
436 }
437
438
439 class MathMacro::UpdateLocker
440 {
441 public:
442         explicit UpdateLocker(MathMacro & mm) : mac(mm)
443         {
444                 mac.d->isUpdating_ = true;
445         }
446         ~UpdateLocker() { mac.d->isUpdating_ = false; }
447 private:
448         MathMacro & mac;
449 };
450 /** Avoid wrong usage of UpdateLocker.
451     To avoid wrong usage:
452     UpdateLocker(...); // wrong
453     UpdateLocker locker(...); // right
454 */
455 #define UpdateLocker(x) unnamed_UpdateLocker;
456 // Tip gotten from Bobby Schmidt's column in C/C++ Users Journal
457
458
459 void MathMacro::updateRepresentation(Cursor * cur, MacroContext const & mc,
460                 UpdateType utype)
461 {
462         // block recursive calls (bug 8999)
463         if (d->isUpdating_)
464                 return;
465
466         UpdateLocker locker(*this);
467
468         // known macro?
469         if (d->macro_ == 0)
470                 return;
471
472         // update requires
473         d->requires_ = d->macro_->requires();
474
475         if (!d->needsUpdate_
476                 // non-normal mode? We are done!
477                 || (d->displayMode_ != DISPLAY_NORMAL))
478                 return;
479
480         d->needsUpdate_ = false;
481
482         // get default values of macro
483         vector<docstring> const & defaults = d->macro_->defaults();
484
485         // create MathMacroArgumentValue objects pointing to the cells of the macro
486         vector<MathData> values(nargs());
487         for (size_t i = 0; i < nargs(); ++i) {
488                 ArgumentProxy * proxy;
489                 if (i < defaults.size())
490                         proxy = new ArgumentProxy(this, i, defaults[i]);
491                 else
492                         proxy = new ArgumentProxy(this, i);
493                 values[i].insert(0, MathAtom(proxy));
494         }
495         // expanding macro with the values
496         // Only update the argument macros if anything was expanded, otherwise
497         // we would get an endless loop (bug 9140). UpdateLocker does not work
498         // in this case, since MacroData::expand() creates new MathMacro
499         // objects, so this would be a different recursion path than the one
500         // protected by UpdateLocker.
501         if (d->macro_->expand(values, d->expanded_)) {
502                 if (utype == OutputUpdate && !d->expanded_.empty())
503                         d->expanded_.updateMacros(cur, mc, utype);
504         }
505         // get definition for list edit mode
506         docstring const & display = d->macro_->display();
507         asArray(display.empty() ? d->macro_->definition() : display, d->definition_);
508 }
509
510
511 void MathMacro::draw(PainterInfo & pi, int x, int y) const
512 {
513         Dimension const dim = dimension(*pi.base.bv);
514
515         setPosCache(pi, x, y);
516         int expx = x;
517         int expy = y;
518
519         if (d->displayMode_ == DISPLAY_INIT || d->displayMode_ == DISPLAY_INTERACTIVE_INIT) {
520                 FontSetChanger dummy(pi.base, "lyxtex");
521                 pi.pain.text(x, y, from_ascii("\\") + name(), pi.base.font);
522         } else if (d->displayMode_ == DISPLAY_UNFOLDED) {
523                 FontSetChanger dummy(pi.base, "lyxtex");
524                 pi.pain.text(x, y, from_ascii("\\"), pi.base.font);
525                 x += mathed_string_width(pi.base.font, from_ascii("\\")) + 1;
526                 cell(0).draw(pi, x, y);
527                 drawMarkers(pi, expx, expy);
528         } else if (lyxrc.macro_edit_style == LyXRC::MACRO_EDIT_LIST
529                    && d->editing_[pi.base.bv]) {
530                 // Macro will be edited in a old-style list mode here:
531
532                 CoordCache const & coords = pi.base.bv->coordCache();
533                 FontInfo const & labelFont = sane_font;
534
535                 // markers and box needs two pixels
536                 x += 2;
537
538                 // get maximal font height
539                 Dimension fontDim;
540                 math_font_max_dim(pi.base.font, fontDim.asc, fontDim.des);
541
542                 // draw label
543                 docstring label = from_ascii("Macro \\") + name() + from_ascii(": ");
544                 pi.pain.text(x, y, label, labelFont);
545                 x += mathed_string_width(labelFont, label);
546
547                 // draw definition
548                 d->definition_.draw(pi, x, y);
549                 Dimension const & defDim = coords.getArrays().dim(&d->definition_);
550                 y += max(fontDim.des, defDim.des);
551
552                 // draw parameters
553                 docstring str = from_ascii("#9");
554                 int strw1 = mathed_string_width(labelFont, from_ascii("#9"));
555                 int strw2 = mathed_string_width(labelFont, from_ascii(": "));
556
557                 for (idx_type i = 0; i < nargs(); ++i) {
558                         // position of label
559                         Dimension const & cdim = coords.getArrays().dim(&cell(i));
560                         x = expx + 2;
561                         y += max(fontDim.asc, cdim.asc) + 1;
562
563                         // draw label
564                         str[1] = '1' + i;
565                         pi.pain.text(x, y, str, labelFont);
566                         x += strw1;
567                         pi.pain.text(x, y, from_ascii(":"), labelFont);
568                         x += strw2;
569
570                         // draw paramter
571                         cell(i).draw(pi, x, y);
572
573                         // next line
574                         y += max(fontDim.des, cdim.des);
575                 }
576
577                 pi.pain.rectangle(expx + 1, expy - dim.asc + 1, dim.wid - 3,
578                                   dim.height() - 2, Color_mathmacroframe);
579                 drawMarkers2(pi, expx, expy);
580         } else {
581                 bool drawBox = lyxrc.macro_edit_style == LyXRC::MACRO_EDIT_INLINE_BOX;
582
583                 // warm up cells
584                 for (size_t i = 0; i < nargs(); ++i)
585                         cell(i).setXY(*pi.base.bv, x, y);
586
587                 if (drawBox && d->editing_[pi.base.bv]) {
588                         // draw header and rectangle around
589                         FontInfo font = pi.base.font;
590                         augmentFont(font, from_ascii("lyxtex"));
591                         font.setSize(FONT_SIZE_TINY);
592                         font.setColor(Color_mathmacrolabel);
593                         Dimension namedim;
594                         mathed_string_dim(font, name(), namedim);
595
596                         pi.pain.fillRectangle(x, y - dim.asc, dim.wid, 1 + namedim.height() + 1, Color_mathmacrobg);
597                         pi.pain.text(x + 1, y - dim.asc + namedim.asc + 2, name(), font);
598                         expx += (dim.wid - d->expanded_.dimension(*pi.base.bv).width()) / 2;
599                 }
600
601                 if (d->editing_[pi.base.bv]) {
602                         pi.pain.enterMonochromeMode(Color_mathbg, Color_mathmacroblend);
603                         d->expanded_.draw(pi, expx, expy);
604                         pi.pain.leaveMonochromeMode();
605
606                         if (drawBox)
607                                 pi.pain.rectangle(x, y - dim.asc, dim.wid,
608                                                   dim.height(), Color_mathmacroframe);
609                 } else
610                         d->expanded_.draw(pi, expx, expy);
611
612                 if (!drawBox)
613                         drawMarkers(pi, x, y);
614         }
615
616         // edit mode changed?
617         if (d->editing_[pi.base.bv] != editMode(pi.base.bv))
618                 pi.base.bv->cursor().screenUpdateFlags(Update::SinglePar);
619 }
620
621
622 void MathMacro::drawSelection(PainterInfo & pi, int x, int y) const
623 {
624         // We may have 0 arguments, but InsetMathNest requires at least one.
625         if (!cells_.empty())
626                 InsetMathNest::drawSelection(pi, x, y);
627 }
628
629
630 void MathMacro::setDisplayMode(MathMacro::DisplayMode mode, int appetite)
631 {
632         if (d->displayMode_ != mode) {
633                 // transfer name if changing from or to DISPLAY_UNFOLDED
634                 if (mode == DISPLAY_UNFOLDED) {
635                         cells_.resize(1);
636                         asArray(d->name_, cell(0));
637                 } else if (d->displayMode_ == DISPLAY_UNFOLDED) {
638                         d->name_ = asString(cell(0));
639                         cells_.resize(0);
640                 }
641
642                 d->displayMode_ = mode;
643                 d->needsUpdate_ = true;
644         }
645
646         // the interactive init mode is non-greedy by default
647         if (appetite == -1)
648                 d->appetite_ = (mode == DISPLAY_INTERACTIVE_INIT) ? 0 : 9;
649         else
650                 d->appetite_ = size_t(appetite);
651 }
652
653
654 MathMacro::DisplayMode MathMacro::computeDisplayMode() const
655 {
656         if (d->nextFoldMode_ == true && d->macro_ && !d->macro_->locked())
657                 return DISPLAY_NORMAL;
658         else
659                 return DISPLAY_UNFOLDED;
660 }
661
662
663 bool MathMacro::validName() const
664 {
665         docstring n = name();
666
667         if (n.empty())
668                 return false;
669
670         // converting back and force doesn't swallow anything?
671         /*MathData ma;
672         asArray(n, ma);
673         if (asString(ma) != n)
674                 return false;*/
675
676         // valid characters?
677         for (size_t i = 0; i<n.size(); ++i) {
678                 if (!(n[i] >= 'a' && n[i] <= 'z')
679                     && !(n[i] >= 'A' && n[i] <= 'Z')
680                     && n[i] != '*')
681                         return false;
682         }
683
684         return true;
685 }
686
687
688 size_t MathMacro::arity() const
689 {
690         if (d->displayMode_ == DISPLAY_NORMAL )
691                 return cells_.size();
692         else
693                 return 0;
694 }
695
696
697 size_t MathMacro::optionals() const
698 {
699         return d->optionals_;
700 }
701
702
703 void MathMacro::setOptionals(int n)
704 {
705         if (n <= int(nargs()))
706                 d->optionals_ = n;
707 }
708
709
710 size_t MathMacro::appetite() const
711 {
712         return d->appetite_;
713 }
714
715
716 void MathMacro::validate(LaTeXFeatures & features) const
717 {
718         // Immediately after a document is loaded, in some cases the MacroData
719         // of the global macros defined in the lib/symbols file may still not
720         // be known to the macro machinery because it will be set only after
721         // the first call to updateMacros(). This is not a problem unless
722         // instant preview is on for math, in which case we will be missing
723         // the corresponding requirements.
724         // In this case, we get the required info from the global macro table.
725         if (!d->requires_.empty())
726                 features.require(d->requires_);
727         else if (!d->macro_) {
728                 // Update requires for known global macros.
729                 MacroData const * data = MacroTable::globalMacros().get(name());
730                 if (data && !data->requires().empty())
731                         features.require(data->requires());
732         }
733
734         if (name() == "binom")
735                 features.require("binom");
736
737         // validate the cells and the definition
738         if (displayMode() == DISPLAY_NORMAL) {
739                 d->definition_.validate(features);
740                 InsetMathNest::validate(features);
741         }
742 }
743
744
745 void MathMacro::edit(Cursor & cur, bool front, EntryDirection entry_from)
746 {
747         cur.screenUpdateFlags(Update::SinglePar);
748         InsetMathNest::edit(cur, front, entry_from);
749 }
750
751
752 Inset * MathMacro::editXY(Cursor & cur, int x, int y)
753 {
754         // We may have 0 arguments, but InsetMathNest requires at least one.
755         if (nargs() > 0) {
756                 cur.screenUpdateFlags(Update::SinglePar);
757                 return InsetMathNest::editXY(cur, x, y);
758         } else
759                 return this;
760 }
761
762
763 void MathMacro::removeArgument(Inset::pos_type pos) {
764         if (d->displayMode_ == DISPLAY_NORMAL) {
765                 LASSERT(size_t(pos) < cells_.size(), return);
766                 cells_.erase(cells_.begin() + pos);
767                 if (size_t(pos) < d->attachedArgsNum_)
768                         --d->attachedArgsNum_;
769                 if (size_t(pos) < d->optionals_) {
770                         --d->optionals_;
771                 }
772
773                 d->needsUpdate_ = true;
774         }
775 }
776
777
778 void MathMacro::insertArgument(Inset::pos_type pos) {
779         if (d->displayMode_ == DISPLAY_NORMAL) {
780                 LASSERT(size_t(pos) <= cells_.size(), return);
781                 cells_.insert(cells_.begin() + pos, MathData());
782                 if (size_t(pos) < d->attachedArgsNum_)
783                         ++d->attachedArgsNum_;
784                 if (size_t(pos) < d->optionals_)
785                         ++d->optionals_;
786
787                 d->needsUpdate_ = true;
788         }
789 }
790
791
792 void MathMacro::detachArguments(vector<MathData> & args, bool strip)
793 {
794         LASSERT(d->displayMode_ == DISPLAY_NORMAL, return);
795         args = cells_;
796
797         // strip off empty cells, but not more than arity-attachedArgsNum_
798         if (strip) {
799                 size_t i;
800                 for (i = cells_.size(); i > d->attachedArgsNum_; --i)
801                         if (!cell(i - 1).empty()) break;
802                 args.resize(i);
803         }
804
805         d->attachedArgsNum_ = 0;
806         d->expanded_ = MathData();
807         cells_.resize(0);
808
809         d->needsUpdate_ = true;
810 }
811
812
813 void MathMacro::attachArguments(vector<MathData> const & args, size_t arity, int optionals)
814 {
815         LASSERT(d->displayMode_ == DISPLAY_NORMAL, return);
816         cells_ = args;
817         d->attachedArgsNum_ = args.size();
818         cells_.resize(arity);
819         d->expanded_ = MathData();
820         d->optionals_ = optionals;
821
822         d->needsUpdate_ = true;
823 }
824
825
826 bool MathMacro::idxFirst(Cursor & cur) const
827 {
828         cur.screenUpdateFlags(Update::SinglePar);
829         return InsetMathNest::idxFirst(cur);
830 }
831
832
833 bool MathMacro::idxLast(Cursor & cur) const
834 {
835         cur.screenUpdateFlags(Update::SinglePar);
836         return InsetMathNest::idxLast(cur);
837 }
838
839
840 bool MathMacro::notifyCursorLeaves(Cursor const & old, Cursor & cur)
841 {
842         if (d->displayMode_ == DISPLAY_UNFOLDED) {
843                 docstring const & unfolded_name = name();
844                 if (unfolded_name != d->name_) {
845                         // The macro name was changed
846                         Cursor inset_cursor = old;
847                         int macroSlice = inset_cursor.find(this);
848                         // returning true means the cursor is "now" invalid,
849                         // which it was.
850                         LASSERT(macroSlice != -1, return true);
851                         inset_cursor.cutOff(macroSlice);
852                         inset_cursor.recordUndoInset();
853                         inset_cursor.pop();
854                         inset_cursor.cell().erase(inset_cursor.pos());
855                         inset_cursor.cell().insert(inset_cursor.pos(),
856                                 createInsetMath(unfolded_name, cur.buffer()));
857                         cur.resetAnchor();
858                         cur.screenUpdateFlags(cur.result().screenUpdate() | Update::SinglePar);
859                         return true;
860                 }
861         }
862         cur.screenUpdateFlags(Update::Force);
863         return InsetMathNest::notifyCursorLeaves(old, cur);
864 }
865
866
867 void MathMacro::fold(Cursor & cur)
868 {
869         if (!d->nextFoldMode_) {
870                 d->nextFoldMode_ = true;
871                 cur.screenUpdateFlags(Update::SinglePar);
872         }
873 }
874
875
876 void MathMacro::unfold(Cursor & cur)
877 {
878         if (d->nextFoldMode_) {
879                 d->nextFoldMode_ = false;
880                 cur.screenUpdateFlags(Update::SinglePar);
881         }
882 }
883
884
885 bool MathMacro::folded() const
886 {
887         return d->nextFoldMode_;
888 }
889
890
891 void MathMacro::write(WriteStream & os) const
892 {
893         MathEnsurer ensurer(os, d->macro_ != 0, true);
894
895         // non-normal mode
896         if (d->displayMode_ != DISPLAY_NORMAL) {
897                 os << "\\" << name();
898                 if (name().size() != 1 || isAlphaASCII(name()[0]))
899                         os.pendingSpace(true);
900                 return;
901         }
902
903         // normal mode
904         // we should be ok to continue even if this fails.
905         LATTEST(d->macro_);
906
907         // Always protect macros in a fragile environment
908         if (os.fragile())
909                 os << "\\protect";
910
911         os << "\\" << name();
912         bool first = true;
913
914         // Optional arguments:
915         // First find last non-empty optional argument
916         idx_type emptyOptFrom = 0;
917         idx_type i = 0;
918         for (; i < cells_.size() && i < d->optionals_; ++i) {
919                 if (!cell(i).empty())
920                         emptyOptFrom = i + 1;
921         }
922
923         // print out optionals
924         for (i=0; i < cells_.size() && i < emptyOptFrom; ++i) {
925                 first = false;
926                 os << "[" << cell(i) << "]";
927         }
928
929         // skip the tailing empty optionals
930         i = d->optionals_;
931
932         // Print remaining arguments
933         for (; i < cells_.size(); ++i) {
934                 if (cell(i).size() == 1
935                         && cell(i)[0].nucleus()->asCharInset()
936                         && isASCII(cell(i)[0].nucleus()->asCharInset()->getChar())) {
937                         if (first)
938                                 os << " ";
939                         os << cell(i);
940                 } else
941                         os << "{" << cell(i) << "}";
942                 first = false;
943         }
944
945         // add space if there was no argument
946         if (first)
947                 os.pendingSpace(true);
948 }
949
950
951 void MathMacro::maple(MapleStream & os) const
952 {
953         lyx::maple(d->expanded_, os);
954 }
955
956
957 void MathMacro::maxima(MaximaStream & os) const
958 {
959         lyx::maxima(d->expanded_, os);
960 }
961
962
963 void MathMacro::mathematica(MathematicaStream & os) const
964 {
965         lyx::mathematica(d->expanded_, os);
966 }
967
968
969 void MathMacro::mathmlize(MathStream & os) const
970 {
971         // macro_ is 0 if this is an unknown macro
972         LATTEST(d->macro_ || d->displayMode_ != DISPLAY_NORMAL);
973         if (d->macro_) {
974                 docstring const xmlname = d->macro_->xmlname();
975                 if (!xmlname.empty()) {
976                         char const * type = d->macro_->MathMLtype();
977                         os << '<' << type << "> " << xmlname << " /<"
978                            << type << '>';
979                         return;
980                 }
981         }
982         if (d->expanded_.empty()) {
983                 // this means that we do not recognize the macro
984                 throw MathExportException();
985         }
986         os << d->expanded_;
987 }
988
989
990 void MathMacro::htmlize(HtmlStream & os) const
991 {
992         // macro_ is 0 if this is an unknown macro
993         LATTEST(d->macro_ || d->displayMode_ != DISPLAY_NORMAL);
994         if (d->macro_) {
995                 docstring const xmlname = d->macro_->xmlname();
996                 if (!xmlname.empty()) {
997                         os << ' ' << xmlname << ' ';
998                         return;
999                 }
1000         }
1001         if (d->expanded_.empty()) {
1002                 // this means that we do not recognize the macro
1003                 throw MathExportException();
1004         }
1005         os << d->expanded_;
1006 }
1007
1008
1009 void MathMacro::octave(OctaveStream & os) const
1010 {
1011         lyx::octave(d->expanded_, os);
1012 }
1013
1014
1015 void MathMacro::infoize(odocstream & os) const
1016 {
1017         os << bformat(_("Macro: %1$s"), name());
1018 }
1019
1020
1021 void MathMacro::infoize2(odocstream & os) const
1022 {
1023         os << bformat(_("Macro: %1$s"), name());
1024 }
1025
1026
1027 bool MathMacro::completionSupported(Cursor const & cur) const
1028 {
1029         if (displayMode() != DISPLAY_UNFOLDED)
1030                 return InsetMathNest::completionSupported(cur);
1031
1032         return lyxrc.completion_popup_math
1033                 && displayMode() == DISPLAY_UNFOLDED
1034                 && cur.bv().cursor().pos() == int(name().size());
1035 }
1036
1037
1038 bool MathMacro::inlineCompletionSupported(Cursor const & cur) const
1039 {
1040         if (displayMode() != DISPLAY_UNFOLDED)
1041                 return InsetMathNest::inlineCompletionSupported(cur);
1042
1043         return lyxrc.completion_inline_math
1044                 && displayMode() == DISPLAY_UNFOLDED
1045                 && cur.bv().cursor().pos() == int(name().size());
1046 }
1047
1048
1049 bool MathMacro::automaticInlineCompletion() const
1050 {
1051         if (displayMode() != DISPLAY_UNFOLDED)
1052                 return InsetMathNest::automaticInlineCompletion();
1053
1054         return lyxrc.completion_inline_math;
1055 }
1056
1057
1058 bool MathMacro::automaticPopupCompletion() const
1059 {
1060         if (displayMode() != DISPLAY_UNFOLDED)
1061                 return InsetMathNest::automaticPopupCompletion();
1062
1063         return lyxrc.completion_popup_math;
1064 }
1065
1066
1067 CompletionList const *
1068 MathMacro::createCompletionList(Cursor const & cur) const
1069 {
1070         if (displayMode() != DISPLAY_UNFOLDED)
1071                 return InsetMathNest::createCompletionList(cur);
1072
1073         return new MathCompletionList(cur.bv().cursor());
1074 }
1075
1076
1077 docstring MathMacro::completionPrefix(Cursor const & cur) const
1078 {
1079         if (displayMode() != DISPLAY_UNFOLDED)
1080                 return InsetMathNest::completionPrefix(cur);
1081
1082         if (!completionSupported(cur))
1083                 return docstring();
1084
1085         return "\\" + name();
1086 }
1087
1088
1089 bool MathMacro::insertCompletion(Cursor & cur, docstring const & s,
1090                                         bool finished)
1091 {
1092         if (displayMode() != DISPLAY_UNFOLDED)
1093                 return InsetMathNest::insertCompletion(cur, s, finished);
1094
1095         if (!completionSupported(cur))
1096                 return false;
1097
1098         // append completion
1099         docstring newName = name() + s;
1100         asArray(newName, cell(0));
1101         cur.bv().cursor().pos() = name().size();
1102         cur.screenUpdateFlags(Update::SinglePar);
1103
1104         // finish macro
1105         if (finished) {
1106                 cur.bv().cursor().pop();
1107                 ++cur.bv().cursor().pos();
1108                 cur.screenUpdateFlags(Update::SinglePar);
1109         }
1110
1111         return true;
1112 }
1113
1114
1115 void MathMacro::completionPosAndDim(Cursor const & cur, int & x, int & y,
1116         Dimension & dim) const
1117 {
1118         if (displayMode() != DISPLAY_UNFOLDED)
1119                 InsetMathNest::completionPosAndDim(cur, x, y, dim);
1120
1121         // get inset dimensions
1122         dim = cur.bv().coordCache().insets().dim(this);
1123         // FIXME: these 3 are no accurate, but should depend on the font.
1124         // Now the popup jumps down if you enter a char with descent > 0.
1125         dim.des += 3;
1126         dim.asc += 3;
1127
1128         // and position
1129         Point xy
1130         = cur.bv().coordCache().insets().xy(this);
1131         x = xy.x_;
1132         y = xy.y_;
1133 }
1134
1135
1136 } // namespace lyx