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), type_(from_ascii("newcommand"))
99 MathMacroTemplate::MathMacroTemplate(docstring const & name, int numargs,
100 int optionals, docstring const & type,
101 vector<MathData> const & optionalValues,
102 MathData const & def, MathData const & display)
103 : InsetMathNest(optionals + 3), numargs_(numargs),
104 optionals_(optionals), optionalValues_(optionalValues), type_(type)
109 lyxerr << "MathMacroTemplate::MathMacroTemplate: wrong # of arguments: "
112 asArray(name, cell(0));
113 optionalValues_.resize(9);
114 for (int i = 0; i < optionals_; ++i)
115 cell(optIdx(i)) = optionalValues_[i];
116 cell(defIdx()) = def;
117 cell(displayIdx()) = display;
121 MathMacroTemplate::MathMacroTemplate(docstring const & str)
122 : InsetMathNest(3), numargs_(0), optionals_(0),
123 type_(from_ascii("newcommand"))
128 mathed_parse_cell(ar, str);
129 if (ar.size() != 1 || !ar[0]->asMacroTemplate()) {
130 lyxerr << "Cannot read macro from '" << ar << "'" << endl;
131 asArray(from_ascii("invalidmacro"), cell(0));
132 // FIXME: The macro template does not make sense after this.
133 // The whole parsing should not be in a constructor which
134 // has no chance to report failure.
137 operator=( *(ar[0]->asMacroTemplate()) );
141 Inset * MathMacroTemplate::clone() const
143 return new MathMacroTemplate(*this);
147 docstring MathMacroTemplate::name() const
149 return asString(cell(0));
153 void MathMacroTemplate::metrics(MetricsInfo & mi, Dimension & dim) const
155 FontSetChanger dummy1(mi.base, from_ascii("mathnormal"));
156 StyleChanger dummy2(mi.base, LM_ST_TEXT);
159 MacroData const * macro = 0;
160 if (validName() && mi.macrocontext.has(name())) {
161 macro = &mi.macrocontext.get(name());
162 if (type_ == from_ascii("newcommand") || type_ == from_ascii("renewcommand")) {
163 // use the MacroData::redefinition_ information instead of MacroContext::has
164 // because the macro is known here already anyway to detect recursive definitions
165 type_ = macro->redefinition() ? from_ascii("renewcommand") : from_ascii("newcommand");
169 // create label "{#1}{#2}:="
172 for (; i < optionals_; ++i) {
173 label_.push_back(MathAtom(new InsetMathChar('[')));
174 label_.push_back(MathAtom(new InsetMathWrapper(&cell(1 + i))));
175 label_.push_back(MathAtom(new InsetMathChar(']')));
177 for (; i < numargs_; ++i) {
179 arg.push_back(MathAtom(new MathMacroArgument(i + 1)));
180 label_.push_back(MathAtom(new InsetMathBrace(arg)));
182 label_.push_back(MathAtom(new InsetMathChar(':')));
183 label_.push_back(MathAtom(new InsetMathChar('=')));
194 cell(0).metrics(mi, dim0);
195 label_.metrics(mi, labeldim);
196 cell(defIdx()).metrics(mi, defdim);
197 cell(displayIdx()).metrics(mi, dspdim);
202 // calculate metrics taking all cells and labels into account
203 dim.wid = 2 + mathed_string_width(mi.base.font, from_ascii("\\")) +
206 defdim.width() + 16 + dspdim.width() + 2;
208 dim.asc = dim0.ascent();
209 dim.asc = max(dim.asc, labeldim.ascent());
210 dim.asc = max(dim.asc, defdim.ascent());
211 dim.asc = max(dim.asc, dspdim.ascent());
213 dim.des = dim0.descent();
214 dim.des = max(dim.des, labeldim.descent());
215 dim.des = max(dim.des, defdim.descent());
216 dim.des = max(dim.des, dspdim.descent());
218 // make the name cell vertically centered, and 5 pixel lines margin
219 int real_asc = dim.asc - dim0.ascent() / 2;
220 int real_des = dim.des + dim0.ascent() / 2;
221 dim.asc = max(real_asc, real_des) + dim0.ascent() / 2 + 5;
222 dim.des = max(real_asc, real_des) - dim0.ascent() / 2 + 5;
224 setDimCache(mi, dim);
228 void MathMacroTemplate::draw(PainterInfo & pi, int x, int y) const
230 FontSetChanger dummy1(pi.base, from_ascii("mathnormal"));
231 StyleChanger dummy2(pi.base, LM_ST_TEXT);
233 setPosCache(pi, x, y);
234 Dimension const dim = dimension(*pi.base.bv);
237 bool valid = validMacro();
238 FontInfo font = pi.base.font;
240 font.setColor(Color_latex);
242 font.setColor(Color_error);
245 int const a = y - dim.asc + 1;
246 int const w = dim.wid - 2;
247 int const h = dim.height() - 2;
248 pi.pain.rectangle(x, a, w, h, Color_mathframe);
252 pi.pain.text(x, y, from_ascii("\\"), font);
253 x += mathed_string_width(font, from_ascii("\\"));
256 PainterInfo namepi = pi;
257 namepi.base.font = font;
258 cell(0).draw(namepi, x, y);
259 x += cell(0).dimension(*pi.base.bv).width();
262 label_.draw(pi, x, y);
263 x += label_.dimension(*pi.base.bv).width();
266 cell(defIdx()).draw(pi, x + 2, y);
267 int const w1 = cell(defIdx()).dimension(*pi.base.bv).width();
268 pi.pain.rectangle(x, y - dim.ascent() + 3, w1 + 4, dim.height() - 6, Color_mathline);
272 cell(displayIdx()).draw(pi, x + 2, y);
273 int const w2 = cell(displayIdx()).dimension(*pi.base.bv).width();
274 pi.pain.rectangle(x, y - dim.ascent() + 3, w2 + 4, dim.height() - 6, Color_mathline);
278 void MathMacroTemplate::removeArguments(Cursor & cur, int from, int to) {
279 for (DocIterator it = doc_iterator_begin(*this); it; it.forwardChar()) {
282 if (it.nextInset()->lyxCode() != MATHMACROARG_CODE)
284 MathMacroArgument * arg = static_cast<MathMacroArgument*>(it.nextInset());
285 int n = arg->number() - 1;
286 if (from <= n && n <= to) {
287 int cellSlice = cur.find(it.cell());
288 if (cellSlice != -1 && cur[cellSlice].pos() > it.pos())
289 --cur[cellSlice].pos();
291 it.cell().erase(it.pos());
297 void MathMacroTemplate::shiftArguments(size_t from, int by) {
298 for (DocIterator it = doc_iterator_begin(*this); it; it.forwardChar()) {
301 if (it.nextInset()->lyxCode() != MATHMACROARG_CODE)
303 MathMacroArgument * arg = static_cast<MathMacroArgument*>(it.nextInset());
304 if (arg->number() >= from + 1)
305 arg->setNumber(arg->number() + by);
310 // FIXME: factorize those functions here with a functional style, maybe using Boost's function
313 void fixMacroInstancesAddRemove(Cursor const & from, docstring const & name, int n, bool insert) {
316 for (; dit; dit.forwardPos()) {
317 // only until a macro is redefined
318 if (dit.inset().lyxCode() == MATHMACRO_CODE) {
319 MathMacroTemplate const & macroTemplate
320 = static_cast<MathMacroTemplate const &>(dit.inset());
321 if (macroTemplate.name() == name)
325 // in front of macro instance?
326 Inset * inset = dit.nextInset();
328 InsetMath * insetMath = inset->asInsetMath();
330 MathMacro * macro = insetMath->asMacro();
331 if (macro && macro->name() == name && macro->folded()) {
332 // found macro instance
334 macro->insertArgument(n);
336 macro->removeArgument(n);
344 void fixMacroInstancesOptional(Cursor const & from, docstring const & name, int optionals) {
347 for (; dit; dit.forwardPos()) {
348 // only until a macro is redefined
349 if (dit.inset().lyxCode() == MATHMACRO_CODE) {
350 MathMacroTemplate const & macroTemplate
351 = static_cast<MathMacroTemplate const &>(dit.inset());
352 if (macroTemplate.name() == name)
356 // in front of macro instance?
357 Inset * inset = dit.nextInset();
359 InsetMath * insetMath = inset->asInsetMath();
361 MathMacro * macro = insetMath->asMacro();
362 if (macro && macro->name() == name && macro->folded()) {
363 // found macro instance
364 macro->setOptionals(optionals);
373 void fixMacroInstancesFunctional(Cursor const & from,
374 docstring const & name, F & fix) {
377 for (; dit; dit.forwardPos()) {
378 // only until a macro is redefined
379 if (dit.inset().lyxCode() == MATHMACRO_CODE) {
380 MathMacroTemplate const & macroTemplate
381 = static_cast<MathMacroTemplate const &>(dit.inset());
382 if (macroTemplate.name() == name)
386 // in front of macro instance?
387 Inset * inset = dit.nextInset();
389 InsetMath * insetMath = inset->asInsetMath();
391 MathMacro * macro = insetMath->asMacro();
392 if (macro && macro->name() == name && macro->folded())
400 void MathMacroTemplate::insertParameter(Cursor & cur, int pos, bool greedy)
402 if (pos <= numargs_ && pos >= optionals_ && numargs_ < 9) {
404 shiftArguments(pos, 1);
407 cell(defIdx()).push_back(MathAtom(new MathMacroArgument(pos + 1)));
408 if (!cell(displayIdx()).empty())
409 cell(displayIdx()).push_back(MathAtom(new MathMacroArgument(pos + 1)));
413 dit.leaveInset(*this);
414 // TODO: this was dit.forwardPosNoDescend before. Check that this is the same
415 dit.top().forwardPos();
417 // fix macro instances
418 fixMacroInstancesAddRemove(dit, name(), pos, true);
424 void MathMacroTemplate::removeParameter(Cursor & cur, int pos, bool greedy)
426 if (pos < numargs_ && pos >= 0) {
428 removeArguments(cur, pos, pos);
429 shiftArguments(pos + 1, -1);
431 // removed optional parameter?
432 if (pos < optionals_) {
434 optionalValues_[pos] = cell(optIdx(pos));
435 cells_.erase(cells_.begin() + optIdx(pos));
438 int macroSlice = cur.find(this);
439 if (macroSlice != -1) {
440 if (cur[macroSlice].idx() == optIdx(pos)) {
441 cur.cutOff(macroSlice);
442 cur[macroSlice].idx() = 1;
443 cur[macroSlice].pos() = 0;
444 } else if (cur[macroSlice].idx() > optIdx(pos))
445 --cur[macroSlice].idx();
450 // fix macro instances
451 //boost::function<void(MathMacro *)> fix = _1->insertArgument(n);
452 //fixMacroInstancesFunctional(dit, name(), fix);
454 dit.leaveInset(*this);
455 // TODO: this was dit.forwardPosNoDescend before. Check that this is the same
456 dit.top().forwardPos();
457 fixMacroInstancesAddRemove(dit, name(), pos, false);
463 void MathMacroTemplate::makeOptional(Cursor & cur) {
464 if (numargs_ > 0 && optionals_ < numargs_) {
466 cells_.insert(cells_.begin() + optIdx(optionals_ - 1), optionalValues_[optionals_ - 1]);
468 int macroSlice = cur.find(this);
469 if (macroSlice != -1 && cur[macroSlice].idx() >= optIdx(optionals_ - 1))
470 ++cur[macroSlice].idx();
472 // fix macro instances
474 dit.leaveInset(*this);
475 // TODO: this was dit.forwardPosNoDescend before. Check that this is the same
476 dit.top().forwardPos();
477 fixMacroInstancesOptional(dit, name(), optionals_);
482 void MathMacroTemplate::makeNonOptional(Cursor & cur) {
483 if (numargs_ > 0 && optionals_ > 0) {
486 // store default value for later if the use changes his mind
487 optionalValues_[optionals_] = cell(optIdx(optionals_));
488 cells_.erase(cells_.begin() + optIdx(optionals_));
491 int macroSlice = cur.find(this);
492 if (macroSlice != -1) {
493 if (cur[macroSlice].idx() > optIdx(optionals_))
494 --cur[macroSlice].idx();
495 else if (cur[macroSlice].idx() == optIdx(optionals_)) {
496 cur.cutOff(macroSlice);
497 cur[macroSlice].idx() = optIdx(optionals_);
498 cur[macroSlice].pos() = 0;
502 // fix macro instances
504 dit.leaveInset(*this);
505 // TODO: this was dit.forwardPosNoDescend before. Check that this is the same
506 dit.top().forwardPos();
507 fixMacroInstancesOptional(dit, name(), optionals_);
512 void MathMacroTemplate::doDispatch(Cursor & cur, FuncRequest & cmd)
514 string const arg = to_utf8(cmd.argument());
515 switch (cmd.action) {
517 case LFUN_MATH_MACRO_ADD_PARAM:
519 cur.recordUndoFullDocument();
520 size_t pos = numargs_;
522 pos = (size_t)convert<int>(arg) - 1; // it is checked for >=0 in getStatus
523 insertParameter(cur, pos);
528 case LFUN_MATH_MACRO_REMOVE_PARAM:
530 cur.recordUndoFullDocument();
531 size_t pos = numargs_ - 1;
533 pos = (size_t)convert<int>(arg) - 1; // it is checked for >=0 in getStatus
534 removeParameter(cur, pos);
538 case LFUN_MATH_MACRO_APPEND_GREEDY_PARAM:
540 cur.recordUndoFullDocument();
541 insertParameter(cur, numargs_, true);
545 case LFUN_MATH_MACRO_REMOVE_GREEDY_PARAM:
547 cur.recordUndoFullDocument();
548 removeParameter(cur, numargs_ - 1, true);
552 case LFUN_MATH_MACRO_MAKE_OPTIONAL:
553 cur.recordUndoFullDocument();
557 case LFUN_MATH_MACRO_MAKE_NONOPTIONAL:
558 cur.recordUndoFullDocument();
559 makeNonOptional(cur);
562 case LFUN_MATH_MACRO_ADD_OPTIONAL_PARAM:
564 cur.recordUndoFullDocument();
565 insertParameter(cur, optionals_);
570 case LFUN_MATH_MACRO_REMOVE_OPTIONAL_PARAM:
571 if (optionals_ > 0) {
572 cur.recordUndoFullDocument();
573 removeParameter(cur, optionals_ - 1);
576 case LFUN_MATH_MACRO_ADD_GREEDY_OPTIONAL_PARAM:
577 if (numargs_ == optionals_) {
578 cur.recordUndoFullDocument();
579 insertParameter(cur, 0, true);
585 InsetMathNest::doDispatch(cur, cmd);
591 bool MathMacroTemplate::getStatus(Cursor & /*cur*/, FuncRequest const & cmd,
592 FuncStatus & flag) const
595 string const arg = to_utf8(cmd.argument());
596 switch (cmd.action) {
597 case LFUN_MATH_MACRO_ADD_PARAM: {
598 int num = numargs_ + 1;
600 num = convert<int>(arg);
601 bool on = (num >= optionals_ && numargs_ < 9 && num <= numargs_ + 1);
606 case LFUN_MATH_MACRO_APPEND_GREEDY_PARAM:
607 flag.enabled(numargs_ < 9);
610 case LFUN_MATH_MACRO_REMOVE_PARAM: {
613 num = convert<int>(arg);
614 flag.enabled(num >= 1 && num <= numargs_);
618 case LFUN_MATH_MACRO_MAKE_OPTIONAL:
619 flag.enabled(numargs_ > 0 && optionals_ < numargs_ && type_ != from_ascii("def"));
622 case LFUN_MATH_MACRO_MAKE_NONOPTIONAL:
623 flag.enabled(optionals_ > 0 && type_ != from_ascii("def"));
626 case LFUN_MATH_MACRO_ADD_OPTIONAL_PARAM:
627 flag.enabled(numargs_ < 9);
630 case LFUN_MATH_MACRO_REMOVE_OPTIONAL_PARAM:
631 flag.enabled(optionals_ > 0);
634 case LFUN_MATH_MACRO_ADD_GREEDY_OPTIONAL_PARAM:
635 flag.enabled(numargs_ == 0 && type_ != from_ascii("def"));
646 void MathMacroTemplate::read(Buffer const &, Lexer & lex)
649 mathed_parse_cell(ar, lex.getStream());
650 if (ar.size() != 1 || !ar[0]->asMacroTemplate()) {
651 lyxerr << "Cannot read macro from '" << ar << "'" << endl;
652 lyxerr << "Read: " << to_utf8(asString(ar)) << endl;
655 operator=( *(ar[0]->asMacroTemplate()) );
659 void MathMacroTemplate::write(Buffer const &, ostream & os) const
661 odocstringstream oss;
662 WriteStream wi(oss, false, false);
663 oss << "FormulaMacro\n";
665 os << to_utf8(oss.str());
669 void MathMacroTemplate::write(WriteStream & os) const
671 if (type_ == "def") {
672 os << "\\def\\" << name().c_str();
673 for (int i = 1; i <= numargs_; ++i)
676 // newcommand or renewcommand
677 os << "\\" << type_.c_str() << "{\\" << name().c_str() << '}';
679 os << '[' << numargs_ << ']';
683 // in latex only one optional possible, simulate the others
684 if (optionals_ >= 1) {
685 docstring optValue = asString(cell(optIdx(0)));
686 if (optValue.find(']') != docstring::npos)
687 os << "[{" << cell(optIdx(0)) << "}]";
689 os << "[" << cell(optIdx(0)) << "]";
692 // in lyx we handle all optionals as real optionals
693 for (int i = 0; i < optionals_; ++i) {
694 docstring optValue = asString(cell(optIdx(i)));
695 if (optValue.find(']') != docstring::npos)
696 os << "[{" << cell(optIdx(i)) << "}]";
698 os << "[" << cell(optIdx(i)) << "]";
703 os << "{" << cell(defIdx()) << "}";
706 // writing .tex. done.
709 // writing .lyx, write special .tex export only if necessary
710 if (!cell(displayIdx()).empty())
711 os << "\n{" << cell(displayIdx()) << '}';
716 int MathMacroTemplate::plaintext(Buffer const & buf, odocstream & os,
717 OutputParams const &) const
719 static docstring const str = '[' + buf.B_("math macro") + ']';
726 bool MathMacroTemplate::validName() const
728 docstring n = name();
734 // converting back and force doesn't swallow anything?
737 if (asString(ma) != n)
741 for (size_t i = 0; i < n.size(); ++i) {
742 if (!(n[i] >= 'a' && n[i] <= 'z') &&
743 !(n[i] >= 'A' && n[i] <= 'Z'))
751 bool MathMacroTemplate::validMacro() const
757 MacroData MathMacroTemplate::asMacroData() const
759 vector<docstring> defaults(numargs_);
760 for (int i = 0; i < optionals_; ++i)
761 defaults[i] = asString(cell(optIdx(i)));
762 return MacroData(asString(cell(defIdx())), defaults,
763 numargs_, optionals_, asString(cell(displayIdx())), string());