3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Lars Gullik Bjønnes
10 * \author Jean-Marc Lasgouttes
12 * Full author contact details are available in file CREDITS.
14 * Metrics for an on-screen text row.
21 #include "DocIterator.h"
23 #include "frontends/FontMetrics.h"
25 #include "support/debug.h"
26 #include "support/lassert.h"
31 #include <boost/next_prior.hpp>
37 using frontend::FontMetrics;
39 double Row::Element::pos2x(pos_type const i) const
41 // This can happen with inline completion when clicking on the
42 // row after the completion.
43 if (i < pos || i > endpos)
46 bool const rtl = font.isVisibleRightToLeft();
49 //handle first the two bounds of the element
50 if (i == endpos && type != VIRTUAL
51 && !(inset && inset->lyxCode() == SEPARATOR_CODE))
52 w = rtl ? 0 : full_width();
53 else if (i == pos || type != STRING)
54 w = rtl ? full_width() : 0;
56 FontMetrics const & fm = theFontMetrics(font);
57 w = fm.pos2x(str, i - pos, font.isVisibleRightToLeft());
64 pos_type Row::Element::x2pos(int &x) const
66 //lyxerr << "x2pos: x=" << x << " w=" << width() << " " << *this;
67 bool const rtl = font.isVisibleRightToLeft();
72 FontMetrics const & fm = theFontMetrics(font);
73 i = fm.x2pos(str, x, rtl);
77 // those elements are actually empty (but they have a width)
79 x = rtl ? int(full_width()) : 0;
84 // those elements contain only one position. Round to
86 if (x > full_width()) {
87 x = int(full_width());
95 //lyxerr << "=> p=" << pos + i << " x=" << x << endl;
101 bool Row::Element::breakAt(int w)
103 if (type != STRING || dim.wid <= w)
106 bool const rtl = font.isVisibleRightToLeft();
109 pos_type new_pos = x2pos(w);
112 str = str.substr(0, new_pos - pos);
122 pos_type Row::Element::left_pos() const
124 return font.isVisibleRightToLeft() ? endpos : pos;
128 pos_type Row::Element::right_pos() const
130 return font.isVisibleRightToLeft() ? pos : endpos;
135 : separator(0), label_hfill(0), left_margin(0), right_margin(0),
136 sel_beg(-1), sel_end(-1),
137 begin_margin_sel(false), end_margin_sel(false),
138 changed_(false), crc_(0), pos_(0), end_(0), right_boundary_(false)
142 void Row::setCrc(size_type crc) const
144 changed_ = crc != crc_;
149 bool Row::isMarginSelected(bool left_margin, DocIterator const & beg,
150 DocIterator const & end) const
152 pos_type const sel_pos = left_margin ? sel_beg : sel_end;
153 pos_type const margin_pos = left_margin ? pos_ : end_;
155 // Is the chosen margin selected ?
156 if (sel_pos == margin_pos) {
157 if (beg.pos() == end.pos())
158 // This is a special case in which the space between after
159 // pos i-1 and before pos i is selected, i.e. the margins
160 // (see DocIterator::boundary_).
161 return beg.boundary() && !end.boundary();
162 else if (end.pos() == margin_pos)
163 // If the selection ends around the margin, it is only
164 // drawn if the cursor is after the margin.
165 return !end.boundary();
166 else if (beg.pos() == margin_pos)
167 // If the selection begins around the margin, it is
168 // only drawn if the cursor is before the margin.
169 return beg.boundary();
177 void Row::setSelectionAndMargins(DocIterator const & beg,
178 DocIterator const & end) const
180 setSelection(beg.pos(), end.pos());
183 end_margin_sel = isMarginSelected(false, beg, end);
184 begin_margin_sel = isMarginSelected(true, beg, end);
189 void Row::setSelection(pos_type beg, pos_type end) const
191 if (pos_ >= beg && pos_ <= end)
193 else if (beg > pos_ && beg <= end_)
198 if (end_ >= beg && end_ <= end)
200 else if (end < end_ && end >= pos_)
207 bool Row::selection() const
209 return sel_beg != -1 && sel_end != -1;
213 ostream & operator<<(ostream & os, Row::Element const & e)
215 if (e.font.isVisibleRightToLeft())
216 os << e.endpos << "<<" << e.pos << " ";
218 os << e.pos << ">>" << e.endpos << " ";
222 os << "STRING: `" << to_utf8(e.str) << "', ";
225 os << "VIRTUAL: `" << to_utf8(e.str) << "', ";
228 os << "INSET: " << to_utf8(e.inset->layoutName()) << ", ";
231 os << "SEPARATOR: extra=" << e.extra << ", ";
237 os << "width=" << e.full_width();
242 ostream & operator<<(ostream & os, Row const & row)
244 os << " pos: " << row.pos_ << " end: " << row.end_
245 << " left_margin: " << row.left_margin
246 << " width: " << row.dim_.wid
247 << " right_margin: " << row.right_margin
248 << " ascent: " << row.dim_.asc
249 << " descent: " << row.dim_.des
250 << " separator: " << row.separator
251 << " label_hfill: " << row.label_hfill
252 << " row_boundary: " << row.right_boundary() << "\n";
253 double x = row.left_margin;
254 Row::Elements::const_iterator it = row.elements_.begin();
255 for ( ; it != row.elements_.end() ; ++it) {
256 os << "x=" << x << " => " << *it << endl;
257 x += it->full_width();
263 bool Row::sameString(Font const & f, Change const & ch) const
265 if (elements_.empty())
267 Element const & elt = elements_.back();
268 return elt.type == STRING && !elt.final
269 && elt.font == f && elt.change == ch;
273 void Row::finalizeLast()
275 if (elements_.empty())
277 Element & elt = elements_.back();
282 if (elt.type == STRING) {
283 elt.dim.wid = theFontMetrics(elt.font).width(elt.str);
284 dim_.wid += elt.dim.wid;
289 void Row::add(pos_type const pos, Inset const * ins, Dimension const & dim,
290 Font const & f, Change const & ch)
293 Element e(INSET, pos, f, ch);
296 elements_.push_back(e);
301 void Row::add(pos_type const pos, char_type const c,
302 Font const & f, Change const & ch)
304 if (!sameString(f, ch)) {
306 Element e(STRING, pos, f, ch);
307 elements_.push_back(e);
310 back().endpos = pos + 1;
314 void Row::addVirtual(pos_type const pos, docstring const & s,
315 Font const & f, Change const & ch)
318 Element e(VIRTUAL, pos, f, ch);
320 e.dim.wid = theFontMetrics(f).width(s);
321 dim_.wid += e.dim.wid;
323 elements_.push_back(e);
328 void Row::addSeparator(pos_type const pos, char_type const c,
329 Font const & f, Change const & ch)
332 Element e(SEPARATOR, pos, f, ch);
334 e.dim.wid = theFontMetrics(f).width(c);
335 elements_.push_back(e);
336 dim_.wid += e.dim.wid;
340 void Row::addSpace(pos_type const pos, int const width,
341 Font const & f, Change const & ch)
344 Element e(SPACE, pos, f, ch);
346 elements_.push_back(e);
347 dim_.wid += e.dim.wid;
353 dim_.wid -= elements_.back().dim.wid;
354 elements_.pop_back();
358 void Row::shortenIfNeeded(pos_type const keep, int const w)
360 if (empty() || width() <= w)
363 Elements::iterator const beg = elements_.begin();
364 Elements::iterator const end = elements_.end();
365 Elements::iterator last_sep = elements_.end();
367 int wid = left_margin;
369 Elements::iterator cit = beg;
370 for ( ; cit != end ; ++cit) {
371 if (cit->type == SEPARATOR && cit->pos >= keep) {
375 if (wid + cit->dim.wid > w)
380 if (last_sep != end) {
381 // We have found a suitable separator. This is the
383 end_ = last_sep->endpos;
384 dim_.wid = last_width;
385 elements_.erase(last_sep, end);
390 // This should not happen since the row is too long.
391 LYXERR0("Something is wrong cannot shorten row: " << *this);
395 if (cit != beg && cit->type == VIRTUAL) {
396 // It is not possible to separate a virtual element from the
403 // There is no separator, but several elements (probably
404 // insets) have been added. We can cut at this place.
407 elements_.erase(cit, end);
411 /* If we are here, it means that we have not found a separator
412 * to shorten the row. There is one case where we can do
413 * something: when we have one big string, maybe with some
414 * other things after it.
416 if (cit->breakAt(w - left_margin)) {
418 dim_.wid = left_margin + cit->dim.wid;
419 // If there are other elements, they should be removed.
420 elements_.erase(boost::next(cit), end);
425 void Row::reverseRTL(bool const rtl_par)
428 pos_type const end = elements_.size();
430 // gather a sequence of elements with the same direction
431 bool const rtl = elements_[i].font.isVisibleRightToLeft();
433 while (j < end && elements_[j].font.isVisibleRightToLeft() == rtl)
435 // if the direction is not the same as the paragraph
436 // direction, the sequence has to be reverted.
438 reverse(elements_.begin() + i, elements_.begin() + j);
441 // If the paragraph itself is RTL, reverse everything
443 reverse(elements_.begin(), elements_.end());