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"
17 #include "InsetMathChar.h"
18 #include "MathCompletionList.h"
19 #include "MathExtern.h"
20 #include "MathFactory.h"
21 #include "MathStream.h"
22 #include "MathSupport.h"
25 #include "BufferView.h"
26 #include "CoordCache.h"
28 #include "FuncStatus.h"
29 #include "FuncRequest.h"
30 #include "LaTeXFeatures.h"
35 #include "frontends/Painter.h"
37 #include "support/debug.h"
38 #include "support/lassert.h"
39 #include "support/textutils.h"
49 /// A proxy for the macro values
50 class ArgumentProxy
: public InsetMath
{
53 ArgumentProxy(MathMacro
& mathMacro
, size_t idx
)
54 : mathMacro_(mathMacro
), idx_(idx
) {}
56 ArgumentProxy(MathMacro
& mathMacro
, size_t idx
, docstring
const & def
)
57 : mathMacro_(mathMacro
), idx_(idx
)
62 void metrics(MetricsInfo
& mi
, Dimension
& dim
) const {
63 mathMacro_
.macro()->unlock();
64 mathMacro_
.cell(idx_
).metrics(mi
, dim
);
66 if (!mathMacro_
.editMetrics(mi
.base
.bv
)
67 && mathMacro_
.cell(idx_
).empty())
68 def_
.metrics(mi
, dim
);
70 mathMacro_
.macro()->lock();
73 void draw(PainterInfo
& pi
, int x
, int y
) const {
74 if (mathMacro_
.editMetrics(pi
.base
.bv
)) {
75 // The only way a ArgumentProxy can appear is in a cell of the
76 // MathMacro. Moreover the cells are only drawn in the DISPLAY_FOLDED
77 // mode and then, if the macro is edited the monochrome
78 // mode is entered by the MathMacro before calling the cells' draw
79 // method. Then eventually this code is reached and the proxy leaves
80 // monochrome mode temporarely. Hence, if it is not in monochrome
81 // here (and the assert triggers in pain.leaveMonochromeMode())
83 pi
.pain
.leaveMonochromeMode();
84 mathMacro_
.cell(idx_
).draw(pi
, x
, y
);
85 pi
.pain
.enterMonochromeMode(Color_mathbg
, Color_mathmacroblend
);
86 } else if (mathMacro_
.cell(idx_
).empty()) {
87 mathMacro_
.cell(idx_
).setXY(*pi
.base
.bv
, x
, y
);
90 mathMacro_
.cell(idx_
).draw(pi
, x
, y
);
93 size_t idx() const { return idx_
; }
95 int kerning(BufferView
const * bv
) const
97 if (mathMacro_
.editMetrics(bv
)
98 || !mathMacro_
.cell(idx_
).empty())
99 return mathMacro_
.cell(idx_
).kerning(bv
);
101 return def_
.kerning(bv
);
106 Inset
* clone() const
108 return new ArgumentProxy(*this);
111 MathMacro
& mathMacro_
;
119 MathMacro::MathMacro(Buffer
* buf
, docstring
const & name
)
120 : InsetMathNest(buf
, 0), name_(name
), displayMode_(DISPLAY_INIT
),
121 expanded_(buf
), attachedArgsNum_(0), optionals_(0), nextFoldMode_(true),
122 macroBackup_(buf
), macro_(0), needsUpdate_(false), appetite_(9)
126 Inset
* MathMacro::clone() const
128 MathMacro
* copy
= new MathMacro(*this);
129 copy
->needsUpdate_
= true;
130 copy
->expanded_
.cell(0).clear();
135 docstring
MathMacro::name() const
137 if (displayMode_
== DISPLAY_UNFOLDED
)
138 return asString(cell(0));
144 void MathMacro::cursorPos(BufferView
const & bv
,
145 CursorSlice
const & sl
, bool boundary
, int & x
, int & y
) const
147 // We may have 0 arguments, but InsetMathNest requires at least one.
149 InsetMathNest::cursorPos(bv
, sl
, boundary
, x
, y
);
153 bool MathMacro::editMode(BufferView
const * bv
) const {
154 // find this in cursor trace
155 Cursor
const & cur
= bv
->cursor();
156 for (size_t i
= 0; i
!= cur
.depth(); ++i
)
157 if (&cur
[i
].inset() == this) {
158 // look if there is no other macro in edit mode above
160 for (; i
!= cur
.depth(); ++i
) {
161 MathMacro
const * macro
= dynamic_cast<MathMacro
const *>(&cur
[i
].inset());
162 if (macro
&& macro
->displayMode() == DISPLAY_NORMAL
)
166 // ok, none found, I am the highest one
174 bool MathMacro::editMetrics(BufferView
const * bv
) const
180 void MathMacro::metrics(MetricsInfo
& mi
, Dimension
& dim
) const
182 // set edit mode for which we will have calculated metrics. But only
183 editing_
[mi
.base
.bv
] = editMode(mi
.base
.bv
);
185 // calculate new metrics according to display mode
186 if (displayMode_
== DISPLAY_INIT
|| displayMode_
== DISPLAY_INTERACTIVE_INIT
) {
187 mathed_string_dim(mi
.base
.font
, from_ascii("\\") + name(), dim
);
188 } else if (displayMode_
== DISPLAY_UNFOLDED
) {
189 cell(0).metrics(mi
, dim
);
191 mathed_string_dim(mi
.base
.font
, from_ascii("\\"), bsdim
);
192 dim
.wid
+= bsdim
.width() + 1;
193 dim
.asc
= max(bsdim
.ascent(), dim
.ascent());
194 dim
.des
= max(bsdim
.descent(), dim
.descent());
196 } else if (lyxrc
.macro_edit_style
== LyXRC::MACRO_EDIT_LIST
197 && editing_
[mi
.base
.bv
]) {
198 // Macro will be edited in a old-style list mode here:
200 LASSERT(macro_
!= 0, /**/);
202 FontInfo labelFont
= sane_font
;
203 math_font_max_dim(labelFont
, fontDim
.asc
, fontDim
.des
);
205 // get dimension of components of list view
207 nameDim
.wid
= mathed_string_width(mi
.base
.font
, from_ascii("Macro \\") + name() + ": ");
208 nameDim
.asc
= fontDim
.asc
;
209 nameDim
.des
= fontDim
.des
;
212 argDim
.wid
= mathed_string_width(labelFont
, from_ascii("#9: "));
213 argDim
.asc
= fontDim
.asc
;
214 argDim
.des
= fontDim
.des
;
217 definition_
.metrics(mi
, defDim
);
220 dim
.wid
= nameDim
.wid
+ defDim
.wid
;
221 dim
.asc
= max(nameDim
.asc
, defDim
.asc
);
222 dim
.des
= max(nameDim
.des
, defDim
.des
);
224 for (idx_type i
= 0; i
< nargs(); ++i
) {
226 cell(i
).metrics(mi
, cdim
);
227 dim
.des
+= max(argDim
.height(), cdim
.height()) + 1;
228 dim
.wid
= max(dim
.wid
, argDim
.wid
+ cdim
.wid
);
231 // make space for box and markers, 2 pixels
235 metricsMarkers2(dim
);
237 LASSERT(macro_
!= 0, /**/);
239 // calculate metrics, hoping that all cells are seen
241 expanded_
.cell(0).metrics(mi
, dim
);
243 // otherwise do a manual metrics call
244 CoordCache
& coords
= mi
.base
.bv
->coordCache();
245 for (idx_type i
= 0; i
< nargs(); ++i
) {
246 if (!coords
.getArrays().has(&cell(i
))) {
248 cell(i
).metrics(mi
, tdim
);
253 // calculate dimension with label while editing
254 if (lyxrc
.macro_edit_style
== LyXRC::MACRO_EDIT_INLINE_BOX
255 && editing_
[mi
.base
.bv
]) {
256 FontInfo font
= mi
.base
.font
;
257 augmentFont(font
, from_ascii("lyxtex"));
259 mathed_string_dim(font
, name(), namedim
);
261 dim
.wid
+= 2 + namedim
.wid
+ 2 + 2;
262 dim
.asc
= max(dim
.asc
, namedim
.asc
) + 2;
263 dim
.des
= max(dim
.des
, namedim
.des
) + 2;
265 dim
.wid
= max(1 + namedim
.wid
+ 1, 2 + dim
.wid
+ 2);
266 dim
.asc
+= 1 + namedim
.height() + 1;
274 int MathMacro::kerning(BufferView
const * bv
) const {
275 if (displayMode_
== DISPLAY_NORMAL
&& !editing_
[bv
])
276 return expanded_
.kerning(bv
);
282 void MathMacro::updateMacro(MacroContext
const & mc
)
285 macro_
= mc
.get(name());
286 if (macro_
&& macroBackup_
!= *macro_
) {
287 macroBackup_
= *macro_
;
296 void MathMacro::updateRepresentation()
303 requires_
= macro_
->requires();
305 // non-normal mode? We are done!
306 if (displayMode_
!= DISPLAY_NORMAL
)
313 needsUpdate_
= false;
315 // get default values of macro
316 vector
<docstring
> const & defaults
= macro_
->defaults();
318 // create MathMacroArgumentValue objects pointing to the cells of the macro
319 vector
<MathData
> values(nargs());
320 for (size_t i
= 0; i
< nargs(); ++i
) {
321 ArgumentProxy
* proxy
;
322 if (i
< defaults
.size())
323 proxy
= new ArgumentProxy(*this, i
, defaults
[i
]);
325 proxy
= new ArgumentProxy(*this, i
);
326 values
[i
].insert(0, MathAtom(proxy
));
329 // expanding macro with the values
330 macro_
->expand(values
, expanded_
.cell(0));
331 // get definition for list edit mode
332 docstring
const & display
= macro_
->display();
333 asArray(display
.empty() ? macro_
->definition() : display
, definition_
);
337 void MathMacro::draw(PainterInfo
& pi
, int x
, int y
) const
339 Dimension
const dim
= dimension(*pi
.base
.bv
);
341 setPosCache(pi
, x
, y
);
345 if (displayMode_
== DISPLAY_INIT
|| displayMode_
== DISPLAY_INTERACTIVE_INIT
) {
346 FontSetChanger
dummy(pi
.base
, "lyxtex");
347 pi
.pain
.text(x
, y
, from_ascii("\\") + name(), pi
.base
.font
);
348 } else if (displayMode_
== DISPLAY_UNFOLDED
) {
349 FontSetChanger
dummy(pi
.base
, "lyxtex");
350 pi
.pain
.text(x
, y
, from_ascii("\\"), pi
.base
.font
);
351 x
+= mathed_string_width(pi
.base
.font
, from_ascii("\\")) + 1;
352 cell(0).draw(pi
, x
, y
);
353 drawMarkers(pi
, expx
, expy
);
354 } else if (lyxrc
.macro_edit_style
== LyXRC::MACRO_EDIT_LIST
355 && editing_
[pi
.base
.bv
]) {
356 // Macro will be edited in a old-style list mode here:
358 CoordCache
const & coords
= pi
.base
.bv
->coordCache();
359 FontInfo
const & labelFont
= sane_font
;
361 // markers and box needs two pixels
364 // get maximal font height
366 math_font_max_dim(pi
.base
.font
, fontDim
.asc
, fontDim
.des
);
369 docstring label
= from_ascii("Macro \\") + name() + from_ascii(": ");
370 pi
.pain
.text(x
, y
, label
, labelFont
);
371 x
+= mathed_string_width(labelFont
, label
);
374 definition_
.draw(pi
, x
, y
);
375 Dimension
const & defDim
= coords
.getArrays().dim(&definition_
);
376 y
+= max(fontDim
.des
, defDim
.des
);
379 docstring str
= from_ascii("#9");
380 int strw1
= mathed_string_width(labelFont
, from_ascii("#9"));
381 int strw2
= mathed_string_width(labelFont
, from_ascii(": "));
383 for (idx_type i
= 0; i
< nargs(); ++i
) {
385 Dimension
const & cdim
= coords
.getArrays().dim(&cell(i
));
387 y
+= max(fontDim
.asc
, cdim
.asc
) + 1;
391 pi
.pain
.text(x
, y
, str
, labelFont
);
393 pi
.pain
.text(x
, y
, from_ascii(":"), labelFont
);
397 cell(i
).draw(pi
, x
, y
);
400 y
+= max(fontDim
.des
, cdim
.des
);
403 pi
.pain
.rectangle(expx
+ 1, expy
- dim
.asc
+ 1, dim
.wid
- 3,
404 dim
.height() - 2, Color_mathmacroframe
);
405 drawMarkers2(pi
, expx
, expy
);
407 bool drawBox
= lyxrc
.macro_edit_style
== LyXRC::MACRO_EDIT_INLINE_BOX
;
410 for (size_t i
= 0; i
< nargs(); ++i
)
411 cell(i
).setXY(*pi
.base
.bv
, x
, y
);
413 if (drawBox
&& editing_
[pi
.base
.bv
]) {
414 // draw header and rectangle around
415 FontInfo font
= pi
.base
.font
;
416 augmentFont(font
, from_ascii("lyxtex"));
417 font
.setSize(FONT_SIZE_TINY
);
418 font
.setColor(Color_mathmacrolabel
);
420 mathed_string_dim(font
, name(), namedim
);
422 pi
.pain
.fillRectangle(x
, y
- dim
.asc
, dim
.wid
, 1 + namedim
.height() + 1, Color_mathmacrobg
);
423 pi
.pain
.text(x
+ 1, y
- dim
.asc
+ namedim
.asc
+ 2, name(), font
);
424 expx
+= (dim
.wid
- expanded_
.cell(0).dimension(*pi
.base
.bv
).width()) / 2;
427 if (editing_
[pi
.base
.bv
]) {
428 pi
.pain
.enterMonochromeMode(Color_mathbg
, Color_mathmacroblend
);
429 expanded_
.cell(0).draw(pi
, expx
, expy
);
430 pi
.pain
.leaveMonochromeMode();
433 pi
.pain
.rectangle(x
, y
- dim
.asc
, dim
.wid
,
434 dim
.height(), Color_mathmacroframe
);
436 expanded_
.cell(0).draw(pi
, expx
, expy
);
439 drawMarkers(pi
, x
, y
);
442 // edit mode changed?
443 if (editing_
[pi
.base
.bv
] != editMode(pi
.base
.bv
))
444 pi
.base
.bv
->cursor().updateFlags(Update::SinglePar
);
448 void MathMacro::drawSelection(PainterInfo
& pi
, int x
, int y
) const
450 // We may have 0 arguments, but InsetMathNest requires at least one.
451 if (cells_
.size() > 0)
452 InsetMathNest::drawSelection(pi
, x
, y
);
456 void MathMacro::setDisplayMode(MathMacro::DisplayMode mode
, int appetite
)
458 if (displayMode_
!= mode
) {
459 // transfer name if changing from or to DISPLAY_UNFOLDED
460 if (mode
== DISPLAY_UNFOLDED
) {
462 asArray(name_
, cell(0));
463 } else if (displayMode_
== DISPLAY_UNFOLDED
) {
464 name_
= asString(cell(0));
472 // the interactive init mode is non-greedy by default
474 appetite_
= (mode
== DISPLAY_INTERACTIVE_INIT
) ? 0 : 9;
476 appetite_
= size_t(appetite
);
480 MathMacro::DisplayMode
MathMacro::computeDisplayMode() const
482 if (nextFoldMode_
== true && macro_
&& !macro_
->locked())
483 return DISPLAY_NORMAL
;
485 return DISPLAY_UNFOLDED
;
489 bool MathMacro::validName() const
491 docstring n
= name();
497 // converting back and force doesn't swallow anything?
500 if (asString(ma) != n)
504 for (size_t i
= 0; i
<n
.size(); ++i
) {
505 if (!(n
[i
] >= 'a' && n
[i
] <= 'z')
506 && !(n
[i
] >= 'A' && n
[i
] <= 'Z')
515 void MathMacro::validate(LaTeXFeatures
& features
) const
517 if (!requires_
.empty())
518 features
.require(requires_
);
520 if (name() == "binom")
521 features
.require("binom");
523 // validate the cells and the definition
524 if (displayMode() == DISPLAY_NORMAL
) {
525 definition_
.validate(features
);
526 InsetMathNest::validate(features
);
531 void MathMacro::edit(Cursor
& cur
, bool front
, EntryDirection entry_from
)
533 cur
.updateFlags(Update::SinglePar
);
534 InsetMathNest::edit(cur
, front
, entry_from
);
538 Inset
* MathMacro::editXY(Cursor
& cur
, int x
, int y
)
540 // We may have 0 arguments, but InsetMathNest requires at least one.
542 cur
.updateFlags(Update::SinglePar
);
543 return InsetMathNest::editXY(cur
, x
, y
);
549 void MathMacro::removeArgument(Inset::pos_type pos
) {
550 if (displayMode_
== DISPLAY_NORMAL
) {
551 LASSERT(size_t(pos
) < cells_
.size(), /**/);
552 cells_
.erase(cells_
.begin() + pos
);
553 if (size_t(pos
) < attachedArgsNum_
)
555 if (size_t(pos
) < optionals_
) {
564 void MathMacro::insertArgument(Inset::pos_type pos
) {
565 if (displayMode_
== DISPLAY_NORMAL
) {
566 LASSERT(size_t(pos
) <= cells_
.size(), /**/);
567 cells_
.insert(cells_
.begin() + pos
, MathData());
568 if (size_t(pos
) < attachedArgsNum_
)
570 if (size_t(pos
) < optionals_
)
578 void MathMacro::detachArguments(vector
<MathData
> & args
, bool strip
)
580 LASSERT(displayMode_
== DISPLAY_NORMAL
, /**/);
583 // strip off empty cells, but not more than arity-attachedArgsNum_
586 for (i
= cells_
.size(); i
> attachedArgsNum_
; --i
)
587 if (!cell(i
- 1).empty()) break;
591 attachedArgsNum_
= 0;
592 expanded_
.cell(0) = MathData();
599 void MathMacro::attachArguments(vector
<MathData
> const & args
, size_t arity
, int optionals
)
601 LASSERT(displayMode_
== DISPLAY_NORMAL
, /**/);
603 attachedArgsNum_
= args
.size();
604 cells_
.resize(arity
);
605 expanded_
.cell(0) = MathData();
606 optionals_
= optionals
;
612 bool MathMacro::idxFirst(Cursor
& cur
) const
614 cur
.updateFlags(Update::SinglePar
);
615 return InsetMathNest::idxFirst(cur
);
619 bool MathMacro::idxLast(Cursor
& cur
) const
621 cur
.updateFlags(Update::SinglePar
);
622 return InsetMathNest::idxLast(cur
);
626 bool MathMacro::notifyCursorLeaves(Cursor
const & old
, Cursor
& cur
)
628 if (displayMode_
== DISPLAY_UNFOLDED
) {
629 docstring
const & unfolded_name
= name();
630 if (unfolded_name
!= name_
) {
631 // The macro name was changed
633 bool left
= cur
.pos() == 0;
634 cur
.recordUndoInset();
637 cur
.insert(createInsetMath(unfolded_name
, cur
.buffer()));
640 cur
.updateFlags(Update::Force
);
644 cur
.updateFlags(Update::Force
);
645 return InsetMathNest::notifyCursorLeaves(old
, cur
);
649 void MathMacro::fold(Cursor
& cur
)
651 if (!nextFoldMode_
) {
652 nextFoldMode_
= true;
653 cur
.updateFlags(Update::SinglePar
);
658 void MathMacro::unfold(Cursor
& cur
)
661 nextFoldMode_
= false;
662 cur
.updateFlags(Update::SinglePar
);
667 bool MathMacro::folded() const
669 return nextFoldMode_
;
673 void MathMacro::write(WriteStream
& os
) const
675 MathEnsurer
ensurer(os
, macro_
!= 0, true);
678 if (displayMode_
!= DISPLAY_NORMAL
) {
679 os
<< "\\" << name();
680 if (name().size() != 1 || isAlphaASCII(name()[0]))
681 os
.pendingSpace(true);
686 LASSERT(macro_
, /**/);
688 // optional arguments make macros fragile
689 if (optionals_
> 0 && os
.fragile())
692 os
<< "\\" << name();
695 // Optional arguments:
696 // First find last non-empty optional argument
697 idx_type emptyOptFrom
= 0;
699 for (; i
< cells_
.size() && i
< optionals_
; ++i
) {
700 if (!cell(i
).empty())
701 emptyOptFrom
= i
+ 1;
704 // print out optionals
705 for (i
=0; i
< cells_
.size() && i
< emptyOptFrom
; ++i
) {
707 os
<< "[" << cell(i
) << "]";
710 // skip the tailing empty optionals
713 // Print remaining macros
714 for (; i
< cells_
.size(); ++i
) {
715 if (cell(i
).size() == 1
716 && cell(i
)[0].nucleus()->asCharInset()
717 && cell(i
)[0].nucleus()->asCharInset()->getChar() < 0x80) {
722 os
<< "{" << cell(i
) << "}";
726 // add space if there was no argument
728 os
.pendingSpace(true);
732 void MathMacro::maple(MapleStream
& os
) const
734 lyx::maple(expanded_
.cell(0), os
);
738 void MathMacro::mathmlize(MathStream
& os
) const
740 lyx::mathmlize(expanded_
.cell(0), os
);
744 void MathMacro::octave(OctaveStream
& os
) const
746 lyx::octave(expanded_
.cell(0), os
);
750 void MathMacro::infoize(odocstream
& os
) const
752 os
<< "Macro: " << name();
756 void MathMacro::infoize2(odocstream
& os
) const
758 os
<< "Macro: " << name();
763 bool MathMacro::completionSupported(Cursor
const & cur
) const
765 if (displayMode() != DISPLAY_UNFOLDED
)
766 return InsetMathNest::completionSupported(cur
);
768 return lyxrc
.completion_popup_math
769 && displayMode() == DISPLAY_UNFOLDED
770 && cur
.bv().cursor().pos() == int(name().size());
774 bool MathMacro::inlineCompletionSupported(Cursor
const & cur
) const
776 if (displayMode() != DISPLAY_UNFOLDED
)
777 return InsetMathNest::inlineCompletionSupported(cur
);
779 return lyxrc
.completion_inline_math
780 && displayMode() == DISPLAY_UNFOLDED
781 && cur
.bv().cursor().pos() == int(name().size());
785 bool MathMacro::automaticInlineCompletion() const
787 if (displayMode() != DISPLAY_UNFOLDED
)
788 return InsetMathNest::automaticInlineCompletion();
790 return lyxrc
.completion_inline_math
;
794 bool MathMacro::automaticPopupCompletion() const
796 if (displayMode() != DISPLAY_UNFOLDED
)
797 return InsetMathNest::automaticPopupCompletion();
799 return lyxrc
.completion_popup_math
;
803 CompletionList
const *
804 MathMacro::createCompletionList(Cursor
const & cur
) const
806 if (displayMode() != DISPLAY_UNFOLDED
)
807 return InsetMathNest::createCompletionList(cur
);
809 return new MathCompletionList(cur
.bv().cursor());
813 docstring
MathMacro::completionPrefix(Cursor
const & cur
) const
815 if (displayMode() != DISPLAY_UNFOLDED
)
816 return InsetMathNest::completionPrefix(cur
);
818 if (!completionSupported(cur
))
821 return "\\" + name();
825 bool MathMacro::insertCompletion(Cursor
& cur
, docstring
const & s
,
828 if (displayMode() != DISPLAY_UNFOLDED
)
829 return InsetMathNest::insertCompletion(cur
, s
, finished
);
831 if (!completionSupported(cur
))
835 docstring newName
= name() + s
;
836 asArray(newName
, cell(0));
837 cur
.bv().cursor().pos() = name().size();
838 cur
.updateFlags(Update::SinglePar
);
842 cur
.bv().cursor().pop();
843 ++cur
.bv().cursor().pos();
844 cur
.updateFlags(Update::SinglePar
);
851 void MathMacro::completionPosAndDim(Cursor
const & cur
, int & x
, int & y
,
852 Dimension
& dim
) const
854 if (displayMode() != DISPLAY_UNFOLDED
)
855 InsetMathNest::completionPosAndDim(cur
, x
, y
, dim
);
857 // get inset dimensions
858 dim
= cur
.bv().coordCache().insets().dim(this);
859 // FIXME: these 3 are no accurate, but should depend on the font.
860 // Now the popup jumps down if you enter a char with descent > 0.
866 = cur
.bv().coordCache().insets().xy(this);