]> git.lyx.org Git - lyx.git/blob - src/mathed/MathMacro.cpp
Add empty InsetLayout for undefined cases. Should avoid possible bugs caused by empty...
[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 #include "MathSupport.h"
17 #include "MathExtern.h"
18 #include "MathStream.h"
19
20 #include "Buffer.h"
21 #include "BufferView.h"
22 #include "CoordCache.h"
23 #include "Cursor.h"
24 #include "LaTeXFeatures.h"
25 #include "LyXRC.h"
26 #include "FuncStatus.h"
27 #include "FuncRequest.h"
28 #include "Undo.h"
29
30 #include "frontends/Painter.h"
31
32 #include "support/debug.h"
33
34 #include <ostream>
35 #include <vector>
36
37 using namespace std;
38
39 namespace lyx {
40
41
42 /// A proxy for the macro values
43 class ArgumentProxy : public InsetMath {
44 public:
45         ///
46         ArgumentProxy(MathMacro & mathMacro, size_t idx) 
47                 : mathMacro_(mathMacro), idx_(idx) {}
48         ///
49         ArgumentProxy(MathMacro & mathMacro, size_t idx, docstring const & def) 
50                 : mathMacro_(mathMacro), idx_(idx) 
51         {
52                         asArray(def, def_);
53         }
54         ///
55         void metrics(MetricsInfo & mi, Dimension & dim) const {
56                 mathMacro_.macro()->unlock();
57                 if (!mathMacro_.editMetrics(mi.base.bv) 
58                     && mathMacro_.cell(idx_).empty())
59                         def_.metrics(mi, dim);
60                 else {
61                         CoordCache & coords = mi.base.bv->coordCache();
62                         dim = coords.arrays().dim(&mathMacro_.cell(idx_));
63                 }
64                 mathMacro_.macro()->lock();
65         }
66         ///
67         void draw(PainterInfo & pi, int x, int y) const {
68                 if (mathMacro_.editMetrics(pi.base.bv)) {
69                         // The only way a ArgumentProxy can appear is in a cell of the 
70                         // MathMacro. Moreover the cells are only drawn in the DISPLAY_FOLDED 
71                         // mode and then, if the macro is edited the monochrome 
72                         // mode is entered by the MathMacro before calling the cells' draw
73                         // method. Then eventually this code is reached and the proxy leaves
74                         // monochrome mode temporarely. Hence, if it is not in monochrome 
75                         // here (and the assert triggers in pain.leaveMonochromeMode()) 
76                         // it's a bug.
77                         pi.pain.leaveMonochromeMode();
78                         mathMacro_.cell(idx_).draw(pi, x, y);
79                         pi.pain.enterMonochromeMode(Color_mathbg, Color_mathmacroblend);
80                 } else if (mathMacro_.cell(idx_).empty()) {
81                         mathMacro_.cell(idx_).setXY(*pi.base.bv, x, y);
82                         def_.draw(pi, x, y);
83                 } else
84                         mathMacro_.cell(idx_).draw(pi, x, y);
85         }
86         ///
87         size_t idx() const { return idx_; }
88         ///
89         int kerning(BufferView const * bv) const
90         { 
91                 if (mathMacro_.editMetrics(bv)
92                     || !mathMacro_.cell(idx_).empty())
93                         return mathMacro_.cell(idx_).kerning(bv); 
94                 else
95                         return def_.kerning(bv);
96         }
97
98 private:
99         ///
100         Inset * clone() const 
101         {
102                 return new ArgumentProxy(*this);
103         }
104         ///
105         MathMacro & mathMacro_;
106         ///
107         size_t idx_;
108         ///
109         MathData def_;
110 };
111
112
113 MathMacro::MathMacro(docstring const & name)
114         : InsetMathNest(0), name_(name), displayMode_(DISPLAY_INIT),
115                 attachedArgsNum_(0), optionals_(0), nextFoldMode_(true),
116                 macro_(0), needsUpdate_(false)
117 {}
118
119
120 Inset * MathMacro::clone() const
121 {
122         MathMacro * copy = new MathMacro(*this);
123         copy->needsUpdate_ = true;
124         copy->expanded_.cell(0).clear();
125         return copy;
126 }
127
128
129 docstring MathMacro::name() const
130 {
131         if (displayMode_ == DISPLAY_UNFOLDED)
132                 return asString(cell(0));
133         else
134                 return name_;
135 }
136
137
138 void MathMacro::cursorPos(BufferView const & bv,
139                 CursorSlice const & sl, bool boundary, int & x, int & y) const
140 {
141         // We may have 0 arguments, but InsetMathNest requires at least one.
142         if (nargs() > 0)
143                 InsetMathNest::cursorPos(bv, sl, boundary, x, y);
144 }
145
146
147 bool MathMacro::editMode(BufferView const * bv) const {
148         // find this in cursor trace
149         Cursor const & cur = bv->cursor();
150         for (size_t i = 0; i != cur.depth(); ++i)
151                 if (&cur[i].inset() == this) {
152                         // look if there is no other macro in edit mode above
153                         ++i;
154                         for (; i != cur.depth(); ++i) {
155                                 MathMacro const * macro = dynamic_cast<MathMacro const *>(&cur[i].inset());
156                                 if (macro && macro->displayMode() == DISPLAY_NORMAL)
157                                         return false;
158                         }
159
160                         // ok, none found, I am the highest one
161                         return true;
162                 }
163
164         return false;
165 }
166
167
168 bool MathMacro::editMetrics(BufferView const * bv) const
169 {
170         return editing_[bv];
171 }
172
173
174 void MathMacro::metrics(MetricsInfo & mi, Dimension & dim) const
175 {
176         // set edit mode for which we will have calculated metrics. But only
177         editing_[mi.base.bv] = editMode(mi.base.bv);
178
179         // calculate new metrics according to display mode
180         if (displayMode_ == DISPLAY_INIT || displayMode_ == DISPLAY_INTERACTIVE_INIT) {
181                 mathed_string_dim(mi.base.font, from_ascii("\\") + name(), dim);
182         } else if (displayMode_ == DISPLAY_UNFOLDED) {
183                 cell(0).metrics(mi, dim);
184                 Dimension bsdim;
185                 mathed_string_dim(mi.base.font, from_ascii("\\"), bsdim);
186                 dim.wid += bsdim.width() + 1;
187                 dim.asc = max(bsdim.ascent(), dim.ascent());
188                 dim.des = max(bsdim.descent(), dim.descent());
189                 metricsMarkers(dim);
190         } else if (lyxrc.macro_edit_style == LyXRC::MACRO_EDIT_LIST 
191                    && editing_[mi.base.bv]) {
192                 // Macro will be edited in a old-style list mode here:
193
194                 BOOST_ASSERT(macro_ != 0);
195                 Dimension fontDim;
196                 FontInfo labelFont = sane_font;
197                 math_font_max_dim(labelFont, fontDim.asc, fontDim.des);
198                 
199                 // get dimension of components of list view
200                 Dimension nameDim;
201                 nameDim.wid = mathed_string_width(mi.base.font, from_ascii("Macro \\") + name() + ": ");
202                 nameDim.asc = fontDim.asc;
203                 nameDim.des = fontDim.des;
204
205                 Dimension argDim;
206                 argDim.wid = mathed_string_width(labelFont, from_ascii("#9: "));
207                 argDim.asc = fontDim.asc;
208                 argDim.des = fontDim.des;
209                 
210                 Dimension defDim;
211                 definition_.metrics(mi, defDim);
212                 
213                 // add them up
214                 dim.wid = nameDim.wid + defDim.wid;
215                 dim.asc = max(nameDim.asc, defDim.asc);
216                 dim.des = max(nameDim.des, defDim.des);
217                 
218                 for (idx_type i = 0; i < nargs(); ++i) {
219                         Dimension cdim;
220                         cell(i).metrics(mi, cdim);
221                         dim.des += max(argDim.height(), cdim.height()) + 1;
222                         dim.wid = max(dim.wid, argDim.wid + cdim.wid);
223                 }
224                 
225                 // make space for box and markers, 2 pixels
226                 dim.asc += 1;
227                 dim.des += 1;
228                 dim.wid += 2;
229                 metricsMarkers2(dim);
230         } else {
231                 BOOST_ASSERT(macro_ != 0);
232
233                 // metrics are computed here for the cells,
234                 // in the proxy we will then use the dim from the cache
235                 InsetMathNest::metrics(mi);
236                 
237                 // calculate metrics finally
238                 macro_->lock();
239                 expanded_.cell(0).metrics(mi, dim);
240                 macro_->unlock();
241
242                 // calculate dimension with label while editing
243                 if (lyxrc.macro_edit_style == LyXRC::MACRO_EDIT_INLINE_BOX 
244                     && editing_[mi.base.bv]) {
245                         FontInfo font = mi.base.font;
246                         augmentFont(font, from_ascii("lyxtex"));
247                         Dimension namedim;
248                         mathed_string_dim(font, name(), namedim);
249 #if 0
250                         dim.wid += 2 + namedim.wid + 2 + 2;
251                         dim.asc = max(dim.asc, namedim.asc) + 2;
252                         dim.des = max(dim.des, namedim.des) + 2;
253 #endif
254                         dim.wid = max(1 + namedim.wid + 1, 2 + dim.wid + 2);
255                         dim.asc += 1 + namedim.height() + 1;
256                         dim.des += 2;
257                 }
258          
259         }
260 }
261
262
263 int MathMacro::kerning(BufferView const * bv) const {
264         if (displayMode_ == DISPLAY_NORMAL && !editing_[bv])
265                 return expanded_.kerning(bv);
266         else
267                 return 0;
268 }
269
270
271 void MathMacro::updateMacro(MacroContext const & mc) 
272 {
273         if (validName()) {
274                 macro_ = mc.get(name());            
275                 if (macro_ && macroBackup_ != *macro_) {
276                         macroBackup_ = *macro_;
277                         needsUpdate_ = true;
278                 }
279         } else {
280                 macro_ = 0;
281         }
282 }
283
284
285 void MathMacro::updateRepresentation(Cursor const * bvCur)
286 {
287         // known macro?
288         if (macro_ == 0)
289                 return;
290
291         // update requires
292         requires_ = macro_->requires();
293         
294         // non-normal mode? We are done!
295         if (displayMode_ != DISPLAY_NORMAL)
296                 return;
297
298         // macro changed?
299         if (needsUpdate_) {
300                 needsUpdate_ = false;
301                 
302                 // get default values of macro
303                 vector<docstring> const & defaults = macro_->defaults();
304                 
305                 // create MathMacroArgumentValue objects pointing to the cells of the macro
306                 vector<MathData> values(nargs());
307                 for (size_t i = 0; i < nargs(); ++i) {
308                         ArgumentProxy * proxy;
309                         if (i < defaults.size()) 
310                                 proxy = new ArgumentProxy(*this, i, defaults[i]);
311                         else
312                                 proxy = new ArgumentProxy(*this, i);
313                         values[i].insert(0, MathAtom(proxy));
314                 }
315                 
316                 // expanding macro with the values
317                 macro_->expand(values, expanded_.cell(0));
318
319                 // get definition for list edit mode
320                 docstring const & display = macro_->display();
321                 asArray(display.empty() ? macro_->definition() : display, definition_);
322         }
323 }
324
325
326 void MathMacro::draw(PainterInfo & pi, int x, int y) const
327 {
328         Dimension const dim = dimension(*pi.base.bv);
329
330         setPosCache(pi, x, y);
331         int expx = x;
332         int expy = y;
333
334         if (displayMode_ == DISPLAY_INIT || displayMode_ == DISPLAY_INTERACTIVE_INIT) {         
335                 PainterInfo pi2(pi.base.bv, pi.pain);
336                 pi2.base.font.setColor(macro_ ? Color_latex : Color_error);
337                 //pi2.base.style = LM_ST_TEXT;
338                 pi2.pain.text(x, y, from_ascii("\\") + name(), pi2.base.font);
339         } else if (displayMode_ == DISPLAY_UNFOLDED) {
340                 PainterInfo pi2(pi.base.bv, pi.pain);
341                 pi2.base.font.setColor(macro_ ? Color_latex : Color_error);
342                 //pi2.base.style = LM_ST_TEXT;
343                 pi2.pain.text(x, y, from_ascii("\\"), pi2.base.font);
344                 x += mathed_string_width(pi2.base.font, from_ascii("\\")) + 1;
345                 cell(0).draw(pi2, x, y);
346                 drawMarkers(pi2, expx, expy);
347         } else if (lyxrc.macro_edit_style == LyXRC::MACRO_EDIT_LIST
348                    && editing_[pi.base.bv]) {
349                 // Macro will be edited in a old-style list mode here:
350                 
351                 CoordCache & coords = pi.base.bv->coordCache();
352                 FontInfo const & labelFont = sane_font;
353                 
354                 // markers and box needs two pixels
355                 x += 2;
356                 
357                 // get maximal font height
358                 Dimension fontDim;
359                 math_font_max_dim(pi.base.font, fontDim.asc, fontDim.des);
360                 
361                 // draw label
362                 docstring label = from_ascii("Macro \\") + name() + from_ascii(": ");
363                 pi.pain.text(x, y, label, labelFont);
364                 x += mathed_string_width(labelFont, label);
365
366                 // draw definition
367                 definition_.draw(pi, x, y);
368                 Dimension defDim
369                 = coords.arrays().dim(&definition_);
370                 y += max(fontDim.des, defDim.des);
371                                 
372                 // draw parameters
373                 docstring str = from_ascii("#9");
374                 int strw1 = mathed_string_width(labelFont, from_ascii("#9"));
375                 int strw2 = mathed_string_width(labelFont, from_ascii(": "));
376                 
377                 for (idx_type i = 0; i < nargs(); ++i) {
378                         // position of label
379                         Dimension cdim
380                         = coords.arrays().dim(&cell(i));
381                         x = expx + 2;
382                         y += max(fontDim.asc, cdim.asc) + 1;
383                         
384                         // draw label
385                         str[1] = '1' + i;
386                         pi.pain.text(x, y, str, labelFont);
387                         x += strw1;
388                         pi.pain.text(x, y, from_ascii(":"), labelFont);
389                         x += strw2;
390                         
391                         // draw paramter
392                         cell(i).draw(pi, x, y);
393                         
394                         // next line
395                         y += max(fontDim.des, cdim.des);
396                 }
397                 
398                 pi.pain.rectangle(expx + 1, expy - dim.asc + 1, dim.wid - 3, 
399                                   dim.height() - 2, Color_mathmacroframe);
400                 drawMarkers2(pi, expx, expy);
401         } else {
402                 bool drawBox = lyxrc.macro_edit_style == LyXRC::MACRO_EDIT_INLINE_BOX;
403                 
404                 // warm up cells
405                 for (size_t i = 0; i < nargs(); ++i)
406                         cell(i).setXY(*pi.base.bv, x, y);
407
408                 if (drawBox && editing_[pi.base.bv]) {
409                         // draw header and rectangle around
410                         FontInfo font = pi.base.font;
411                         augmentFont(font, from_ascii("lyxtex"));
412                         font.setSize(FONT_SIZE_TINY);
413                         font.setColor(Color_mathmacrolabel);
414                         Dimension namedim;
415                         mathed_string_dim(font, name(), namedim);
416
417                         pi.pain.fillRectangle(x, y - dim.asc, dim.wid, 1 + namedim.height() + 1, Color_mathmacrobg);
418                         pi.pain.text(x + 1, y - dim.asc + namedim.asc + 2, name(), font);
419                         expx += (dim.wid - expanded_.cell(0).dimension(*pi.base.bv).width()) / 2;
420                 }
421
422                 if (editing_[pi.base.bv]) {
423                         pi.pain.enterMonochromeMode(Color_mathbg, Color_mathmacroblend);
424                         expanded_.cell(0).draw(pi, expx, expy);
425                         pi.pain.leaveMonochromeMode();
426
427                         if (drawBox)
428                                 pi.pain.rectangle(x, y - dim.asc, dim.wid, 
429                                                   dim.height(), Color_mathmacroframe);
430                 } else
431                         expanded_.cell(0).draw(pi, expx, expy);
432
433                 if (!drawBox)
434                         drawMarkers(pi, x, y);
435         }
436
437         // edit mode changed?
438         if (editing_[pi.base.bv] != editMode(pi.base.bv))
439                 pi.base.bv->cursor().updateFlags(Update::Force);
440 }
441
442
443 void MathMacro::drawSelection(PainterInfo & pi, int x, int y) const
444 {
445         // We may have 0 arguments, but InsetMathNest requires at least one.
446         if (cells_.size() > 0)
447                 InsetMathNest::drawSelection(pi, x, y);
448 }
449
450
451 void MathMacro::setDisplayMode(MathMacro::DisplayMode mode)
452 {
453         if (displayMode_ != mode) {             
454                 // transfer name if changing from or to DISPLAY_UNFOLDED
455                 if (mode == DISPLAY_UNFOLDED) {
456                         cells_.resize(1);
457                         asArray(name_, cell(0));
458                 } else if (displayMode_ == DISPLAY_UNFOLDED) {
459                         name_ = asString(cell(0));
460                         cells_.resize(0);
461                 }
462
463                 displayMode_ = mode;
464                 needsUpdate_ = true;
465         }
466 }
467
468
469 MathMacro::DisplayMode MathMacro::computeDisplayMode() const
470 {
471         if (nextFoldMode_ == true && macro_ && !macro_->locked())
472                 return DISPLAY_NORMAL;
473         else
474                 return DISPLAY_UNFOLDED;
475 }
476
477
478 bool MathMacro::validName() const
479 {
480         docstring n = name();
481
482         // empty name?
483         if (n.size() == 0)
484                 return false;
485
486         // converting back and force doesn't swallow anything?
487         /*MathData ma;
488         asArray(n, ma);
489         if (asString(ma) != n)
490                 return false;*/
491
492         // valid characters?
493         for (size_t i = 0; i<n.size(); ++i) {
494                 if (!(n[i] >= 'a' && n[i] <= 'z') &&
495                                 !(n[i] >= 'A' && n[i] <= 'Z')) 
496                         return false;
497         }
498
499         return true;
500 }
501
502
503 void MathMacro::validate(LaTeXFeatures & features) const
504 {
505         if (!requires_.empty())
506                 features.require(requires_);
507
508         if (name() == "binom" || name() == "mathcircumflex")
509                 features.require(to_utf8(name()));
510 }
511
512
513 void MathMacro::edit(Cursor & cur, bool front, EntryDirection entry_from)
514 {
515         cur.updateFlags(Update::Force);
516         InsetMathNest::edit(cur, front, entry_from);
517 }
518
519
520 Inset * MathMacro::editXY(Cursor & cur, int x, int y)
521 {
522         // We may have 0 arguments, but InsetMathNest requires at least one.
523         if (nargs() > 0) {
524                 cur.updateFlags(Update::Force);
525                 return InsetMathNest::editXY(cur, x, y);                
526         } else
527                 return this;
528 }
529
530
531 void MathMacro::removeArgument(Inset::pos_type pos) {
532         if (displayMode_ == DISPLAY_NORMAL) {
533                 BOOST_ASSERT(size_t(pos) < cells_.size());
534                 cells_.erase(cells_.begin() + pos);
535                 if (size_t(pos) < attachedArgsNum_)
536                         --attachedArgsNum_;
537                 if (size_t(pos) < optionals_) {
538                         --optionals_;
539                 }
540
541                 needsUpdate_ = true;
542         }
543 }
544
545
546 void MathMacro::insertArgument(Inset::pos_type pos) {
547         if (displayMode_ == DISPLAY_NORMAL) {
548                 BOOST_ASSERT(size_t(pos) <= cells_.size());
549                 cells_.insert(cells_.begin() + pos, MathData());
550                 if (size_t(pos) < attachedArgsNum_)
551                         ++attachedArgsNum_;
552                 if (size_t(pos) < optionals_)
553                         ++optionals_;
554
555                 needsUpdate_ = true;
556         }
557 }
558
559
560 void MathMacro::detachArguments(vector<MathData> & args, bool strip)
561 {
562         BOOST_ASSERT(displayMode_ == DISPLAY_NORMAL);   
563         args = cells_;
564
565         // strip off empty cells, but not more than arity-attachedArgsNum_
566         if (strip) {
567                 size_t i;
568                 for (i = cells_.size(); i > attachedArgsNum_; --i)
569                         if (!cell(i - 1).empty()) break;
570                 args.resize(i);
571         }
572
573         attachedArgsNum_ = 0;
574         expanded_.cell(0) = MathData();
575         cells_.resize(0);
576
577         needsUpdate_ = true;
578 }
579
580
581 void MathMacro::attachArguments(vector<MathData> const & args, size_t arity, int optionals)
582 {
583         BOOST_ASSERT(displayMode_ == DISPLAY_NORMAL);
584         cells_ = args;
585         attachedArgsNum_ = args.size();
586         cells_.resize(arity);
587         expanded_.cell(0) = MathData();
588         optionals_ = optionals;
589
590         needsUpdate_ = true;
591 }
592
593
594 bool MathMacro::idxFirst(Cursor & cur) const 
595 {
596         cur.updateFlags(Update::Force);
597         return InsetMathNest::idxFirst(cur);
598 }
599
600
601 bool MathMacro::idxLast(Cursor & cur) const 
602 {
603         cur.updateFlags(Update::Force);
604         return InsetMathNest::idxLast(cur);
605 }
606
607
608 bool MathMacro::notifyCursorLeaves(Cursor & cur)
609 {
610         cur.updateFlags(Update::Force);
611         return InsetMathNest::notifyCursorLeaves(cur);
612 }
613
614
615 void MathMacro::fold(Cursor & cur)
616 {
617         if (!nextFoldMode_) {
618                 nextFoldMode_ = true;
619                 cur.updateFlags(Update::Force);
620         }
621 }
622
623
624 void MathMacro::unfold(Cursor & cur)
625 {
626         if (nextFoldMode_) {
627                 nextFoldMode_ = false;
628                 cur.updateFlags(Update::Force);
629         }
630 }
631
632
633 bool MathMacro::folded() const
634 {
635         return nextFoldMode_;
636 }
637
638
639 void MathMacro::write(WriteStream & os) const
640 {
641         // non-normal mode
642         if (displayMode_ != DISPLAY_NORMAL) {
643                 os << "\\" << name() << " ";
644                 os.pendingSpace(true);
645                 return;
646         }
647
648         // normal mode
649         BOOST_ASSERT(macro_);
650
651         // optional arguments make macros fragile
652         if (optionals_ > 0 && os.fragile())
653                 os << "\\protect";
654         
655         os << "\\" << name();
656         bool first = true;
657         
658         // Optional arguments:
659         // First find last non-empty optional argument
660         idx_type emptyOptFrom = 0;
661         idx_type i = 0;
662         for (; i < cells_.size() && i < optionals_; ++i) {
663                 if (!cell(i).empty())
664                         emptyOptFrom = i + 1;
665         }
666         
667         // print out optionals
668         for (i=0; i < cells_.size() && i < emptyOptFrom; ++i) {
669                 first = false;
670                 os << "[" << cell(i) << "]";
671         }
672         
673         // skip the tailing empty optionals
674         i = optionals_;
675         
676         // Print remaining macros 
677         for (; i < cells_.size(); ++i) {
678                 if (cell(i).size() == 1 
679                          && cell(i)[0].nucleus()->asCharInset()) {
680                         if (first)
681                                 os << " ";
682                         os << cell(i);
683                 } else
684                         os << "{" << cell(i) << "}";
685                 first = false;
686         }
687
688         // add space if there was no argument
689         if (first)
690                 os.pendingSpace(true);
691 }
692
693
694 void MathMacro::maple(MapleStream & os) const
695 {
696         lyx::maple(expanded_.cell(0), os);
697 }
698
699
700 void MathMacro::mathmlize(MathStream & os) const
701 {
702         lyx::mathmlize(expanded_.cell(0), os);
703 }
704
705
706 void MathMacro::octave(OctaveStream & os) const
707 {
708         lyx::octave(expanded_.cell(0), os);
709 }
710
711
712 void MathMacro::infoize(odocstream & os) const
713 {
714         os << "Macro: " << name();
715 }
716
717
718 void MathMacro::infoize2(odocstream & os) const
719 {
720         os << "Macro: " << name();
721
722 }
723
724
725 } // namespace lyx