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