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