3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Alejandro Aguilar Sierra
8 * \author Stefan Schimanski
10 * Full author contact details are available in file CREDITS.
15 #include "MathMacro.h"
16 #include "MathSupport.h"
17 #include "MathExtern.h"
18 #include "MathStream.h"
21 #include "BufferView.h"
24 #include "LaTeXFeatures.h"
25 #include "FuncStatus.h"
26 #include "FuncRequest.h"
29 #include "frontends/Painter.h"
39 /// A proxy for the macro values
40 class ArgumentProxy : public InsetMath {
43 ArgumentProxy(MathMacro & mathMacro, size_t idx)
44 : mathMacro_(mathMacro), idx_(idx) {}
46 ArgumentProxy(MathMacro & mathMacro, size_t idx, docstring const & def)
47 : mathMacro_(mathMacro), idx_(idx)
52 void metrics(MetricsInfo & mi, Dimension & dim) const {
53 mathMacro_.macro()->unlock();
54 mathMacro_.cell(idx_).metrics(mi, dim);
55 if (!mathMacro_.editing() && !def_.empty())
56 def_.metrics(mi, dim);
57 mathMacro_.macro()->lock();
60 void draw(PainterInfo & pi, int x, int y) const {
61 if (mathMacro_.editing()) {
62 pi.pain.leaveMonochromeMode();
63 mathMacro_.cell(idx_).draw(pi, x, y);
64 // FIXME: use real min/max colors here, not necessarely the ones of MathMacro are set
65 pi.pain.enterMonochromeMode(Color_mathbg, Color_mathmacroblend);
68 mathMacro_.cell(idx_).draw(pi, x, y);
70 mathMacro_.cell(idx_).setXY(*pi.base.bv, x, y);
76 size_t idx() const { return idx_; }
78 int kerning() const { return mathMacro_.cell(idx_).kerning(); }
84 return new ArgumentProxy(*this);
87 MathMacro & mathMacro_;
95 MathMacro::MathMacro(docstring const & name)
96 : InsetMathNest(0), name_(name), displayMode_(DISPLAY_INIT),
97 attachedArgsNum_(0), previousCurIdx_(-1),
98 optionals_(0), nextFoldMode_(true),
99 macro_(0), editing_(false), needsUpdate_(false)
103 Inset * MathMacro::clone() const
105 MathMacro * copy = new MathMacro(*this);
106 copy->needsUpdate_ = true;
107 copy->expanded_.cell(0).clear();
112 docstring MathMacro::name() const
114 if (displayMode_ == DISPLAY_UNFOLDED)
115 return asString(cell(0));
121 void MathMacro::cursorPos(BufferView const & bv,
122 CursorSlice const & sl, bool boundary, int & x, int & y) const
124 // We may have 0 arguments, but InsetMathNest requires at least one.
126 InsetMathNest::cursorPos(bv, sl, boundary, x, y);
130 int MathMacro::cursorIdx(Cursor const & cur) const {
131 for (size_t i = 0; i != cur.depth(); ++i)
132 if (&cur[i].inset() == this)
138 bool MathMacro::editMode(Cursor const & cur) const {
139 // find this in cursor trace
140 for (size_t i = 0; i != cur.depth(); ++i)
141 if (&cur[i].inset() == this) {
142 // look if there is no other macro in edit mode above
144 for (; i != cur.depth(); ++i) {
145 MathMacro const * macro = dynamic_cast<MathMacro const *>(&cur[i].inset());
146 if (macro && macro->displayMode() == DISPLAY_NORMAL)
150 // ok, none found, I am the highest one
158 void MathMacro::metrics(MetricsInfo & mi, Dimension & dim) const
160 // calculate new metrics according to display mode
161 if (displayMode_ == DISPLAY_INIT) {
162 mathed_string_dim(mi.base.font, from_ascii("\\") + name(), dim);
163 } else if (displayMode_ == DISPLAY_UNFOLDED) {
164 cell(0).metrics(mi, dim);
166 mathed_string_dim(mi.base.font, from_ascii("\\"), bsdim);
167 dim.wid += bsdim.width() + 1;
168 dim.asc = std::max(bsdim.ascent(), dim.ascent());
169 dim.des = std::max(bsdim.descent(), dim.descent());
172 BOOST_ASSERT(macro_ != 0);
174 // calculate metric finally
176 expanded_.cell(0).metrics(mi, dim);
179 // calculate dimension with label while editing
181 FontInfo font = mi.base.font;
182 augmentFont(font, from_ascii("lyxtex"));
184 mathed_string_dim(font, name(), namedim);
186 dim.wid += 2 + namedim.wid + 2 + 2;
187 dim.asc = std::max(dim.asc, namedim.asc) + 2;
188 dim.des = std::max(dim.des, namedim.des) + 2;
190 dim.wid = std::max(1 + namedim.wid + 1, 2 + dim.wid + 2);
191 dim.asc += 1 + namedim.height() + 1;
196 // Cache the inset dimension.
197 setDimCache(mi, dim);
201 int MathMacro::kerning() const {
202 if (displayMode_ == DISPLAY_NORMAL && !editing_)
203 return expanded_.kerning();
209 void MathMacro::updateMacro(MetricsInfo & mi)
211 if (validName() && mi.macrocontext.has(name())) {
212 macro_ = &mi.macrocontext.get(name());
213 if (macroBackup_ != *macro_) {
214 macroBackup_ = *macro_;
223 void MathMacro::updateRepresentation(MetricsInfo & mi)
225 // index of child where the cursor is (or -1 if none is edited)
226 int curIdx = cursorIdx(mi.base.bv->cursor());
227 previousCurIdx_ = curIdx;
231 requires_ = macro_->requires();
233 if (displayMode_ == DISPLAY_NORMAL) {
234 // set edit mode to draw box around if needed
235 bool prevEditing = editing_;
236 editing_ = editMode(mi.base.bv->cursor());
238 // editMode changed and we have to switch default value and hole of optional?
239 if (optionals_ > 0 && nargs() > 0 &&
240 prevEditing != editing_)
245 needsUpdate_ = false;
247 // get default values of macro
248 std::vector<docstring> const & defaults = macro_->defaults();
250 // create MathMacroArgumentValue objects pointing to the cells of the macro
251 std::vector<MathData> values(nargs());
252 for (size_t i = 0; i < nargs(); ++i) {
253 if (!cell(i).empty() || i >= defaults.size() ||
254 defaults[i].empty() || curIdx == (int)i)
255 values[i].insert(0, MathAtom(new ArgumentProxy(*this, i)));
257 values[i].insert(0, MathAtom(new ArgumentProxy(*this, i, defaults[i])));
260 // expanding macro with the values
261 macro_->expand(values, expanded_.cell(0));
268 void MathMacro::draw(PainterInfo & pi, int x, int y) const
270 Dimension const dim = dimension(*pi.base.bv);
272 setPosCache(pi, x, y);
276 if (displayMode_ == DISPLAY_INIT) {
277 PainterInfo pi2(pi.base.bv, pi.pain);
278 pi2.base.font.setColor(macro_ ? Color_latex : Color_error);
279 //pi2.base.style = LM_ST_TEXT;
280 pi2.pain.text(x, y, from_ascii("\\") + name(), pi2.base.font);
281 } else if (displayMode_ == DISPLAY_UNFOLDED) {
282 PainterInfo pi2(pi.base.bv, pi.pain);
283 pi2.base.font.setColor(macro_ ? Color_latex : Color_error);
284 //pi2.base.style = LM_ST_TEXT;
285 pi2.pain.text(x, y, from_ascii("\\"), pi2.base.font);
286 x += mathed_string_width(pi2.base.font, from_ascii("\\")) + 1;
287 cell(0).draw(pi2, x, y);
288 drawMarkers(pi2, expx, expy);
291 for (size_t i = 0; i < nargs(); ++i)
292 cell(i).setXY(*pi.base.bv, x, y);
295 // draw header and rectangle around
296 FontInfo font = pi.base.font;
297 augmentFont(font, from_ascii("lyxtex"));
298 font.setSize(FONT_SIZE_TINY);
299 font.setColor(Color_mathmacrolabel);
301 mathed_string_dim(font, name(), namedim);
303 pi.pain.fillRectangle(x, y - dim.asc, 2 + namedim.width() + 2, dim.height(), Color_mathmacrobg);
304 pi.pain.text(x + 2, y, name(), font);
305 expx += 2 + namew + 2;
307 pi.pain.fillRectangle(x, y - dim.asc, dim.wid, 1 + namedim.height() + 1, Color_mathmacrobg);
308 pi.pain.text(x + 1, y - dim.asc + namedim.asc + 2, name(), font);
309 expx += (dim.wid - expanded_.cell(0).dimension(*pi.base.bv).width()) / 2;
311 pi.pain.enterMonochromeMode(Color_mathbg, Color_mathmacroblend);
312 expanded_.cell(0).draw(pi, expx, expy);
313 pi.pain.leaveMonochromeMode();
315 expanded_.cell(0).draw(pi, expx, expy);
317 // draw frame while editing
319 pi.pain.rectangle(x, y - dim.asc, dim.wid, dim.height(), Color_mathmacroframe);
322 // another argument selected?
323 int curIdx = cursorIdx(pi.base.bv->cursor());
324 if (previousCurIdx_ != curIdx || editing_ != editMode(pi.base.bv->cursor()))
325 pi.base.bv->cursor().updateFlags(Update::Force);
329 void MathMacro::drawSelection(PainterInfo & pi, int x, int y) const
331 // We may have 0 arguments, but InsetMathNest requires at least one.
332 if (cells_.size() > 0)
333 InsetMathNest::drawSelection(pi, x, y);
337 void MathMacro::setDisplayMode(MathMacro::DisplayMode mode)
339 if (displayMode_ != mode) {
340 // transfer name if changing from or to DISPLAY_UNFOLDED
341 if (mode == DISPLAY_UNFOLDED) {
343 asArray(name_, cell(0));
344 } else if (displayMode_ == DISPLAY_UNFOLDED) {
345 name_ = asString(cell(0));
355 MathMacro::DisplayMode MathMacro::computeDisplayMode(MetricsInfo const & mi) const
357 if (nextFoldMode_ == true && macro_ && !macro_->locked())
358 return DISPLAY_NORMAL;
360 return DISPLAY_UNFOLDED;
364 bool MathMacro::validName() const
366 docstring n = name();
372 // converting back and force doesn't swallow anything?
375 if (asString(ma) != n)
379 for (size_t i = 0; i<n.size(); ++i) {
380 if (!(n[i] >= 'a' && n[i] <= 'z') &&
381 !(n[i] >= 'A' && n[i] <= 'Z'))
389 void MathMacro::validate(LaTeXFeatures & features) const
391 if (!requires_.empty())
392 features.require(requires_);
394 if (name() == "binom" || name() == "mathcircumflex")
395 features.require(to_utf8(name()));
399 void MathMacro::edit(Cursor & cur, bool left)
401 cur.updateFlags(Update::Force);
402 InsetMathNest::edit(cur, left);
406 Inset * MathMacro::editXY(Cursor & cur, int x, int y)
408 // We may have 0 arguments, but InsetMathNest requires at least one.
410 cur.updateFlags(Update::Force);
411 return InsetMathNest::editXY(cur, x, y);
417 void MathMacro::removeArgument(size_t pos) {
418 if (displayMode_ == DISPLAY_NORMAL) {
419 BOOST_ASSERT(pos >= 0 && pos < cells_.size());
420 cells_.erase(cells_.begin() + pos);
421 if (pos < attachedArgsNum_)
423 if (pos < optionals_) {
432 void MathMacro::insertArgument(size_t pos) {
433 if (displayMode_ == DISPLAY_NORMAL) {
434 BOOST_ASSERT(pos >= 0 && pos <= cells_.size());
435 cells_.insert(cells_.begin() + pos, MathData());
436 if (pos < attachedArgsNum_)
438 if (pos < optionals_)
446 void MathMacro::detachArguments(std::vector<MathData> & args, bool strip)
448 BOOST_ASSERT(displayMode_ == DISPLAY_NORMAL);
451 // strip off empty cells, but not more than arity-attachedArgsNum_
454 for (i = cells_.size(); i > attachedArgsNum_; --i)
455 if (!cell(i - 1).empty()) break;
459 attachedArgsNum_ = 0;
460 expanded_.cell(0) = MathData();
467 void MathMacro::attachArguments(std::vector<MathData> const & args, size_t arity, int optionals)
469 BOOST_ASSERT(displayMode_ == DISPLAY_NORMAL);
471 attachedArgsNum_ = args.size();
472 cells_.resize(arity);
473 expanded_.cell(0) = MathData();
474 optionals_ = optionals;
480 bool MathMacro::idxFirst(Cursor & cur) const
482 cur.updateFlags(Update::Force);
483 return InsetMathNest::idxFirst(cur);
487 bool MathMacro::idxLast(Cursor & cur) const
489 cur.updateFlags(Update::Force);
490 return InsetMathNest::idxLast(cur);
494 bool MathMacro::notifyCursorLeaves(Cursor & cur)
496 cur.updateFlags(Update::Force);
497 return InsetMathNest::notifyCursorLeaves(cur);
501 void MathMacro::fold(Cursor & cur)
503 if (!nextFoldMode_) {
504 nextFoldMode_ = true;
505 cur.updateFlags(Update::Force);
510 void MathMacro::unfold(Cursor & cur)
513 nextFoldMode_ = false;
514 cur.updateFlags(Update::Force);
519 bool MathMacro::folded() const
521 return nextFoldMode_;
525 void MathMacro::write(WriteStream & os) const
527 if (displayMode_ == DISPLAY_NORMAL) {
528 BOOST_ASSERT(macro_);
530 os << "\\" << name();
534 // Use macroBackup_ instead of macro_ here, because
535 // this is outside the metrics/draw calls, hence the macro_
536 // variable can point to a MacroData which was freed already.
537 std::vector<docstring> const & defaults = macroBackup_.defaults();
541 if (i < optionals_) {
542 // the first real optional, the others are non-optional in latex
543 if (!cell(i).empty()) {
545 os << "[" << cell(0) << "]";
551 // In lyx mode print all in any case
552 for (; i < cells_.size() && i < optionals_; ++i) {
554 os << "[" << cell(i) << "]";
558 for (; i < cells_.size(); ++i) {
559 if (cell(i).empty() && i < optionals_) {
560 os << "{" << defaults[i] << "}";
561 } else if (cell(i).size() == 1 && cell(i)[0].nucleus()->asCharInset()) {
566 os << "{" << cell(i) << "}";
570 os.pendingSpace(true);
572 os << "\\" << name() << " ";
573 os.pendingSpace(true);
578 void MathMacro::maple(MapleStream & os) const
580 lyx::maple(expanded_.cell(0), os);
584 void MathMacro::mathmlize(MathStream & os) const
586 lyx::mathmlize(expanded_.cell(0), os);
590 void MathMacro::octave(OctaveStream & os) const
592 lyx::octave(expanded_.cell(0), os);
596 void MathMacro::infoize(odocstream & os) const
598 os << "Macro: " << name();
602 void MathMacro::infoize2(odocstream & os) const
604 os << "Macro: " << name();