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