2 * \file math_macrotemplate.C
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 "DocIterator.h"
14 #include "InsetMathBrace.h"
15 #include "InsetMathChar.h"
16 #include "InsetMathSqrt.h"
17 #include "MathMacro.h"
18 #include "MathMacroArgument.h"
19 #include "MathMacroTemplate.h"
20 #include "MathStream.h"
21 #include "MathParser.h"
22 #include "MathSupport.h"
23 #include "MathMacroArgument.h"
29 #include "DispatchResult.h"
30 #include "FuncRequest.h"
31 #include "FuncStatus.h"
36 #include "frontends/FontMetrics.h"
37 #include "frontends/Painter.h"
39 #include "support/convert.h"
40 #include "support/lstrings.h"
44 #include <boost/assert.hpp>
45 #include <boost/bind.hpp>
46 #include <boost/function.hpp>
53 using support::bformat;
59 class InsetMathWrapper : public InsetMath {
62 InsetMathWrapper(MathData const * value) : value_(value) {}
64 void metrics(MetricsInfo & mi, Dimension & dim) const;
66 void draw(PainterInfo &, int x, int y) const;
70 Inset * clone() const;
72 MathData const * value_;
76 Inset * InsetMathWrapper::clone() const
78 return new InsetMathWrapper(*this);
82 void InsetMathWrapper::metrics(MetricsInfo & mi, Dimension & dim) const
84 value_->metrics(mi, dim);
85 //metricsMarkers2(dim);
89 void InsetMathWrapper::draw(PainterInfo & pi, int x, int y) const
91 value_->draw(pi, x, y);
92 //drawMarkers(pi, x, y);
96 MathMacroTemplate::MathMacroTemplate()
97 : InsetMathNest(3), numargs_(0), optionals_(0), type_(from_ascii("newcommand"))
103 MathMacroTemplate::MathMacroTemplate(docstring const & name, int numargs, int optionals,
104 docstring const & type,
105 std::vector<MathData> const & optionalValues,
106 MathData const & def, MathData const & display)
107 : InsetMathNest(optionals + 3), numargs_(numargs),
108 optionals_(optionals), optionalValues_(optionalValues), type_(type)
113 lyxerr << "MathMacroTemplate::MathMacroTemplate: wrong # of arguments: "
114 << numargs_ << std::endl;
116 asArray(name, cell(0));
117 optionalValues_.resize(9);
118 for (int i = 0; i < optionals_; ++i)
119 cell(optIdx(i)) = optionalValues_[i];
120 cell(defIdx()) = def;
121 cell(displayIdx()) = display;
125 MathMacroTemplate::MathMacroTemplate(docstring const & str)
126 : InsetMathNest(3), numargs_(0)
131 mathed_parse_cell(ar, str);
132 if (ar.size() != 1 || !ar[0]->asMacroTemplate()) {
133 lyxerr << "Cannot read macro from '" << ar << "'" << endl;
136 operator=( *(ar[0]->asMacroTemplate()) );
140 Inset * MathMacroTemplate::clone() const
142 return new MathMacroTemplate(*this);
146 docstring MathMacroTemplate::name() const
148 return asString(cell(0));
152 void MathMacroTemplate::metrics(MetricsInfo & mi, Dimension & dim) const
154 FontSetChanger dummy1(mi.base, from_ascii("mathnormal"));
155 StyleChanger dummy2(mi.base, LM_ST_TEXT);
158 MacroData const * macro = 0;
159 if (validName() && mi.macrocontext.has(name())) {
160 macro = &mi.macrocontext.get(name());
161 if (type_ == from_ascii("newcommand") || type_ == from_ascii("renewcommand")) {
162 // use the MacroData::redefinition_ information instead of MacroContext::has
163 // because the macro is known here already anyway to detect recursive definitions
164 type_ = macro->redefinition() ? from_ascii("renewcommand") : from_ascii("newcommand");
168 // create label "{#1}{#2}:="
171 for (; i < optionals_; ++i) {
172 label_.push_back(MathAtom(new InsetMathChar('[')));
173 label_.push_back(MathAtom(new InsetMathWrapper(&cell(1 + i))));
174 label_.push_back(MathAtom(new InsetMathChar(']')));
176 for (; i < numargs_; ++i) {
178 arg.push_back(MathAtom(new MathMacroArgument(i + 1)));
179 label_.push_back(MathAtom(new InsetMathBrace(arg)));
181 label_.push_back(MathAtom(new InsetMathChar(':')));
182 label_.push_back(MathAtom(new InsetMathChar('=')));
193 cell(0).metrics(mi, dim0);
194 label_.metrics(mi, labeldim);
195 cell(defIdx()).metrics(mi, defdim);
196 cell(displayIdx()).metrics(mi, dspdim);
201 // calculate metrics taking all cells and labels into account
202 dim.wid = 2 + mathed_string_width(mi.base.font, from_ascii("\\")) +
205 defdim.width() + 16 + dspdim.width() + 2;
207 dim.asc = dim0.ascent();
208 dim.asc = std::max(dim.asc, labeldim.ascent());
209 dim.asc = std::max(dim.asc, defdim.ascent());
210 dim.asc = std::max(dim.asc, dspdim.ascent());
212 dim.des = dim0.descent();
213 dim.des = std::max(dim.des, labeldim.descent());
214 dim.des = std::max(dim.des, defdim.descent());
215 dim.des = std::max(dim.des, dspdim.descent());
217 // make the name cell vertically centered, and 5 pixel lines margin
218 int real_asc = dim.asc - dim0.ascent() / 2;
219 int real_des = dim.des + dim0.ascent() / 2;
220 dim.asc = std::max(real_asc, real_des) + dim0.ascent() / 2 + 5;
221 dim.des = std::max(real_asc, real_des) - dim0.ascent() / 2 + 5;
223 setDimCache(mi, dim);
227 void MathMacroTemplate::draw(PainterInfo & pi, int x, int y) const
229 FontSetChanger dummy1(pi.base, from_ascii("mathnormal"));
230 StyleChanger dummy2(pi.base, LM_ST_TEXT);
232 setPosCache(pi, x, y);
233 Dimension const dim = dimension(*pi.base.bv);
236 bool valid = validMacro();
237 FontInfo font = pi.base.font;
239 font.setColor(Color_latex);
241 font.setColor(Color_error);
244 int const a = y - dim.asc + 1;
245 int const w = dim.wid - 2;
246 int const h = dim.height() - 2;
247 pi.pain.rectangle(x, a, w, h, Color_mathframe);
251 pi.pain.text(x, y, from_ascii("\\"), font);
252 x += mathed_string_width(font, from_ascii("\\"));
255 PainterInfo namepi = pi;
256 namepi.base.font = font;
257 cell(0).draw(namepi, x, y);
258 x += cell(0).dimension(*pi.base.bv).width();
261 label_.draw(pi, x, y);
262 x += label_.dimension(*pi.base.bv).width();
265 cell(defIdx()).draw(pi, x + 2, y);
266 int const w1 = cell(defIdx()).dimension(*pi.base.bv).width();
267 pi.pain.rectangle(x, y - dim.ascent() + 3, w1 + 4, dim.height() - 6, Color_mathline);
271 cell(displayIdx()).draw(pi, x + 2, y);
272 int const w2 = cell(displayIdx()).dimension(*pi.base.bv).width();
273 pi.pain.rectangle(x, y - dim.ascent() + 3, w2 + 4, dim.height() - 6, Color_mathline);
277 void MathMacroTemplate::removeArguments(Cursor & cur, int from, int to) {
278 for (DocIterator it = doc_iterator_begin(*this); it; it.forwardChar()) {
281 if (it.nextInset()->lyxCode() != MATHMACROARG_CODE)
283 MathMacroArgument * arg = static_cast<MathMacroArgument*>(it.nextInset());
284 int n = arg->number() - 1;
285 if (from <= n && n <= to) {
286 int cellSlice = cur.find(it.cell());
287 if (cellSlice != -1 && cur[cellSlice].pos() > it.pos())
288 --cur[cellSlice].pos();
290 it.cell().erase(it.pos());
296 void MathMacroTemplate::shiftArguments(size_t from, int by) {
297 for (DocIterator it = doc_iterator_begin(*this); it; it.forwardChar()) {
300 if (it.nextInset()->lyxCode() != MATHMACROARG_CODE)
302 MathMacroArgument * arg = static_cast<MathMacroArgument*>(it.nextInset());
303 if (arg->number() >= from + 1)
304 arg->setNumber(arg->number() + by);
309 // FIXME: factorize those functions here with a functional style, maybe using Boost's function
312 void fixMacroInstancesAddRemove(Cursor const & from, docstring const & name, int n, bool insert) {
315 for (; dit; dit.forwardPos()) {
316 // only until a macro is redefined
317 if (dit.inset().lyxCode() == MATHMACRO_CODE) {
318 MathMacroTemplate const & macroTemplate
319 = static_cast<MathMacroTemplate const &>(dit.inset());
320 if (macroTemplate.name() == name)
324 // in front of macro instance?
325 Inset * inset = dit.nextInset();
327 InsetMath * insetMath = inset->asInsetMath();
329 MathMacro * macro = insetMath->asMacro();
330 if (macro && macro->name() == name && macro->folded()) {
331 // found macro instance
333 macro->insertArgument(n);
335 macro->removeArgument(n);
343 void fixMacroInstancesOptional(Cursor const & from, docstring const & name, int optionals) {
346 for (; dit; dit.forwardPos()) {
347 // only until a macro is redefined
348 if (dit.inset().lyxCode() == MATHMACRO_CODE) {
349 MathMacroTemplate const & macroTemplate
350 = static_cast<MathMacroTemplate const &>(dit.inset());
351 if (macroTemplate.name() == name)
355 // in front of macro instance?
356 Inset * inset = dit.nextInset();
358 InsetMath * insetMath = inset->asInsetMath();
360 MathMacro * macro = insetMath->asMacro();
361 if (macro && macro->name() == name && macro->folded()) {
362 // found macro instance
363 macro->setOptionals(optionals);
372 void fixMacroInstancesFunctional(Cursor const & from,
373 docstring const & name, F & fix) {
376 for (; dit; dit.forwardPos()) {
377 // only until a macro is redefined
378 if (dit.inset().lyxCode() == MATHMACRO_CODE) {
379 MathMacroTemplate const & macroTemplate
380 = static_cast<MathMacroTemplate const &>(dit.inset());
381 if (macroTemplate.name() == name)
385 // in front of macro instance?
386 Inset * inset = dit.nextInset();
388 InsetMath * insetMath = inset->asInsetMath();
390 MathMacro * macro = insetMath->asMacro();
391 if (macro && macro->name() == name && macro->folded())
399 void MathMacroTemplate::insertParameter(Cursor & cur, int pos, bool greedy)
401 if (pos <= numargs_ && pos >= optionals_ && numargs_ < 9) {
403 shiftArguments(pos, 1);
406 cell(defIdx()).push_back(MathAtom(new MathMacroArgument(pos + 1)));
407 if (!cell(displayIdx()).empty())
408 cell(displayIdx()).push_back(MathAtom(new MathMacroArgument(pos + 1)));
412 dit.leaveInset(*this);
413 // TODO: this was dit.forwardPosNoDescend before. Check that this is the same
414 dit.top().forwardPos();
416 // fix macro instances
417 fixMacroInstancesAddRemove(dit, name(), pos, true);
423 void MathMacroTemplate::removeParameter(Cursor & cur, int pos, bool greedy)
425 if (pos < numargs_ && pos >= 0) {
427 removeArguments(cur, pos, pos);
428 shiftArguments(pos + 1, -1);
430 // removed optional parameter?
431 if (pos < optionals_) {
433 optionalValues_[pos] = cell(optIdx(pos));
434 cells_.erase(cells_.begin() + optIdx(pos));
437 int macroSlice = cur.find(this);
438 if (macroSlice != -1) {
439 if (cur[macroSlice].idx() == optIdx(pos)) {
440 cur.cutOff(macroSlice);
441 cur[macroSlice].idx() = 1;
442 cur[macroSlice].pos() = 0;
443 } else if (cur[macroSlice].idx() > optIdx(pos))
444 --cur[macroSlice].idx();
449 // fix macro instances
450 //boost::function<void(MathMacro *)> fix = _1->insertArgument(n);
451 //fixMacroInstancesFunctional(dit, name(), fix);
453 dit.leaveInset(*this);
454 // TODO: this was dit.forwardPosNoDescend before. Check that this is the same
455 dit.top().forwardPos();
456 fixMacroInstancesAddRemove(dit, name(), pos, false);
462 void MathMacroTemplate::makeOptional(Cursor & cur) {
463 if (numargs_ > 0 && optionals_ < numargs_) {
465 cells_.insert(cells_.begin() + optIdx(optionals_ - 1), optionalValues_[optionals_ - 1]);
467 int macroSlice = cur.find(this);
468 if (macroSlice != -1 && cur[macroSlice].idx() >= optIdx(optionals_ - 1))
469 ++cur[macroSlice].idx();
471 // fix macro instances
473 dit.leaveInset(*this);
474 // TODO: this was dit.forwardPosNoDescend before. Check that this is the same
475 dit.top().forwardPos();
476 fixMacroInstancesOptional(dit, name(), optionals_);
481 void MathMacroTemplate::makeNonOptional(Cursor & cur) {
482 if (numargs_ > 0 && optionals_ > 0) {
484 optionalValues_[optionals_ - 1] = cell(optIdx(optionals_));
485 cells_.erase(cells_.begin() + optIdx(optionals_));
488 int macroSlice = cur.find(this);
489 if (macroSlice != -1) {
490 if (cur[macroSlice].idx() > optIdx(optionals_))
491 --cur[macroSlice].idx();
492 else if (cur[macroSlice].idx() == optIdx(optionals_)) {
493 cur.cutOff(macroSlice);
494 cur[macroSlice].idx() = optIdx(optionals_);
495 cur[macroSlice].pos() = 0;
499 // fix macro instances
501 dit.leaveInset(*this);
502 // TODO: this was dit.forwardPosNoDescend before. Check that this is the same
503 dit.top().forwardPos();
504 fixMacroInstancesOptional(dit, name(), optionals_);
509 void MathMacroTemplate::doDispatch(Cursor & cur, FuncRequest & cmd)
511 std::string const arg = to_utf8(cmd.argument());
512 switch (cmd.action) {
514 case LFUN_MATH_MACRO_ADD_PARAM:
516 cur.recordUndoFullDocument();
517 size_t pos = numargs_;
519 pos = (size_t)convert<int>(arg) - 1; // it is checked for >=0 in getStatus
520 insertParameter(cur, pos);
525 case LFUN_MATH_MACRO_REMOVE_PARAM:
527 cur.recordUndoFullDocument();
528 size_t pos = numargs_ - 1;
530 pos = (size_t)convert<int>(arg) - 1; // it is checked for >=0 in getStatus
531 removeParameter(cur, pos);
535 case LFUN_MATH_MACRO_APPEND_GREEDY_PARAM:
537 cur.recordUndoFullDocument();
538 insertParameter(cur, numargs_, true);
542 case LFUN_MATH_MACRO_REMOVE_GREEDY_PARAM:
544 cur.recordUndoFullDocument();
545 removeParameter(cur, numargs_ - 1, true);
549 case LFUN_MATH_MACRO_MAKE_OPTIONAL:
550 cur.recordUndoFullDocument();
554 case LFUN_MATH_MACRO_MAKE_NONOPTIONAL:
555 cur.recordUndoFullDocument();
556 makeNonOptional(cur);
559 case LFUN_MATH_MACRO_ADD_OPTIONAL_PARAM:
561 cur.recordUndoFullDocument();
562 insertParameter(cur, optionals_);
567 case LFUN_MATH_MACRO_REMOVE_OPTIONAL_PARAM:
569 removeParameter(cur, optionals_ - 1);
572 case LFUN_MATH_MACRO_ADD_GREEDY_OPTIONAL_PARAM:
573 if (numargs_ == optionals_) {
574 cur.recordUndoFullDocument();
575 insertParameter(cur, 0, true);
581 InsetMathNest::doDispatch(cur, cmd);
587 bool MathMacroTemplate::getStatus(Cursor & cur, FuncRequest const & cmd,
588 FuncStatus & flag) const
591 std::string const arg = to_utf8(cmd.argument());
592 switch (cmd.action) {
593 case LFUN_MATH_MACRO_ADD_PARAM: {
594 int num = numargs_ + 1;
596 num = convert<int>(arg);
597 bool on = (num >= optionals_ && numargs_ < 9 && num <= numargs_ + 1);
602 case LFUN_MATH_MACRO_APPEND_GREEDY_PARAM:
603 flag.enabled(numargs_ < 9);
606 case LFUN_MATH_MACRO_REMOVE_PARAM: {
609 num = convert<int>(arg);
610 flag.enabled(num >= 1 && num <= numargs_);
614 case LFUN_MATH_MACRO_MAKE_OPTIONAL:
615 flag.enabled(numargs_ > 0 && optionals_ < numargs_ && type_ != from_ascii("def"));
618 case LFUN_MATH_MACRO_MAKE_NONOPTIONAL:
619 flag.enabled(optionals_ > 0 && type_ != from_ascii("def"));
622 case LFUN_MATH_MACRO_ADD_OPTIONAL_PARAM:
623 flag.enabled(numargs_ < 9);
626 case LFUN_MATH_MACRO_REMOVE_OPTIONAL_PARAM:
627 flag.enabled(optionals_ > 0);
630 case LFUN_MATH_MACRO_ADD_GREEDY_OPTIONAL_PARAM:
631 flag.enabled(numargs_ == 0 && type_ != from_ascii("def"));
642 void MathMacroTemplate::read(Buffer const &, Lexer & lex)
645 mathed_parse_cell(ar, lex.getStream());
646 if (ar.size() != 1 || !ar[0]->asMacroTemplate()) {
647 lyxerr << "Cannot read macro from '" << ar << "'" << endl;
648 lyxerr << "Read: " << to_utf8(asString(ar)) << endl;
651 operator=( *(ar[0]->asMacroTemplate()) );
655 void MathMacroTemplate::write(Buffer const &, std::ostream & os) const
657 odocstringstream oss;
658 WriteStream wi(oss, false, false);
659 oss << "FormulaMacro\n";
661 os << to_utf8(oss.str());
665 void MathMacroTemplate::write(WriteStream & os) const
667 if (type_ == "def") {
668 os << "\\def\\" << name().c_str();
669 for (int i = 1; i <= numargs_; ++i)
672 // newcommand or renewcommand
673 os << "\\" << type_.c_str() << "{\\" << name().c_str() << '}';
675 os << '[' << numargs_ << ']';
679 // in latex only one optional possible, simulate the others
680 if (optionals_ >= 1) {
681 docstring optValue = asString(cell(optIdx(0)));
682 if (optValue.find(']') != docstring::npos)
683 os << "[{" << cell(optIdx(0)) << "}]";
685 os << "[" << cell(optIdx(0)) << "]";
688 // in lyx we handle all optionals as real optionals
689 for (int i = 0; i < optionals_; ++i) {
690 docstring optValue = asString(cell(optIdx(i)));
691 if (optValue.find(']') != docstring::npos)
692 os << "[{" << cell(optIdx(i)) << "}]";
694 os << "[" << cell(optIdx(i)) << "]";
699 os << "{" << cell(defIdx()) << "}";
702 // writing .tex. done.
705 // writing .lyx, write special .tex export only if necessary
706 if (!cell(displayIdx()).empty())
707 os << "\n{" << cell(displayIdx()) << '}';
712 int MathMacroTemplate::plaintext(Buffer const & buf, odocstream & os,
713 OutputParams const &) const
715 static docstring const str = '[' + buf.B_("math macro") + ']';
722 bool MathMacroTemplate::validName() const
724 docstring n = name();
730 // converting back and force doesn't swallow anything?
733 if (asString(ma) != n)
737 for (size_t i = 0; i < n.size(); ++i) {
738 if (!(n[i] >= 'a' && n[i] <= 'z') &&
739 !(n[i] >= 'A' && n[i] <= 'Z'))
747 bool MathMacroTemplate::validMacro() const
753 MacroData MathMacroTemplate::asMacroData() const
755 std::vector<docstring> defaults(numargs_);
756 for (int i = 0; i < optionals_; ++i)
757 defaults[i] = asString(cell(optIdx(i)));
758 return MacroData(asString(cell(defIdx())), defaults,
759 numargs_, optionals_, asString(cell(displayIdx())), std::string());