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