#include "MathRow.h"
-#include "InsetMath.h"
-#include "MathClass.h"
#include "MathData.h"
-#include "MathMacro.h"
#include "MathSupport.h"
#include "BufferView.h"
+#include "ColorSet.h"
#include "CoordCache.h"
#include "MetricsInfo.h"
+#include "mathed/InsetMath.h"
+
+#include "frontends/FontMetrics.h"
#include "frontends/Painter.h"
#include "support/debug.h"
#include "support/docstring.h"
#include "support/lassert.h"
+#include <algorithm>
#include <ostream>
using namespace std;
namespace lyx {
-MathRow::Element::Element(Type t, MathClass const mc)
- : type(t),
- inset(0), mclass(mc), before(0), after(0), compl_unique_to(0),
- macro(0)
+MathRow::Element::Element(MetricsInfo const & mi, Type t, MathClass mc)
+ : type(t), mclass(mc), before(0), after(0), macro_nesting(mi.base.macro_nesting),
+ marker(marker_type::NO_MARKER), inset(nullptr), compl_unique_to(0), ar(nullptr),
+ color(Color_red)
{}
-MathRow::MathRow(MetricsInfo const & mi, MathData const * ar)
+namespace {
+
+// Helper functions for markers
+
+int markerMargin(MathRow::Element const & e)
+{
+ switch(e.marker) {
+ case marker_type::MARKER:
+ case marker_type::MARKER2:
+ case marker_type::BOX_MARKER:
+ return 2;
+ case marker_type::NO_MARKER:
+ return 0;
+ }
+ // should not happen
+ return 0;
+}
+
+
+void afterMetricsMarkers(MetricsInfo const & , MathRow::Element & e,
+ Dimension & dim)
+{
+ // handle vertical space for markers
+ switch(e.marker) {
+ case marker_type::NO_MARKER:
+ break;
+ case marker_type::MARKER:
+ ++dim.des;
+ break;
+ case marker_type::MARKER2:
+ ++dim.asc;
+ ++dim.des;
+ break;
+ case marker_type::BOX_MARKER:
+ FontInfo font;
+ font.setSize(TINY_SIZE);
+ Dimension namedim;
+ mathed_string_dim(font, e.inset->name(), namedim);
+ int const namewid = 1 + namedim.wid + 1;
+
+ if (namewid > dim.wid)
+ e.after += namewid - dim.wid;
+ ++dim.asc;
+ dim.des += 3 + namedim.height();
+ }
+}
+
+
+void drawMarkers(PainterInfo const & pi, MathRow::Element const & e,
+ int const x, int const y)
{
- if (ar->empty())
+ if (e.marker == marker_type::NO_MARKER)
return;
+ CoordCache const & coords = pi.base.bv->coordCache();
+ Dimension const dim = coords.getInsets().dim(e.inset);
+
+ // the marker is before/after the inset. Necessary space has been reserved already.
+ int const l = x + e.before - (markerMargin(e) > 0 ? 1 : 0);
+ int const r = x + dim.width() - e.after;
+
+ // Grey lower box
+ if (e.marker == marker_type::BOX_MARKER) {
+ // draw header and rectangle around
+ FontInfo font;
+ font.setSize(TINY_SIZE);
+ font.setColor(Color_mathmacrolabel);
+ Dimension namedim;
+ mathed_string_dim(font, e.inset->name(), namedim);
+ pi.pain.fillRectangle(l, y + dim.des - namedim.height() - 2,
+ dim.wid, namedim.height() + 2, Color_mathmacrobg);
+ pi.pain.text(l, y + dim.des - namedim.des - 1, e.inset->name(), font);
+ }
+
+ // Color for corners
+ bool const highlight = e.inset->mouseHovered(pi.base.bv)
+ || e.inset->editing(pi.base.bv);
+ ColorCode const pen_color = highlight ? Color_mathframe : Color_mathcorners;
+ // If the corners have the same color as the background, do not paint them.
+ if (lcolor.getX11HexName(Color_mathbg) == lcolor.getX11HexName(pen_color))
+ return;
+
+ // Lower corners in all cases
+ int const d = y + dim.descent();
+ pi.pain.line(l, d - 3, l, d, pen_color);
+ pi.pain.line(r, d - 3, r, d, pen_color);
+ pi.pain.line(l, d, l + 3, d, pen_color);
+ pi.pain.line(r - 3, d, r, d, pen_color);
+
+ // Upper corners
+ if (e.marker == marker_type::BOX_MARKER
+ || e.marker == marker_type::MARKER2) {
+ int const a = y - dim.ascent();
+ pi.pain.line(l, a + 3, l, a, pen_color);
+ pi.pain.line(r, a + 3, r, a, pen_color);
+ pi.pain.line(l, a, l + 3, a, pen_color);
+ pi.pain.line(r - 3, a, r, a, pen_color);
+ }
+}
+
+} // namespace
+
+
+MathRow::MathRow(MetricsInfo & mi, MathData const * ar)
+{
// First there is a dummy element of type "open"
- push_back(Element(BEGIN, MC_OPEN));
+ push_back(Element(mi, DUMMY, MC_OPEN));
// Then insert the MathData argument
- ar->addToMathRow(*this, mi);
+ bool const has_contents = ar->addToMathRow(*this, mi);
+
+ // A MathRow should not be completely empty
+ if (!has_contents) {
+ Element e(mi, BOX, MC_ORD);
+ // empty arrays are visible when they are editable
+ e.color = mi.base.macro_nesting == 0 ? Color_mathline : Color_none;
+ push_back(e);
+ }
// Finally there is a dummy element of type "close"
- push_back(Element(END, MC_CLOSE));
+ push_back(Element(mi, DUMMY, MC_CLOSE));
/* Do spacing only in math mode. This test is a bit clumsy,
* but it is used in other places for guessing the current mode.
*/
- if (!isMathFont(mi.base.fontname))
- return;
+ bool const dospacing = isMathFont(mi.base.fontname);
// update classes
- for (int i = 1 ; i != static_cast<int>(elements_.size()) - 1 ; ++i) {
- if (elements_[i].type != INSET)
- continue;
- update_class(elements_[i].mclass, elements_[before(i)].mclass,
- elements_[after(i)].mclass);
+ if (dospacing) {
+ for (int i = 1 ; i != static_cast<int>(elements_.size()) - 1 ; ++i) {
+ if (elements_[i].mclass != MC_UNKNOWN)
+ update_class(elements_[i].mclass, elements_[before(i)].mclass,
+ elements_[after(i)].mclass);
+ }
}
// set spacing
// We go to the end to handle spacing at the end of equation
for (int i = 1 ; i != static_cast<int>(elements_.size()) ; ++i) {
- if (elements_[i].type != INSET)
- continue;
+ Element & e = elements_[i];
+
Element & bef = elements_[before(i)];
- int spc = class_spacing(bef.mclass, elements_[i].mclass, mi.base);
- bef.after = spc / 2;
- // this is better than spc / 2 to avoid rounding problems
- elements_[i].before = spc - spc / 2;
+ if (dospacing && e.mclass != MC_UNKNOWN) {
+ int spc = class_spacing(bef.mclass, e.mclass, mi.base);
+ bef.after += spc / 2;
+ // this is better than spc / 2 to avoid rounding problems
+ e.before += spc - spc / 2;
+ }
+
+ // finally reserve space for markers
+ bef.after = max(bef.after, markerMargin(bef));
+ if (e.mclass != MC_UNKNOWN)
+ e.before = max(e.before, markerMargin(e));
+ // for linearized insets (macros...) too
+ if (e.type == BEGIN)
+ bef.after = max(bef.after, markerMargin(e));
+ if (e.type == END && e.marker != marker_type::NO_MARKER) {
+ Element & aft = elements_[after(i)];
+ aft.before = max(aft.before, markerMargin(e));
+ }
}
+
// Do not lose spacing allocated to extremities
if (!elements_.empty()) {
elements_[after(0)].before += elements_.front().after;
{
do
--i;
- while (elements_[i].type != BEGIN
- && elements_[i].type != INSET);
+ while (elements_[i].mclass == MC_UNKNOWN);
return i;
}
{
do
++i;
- while (elements_[i].type != END
- && elements_[i].type != INSET);
+ while (elements_[i].mclass == MC_UNKNOWN);
return i;
}
-void MathRow::metrics(MetricsInfo & mi, Dimension & dim) const
+void MathRow::metrics(MetricsInfo & mi, Dimension & dim)
{
- dim.asc = 0;
dim.wid = 0;
// In order to compute the dimension of macros and their
// arguments, it is necessary to keep track of them.
- map<MathMacro const *, Dimension> dim_macros;
- map<MathData const *, Dimension> dim_arrays;
+ vector<pair<InsetMath const *, Dimension>> dim_insets;
+ vector<pair<MathData const *, Dimension>> dim_arrays;
CoordCache & coords = mi.base.bv->coordCache();
-
- for (Element const & e : elements_) {
+ for (Element & e : elements_) {
+ mi.base.macro_nesting = e.macro_nesting;
Dimension d;
switch (e.type) {
- case BEGIN:
- case END:
+ case DUMMY:
break;
case INSET:
e.inset->metrics(mi, d);
d.wid += e.before + e.after;
coords.insets().add(e.inset, d);
+ break;
+ case BEGIN:
+ if (e.inset) {
+ dim_insets.push_back(make_pair(e.inset, Dimension()));
+ dim_insets.back().second.wid += e.before + e.after;
+ d.wid = e.before + e.after;
+ e.inset->beforeMetrics();
+ }
+ if (e.ar)
+ dim_arrays.push_back(make_pair(e.ar, Dimension()));
+ break;
+ case END:
+ if (e.inset) {
+ e.inset->afterMetrics();
+ LATTEST(dim_insets.back().first == e.inset);
+ d = dim_insets.back().second;
+ afterMetricsMarkers(mi, e, d);
+ d.wid += e.before + e.after;
+ coords.insets().add(e.inset, d);
+ dim_insets.pop_back();
+ // We do not want to count the width again, but the
+ // padding and the vertical dimension are meaningful.
+ d.wid = e.before + e.after;
+ }
+ if (e.ar) {
+ LATTEST(dim_arrays.back().first == e.ar);
+ coords.arrays().add(e.ar, dim_arrays.back().second);
+ dim_arrays.pop_back();
+ }
+ break;
+ case BOX:
+ d = theFontMetrics(mi.base.font).dimension('I');
+ if (e.color != Color_none) {
+ // allow for one pixel before/after the box.
+ d.wid += e.before + e.after + 2;
+ } else {
+ // hide the box, but keep its height
+ d.wid = 0;
+ }
+ break;
+ }
+
+ if (!d.empty()) {
dim += d;
// Now add the dimension to current macros and arguments.
- for (auto & dim_macro : dim_macros)
+ for (auto & dim_macro : dim_insets)
dim_macro.second += d;
for (auto & dim_array : dim_arrays)
dim_array.second += d;
- break;
- case BEG_MACRO:
- e.macro->macro()->lock();
- // Add a macro to current list
- dim_macros[e.macro] = Dimension();
- break;
- case END_MACRO:
- LATTEST(dim_macros.find(e.macro) != dim_macros.end());
- e.macro->macro()->unlock();
- // Cache the dimension of the macro and remove it from
- // tracking map.
- coords.insets().add(e.macro, dim_macros[e.macro]);
- dim_macros.erase(e.macro);
- break;
- // This is basically like macros
- case BEG_ARG:
- if (e.macro)
- e.macro->macro()->unlock();
- dim_arrays[e.ar] = Dimension();
- break;
- case END_ARG:
- LATTEST(dim_arrays.find(e.ar) != dim_arrays.end());
- if (e.macro)
- e.macro->macro()->lock();
- coords.arrays().add(e.ar, dim_arrays[e.ar]);
- dim_arrays.erase(e.ar);
- break;
}
if (e.compl_text.empty())
augmentFont(font, "mathnormal");
dim.wid += mathed_string_width(font, e.compl_text);
}
- LATTEST(dim_macros.empty() && dim_arrays.empty());
+ LATTEST(dim_insets.empty() && dim_arrays.empty());
}
{
CoordCache & coords = pi.base.bv->coordCache();
for (Element const & e : elements_) {
- Dimension d;
switch (e.type) {
case INSET: {
// This is hackish: the math inset does not know that space
Dimension d2 = d;
d2.wid -= e.before + e.after;
coords.insets().add(e.inset, d2);
- e.inset->drawSelection(pi, x + e.before, y);
+ if (pi.pain.develMode() && !e.inset->isBufferValid())
+ pi.pain.fillRectangle(x + e.before, y - d2.ascent(),
+ d2.width(), d2.height(), Color_error);
e.inset->draw(pi, x + e.before, y);
coords.insets().add(e.inset, x, y);
coords.insets().add(e.inset, d);
+ drawMarkers(pi, e, x, y);
x += d.wid;
break;
}
- case BEG_MACRO:
- coords.insets().add(e.macro, x, y);
+ case BEGIN:
+ if (e.ar) {
+ coords.arrays().add(e.ar, x, y);
+ e.ar->drawSelection(pi, x, y);
+ }
+ if (e.inset) {
+ coords.insets().add(e.inset, x, y);
+ drawMarkers(pi, e, x, y);
+ e.inset->beforeDraw(pi);
+ }
+ x += e.before + e.after;
break;
- case BEG_ARG:
- coords.arrays().add(e.ar, x, y);
- // if the macro is being edited, then the painter is in
- // monochrome mode.
- if (e.macro->editMetrics(pi.base.bv))
- pi.pain.leaveMonochromeMode();
+ case END:
+ if (e.inset)
+ e.inset->afterDraw(pi);
+ x += e.before + e.after;
break;
- case END_ARG:
- if (e.macro->editMetrics(pi.base.bv))
- pi.pain.enterMonochromeMode(Color_mathbg, Color_mathmacroblend);
+ case BOX: {
+ if (e.color == Color_none)
+ break;
+ Dimension const d = theFontMetrics(pi.base.font).dimension('I');
+ pi.pain.rectangle(x + e.before + 1, y - d.ascent(),
+ d.width() - 1, d.height() - 1, e.color);
+ x += d.wid + 2 + e.before + e.after;
break;
- case BEGIN:
- case END:
- case END_MACRO:
+ }
+ case DUMMY:
break;
}
if (!s1.empty()) {
f.setColor(Color_inlinecompletion);
- pi.pain.text(x, y, s1, f);
+ // offset the text by e.after to make sure that the
+ // spacing is after the completion, not before.
+ pi.pain.text(x - e.after, y, s1, f);
x += mathed_string_width(f, s1);
}
if (!s2.empty()) {
f.setColor(Color_nonunique_inlinecompletion);
- pi.pain.text(x, y, s2, f);
+ pi.pain.text(x - e.after, y, s2, f);
x += mathed_string_width(f, s2);
}
}
ostream & operator<<(ostream & os, MathRow::Element const & e)
{
switch (e.type) {
- case MathRow::BEGIN:
- os << "{";
- break;
- case MathRow::END:
- os << "}";
+ case MathRow::DUMMY:
+ os << (e.mclass == MC_OPEN ? "{" : "}");
break;
case MathRow::INSET:
os << "<" << e.before << "-"
<< to_utf8(class_to_string(e.mclass))
<< "-" << e.after << ">";
break;
- case MathRow::BEG_MACRO:
- os << "\\" << to_utf8(e.macro->name()) << "[";
- break;
- case MathRow::END_MACRO:
- os << "]";
+ case MathRow::BEGIN:
+ if (e.inset)
+ os << "\\" << to_utf8(e.inset->name())
+ << "^" << e.macro_nesting << "[";
+ if (e.ar)
+ os << "(";
break;
- case MathRow::BEG_ARG:
- os << "#(";
+ case MathRow::END:
+ if (e.ar)
+ os << ")";
+ if (e.inset)
+ os << "]";
break;
- case MathRow::END_ARG:
- os << ")";
+ case MathRow::BOX:
+ os << "<" << e.before << "-[]-" << e.after << ">";
break;
}
return os;