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