lyx_mirror/src/mathed/InsetMathScript.cpp
Georg Baum 5261ae6a29 Fix bug #1424: Native \sideset inset
The toolbar image is the one Uwe attached to the bug report. Note that
\sideset works only for operators like \sum in the nucleus. LyX allows
any content, so you might get a LaTeX error. I don't know how to prevent
wrong content in the nucleus.
2013-02-17 11:08:58 +01:00

792 lines
16 KiB
C++

/**
* \file InsetMathScript.cpp
* This file is part of LyX, the document processor.
* Licence details can be found in the file COPYING.
*
* \author André Pönitz
*
* Full author contact details are available in file CREDITS.
*/
#include <config.h>
#include "BufferView.h"
#include "Cursor.h"
#include "DispatchResult.h"
#include "FuncRequest.h"
#include "FuncStatus.h"
#include "InsetMathBrace.h"
#include "InsetMathScript.h"
#include "InsetMathSymbol.h"
#include "LaTeXFeatures.h"
#include "MathData.h"
#include "MathStream.h"
#include "MathSupport.h"
#include "support/debug.h"
#include "support/lassert.h"
using namespace std;
namespace lyx {
InsetMathScript::InsetMathScript(Buffer * buf)
: InsetMathNest(buf, 1), cell_1_is_up_(false), limits_(0)
{}
InsetMathScript::InsetMathScript(Buffer * buf, bool up)
: InsetMathNest(buf, 2), cell_1_is_up_(up), limits_(0)
{}
InsetMathScript::InsetMathScript(Buffer * buf, MathAtom const & at, bool up)
: InsetMathNest(buf, 2), cell_1_is_up_(up), limits_(0)
{
LASSERT(nargs() >= 1, /**/);
cell(0).push_back(at);
}
Inset * InsetMathScript::clone() const
{
return new InsetMathScript(*this);
}
InsetMathScript const * InsetMathScript::asScriptInset() const
{
return this;
}
InsetMathScript * InsetMathScript::asScriptInset()
{
return this;
}
bool InsetMathScript::idxFirst(Cursor & cur) const
{
cur.idx() = 0;
cur.pos() = 0;
return true;
}
bool InsetMathScript::idxLast(Cursor & cur) const
{
cur.idx() = 0;
cur.pos() = nuc().size();
return true;
}
MathData const & InsetMathScript::down() const
{
if (nargs() == 3)
return cell(2);
LASSERT(nargs() > 1, /**/);
return cell(1);
}
MathData & InsetMathScript::down()
{
if (nargs() == 3)
return cell(2);
LASSERT(nargs() > 1, /**/);
return cell(1);
}
MathData const & InsetMathScript::up() const
{
LASSERT(nargs() > 1, /**/);
return cell(1);
}
MathData & InsetMathScript::up()
{
LASSERT(nargs() > 1, /**/);
return cell(1);
}
void InsetMathScript::ensure(bool up)
{
if (nargs() == 1) {
// just nucleus so far
cells_.push_back(MathData());
cell_1_is_up_ = up;
} else if (nargs() == 2 && !has(up)) {
if (up) {
cells_.push_back(cell(1));
cell(1).clear();
} else {
cells_.push_back(MathData());
}
}
}
MathData const & InsetMathScript::nuc() const
{
return cell(0);
}
MathData & InsetMathScript::nuc()
{
return cell(0);
}
int InsetMathScript::dy01(BufferView const & bv, int asc, int des, int what) const
{
int dasc = 0;
int slevel = 0;
bool isCharBox = !nuc().empty() ? isAlphaSymbol(nuc().back()) : false;
if (hasDown()) {
Dimension const & dimdown = down().dimension(bv);
dasc = dimdown.ascent();
slevel = nuc().slevel();
int ascdrop = dasc - slevel;
int desdrop = isCharBox ? 0 : des + nuc().sshift();
int mindes = nuc().mindes();
des = max(desdrop, ascdrop);
des = max(mindes, des);
}
if (hasUp()) {
Dimension const & dimup = up().dimension(bv);
int minasc = nuc().minasc();
int ascdrop = isCharBox ? 0 : asc - up().mindes();
int udes = dimup.descent();
asc = udes + nuc().sshift();
asc = max(ascdrop, asc);
asc = max(minasc, asc);
if (hasDown()) {
int del = asc - udes - dasc;
if (del + des <= 2) {
int newdes = 2 - del;
del = slevel - asc + udes;
if (del > 0) {
asc += del;
newdes -= del;
}
des = max(des, newdes);
}
}
}
return what ? asc : des;
}
int InsetMathScript::dy0(BufferView const & bv) const
{
int nd = ndes(bv);
if (!hasDown())
return nd;
int des = down().dimension(bv).ascent();
if (hasLimits())
des += nd + 2;
else {
int na = nasc(bv);
des = dy01(bv, na, nd, 0);
}
return des;
}
int InsetMathScript::dy1(BufferView const & bv) const
{
int na = nasc(bv);
if (!hasUp())
return na;
int asc = up().dimension(bv).descent();
if (hasLimits())
asc += na + 2;
else {
int nd = ndes(bv);
asc = dy01(bv, na, nd, 1);
}
asc = max(asc, 5);
return asc;
}
int InsetMathScript::dx0(BufferView const & bv) const
{
LASSERT(hasDown(), /**/);
Dimension const dim = dimension(bv);
return hasLimits() ? (dim.wid - down().dimension(bv).width()) / 2 : nwid(bv);
}
int InsetMathScript::dx1(BufferView const & bv) const
{
LASSERT(hasUp(), /**/);
Dimension const dim = dimension(bv);
return hasLimits() ? (dim.wid - up().dimension(bv).width()) / 2 : nwid(bv) + nker(&bv);
}
int InsetMathScript::dxx(BufferView const & bv) const
{
Dimension const dim = dimension(bv);
return hasLimits() ? (dim.wid - nwid(bv)) / 2 : 0;
}
int InsetMathScript::nwid(BufferView const & bv) const
{
return !nuc().empty() ? nuc().dimension(bv).width() : 2;
}
int InsetMathScript::nasc(BufferView const & bv) const
{
return !nuc().empty() ? nuc().dimension(bv).ascent() : 5;
}
int InsetMathScript::ndes(BufferView const & bv) const
{
return !nuc().empty() ? nuc().dimension(bv).descent() : 0;
}
int InsetMathScript::nker(BufferView const * bv) const
{
if (!nuc().empty()) {
int kerning = nuc().kerning(bv);
return kerning > 0 ? kerning : 0;
}
return 0;
}
void InsetMathScript::metrics(MetricsInfo & mi, Dimension & dim) const
{
Dimension dim0;
Dimension dim1;
Dimension dim2;
cell(0).metrics(mi, dim0);
ScriptChanger dummy(mi.base);
if (nargs() > 1)
cell(1).metrics(mi, dim1);
if (nargs() > 2)
cell(2).metrics(mi, dim2);
dim.wid = 0;
BufferView & bv = *mi.base.bv;
// FIXME: data copying... not very efficient.
Dimension dimup;
Dimension dimdown;
if (hasUp())
dimup = up().dimension(bv);
if (hasDown())
dimdown = down().dimension(bv);
if (hasLimits()) {
dim.wid = nwid(bv);
if (hasUp())
dim.wid = max(dim.wid, dimup.width());
if (hasDown())
dim.wid = max(dim.wid, dimdown.width());
} else {
if (hasUp())
dim.wid = max(dim.wid, nker(mi.base.bv) + dimup.width());
if (hasDown())
dim.wid = max(dim.wid, dimdown.width());
dim.wid += nwid(bv);
}
int na = nasc(bv);
if (hasUp()) {
int asc = dy1(bv) + dimup.ascent();
dim.asc = max(na, asc);
} else
dim.asc = na;
int nd = ndes(bv);
if (hasDown()) {
int des = dy0(bv) + dimdown.descent();
dim.des = max(nd, des);
} else
dim.des = nd;
metricsMarkers(dim);
}
void InsetMathScript::draw(PainterInfo & pi, int x, int y) const
{
BufferView & bv = *pi.base.bv;
if (!nuc().empty())
nuc().draw(pi, x + dxx(bv), y);
else {
nuc().setXY(bv, x + dxx(bv), y);
if (editing(&bv))
pi.draw(x + dxx(bv), y, char_type('.'));
}
ScriptChanger dummy(pi.base);
if (hasUp())
up().draw(pi, x + dx1(bv), y - dy1(bv));
if (hasDown())
down().draw(pi, x + dx0(bv), y + dy0(bv));
drawMarkers(pi, x, y);
}
void InsetMathScript::metricsT(TextMetricsInfo const & mi, Dimension & dim) const
{
if (hasUp())
up().metricsT(mi, dim);
if (hasDown())
down().metricsT(mi, dim);
nuc().metricsT(mi, dim);
}
void InsetMathScript::drawT(TextPainter & pain, int x, int y) const
{
// FIXME: BROKEN
if (!nuc().empty())
nuc().drawT(pain, x + 1, y);
if (hasUp())
up().drawT(pain, x + 1, y - 1 /*dy1()*/);
if (hasDown())
down().drawT(pain, x + 1, y + 1 /*dy0()*/);
}
bool InsetMathScript::hasLimits() const
{
// obvious cases
if (limits_ == 1)
return true;
if (limits_ == -1)
return false;
// we can only display limits if the nucleus wants some
if (nuc().empty())
return false;
if (!nuc().back()->isScriptable())
return false;
if (nuc().back()->asSymbolInset()) {
// \intop is an alias for \int\limits, \ointop == \oint\limits
if (nuc().back()->asSymbolInset()->name().find(from_ascii("intop")) != string::npos)
return true;
// per default \int has limits beside the \int even in displayed formulas
if (nuc().back()->asSymbolInset()->name().find(from_ascii("int")) != string::npos)
return false;
}
// assume "real" limits for everything else
return true;
}
void InsetMathScript::removeScript(bool up)
{
if (nargs() == 2) {
if (up == cell_1_is_up_)
cells_.pop_back();
} else if (nargs() == 3) {
if (up == true) {
swap(cells_[1], cells_[2]);
cell_1_is_up_ = false;
} else {
cell_1_is_up_ = true;
}
cells_.pop_back();
}
}
bool InsetMathScript::has(bool up) const
{
return idxOfScript(up);
}
bool InsetMathScript::hasUp() const
{
//lyxerr << "1up: " << bool(cell_1_is_up_));
//lyxerr << "hasUp: " << bool(idxOfScript(true)));
return idxOfScript(true);
}
bool InsetMathScript::hasDown() const
{
//LYXERR0("1up: " << bool(cell_1_is_up_));
//LYXERR0("hasDown: " << bool(idxOfScript(false)));
return idxOfScript(false);
}
Inset::idx_type InsetMathScript::idxOfScript(bool up) const
{
if (nargs() == 1)
return 0;
if (nargs() == 2)
return (cell_1_is_up_ == up) ? 1 : 0;
if (nargs() == 3)
return up ? 1 : 2;
LASSERT(false, /**/);
// Silence compiler
return 0;
}
bool InsetMathScript::idxForward(Cursor &) const
{
return false;
}
bool InsetMathScript::idxBackward(Cursor &) const
{
return false;
}
bool InsetMathScript::idxUpDown(Cursor & cur, bool up) const
{
// in nucleus?
if (cur.idx() == 0) {
// don't go up/down if there is no cell in this direction
if (!has(up))
return false;
// go up/down only if in the last position
// or in the first position of something with displayed limits
if (cur.pos() == cur.lastpos() || (cur.pos() == 0 && hasLimits())) {
cur.idx() = idxOfScript(up);
cur.pos() = 0;
return true;
}
return false;
}
// Are we 'up'?
if (cur.idx() == idxOfScript(true)) {
// can't go further up
if (up)
return false;
// otherwise go to last position in the nucleus
cur.idx() = 0;
cur.pos() = cur.lastpos();
return true;
}
// Are we 'down'?
if (cur.idx() == idxOfScript(false)) {
// can't go further down
if (!up)
return false;
// otherwise go to last position in the nucleus
cur.idx() = 0;
cur.pos() = cur.lastpos();
return true;
}
return false;
}
void InsetMathScript::write(WriteStream & os) const
{
MathEnsurer ensurer(os);
if (!nuc().empty()) {
os << nuc();
//if (nuc().back()->takesLimits()) {
if (limits_ == -1)
os << "\\nolimits ";
if (limits_ == 1)
os << "\\limits ";
//}
} else {
if (os.firstitem())
LYXERR(Debug::MATHED, "suppressing {} when writing");
else
os << "{}";
}
if (hasDown() /*&& !down().empty()*/)
os << "_{" << down() << '}';
if (hasUp() /*&& !up().empty()*/) {
// insert space if up() is empty or an empty brace inset
// (see bug 8305)
if (os.latex() && (up().empty() ||
(up().size() == 1 && up().back()->asBraceInset() &&
up().back()->asBraceInset()->cell(0).empty())))
os << "^ {}";
else
os << "^{" << up() << '}';
}
if (lock_ && !os.latex())
os << "\\lyxlock ";
}
void InsetMathScript::normalize(NormalStream & os) const
{
bool d = hasDown() && !down().empty();
bool u = hasUp() && !up().empty();
if (u && d)
os << "[subsup ";
else if (u)
os << "[sup ";
else if (d)
os << "[sub ";
if (!nuc().empty())
os << nuc() << ' ';
else
os << "[par]";
if (u && d)
os << down() << ' ' << up() << ']';
else if (d)
os << down() << ']';
else if (u)
os << up() << ']';
}
void InsetMathScript::maple(MapleStream & os) const
{
if (!nuc().empty())
os << nuc();
if (hasDown() && !down().empty())
os << '[' << down() << ']';
if (hasUp() && !up().empty())
os << "^(" << up() << ')';
}
void InsetMathScript::mathematica(MathematicaStream & os) const
{
bool d = hasDown() && !down().empty();
bool u = hasUp() && !up().empty();
if (!nuc().empty()) {
if (d)
os << "Subscript[" << nuc();
else
os << nuc();
}
if (u)
os << "^(" << up() << ')';
if (!nuc().empty()) {
if (d)
os << ',' << down() << ']';
}
}
// FIXME XHTML
// It may be worth trying to output munder, mover, and munderover
// in certain cases, e.g., for display formulas. But then we would
// need to know if we're in a display formula.
void InsetMathScript::mathmlize(MathStream & os) const
{
bool d = hasDown() && !down().empty();
bool u = hasUp() && !up().empty();
if (u && d)
os << MTag("msubsup");
else if (u)
os << MTag("msup");
else if (d)
os << MTag("msub");
if (!nuc().empty())
os << MTag("mrow") << nuc() << ETag("mrow");
else
os << "<mrow />";
if (u && d)
os << MTag("mrow") << down() << ETag("mrow")
<< MTag("mrow") << up() << ETag("mrow")
<< ETag("msubsup");
else if (u)
os << MTag("mrow") << up() << ETag("mrow") << ETag("msup");
else if (d)
os << MTag("mrow") << down() << ETag("mrow") << ETag("msub");
}
void InsetMathScript::htmlize(HtmlStream & os) const
{
bool d = hasDown() && !down().empty();
bool u = hasUp() && !up().empty();
if (!nuc().empty())
os << nuc();
if (u && d)
os << MTag("span", "class='scripts'")
<< MTag("span") << up() << ETag("span")
<< MTag("span") << down() << ETag("span")
<< ETag("span");
else if (u)
os << MTag("sup", "class='math'") << up() << ETag("sup");
else if (d)
os << MTag("sub", "class='math'") << down() << ETag("sub");
}
void InsetMathScript::octave(OctaveStream & os) const
{
if (!nuc().empty())
os << nuc();
if (hasDown() && !down().empty())
os << '[' << down() << ']';
if (hasUp() && !up().empty())
os << "^(" << up() << ')';
}
void InsetMathScript::infoize(odocstream & os) const
{
os << "Scripts";
}
void InsetMathScript::infoize2(odocstream & os) const
{
if (limits_)
os << from_ascii(limits_ == 1 ? ", Displayed limits" : ", Inlined limits");
}
bool InsetMathScript::notifyCursorLeaves(Cursor const & old, Cursor & cur)
{
InsetMathNest::notifyCursorLeaves(old, cur);
//LYXERR0("InsetMathScript::notifyCursorLeaves: 1 " << cur);
// Remove empty scripts if possible:
// The case of two scripts, but only one got empty (1 = super, 2 = sub).
// We keep the script inset, but remove the empty script.
if (nargs() > 2 && (!cell(1).empty() || !cell(2).empty())) {
if (cell(2).empty()) {
// must be a subscript...
old.recordUndoInset();
removeScript(false);
cur.screenUpdateFlags(cur.result().screenUpdate() | Update::SinglePar);
return true;
} else if (cell(1).empty()) {
// must be a superscript...
old.recordUndoInset();
removeScript(true);
cur.screenUpdateFlags(cur.result().screenUpdate() | Update::SinglePar);
return true;
}
}
// Now the two suicide cases:
// * we have only one script which is empty
// * we have two scripts which are both empty.
// The script inset is removed completely.
if ((nargs() == 2 && cell(1).empty())
|| (nargs() == 3 && cell(1).empty() && cell(2).empty())) {
// Make undo step. We cannot use cur for this because
// it does not necessarily point to us anymore. But we
// should be on top of the cursor old.
Cursor insetCur = old;
int scriptSlice = insetCur.find(this);
LASSERT(scriptSlice != -1, /**/);
insetCur.cutOff(scriptSlice);
insetCur.recordUndoInset();
// Let the script inset commit suicide. This is
// modelled on Cursor.pullArg(), but tries not to
// invoke notifyCursorLeaves again and does not touch
// cur (since the top slice will be deleted
// afterwards)
MathData ar = cell(0);
insetCur.pop();
insetCur.cell().erase(insetCur.pos());
insetCur.cell().insert(insetCur.pos(), ar);
// redraw
cur.screenUpdateFlags(cur.result().screenUpdate() | Update::SinglePar);
return true;
}
//LYXERR0("InsetMathScript::notifyCursorLeaves: 2 " << cur);
return false;
}
void InsetMathScript::doDispatch(Cursor & cur, FuncRequest & cmd)
{
//LYXERR("InsetMathScript: request: " << cmd);
if (cmd.action() == LFUN_MATH_LIMITS) {
cur.recordUndoInset();
if (!cmd.argument().empty()) {
if (cmd.argument() == "limits")
limits_ = 1;
else if (cmd.argument() == "nolimits")
limits_ = -1;
else
limits_ = 0;
} else if (limits_ == 0)
limits_ = hasLimits() ? -1 : 1;
else
limits_ = 0;
return;
}
InsetMathNest::doDispatch(cur, cmd);
}
bool InsetMathScript::getStatus(Cursor & cur, FuncRequest const & cmd,
FuncStatus & flag) const
{
if (cmd.action() == LFUN_MATH_LIMITS) {
if (!cmd.argument().empty()) {
if (cmd.argument() == "limits")
flag.setOnOff(limits_ == 1);
else if (cmd.argument() == "nolimits")
flag.setOnOff(limits_ == -1);
else
flag.setOnOff(limits_ == 0);
}
flag.setEnabled(true);
return true;
}
return InsetMathNest::getStatus(cur, cmd, flag);
}
// the idea for dual scripts came from the eLyXer code
void InsetMathScript::validate(LaTeXFeatures & features) const
{
if (features.runparams().math_flavor == OutputParams::MathAsHTML)
features.addCSSSnippet(
"span.scripts{display: inline-block; vertical-align: middle; text-align:center; font-size: 75%;}\n"
"span.scripts span {display: block;}\n"
"sub.math{font-size: 75%;}\n"
"sup.math{font-size: 75%;}");
InsetMathNest::validate(features);
}
} // namespace lyx