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