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) {
485 // store default value for later if the use changes his mind
486 optionalValues_[optionals_] = cell(optIdx(optionals_));
487 cells_.erase(cells_.begin() + optIdx(optionals_));
490 int macroSlice = cur.find(this);
491 if (macroSlice != -1) {
492 if (cur[macroSlice].idx() > optIdx(optionals_))
493 --cur[macroSlice].idx();
494 else if (cur[macroSlice].idx() == optIdx(optionals_)) {
495 cur.cutOff(macroSlice);
496 cur[macroSlice].idx() = optIdx(optionals_);
497 cur[macroSlice].pos() = 0;
501 // fix macro instances
503 dit.leaveInset(*this);
504 // TODO: this was dit.forwardPosNoDescend before. Check that this is the same
505 dit.top().forwardPos();
506 fixMacroInstancesOptional(dit, name(), optionals_);
511 void MathMacroTemplate::doDispatch(Cursor & cur, FuncRequest & cmd)
513 std::string const arg = to_utf8(cmd.argument());
514 switch (cmd.action) {
516 case LFUN_MATH_MACRO_ADD_PARAM:
518 cur.recordUndoFullDocument();
519 size_t pos = numargs_;
521 pos = (size_t)convert<int>(arg) - 1; // it is checked for >=0 in getStatus
522 insertParameter(cur, pos);
527 case LFUN_MATH_MACRO_REMOVE_PARAM:
529 cur.recordUndoFullDocument();
530 size_t pos = numargs_ - 1;
532 pos = (size_t)convert<int>(arg) - 1; // it is checked for >=0 in getStatus
533 removeParameter(cur, pos);
537 case LFUN_MATH_MACRO_APPEND_GREEDY_PARAM:
539 cur.recordUndoFullDocument();
540 insertParameter(cur, numargs_, true);
544 case LFUN_MATH_MACRO_REMOVE_GREEDY_PARAM:
546 cur.recordUndoFullDocument();
547 removeParameter(cur, numargs_ - 1, true);
551 case LFUN_MATH_MACRO_MAKE_OPTIONAL:
552 cur.recordUndoFullDocument();
556 case LFUN_MATH_MACRO_MAKE_NONOPTIONAL:
557 cur.recordUndoFullDocument();
558 makeNonOptional(cur);
561 case LFUN_MATH_MACRO_ADD_OPTIONAL_PARAM:
563 cur.recordUndoFullDocument();
564 insertParameter(cur, optionals_);
569 case LFUN_MATH_MACRO_REMOVE_OPTIONAL_PARAM:
571 removeParameter(cur, optionals_ - 1);
574 case LFUN_MATH_MACRO_ADD_GREEDY_OPTIONAL_PARAM:
575 if (numargs_ == optionals_) {
576 cur.recordUndoFullDocument();
577 insertParameter(cur, 0, true);
583 InsetMathNest::doDispatch(cur, cmd);
589 bool MathMacroTemplate::getStatus(Cursor & cur, FuncRequest const & cmd,
590 FuncStatus & flag) const
593 std::string const arg = to_utf8(cmd.argument());
594 switch (cmd.action) {
595 case LFUN_MATH_MACRO_ADD_PARAM: {
596 int num = numargs_ + 1;
598 num = convert<int>(arg);
599 bool on = (num >= optionals_ && numargs_ < 9 && num <= numargs_ + 1);
604 case LFUN_MATH_MACRO_APPEND_GREEDY_PARAM:
605 flag.enabled(numargs_ < 9);
608 case LFUN_MATH_MACRO_REMOVE_PARAM: {
611 num = convert<int>(arg);
612 flag.enabled(num >= 1 && num <= numargs_);
616 case LFUN_MATH_MACRO_MAKE_OPTIONAL:
617 flag.enabled(numargs_ > 0 && optionals_ < numargs_ && type_ != from_ascii("def"));
620 case LFUN_MATH_MACRO_MAKE_NONOPTIONAL:
621 flag.enabled(optionals_ > 0 && type_ != from_ascii("def"));
624 case LFUN_MATH_MACRO_ADD_OPTIONAL_PARAM:
625 flag.enabled(numargs_ < 9);
628 case LFUN_MATH_MACRO_REMOVE_OPTIONAL_PARAM:
629 flag.enabled(optionals_ > 0);
632 case LFUN_MATH_MACRO_ADD_GREEDY_OPTIONAL_PARAM:
633 flag.enabled(numargs_ == 0 && type_ != from_ascii("def"));
644 void MathMacroTemplate::read(Buffer const &, Lexer & lex)
647 mathed_parse_cell(ar, lex.getStream());
648 if (ar.size() != 1 || !ar[0]->asMacroTemplate()) {
649 lyxerr << "Cannot read macro from '" << ar << "'" << endl;
650 lyxerr << "Read: " << to_utf8(asString(ar)) << endl;
653 operator=( *(ar[0]->asMacroTemplate()) );
657 void MathMacroTemplate::write(Buffer const &, std::ostream & os) const
659 odocstringstream oss;
660 WriteStream wi(oss, false, false);
661 oss << "FormulaMacro\n";
663 os << to_utf8(oss.str());
667 void MathMacroTemplate::write(WriteStream & os) const
669 if (type_ == "def") {
670 os << "\\def\\" << name().c_str();
671 for (int i = 1; i <= numargs_; ++i)
674 // newcommand or renewcommand
675 os << "\\" << type_.c_str() << "{\\" << name().c_str() << '}';
677 os << '[' << numargs_ << ']';
681 // in latex only one optional possible, simulate the others
682 if (optionals_ >= 1) {
683 docstring optValue = asString(cell(optIdx(0)));
684 if (optValue.find(']') != docstring::npos)
685 os << "[{" << cell(optIdx(0)) << "}]";
687 os << "[" << cell(optIdx(0)) << "]";
690 // in lyx we handle all optionals as real optionals
691 for (int i = 0; i < optionals_; ++i) {
692 docstring optValue = asString(cell(optIdx(i)));
693 if (optValue.find(']') != docstring::npos)
694 os << "[{" << cell(optIdx(i)) << "}]";
696 os << "[" << cell(optIdx(i)) << "]";
701 os << "{" << cell(defIdx()) << "}";
704 // writing .tex. done.
707 // writing .lyx, write special .tex export only if necessary
708 if (!cell(displayIdx()).empty())
709 os << "\n{" << cell(displayIdx()) << '}';
714 int MathMacroTemplate::plaintext(Buffer const & buf, odocstream & os,
715 OutputParams const &) const
717 static docstring const str = '[' + buf.B_("math macro") + ']';
724 bool MathMacroTemplate::validName() const
726 docstring n = name();
732 // converting back and force doesn't swallow anything?
735 if (asString(ma) != n)
739 for (size_t i = 0; i < n.size(); ++i) {
740 if (!(n[i] >= 'a' && n[i] <= 'z') &&
741 !(n[i] >= 'A' && n[i] <= 'Z'))
749 bool MathMacroTemplate::validMacro() const
755 MacroData MathMacroTemplate::asMacroData() const
757 std::vector<docstring> defaults(numargs_);
758 for (int i = 0; i < optionals_; ++i)
759 defaults[i] = asString(cell(optIdx(i)));
760 return MacroData(asString(cell(defIdx())), defaults,
761 numargs_, optionals_, asString(cell(displayIdx())), std::string());