2 * \file MathMacroTemplate.cpp
3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
8 * Full author contact details are available in file CREDITS.
13 #include "MathMacroTemplate.h"
15 #include "DocIterator.h"
16 #include "InsetMathBrace.h"
17 #include "InsetMathChar.h"
18 #include "InsetMathSqrt.h"
19 #include "MathMacro.h"
20 #include "MathMacroArgument.h"
21 #include "MathStream.h"
22 #include "MathParser.h"
23 #include "MathSupport.h"
24 #include "MathMacroArgument.h"
29 #include "support/debug.h"
30 #include "DispatchResult.h"
31 #include "FuncRequest.h"
32 #include "FuncStatus.h"
33 #include "support/gettext.h"
37 #include "frontends/FontMetrics.h"
38 #include "frontends/Painter.h"
40 #include "support/convert.h"
41 #include "support/docstream.h"
42 #include "support/lstrings.h"
44 #include "support/debug.h"
52 using support::bformat;
55 class InsetMathWrapper : public InsetMath {
58 InsetMathWrapper(MathData const * value) : value_(value) {}
60 void metrics(MetricsInfo & mi, Dimension & dim) const;
62 void draw(PainterInfo &, int x, int y) const;
66 Inset * clone() const;
68 MathData const * value_;
72 Inset * InsetMathWrapper::clone() const
74 return new InsetMathWrapper(*this);
78 void InsetMathWrapper::metrics(MetricsInfo & mi, Dimension & dim) const
80 value_->metrics(mi, dim);
81 //metricsMarkers2(dim);
85 void InsetMathWrapper::draw(PainterInfo & pi, int x, int y) const
87 value_->draw(pi, x, y);
88 //drawMarkers(pi, x, y);
92 MathMacroTemplate::MathMacroTemplate()
93 : InsetMathNest(3), numargs_(0), optionals_(0),
94 type_(MacroTypeNewcommand)
100 MathMacroTemplate::MathMacroTemplate(docstring const & name, int numargs,
101 int optionals, MacroType type,
102 vector<MathData> const & optionalValues,
103 MathData const & def, MathData const & display)
104 : InsetMathNest(optionals + 3), numargs_(numargs),
105 optionals_(optionals), optionalValues_(optionalValues),
111 lyxerr << "MathMacroTemplate::MathMacroTemplate: wrong # of arguments: "
114 asArray(name, cell(0));
115 optionalValues_.resize(9);
116 for (int i = 0; i < optionals_; ++i)
117 cell(optIdx(i)) = optionalValues_[i];
118 cell(defIdx()) = def;
119 cell(displayIdx()) = display;
123 MathMacroTemplate::MathMacroTemplate(docstring const & str)
124 : InsetMathNest(3), numargs_(0), optionals_(0),
125 type_(MacroTypeNewcommand)
130 mathed_parse_cell(ar, str);
131 if (ar.size() != 1 || !ar[0]->asMacroTemplate()) {
132 lyxerr << "Cannot read macro from '" << ar << "'" << endl;
133 asArray(from_ascii("invalidmacro"), cell(0));
134 // FIXME: The macro template does not make sense after this.
135 // The whole parsing should not be in a constructor which
136 // has no chance to report failure.
139 operator=( *(ar[0]->asMacroTemplate()) );
143 Inset * MathMacroTemplate::clone() const
145 return new MathMacroTemplate(*this);
149 docstring MathMacroTemplate::name() const
151 return asString(cell(0));
155 void MathMacroTemplate::updateToContext(MacroContext const & mc) const
157 redefinition_ = mc.get(name()) != 0;
161 void MathMacroTemplate::metrics(MetricsInfo & mi, Dimension & dim) const
163 FontSetChanger dummy1(mi.base, from_ascii("mathnormal"));
164 StyleChanger dummy2(mi.base, LM_ST_TEXT);
166 // tiny font for sublabels
167 FontInfo slFont = sane_font;
173 bool edit = editing(mi.base.bv);
176 MacroData const * macro = 0;
178 macro = mi.macrocontext.get(name());
180 // updateToContext() - avoids another lookup
181 redefinition_ = macro != 0;
184 // create label "{#1}{#2}:="
187 for (; i < optionals_; ++i) {
188 label_.push_back(MathAtom(new InsetMathChar('[')));
189 label_.push_back(MathAtom(new InsetMathWrapper(&cell(1 + i))));
190 label_.push_back(MathAtom(new InsetMathChar(']')));
192 for (; i < numargs_; ++i) {
194 arg.push_back(MathAtom(new MathMacroArgument(i + 1)));
195 label_.push_back(MathAtom(new InsetMathBrace(arg)));
197 label_.push_back(MathAtom(new InsetMathChar(':')));
198 label_.push_back(MathAtom(new InsetMathChar('=')));
209 cell(0).metrics(mi, dim0);
210 label_.metrics(mi, labeldim);
211 cell(defIdx()).metrics(mi, defdim);
212 cell(displayIdx()).metrics(mi, dspdim);
217 // calculate metrics taking all cells and labels into account
218 dim.wid = 2 + mathed_string_width(mi.base.font, from_ascii("\\")) +
223 dim.asc = dim0.ascent();
224 dim.des = dim0.descent();
226 dim.asc = max(dim.asc, labeldim.ascent());
227 dim.des = max(dim.des, labeldim.descent());
229 dim.asc = max(dim.asc, defdim.ascent());
230 dim.des = max(dim.des, defdim.descent());
232 // hide empty display cells if not edited
233 if (edit || !cell(displayIdx()).empty()) {
235 dim.asc = max(dim.asc, dspdim.ascent());
236 dim.des = max(dim.des, dspdim.descent());
238 mathed_string_dim(slFont, from_ascii("LyX"), slDim);
239 dim.wid += max(dspdim.width(), slDim.wid) + 8;
241 dim.wid += dspdim.width() + 8;
244 // make the name cell vertically centered, and 5 pixel lines margin
245 int real_asc = dim.asc - dim0.ascent() / 2;
246 int real_des = dim.des + dim0.ascent() / 2;
247 dim.asc = max(real_asc, real_des) + dim0.ascent() / 2 + 5;
248 dim.des = max(real_asc, real_des) - dim0.ascent() / 2 + 5;
251 // add sublabels below
253 mathed_string_dim(slFont, from_ascii("Mg"), slDim);
254 dim.des += 2 + slDim.asc + slDim.des + 2;
257 setDimCache(mi, dim);
261 void MathMacroTemplate::draw(PainterInfo & pi, int x, int y) const
263 FontSetChanger dummy1(pi.base, from_ascii("mathnormal"));
264 StyleChanger dummy2(pi.base, LM_ST_TEXT);
266 setPosCache(pi, x, y);
267 Dimension const dim = dimension(*pi.base.bv);
270 FontInfo slFont = sane_font;
271 slFont.setColor(Color_command);
276 bool edit = editing(pi.base.bv);
280 mathed_string_dim(slFont, from_ascii("Mg"), slDim);
281 int const sly = y + cellDim_.des + slDim.asc + 2;
284 bool valid = validMacro();
285 FontInfo font = pi.base.font;
287 font.setColor(Color_latex);
289 font.setColor(Color_error);
292 int const a = y - dim.asc + 1;
293 int const w = dim.wid - 2;
294 int const h = dim.height() - 2;
295 pi.pain.rectangle(x, a, w, h, Color_mathframe);
299 pi.pain.text(x, y, from_ascii("\\"), font);
300 x += mathed_string_width(font, from_ascii("\\"));
303 PainterInfo namepi = pi;
304 namepi.base.font = font;
305 cell(0).draw(namepi, x, y);
306 x += cell(0).dimension(*pi.base.bv).width();
309 label_.draw(pi, x, y);
310 x += label_.dimension(*pi.base.bv).width();
313 cell(defIdx()).draw(pi, x + 2, y);
314 int const w1 = cell(defIdx()).dimension(*pi.base.bv).width();
315 pi.pain.rectangle(x, y - dim.ascent() + 3, w1 + 4, cellDim_.height() - 6,
319 // hide empty display cells if not edited
320 if (edit || !cell(displayIdx()).empty()) {
323 pi.pain.text(x + 2, sly, from_ascii("LyX"), slFont);
326 cell(displayIdx()).draw(pi, x + 2, y);
327 int const w2 = cell(displayIdx()).dimension(*pi.base.bv).width();
328 pi.pain.rectangle(x, y - dim.ascent() + 3, w2 + 4, cellDim_.height() - 6,
331 // at least update the coord cache if not drawn
332 cell(displayIdx()).setXY(*pi.base.bv, x + 2, y);
337 void MathMacroTemplate::edit(Cursor & cur, bool left)
339 cur.updateFlags(Update::Force);
340 InsetMathNest::edit(cur, left);
344 bool MathMacroTemplate::notifyCursorLeaves(Cursor & cur)
346 cur.updateFlags(Update::Force);
347 return InsetMathNest::notifyCursorLeaves(cur);
351 void MathMacroTemplate::removeArguments(Cursor & cur, int from, int to) {
352 for (DocIterator it = doc_iterator_begin(*this); it; it.forwardChar()) {
355 if (it.nextInset()->lyxCode() != MATHMACROARG_CODE)
357 MathMacroArgument * arg = static_cast<MathMacroArgument*>(it.nextInset());
358 int n = arg->number() - 1;
359 if (from <= n && n <= to) {
360 int cellSlice = cur.find(it.cell());
361 if (cellSlice != -1 && cur[cellSlice].pos() > it.pos())
362 --cur[cellSlice].pos();
364 it.cell().erase(it.pos());
370 void MathMacroTemplate::shiftArguments(size_t from, int by) {
371 for (DocIterator it = doc_iterator_begin(*this); it; it.forwardChar()) {
374 if (it.nextInset()->lyxCode() != MATHMACROARG_CODE)
376 MathMacroArgument * arg = static_cast<MathMacroArgument*>(it.nextInset());
377 if (arg->number() >= from + 1)
378 arg->setNumber(arg->number() + by);
383 // FIXME: factorize those functions here with a functional style, maybe using Boost's function
386 void fixMacroInstancesAddRemove(Cursor const & from, docstring const & name, int n, bool insert) {
389 for (; dit; dit.forwardPos()) {
390 // only until a macro is redefined
391 if (dit.inset().lyxCode() == MATHMACRO_CODE) {
392 MathMacroTemplate const & macroTemplate
393 = static_cast<MathMacroTemplate const &>(dit.inset());
394 if (macroTemplate.name() == name)
398 // in front of macro instance?
399 Inset * inset = dit.nextInset();
401 InsetMath * insetMath = inset->asInsetMath();
403 MathMacro * macro = insetMath->asMacro();
404 if (macro && macro->name() == name && macro->folded()) {
405 // found macro instance
407 macro->insertArgument(n);
409 macro->removeArgument(n);
417 void fixMacroInstancesOptional(Cursor const & from, docstring const & name, int optionals) {
420 for (; dit; dit.forwardPos()) {
421 // only until a macro is redefined
422 if (dit.inset().lyxCode() == MATHMACRO_CODE) {
423 MathMacroTemplate const & macroTemplate
424 = static_cast<MathMacroTemplate const &>(dit.inset());
425 if (macroTemplate.name() == name)
429 // in front of macro instance?
430 Inset * inset = dit.nextInset();
432 InsetMath * insetMath = inset->asInsetMath();
434 MathMacro * macro = insetMath->asMacro();
435 if (macro && macro->name() == name && macro->folded()) {
436 // found macro instance
437 macro->setOptionals(optionals);
446 void fixMacroInstancesFunctional(Cursor const & from,
447 docstring const & name, F & fix) {
450 for (; dit; dit.forwardPos()) {
451 // only until a macro is redefined
452 if (dit.inset().lyxCode() == MATHMACRO_CODE) {
453 MathMacroTemplate const & macroTemplate
454 = static_cast<MathMacroTemplate const &>(dit.inset());
455 if (macroTemplate.name() == name)
459 // in front of macro instance?
460 Inset * inset = dit.nextInset();
462 InsetMath * insetMath = inset->asInsetMath();
464 MathMacro * macro = insetMath->asMacro();
465 if (macro && macro->name() == name && macro->folded())
473 void MathMacroTemplate::insertParameter(Cursor & cur, int pos, bool greedy)
475 if (pos <= numargs_ && pos >= optionals_ && numargs_ < 9) {
477 shiftArguments(pos, 1);
480 cell(defIdx()).push_back(MathAtom(new MathMacroArgument(pos + 1)));
481 if (!cell(displayIdx()).empty())
482 cell(displayIdx()).push_back(MathAtom(new MathMacroArgument(pos + 1)));
486 dit.leaveInset(*this);
487 // TODO: this was dit.forwardPosNoDescend before. Check that this is the same
488 dit.top().forwardPos();
490 // fix macro instances
491 fixMacroInstancesAddRemove(dit, name(), pos, true);
497 void MathMacroTemplate::removeParameter(Cursor & cur, int pos, bool greedy)
499 if (pos < numargs_ && pos >= 0) {
501 removeArguments(cur, pos, pos);
502 shiftArguments(pos + 1, -1);
504 // removed optional parameter?
505 if (pos < optionals_) {
507 optionalValues_[pos] = cell(optIdx(pos));
508 cells_.erase(cells_.begin() + optIdx(pos));
511 int macroSlice = cur.find(this);
512 if (macroSlice != -1) {
513 if (cur[macroSlice].idx() == optIdx(pos)) {
514 cur.cutOff(macroSlice);
515 cur[macroSlice].idx() = 1;
516 cur[macroSlice].pos() = 0;
517 } else if (cur[macroSlice].idx() > optIdx(pos))
518 --cur[macroSlice].idx();
523 // fix macro instances
524 //boost::function<void(MathMacro *)> fix = _1->insertArgument(n);
525 //fixMacroInstancesFunctional(dit, name(), fix);
527 dit.leaveInset(*this);
528 // TODO: this was dit.forwardPosNoDescend before. Check that this is the same
529 dit.top().forwardPos();
530 fixMacroInstancesAddRemove(dit, name(), pos, false);
536 void MathMacroTemplate::makeOptional(Cursor & cur) {
537 if (numargs_ > 0 && optionals_ < numargs_) {
539 cells_.insert(cells_.begin() + optIdx(optionals_ - 1), optionalValues_[optionals_ - 1]);
541 int macroSlice = cur.find(this);
542 if (macroSlice != -1 && cur[macroSlice].idx() >= optIdx(optionals_ - 1))
543 ++cur[macroSlice].idx();
545 // fix macro instances
547 dit.leaveInset(*this);
548 // TODO: this was dit.forwardPosNoDescend before. Check that this is the same
549 dit.top().forwardPos();
550 fixMacroInstancesOptional(dit, name(), optionals_);
555 void MathMacroTemplate::makeNonOptional(Cursor & cur) {
556 if (numargs_ > 0 && optionals_ > 0) {
559 // store default value for later if the use changes his mind
560 optionalValues_[optionals_] = cell(optIdx(optionals_));
561 cells_.erase(cells_.begin() + optIdx(optionals_));
564 int macroSlice = cur.find(this);
565 if (macroSlice != -1) {
566 if (cur[macroSlice].idx() > optIdx(optionals_))
567 --cur[macroSlice].idx();
568 else if (cur[macroSlice].idx() == optIdx(optionals_)) {
569 cur.cutOff(macroSlice);
570 cur[macroSlice].idx() = optIdx(optionals_);
571 cur[macroSlice].pos() = 0;
575 // fix macro instances
577 dit.leaveInset(*this);
578 // TODO: this was dit.forwardPosNoDescend before. Check that this is the same
579 dit.top().forwardPos();
580 fixMacroInstancesOptional(dit, name(), optionals_);
585 void MathMacroTemplate::doDispatch(Cursor & cur, FuncRequest & cmd)
587 string const arg = to_utf8(cmd.argument());
588 switch (cmd.action) {
590 case LFUN_MATH_MACRO_ADD_PARAM:
592 cur.recordUndoFullDocument();
593 size_t pos = numargs_;
595 pos = (size_t)convert<int>(arg) - 1; // it is checked for >=0 in getStatus
596 insertParameter(cur, pos);
601 case LFUN_MATH_MACRO_REMOVE_PARAM:
603 cur.recordUndoFullDocument();
604 size_t pos = numargs_ - 1;
606 pos = (size_t)convert<int>(arg) - 1; // it is checked for >=0 in getStatus
607 removeParameter(cur, pos);
611 case LFUN_MATH_MACRO_APPEND_GREEDY_PARAM:
613 cur.recordUndoFullDocument();
614 insertParameter(cur, numargs_, true);
618 case LFUN_MATH_MACRO_REMOVE_GREEDY_PARAM:
620 cur.recordUndoFullDocument();
621 removeParameter(cur, numargs_ - 1, true);
625 case LFUN_MATH_MACRO_MAKE_OPTIONAL:
626 cur.recordUndoFullDocument();
630 case LFUN_MATH_MACRO_MAKE_NONOPTIONAL:
631 cur.recordUndoFullDocument();
632 makeNonOptional(cur);
635 case LFUN_MATH_MACRO_ADD_OPTIONAL_PARAM:
637 cur.recordUndoFullDocument();
638 insertParameter(cur, optionals_);
643 case LFUN_MATH_MACRO_REMOVE_OPTIONAL_PARAM:
644 if (optionals_ > 0) {
645 cur.recordUndoFullDocument();
646 removeParameter(cur, optionals_ - 1);
649 case LFUN_MATH_MACRO_ADD_GREEDY_OPTIONAL_PARAM:
650 if (numargs_ == optionals_) {
651 cur.recordUndoFullDocument();
652 insertParameter(cur, 0, true);
658 InsetMathNest::doDispatch(cur, cmd);
664 bool MathMacroTemplate::getStatus(Cursor & /*cur*/, FuncRequest const & cmd,
665 FuncStatus & flag) const
668 string const arg = to_utf8(cmd.argument());
669 switch (cmd.action) {
670 case LFUN_MATH_MACRO_ADD_PARAM: {
671 int num = numargs_ + 1;
673 num = convert<int>(arg);
674 bool on = (num >= optionals_
675 && numargs_ < 9 && num <= numargs_ + 1);
680 case LFUN_MATH_MACRO_APPEND_GREEDY_PARAM:
681 flag.enabled(numargs_ < 9);
684 case LFUN_MATH_MACRO_REMOVE_PARAM: {
687 num = convert<int>(arg);
688 flag.enabled(num >= 1 && num <= numargs_);
692 case LFUN_MATH_MACRO_MAKE_OPTIONAL:
693 flag.enabled(numargs_ > 0
694 && optionals_ < numargs_
695 && type_ != MacroTypeDef);
698 case LFUN_MATH_MACRO_MAKE_NONOPTIONAL:
699 flag.enabled(optionals_ > 0
700 && type_ != MacroTypeDef);
703 case LFUN_MATH_MACRO_ADD_OPTIONAL_PARAM:
704 flag.enabled(numargs_ < 9);
707 case LFUN_MATH_MACRO_REMOVE_OPTIONAL_PARAM:
708 flag.enabled(optionals_ > 0);
711 case LFUN_MATH_MACRO_ADD_GREEDY_OPTIONAL_PARAM:
712 flag.enabled(numargs_ == 0
713 && type_ != MacroTypeDef);
716 case LFUN_IN_MATHMACROTEMPLATE:
728 void MathMacroTemplate::read(Buffer const &, Lexer & lex)
731 mathed_parse_cell(ar, lex.getStream());
732 if (ar.size() != 1 || !ar[0]->asMacroTemplate()) {
733 lyxerr << "Cannot read macro from '" << ar << "'" << endl;
734 lyxerr << "Read: " << to_utf8(asString(ar)) << endl;
737 operator=( *(ar[0]->asMacroTemplate()) );
741 void MathMacroTemplate::write(Buffer const &, ostream & os) const
743 odocstringstream oss;
744 WriteStream wi(oss, false, false);
745 oss << "FormulaMacro\n";
747 os << to_utf8(oss.str());
751 void MathMacroTemplate::write(WriteStream & os) const
757 void MathMacroTemplate::write(WriteStream & os, bool overwriteRedefinition) const
759 if (type_ == MacroTypeDef) {
760 os << "\\def\\" << name().c_str();
761 for (int i = 1; i <= numargs_; ++i)
764 // newcommand or renewcommand
765 if (redefinition_ && !overwriteRedefinition)
766 os << "\\renewcommand";
768 os << "\\newcommand";
769 os << "{\\" << name().c_str() << '}';
771 os << '[' << numargs_ << ']';
775 // in latex only one optional possible, simulate the others
776 if (optionals_ >= 1) {
777 docstring optValue = asString(cell(optIdx(0)));
778 if (optValue.find(']') != docstring::npos)
779 os << "[{" << cell(optIdx(0)) << "}]";
781 os << "[" << cell(optIdx(0)) << "]";
784 // in lyx we handle all optionals as real optionals
785 for (int i = 0; i < optionals_; ++i) {
786 docstring optValue = asString(cell(optIdx(i)));
787 if (optValue.find(']') != docstring::npos)
788 os << "[{" << cell(optIdx(i)) << "}]";
790 os << "[" << cell(optIdx(i)) << "]";
795 os << "{" << cell(defIdx()) << "}";
798 // writing .tex. done.
801 // writing .lyx, write special .tex export only if necessary
802 if (!cell(displayIdx()).empty())
803 os << "\n{" << cell(displayIdx()) << '}';
808 int MathMacroTemplate::plaintext(Buffer const & buf, odocstream & os,
809 OutputParams const &) const
811 static docstring const str = '[' + buf.B_("math macro") + ']';
818 bool MathMacroTemplate::validName() const
820 docstring n = name();
826 // converting back and force doesn't swallow anything?
829 if (asString(ma) != n)
833 for (size_t i = 0; i < n.size(); ++i) {
834 if (!(n[i] >= 'a' && n[i] <= 'z') &&
835 !(n[i] >= 'A' && n[i] <= 'Z'))
843 bool MathMacroTemplate::validMacro() const
849 bool MathMacroTemplate::fixNameAndCheckIfValid()
851 // check all the characters/insets in the name cell
853 MathData & data = cell(0);
854 while (i < data.size()) {
855 InsetMathChar const * cinset = data[i]->asCharInset();
857 // valid character in [a-zA-Z]?
858 char_type c = cinset->getChar();
859 if ((c >= 'a' && c <= 'z')
860 || (c >= 'A' && c <= 'Z')) {
870 // now it should be valid if anything in the name survived
871 return data.size() > 0;
875 void MathMacroTemplate::getDefaults(vector<docstring> & defaults) const
877 defaults.resize(numargs_);
878 for (int i = 0; i < optionals_; ++i)
879 defaults[i] = asString(cell(optIdx(i)));
883 docstring MathMacroTemplate::definition() const
885 return asString(cell(defIdx()));
889 docstring MathMacroTemplate::displayDefinition() const
891 return asString(cell(displayIdx()));
895 size_t MathMacroTemplate::numArgs() const
901 size_t MathMacroTemplate::numOptionals() const
907 void MathMacroTemplate::infoize(odocstream & os) const
909 os << "Math Macro: \\" << name();