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