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