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