]> git.lyx.org Git - lyx.git/blob - src/mathed/MathRow.cpp
Give a 2 pixels space for markers around math objects
[lyx.git] / src / mathed / MathRow.cpp
1 /**
2  * \file MathRow.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Jean-Marc Lasgouttes
7  *
8  * Full author contact details are available in file CREDITS.
9  */
10
11 #include <config.h>
12
13 #include "MathRow.h"
14
15 #include "InsetMath.h"
16 #include "MathClass.h"
17 #include "MathData.h"
18 #include "MathSupport.h"
19
20 #include "BufferView.h"
21 #include "CoordCache.h"
22 #include "MetricsInfo.h"
23
24 #include "frontends/FontMetrics.h"
25 #include "frontends/Painter.h"
26
27 #include "support/debug.h"
28 #include "support/docstring.h"
29 #include "support/lassert.h"
30
31 #include <algorithm>
32 #include <ostream>
33
34 using namespace std;
35
36 namespace lyx {
37
38
39 MathRow::Element::Element(MetricsInfo const & mi, Type t, MathClass mc)
40         : type(t), mclass(mc), before(0), after(0), macro_nesting(mi.base.macro_nesting),
41           marker(InsetMath::NO_MARKER), inset(0), compl_unique_to(0), ar(0),
42           color(Color_red)
43 {}
44
45
46 namespace {
47
48 // Helper functions for markers
49
50 int markerMargin(MathRow::Element const & e)
51 {
52         switch(e.marker) {
53         case InsetMath::MARKER:
54         case InsetMath::MARKER2:
55         case InsetMath::BOX_MARKER:
56                 return 2;
57         case InsetMath::NO_MARKER:
58                 return 0;
59         }
60         // should not happen
61         return 0;
62 }
63
64
65 void afterMetricsMarkers(MetricsInfo const & , MathRow::Element & e,
66                             Dimension & dim)
67 {
68         // handle vertical space for markers
69         switch(e.marker) {
70         case InsetMath::NO_MARKER:
71                 break;
72         case InsetMath::MARKER:
73                 ++dim.des;
74                 break;
75         case InsetMath::MARKER2:
76                 ++dim.asc;
77                 ++dim.des;
78                 break;
79         case InsetMath::BOX_MARKER:
80                 FontInfo font;
81                 font.setSize(FONT_SIZE_TINY);
82                 Dimension namedim;
83                 mathed_string_dim(font, e.inset->name(), namedim);
84                 int const namewid = 1 + namedim.wid + 1;
85
86                 if (namewid > dim.wid)
87                         e.after += namewid - dim.wid;
88                 dim.des += 3 + namedim.height();
89         }
90 }
91
92
93 void drawMarkers(PainterInfo const & pi, MathRow::Element const & e,
94                  int const x, int const y)
95 {
96         if (e.marker == InsetMath::NO_MARKER)
97                 return;
98
99         CoordCache const & coords = pi.base.bv->coordCache();
100         Dimension const dim = coords.getInsets().dim(e.inset);
101
102         // the marker is before/after the inset. Necessary space has been reserved already.
103         int const l = x + e.before - (markerMargin(e) > 0 ? 1 : 0);
104         int const r = x + dim.width() - e.after;
105
106         if (e.marker == InsetMath::BOX_MARKER) {
107                 // draw header and rectangle around
108                 FontInfo font;
109                 font.setSize(FONT_SIZE_TINY);
110                 font.setColor(Color_mathmacrolabel);
111                 Dimension namedim;
112                 mathed_string_dim(font, e.inset->name(), namedim);
113                 pi.pain.fillRectangle(l, y + dim.des - namedim.height() - 2,
114                                       dim.wid, namedim.height() + 2, Color_mathmacrobg);
115                 pi.pain.text(l, y + dim.des - namedim.des - 1, e.inset->name(), font);
116                 return;
117         }
118
119         // Now markers with corners
120         bool const highlight = e.inset->mouseHovered(pi.base.bv)
121                                || e.inset->editing(pi.base.bv);
122         ColorCode const pen_color = highlight ? Color_mathframe : Color_mathcorners;
123
124         int const d = y + dim.descent();
125         pi.pain.line(l, d - 3, l, d, pen_color);
126         pi.pain.line(r, d - 3, r, d, pen_color);
127         pi.pain.line(l, d, l + 3, d, pen_color);
128         pi.pain.line(r - 3, d, r, d, pen_color);
129
130         if (e.marker == InsetMath::MARKER)
131                 return;
132
133         int const a = y - dim.ascent();
134         pi.pain.line(l, a + 3, l, a, pen_color);
135         pi.pain.line(r, a + 3, r, a, pen_color);
136         pi.pain.line(l, a, l + 3, a, pen_color);
137         pi.pain.line(r - 3, a, r, a, pen_color);
138 }
139
140 }
141
142
143 MathRow::MathRow(MetricsInfo & mi, MathData const * ar)
144 {
145         // First there is a dummy element of type "open"
146         push_back(Element(mi, DUMMY, MC_OPEN));
147
148         // Then insert the MathData argument
149         bool const has_contents = ar->addToMathRow(*this, mi);
150
151         // A MathRow should not be completely empty
152         if (!has_contents) {
153                 Element e(mi, BOX, MC_ORD);
154                 // empty arrays are visible when they are editable
155                 e.color = mi.base.macro_nesting == 0 ? Color_mathline : Color_none;
156                 push_back(e);
157         }
158
159         // Finally there is a dummy element of type "close"
160         push_back(Element(mi, DUMMY, MC_CLOSE));
161
162         /* Do spacing only in math mode. This test is a bit clumsy,
163          * but it is used in other places for guessing the current mode.
164          */
165         bool const dospacing = isMathFont(mi.base.fontname);
166
167         // update classes
168         if (dospacing) {
169                 for (int i = 1 ; i != static_cast<int>(elements_.size()) - 1 ; ++i) {
170                         if (elements_[i].mclass != MC_UNKNOWN)
171                                 update_class(elements_[i].mclass, elements_[before(i)].mclass,
172                                                          elements_[after(i)].mclass);
173                 }
174         }
175
176         // set spacing
177         // We go to the end to handle spacing at the end of equation
178         for (int i = 1 ; i != static_cast<int>(elements_.size()) ; ++i) {
179                 Element & e = elements_[i];
180
181                 Element & bef = elements_[before(i)];
182                 if (dospacing && e.mclass != MC_UNKNOWN) {
183                         int spc = class_spacing(bef.mclass, e.mclass, mi.base);
184                         bef.after += spc / 2;
185                         // this is better than spc / 2 to avoid rounding problems
186                         e.before += spc - spc / 2;
187                 }
188
189                 // finally reserve space for markers
190                 bef.after = max(bef.after, markerMargin(bef));
191                 if (e.mclass != MC_UNKNOWN)
192                         e.before = max(e.before, markerMargin(e));
193                 // for linearized insets (macros...) too
194                 if (e.type == BEGIN)
195                         bef.after = max(bef.after, markerMargin(e));
196                 if (e.type == END && e.marker != InsetMath::NO_MARKER) {
197                         Element & aft = elements_[after(i)];
198                         aft.before = max(aft.before, markerMargin(e));
199                 }
200         }
201
202         // Do not lose spacing allocated to extremities
203         if (!elements_.empty()) {
204                 elements_[after(0)].before += elements_.front().after;
205                 elements_[before(elements_.size() - 1)].after += elements_.back().before;
206         }
207 }
208
209
210 int MathRow::before(int i) const
211 {
212         do
213                 --i;
214         while (elements_[i].mclass == MC_UNKNOWN);
215
216         return i;
217 }
218
219
220 int MathRow::after(int i) const
221 {
222         do
223                 ++i;
224         while (elements_[i].mclass == MC_UNKNOWN);
225
226         return i;
227 }
228
229
230 void MathRow::metrics(MetricsInfo & mi, Dimension & dim)
231 {
232         dim.asc = 0;
233         dim.wid = 0;
234         // In order to compute the dimension of macros and their
235         // arguments, it is necessary to keep track of them.
236         vector<pair<InsetMath const *, Dimension>> dim_insets;
237         vector<pair<MathData const *, Dimension>> dim_arrays;
238         CoordCache & coords = mi.base.bv->coordCache();
239         for (Element & e : elements_) {
240                 mi.base.macro_nesting = e.macro_nesting;
241                 Dimension d;
242                 switch (e.type) {
243                 case DUMMY:
244                         break;
245                 case INSET:
246                         e.inset->metrics(mi, d);
247                         d.wid += e.before + e.after;
248                         coords.insets().add(e.inset, d);
249                         break;
250                 case BEGIN:
251                         if (e.inset) {
252                                 dim_insets.push_back(make_pair(e.inset, Dimension()));
253                                 dim_insets.back().second.wid += e.before + e.after;
254                                 d.wid = e.before + e.after;
255                                 e.inset->beforeMetrics();
256                         }
257                         if (e.ar)
258                                 dim_arrays.push_back(make_pair(e.ar, Dimension()));
259                         break;
260                 case END:
261                         if (e.inset) {
262                                 e.inset->afterMetrics();
263                                 LATTEST(dim_insets.back().first == e.inset);
264                                 d = dim_insets.back().second;
265                                 afterMetricsMarkers(mi, e, d);
266                                 d.wid += e.before + e.after;
267                                 coords.insets().add(e.inset, d);
268                                 dim_insets.pop_back();
269                                 // We do not want to count the width again, but the
270                                 // padding and the vertical dimension are meaningful.
271                                 d.wid = e.before + e.after;
272                         }
273                         if (e.ar) {
274                                 LATTEST(dim_arrays.back().first == e.ar);
275                                 coords.arrays().add(e.ar, dim_arrays.back().second);
276                                 dim_arrays.pop_back();
277                         }
278                         break;
279                 case BOX:
280                         d = theFontMetrics(mi.base.font).dimension('I');
281                         if (e.color != Color_none) {
282                                 // allow for one pixel before/after the box.
283                                 d.wid += e.before + e.after + 2;
284                         } else {
285                                 // hide the box, but keep its height
286                                 d.wid = 0;
287                         }
288                         break;
289                 }
290
291                 if (!d.empty()) {
292                         dim += d;
293                         // Now add the dimension to current macros and arguments.
294                         for (auto & dim_macro : dim_insets)
295                                 dim_macro.second += d;
296                         for (auto & dim_array : dim_arrays)
297                                 dim_array.second += d;
298                 }
299
300                 if (e.compl_text.empty())
301                         continue;
302                 FontInfo font = mi.base.font;
303                 augmentFont(font, "mathnormal");
304                 dim.wid += mathed_string_width(font, e.compl_text);
305         }
306         LATTEST(dim_insets.empty() && dim_arrays.empty());
307 }
308
309
310 void MathRow::draw(PainterInfo & pi, int x, int const y) const
311 {
312         CoordCache & coords = pi.base.bv->coordCache();
313         for (Element const & e : elements_) {
314                 switch (e.type) {
315                 case INSET: {
316                         // This is hackish: the math inset does not know that space
317                         // has been added before and after it; we alter its dimension
318                         // while it is drawing, because it relies on this value.
319                         Dimension const d = coords.insets().dim(e.inset);
320                         Dimension d2 = d;
321                         d2.wid -= e.before + e.after;
322                         coords.insets().add(e.inset, d2);
323                         e.inset->draw(pi, x + e.before, y);
324                         coords.insets().add(e.inset, x, y);
325                         coords.insets().add(e.inset, d);
326                         drawMarkers(pi, e, x, y);
327                         x += d.wid;
328                         break;
329                 }
330                 case BEGIN:
331                         if (e.ar) {
332                                 coords.arrays().add(e.ar, x, y);
333                                 e.ar->drawSelection(pi, x, y);
334                         }
335                         if (e.inset) {
336                                 coords.insets().add(e.inset, x, y);
337                                 drawMarkers(pi, e, x, y);
338                                 e.inset->beforeDraw(pi);
339                         }
340                         x += e.before + e.after;
341                         break;
342                 case END:
343                         if (e.inset)
344                                 e.inset->afterDraw(pi);
345                         x += e.before + e.after;
346                         break;
347                 case BOX: {
348                         if (e.color == Color_none)
349                                 break;
350                         Dimension const d = theFontMetrics(pi.base.font).dimension('I');
351                         pi.pain.rectangle(x + e.before + 1, y - d.ascent(),
352                                           d.width() - 1, d.height() - 1, e.color);
353                         x += d.wid + 2 + e.before + e.after;
354                         break;
355                 }
356                 case DUMMY:
357                         break;
358                 }
359
360                 if (e.compl_text.empty())
361                         continue;
362                 FontInfo f = pi.base.font;
363                 augmentFont(f, "mathnormal");
364
365                 // draw the unique and the non-unique completion part
366                 // Note: this is not time-critical as it is
367                 // only done once per screen.
368                 docstring const s1 = e.compl_text.substr(0, e.compl_unique_to);
369                 docstring const s2 = e.compl_text.substr(e.compl_unique_to);
370
371                 if (!s1.empty()) {
372                         f.setColor(Color_inlinecompletion);
373                         pi.pain.text(x, y, s1, f);
374                         x += mathed_string_width(f, s1);
375                 }
376                 if (!s2.empty()) {
377                         f.setColor(Color_nonunique_inlinecompletion);
378                         pi.pain.text(x, y, s2, f);
379                         x += mathed_string_width(f, s2);
380                 }
381         }
382 }
383
384
385 int MathRow::kerning(BufferView const * bv) const
386 {
387         if (elements_.empty())
388                 return 0;
389         InsetMath const * inset = elements_[before(elements_.size() - 1)].inset;
390         return inset ? inset->kerning(bv) : 0;
391 }
392
393
394 ostream & operator<<(ostream & os, MathRow::Element const & e)
395 {
396         switch (e.type) {
397         case MathRow::DUMMY:
398                 os << (e.mclass == MC_OPEN ? "{" : "}");
399                 break;
400         case MathRow::INSET:
401                 os << "<" << e.before << "-"
402                    << to_utf8(class_to_string(e.mclass))
403                    << "-" << e.after << ">";
404                 break;
405         case MathRow::BEGIN:
406                 if (e.inset)
407                         os << "\\" << to_utf8(e.inset->name())
408                            << "^" << e.macro_nesting << "[";
409                 if (e.ar)
410                         os << "(";
411                 break;
412         case MathRow::END:
413                 if (e.ar)
414                         os << ")";
415                 if (e.inset)
416                         os << "]";
417                 break;
418         case MathRow::BOX:
419                 os << "<" << e.before << "-[]-" << e.after << ">";
420                 break;
421         }
422         return os;
423 }
424
425
426 ostream & operator<<(ostream & os, MathRow const & mrow)
427 {
428         for (MathRow::Element const & e : mrow)
429                 os << e << "  ";
430         return os;
431 }
432
433 } // namespace lyx