]> git.lyx.org Git - features.git/blobdiff - src/TextMetrics.cpp
Transfer Paragraph::hfillExpansion() to ParagraphMetrics. This also reduce the depend...
[features.git] / src / TextMetrics.cpp
index b0f09deef5affa5cd042646c2b0d508b8a6683f5..81a9626966eab4803946b10b1e62f1e0368a9d88 100644 (file)
@@ -22,6 +22,7 @@
 #include "Buffer.h"
 #include "BufferParams.h"
 #include "BufferView.h"
+#include "bufferview_funcs.h"
 #include "Color.h"
 #include "CoordCache.h"
 #include "debug.h"
@@ -40,6 +41,8 @@
 #include "frontends/FontMetrics.h"
 #include "frontends/Painter.h"
 
+#include <boost/current_function.hpp>
+
 using std::max;
 using std::min;
 using std::endl;
@@ -193,6 +196,7 @@ bool TextMetrics::redoParagraph(pit_type const pit)
        pm.reset(par);
 
        Buffer & buffer = bv_->buffer();
+       BufferParams const & bparams = buffer.params();
        main_text_ = (text_ == &buffer.text());
        bool changed = false;
 
@@ -229,36 +233,55 @@ bool TextMetrics::redoParagraph(pit_type const pit)
                Font const & font = ii->inset->noFontChange() ?
                        bufferfont : text_->getFont(buffer, par, ii->pos);
                MetricsInfo mi(bv_, font, w);
-               ii->inset->metrics(mi, dim);
+               changed |= ii->inset->metrics(mi, dim);
                changed |= (old_dim != dim);
        }
 
        par.setBeginOfBody();
-       pos_type z = 0;
+       pos_type first = 0;
+       size_t row_index = 0;
        // maximum pixel width of a row
        int width = max_width_ - right_margin; // - leftMargin(buffer, max_width_, pit, row);
        do {
-               Row row(z);
-               rowBreakPoint(width, pit, row);
-               setRowWidth(right_margin, pit, row);
-               setHeightOfRow(pit, row);
+               Dimension dim;
+               pos_type end = rowBreakPoint(width, pit, first);
+               dim.wid = rowWidth(right_margin, pit, first, end);
+               boost::tie(dim.asc, dim.des) = rowHeight(pit, first, end);
+               if (row_index == pm.rows().size())
+                       pm.rows().push_back(Row());
+               Row & row = pm.rows()[row_index];
+               row.setChanged(false);
+               row.pos(first);
+               row.endpos(end);
+               row.setDimension(dim);
                computeRowMetrics(pit, row);
-               pm.rows().push_back(row);
-               pm.dim().wid = std::max(pm.dim().wid, row.width());
-               pm.dim().des += row.height();
-               z = row.endpos();
-       } while (z < par.size());
+               pm.computeRowSignature(row, bparams);
+               first = end;
+               ++row_index;
+
+               pm.dim().wid = std::max(pm.dim().wid, dim.wid);
+               pm.dim().des += dim.height();
+       } while (first < par.size());
+
+       if (row_index < pm.rows().size())
+               pm.rows().resize(row_index);
 
        // Make sure that if a par ends in newline, there is one more row
        // under it
-       if (z > 0 && par.isNewline(z - 1)) {
-               Row row(z);
-               row.endpos(z);
-               setRowWidth(right_margin, pit, row);
-               setHeightOfRow(pit, row);
+       if (first > 0 && par.isNewline(first - 1)) {
+               Dimension dim;
+               dim.wid = rowWidth(right_margin, pit, first, first);
+               boost::tie(dim.asc, dim.des) = rowHeight(pit, first, first);
+               if (row_index == pm.rows().size())
+                       pm.rows().push_back(Row());
+               Row & row = pm.rows()[row_index];
+               row.setChanged(false);
+               row.pos(first);
+               row.endpos(first);
+               row.setDimension(dim);
                computeRowMetrics(pit, row);
-               pm.rows().push_back(row);
-               pm.dim().des += row.height();
+               pm.computeRowSignature(row, bparams);
+               pm.dim().des += dim.height();
        }
 
        pm.dim().asc += pm.rows()[0].ascent();
@@ -266,10 +289,6 @@ bool TextMetrics::redoParagraph(pit_type const pit)
 
        changed |= old_dim.height() != pm.dim().height();
 
-       // Update the row change statuses. The painter will need that info
-       // in order to know which row has to be repainted.
-       pm.updateRowChangeStatus();
-
        return changed;
 }
 
@@ -451,30 +470,20 @@ int TextMetrics::labelEnd(pit_type const pit) const
 }
 
 
-void TextMetrics::rowBreakPoint(int width, pit_type const pit,
-               Row & row) const
+pit_type TextMetrics::rowBreakPoint(int width, pit_type const pit,
+               pit_type pos) const
 {
        Buffer & buffer = bv_->buffer();
        ParagraphMetrics const & pm = par_metrics_[pit];
        Paragraph const & par = text_->getPar(pit);
        pos_type const end = par.size();
-       pos_type const pos = row.pos();
-       if (pos == end) {
-               row.endpos(end);
-               return;
-       }
-
-       if (width < 0) {
-               row.endpos(end);
-               return;
-       }
+       if (pos == end || width < 0)
+               return end;
 
        LayoutPtr const & layout = par.layout();
 
-       if (layout->margintype == MARGIN_RIGHT_ADDRESS_BOX) {
-               row.endpos(addressBreakPoint(pos, par));
-               return;
-       }
+       if (layout->margintype == MARGIN_RIGHT_ADDRESS_BOX)
+               return addressBreakPoint(pos, par);
 
        pos_type const body_pos = par.beginOfBody();
 
@@ -563,23 +572,22 @@ void TextMetrics::rowBreakPoint(int width, pit_type const pit,
        if (body_pos && point < body_pos)
                point = body_pos;
 
-       row.endpos(point);
+       return point;
 }
 
 
-void TextMetrics::setRowWidth(int right_margin,
-               pit_type const pit, Row & row) const
+int TextMetrics::rowWidth(int right_margin, pit_type const pit,
+               pos_type const first, pos_type const end) const
 {
        Buffer & buffer = bv_->buffer();
        // get the pure distance
-       pos_type const end = row.endpos();
        ParagraphMetrics const & pm = par_metrics_[pit];
        Paragraph const & par = text_->getPar(pit);
-       int w = text_->leftMargin(buffer, max_width_, pit, row.pos());
+       int w = text_->leftMargin(buffer, max_width_, pit, first);
        int label_end = labelEnd(pit);
 
        pos_type const body_pos = par.beginOfBody();
-       pos_type i = row.pos();
+       pos_type i = first;
 
        if (i < end) {
                FontIterator fi = FontIterator(buffer, *text_, par, i);
@@ -605,12 +613,12 @@ void TextMetrics::setRowWidth(int right_margin,
                w = max(w, label_end);
        }
 
-       row.width(w + right_margin);
+       return w + right_margin;
 }
 
 
-void TextMetrics::setHeightOfRow(pit_type const pit,
-               Row & row)
+boost::tuple<int, int> TextMetrics::rowHeight(pit_type const pit, pos_type const first,
+               pos_type const end) const
 {
        Paragraph const & par = text_->getPar(pit);
        // get the maximum ascent and the maximum descent
@@ -628,7 +636,7 @@ void TextMetrics::setHeightOfRow(pit_type const pit,
        // start with so we don't have to do the assignment below too
        // often.
        Buffer const & buffer = bv_->buffer();
-       Font font = text_->getFont(buffer, par, row.pos());
+       Font font = text_->getFont(buffer, par, first);
        Font::FONT_SIZE const tmpsize = font.size();
        font = text_->getLayoutFont(buffer, pit);
        Font::FONT_SIZE const size = font.size();
@@ -650,7 +658,7 @@ void TextMetrics::setHeightOfRow(pit_type const pit,
        InsetList::const_iterator ii = par.insetlist.begin();
        InsetList::const_iterator iend = par.insetlist.end();
        for ( ; ii != iend; ++ii) {
-               if (ii->pos >= row.pos() && ii->pos < row.endpos()) {
+               if (ii->pos >= first && ii->pos < end) {
                        maxasc  = max(maxasc,  ii->inset->ascent());
                        maxdesc = max(maxdesc, ii->inset->descent());
                }
@@ -660,10 +668,9 @@ void TextMetrics::setHeightOfRow(pit_type const pit,
        // This is not completely correct, but we can live with the small,
        // cosmetic error for now.
        int labeladdon = 0;
-       pos_type const pos_end = row.endpos();
 
        Font::FONT_SIZE maxsize =
-               par.highestFontInRange(row.pos(), pos_end, size);
+               par.highestFontInRange(first, end, size);
        if (maxsize > font.size()) {
                // use standard paragraph font with the maximal size
                Font maxfont = font;
@@ -677,11 +684,10 @@ void TextMetrics::setHeightOfRow(pit_type const pit,
        ++maxasc;
        ++maxdesc;
 
-       row.ascent(maxasc);
        ParagraphList const & pars = text_->paragraphs();
 
        // is it a top line?
-       if (row.pos() == 0) {
+       if (first == 0) {
                BufferParams const & bufparams = buffer.params();
                // some parskips VERY EASY IMPLEMENTATION
                if (bufparams.paragraph_separation
@@ -734,7 +740,7 @@ void TextMetrics::setHeightOfRow(pit_type const pit,
                    && prevpar.getLabelWidthString()
                                        == par.getLabelWidthString()) {
                        layoutasc = layout->itemsep * dh;
-               } else if (pit != 0 || row.pos() != 0) {
+               } else if (pit != 0 || first != 0) {
                        if (layout->topsep > 0)
                                layoutasc = layout->topsep * dh;
                }
@@ -752,7 +758,7 @@ void TextMetrics::setHeightOfRow(pit_type const pit,
        }
 
        // is it a bottom line?
-       if (row.endpos() >= par.size()) {
+       if (end >= par.size()) {
                // add the layout spaces, for example before and after
                // a section, or between the items of a itemize or enumerate
                // environment
@@ -788,16 +794,15 @@ void TextMetrics::setHeightOfRow(pit_type const pit,
        // main Text. The following test is thus bogus.
        // Top and bottom margin of the document (only at top-level)
        if (main_text_) {
-               if (pit == 0 && row.pos() == 0)
+               if (pit == 0 && first == 0)
                        maxasc += 20;
                if (pit + 1 == pit_type(pars.size()) &&
-                   row.endpos() == par.size() &&
-                               !(row.endpos() > 0 && par.isNewline(row.endpos() - 1)))
+                   end == par.size() &&
+                               !(end > 0 && par.isNewline(end - 1)))
                        maxdesc += 20;
        }
 
-       row.ascent(maxasc + labeladdon);
-       row.descent(maxdesc);
+       return boost::make_tuple(maxasc + labeladdon, maxdesc);
 }
 
 
@@ -815,6 +820,7 @@ pos_type TextMetrics::getColumnNearX(pit_type const pit,
        int const xo = main_text_? 0 : bv_->coordCache().get(text_, pit).x_;
        x -= xo;
        Paragraph const & par = text_->getPar(pit);
+       ParagraphMetrics const & pm = par_metrics_[pit];
        Bidi bidi;
        bidi.computeTables(par, buffer, row);
 
@@ -851,7 +857,7 @@ pos_type TextMetrics::getColumnNearX(pit_type const pit,
                                tmpx -= singleWidth(pit, body_pos - 1);
                }
 
-               if (par.hfillExpansion(row, c)) {
+               if (pm.hfillExpansion(row, c)) {
                        tmpx += singleWidth(pit, c);
                        if (c >= body_pos)
                                tmpx += row.hfill;
@@ -965,40 +971,22 @@ int TextMetrics::singleWidth(pit_type pit, pos_type pos) const
 // only used for inset right now. should also be used for main text
 void TextMetrics::draw(PainterInfo & pi, int x, int y) const
 {
+       if (par_metrics_.empty())
+               return;
+
        ParMetricsCache::const_iterator it = par_metrics_.begin();
        ParMetricsCache::const_iterator const end = par_metrics_.end();
        y -= it->second.ascent();
        for (; it != end; ++it) {
                ParagraphMetrics const & pmi = it->second;
                y += pmi.ascent();
-               drawParagraph(pi, it->first, x, y, true);
+               drawParagraph(pi, it->first, x, y);
                y += pmi.descent();
        }
 }
 
-namespace {
 
-bool CursorOnRow(PainterInfo & pi, pit_type const pit,
-       RowList::const_iterator rit, Text const & text)
-{
-       // Is there a cursor on this row (or inside inset on row)
-       Cursor & cur = pi.base.bv->cursor();
-       for (size_type d = 0; d < cur.depth(); ++d) {
-               CursorSlice const & sl = cur[d];
-               if (sl.text() == &text
-                   && sl.pit() == pit
-                   && sl.pos() >= rit->pos()
-                   && sl.pos() <= rit->endpos())
-                       return true;
-       }
-       return false;
-}
-
-} // namespace anon
-
-
-void TextMetrics::drawParagraph(PainterInfo & pi, pit_type pit, int x, int y,
-        bool repaintAll) const
+void TextMetrics::drawParagraph(PainterInfo & pi, pit_type pit, int x, int y) const
 {
 //     lyxerr << "  paintPar: pit: " << pit << " at y: " << y << endl;
        int const ww = bv_->workHeight();
@@ -1015,55 +1003,215 @@ void TextMetrics::drawParagraph(PainterInfo & pi, pit_type pit, int x, int y,
        Bidi bidi;
 
        y -= rb->ascent();
-       size_type rowno = 0;
-       for (RowList::const_iterator rit = rb; rit != re; ++rit, ++rowno) {
+       for (RowList::const_iterator rit = rb; rit != re; ++rit) {
                y += rit->ascent();
+
+               bool const inside = (y + rit->descent() >= 0
+                       && y - rit->ascent() < ww);
+               // it is not needed to draw on screen if we are not inside.
+               pi.pain.setDrawingEnabled(inside);
+               RowPainter rp(pi, *text_, pit, *rit, bidi, x, y);
+
                // Row signature; has row changed since last paint?
-               bool row_has_changed = pm.rowChangeStatus()[rowno];
-
-               bool cursor_on_row = CursorOnRow(pi, pit, rit, *text_);
-
-               // If selection is on, the current row signature differs
-               // from cache, or cursor is inside an inset _on this row_,
-               // then paint the row
-               if (repaintAll || row_has_changed || cursor_on_row) {
-                       bool const inside = (y + rit->descent() >= 0
-                               && y - rit->ascent() < ww);
-                       // it is not needed to draw on screen if we are not inside.
-                       pi.pain.setDrawingEnabled(inside);
-                       RowPainter rp(pi, *text_, pit, *rit, bidi, x, y);
-                       // Clear background of this row
-                       // (if paragraph background was not cleared)
-                       if (!repaintAll && row_has_changed)
-                               pi.pain.fillRectangle(x, y - rit->ascent(),
-                                       width(), rit->height(),
-                                       text_->backgroundColor());
-
-                       // Instrumentation for testing row cache (see also
-                       // 12 lines lower):
-                       if (lyxerr.debugging(Debug::PAINTING)) {
-                               if (text_->isMainText(bv_->buffer()))
-                                       LYXERR(Debug::PAINTING) << "#";
-                               else
-                                       LYXERR(Debug::PAINTING) << "[" <<
-                                               repaintAll << row_has_changed <<
-                                               cursor_on_row << "]";
-                       }
-                       rp.paintAppendix();
-                       rp.paintDepthBar();
-                       rp.paintChangeBar();
-                       if (rit == rb)
-                               rp.paintFirst();
-                       rp.paintText();
-                       if (rit + 1 == re)
-                               rp.paintLast();
+               bool row_has_changed = rit->changed();
+               
+               if (!pi.full_repaint && !row_has_changed) {
+                       // Paint the only the insets if the text itself is
+                       // unchanged.
+                       rp.paintOnlyInsets();
+                       y += rit->descent();
+                       continue;
+               }
+
+               // Paint the row if a full repaint has been requested or it has
+               // changed.
+               // Clear background of this row
+               // (if paragraph background was not cleared)
+               if (!pi.full_repaint && row_has_changed)
+                       pi.pain.fillRectangle(x, y - rit->ascent(),
+                       width(), rit->height(),
+                       text_->backgroundColor());
+
+               // Instrumentation for testing row cache (see also
+               // 12 lines lower):
+               if (lyxerr.debugging(Debug::PAINTING)) {
+                       if (text_->isMainText(bv_->buffer()))
+                               LYXERR(Debug::PAINTING) << "{" <<
+                               pi.full_repaint << row_has_changed << "}";
+                       else
+                               LYXERR(Debug::PAINTING) << "[" <<
+                               pi.full_repaint << row_has_changed << "]";
                }
+               rp.paintAppendix();
+               rp.paintDepthBar();
+               rp.paintChangeBar();
+               if (rit == rb)
+                       rp.paintFirst();
+               rp.paintText();
+               if (rit + 1 == re)
+                       rp.paintLast();
                y += rit->descent();
        }
        // Re-enable screen drawing for future use of the painter.
        pi.pain.setDrawingEnabled(true);
 
-       LYXERR(Debug::PAINTING) << "." << endl;
+       //LYXERR(Debug::PAINTING) << "." << endl;
+}
+
+
+// only used for inset right now. should also be used for main text
+void TextMetrics::drawSelection(PainterInfo & pi, int x, int) const
+{
+       Cursor & cur = bv_->cursor();
+       if (!cur.selection())
+               return;
+       if (!ptr_cmp(cur.text(), text_))
+               return;
+
+       LYXERR(Debug::DEBUG)
+               << BOOST_CURRENT_FUNCTION
+               << "draw selection at " << x
+               << endl;
+
+       DocIterator beg = cur.selectionBegin();
+       DocIterator end = cur.selectionEnd();
+
+       // the selection doesn't touch the visible screen?
+       if (bv_funcs::status(bv_, beg) == bv_funcs::CUR_BELOW
+           || bv_funcs::status(bv_, end) == bv_funcs::CUR_ABOVE)
+               return;
+
+       ParagraphMetrics const & pm1 = par_metrics_[beg.pit()];
+       ParagraphMetrics const & pm2 = par_metrics_[end.pit()];
+       Row const & row1 = pm1.getRow(beg.pos(), beg.boundary());
+       Row const & row2 = pm2.getRow(end.pos(), end.boundary());
+
+       // clip above
+       int middleTop;
+       bool const clipAbove = 
+               (bv_funcs::status(bv_, beg) == bv_funcs::CUR_ABOVE);
+       if (clipAbove)
+               middleTop = 0;
+       else
+               middleTop = bv_funcs::getPos(*bv_, beg, beg.boundary()).y_ + row1.descent();
+       
+       // clip below
+       int middleBottom;
+       bool const clipBelow = 
+               (bv_funcs::status(bv_, end) == bv_funcs::CUR_BELOW);
+       if (clipBelow)
+               middleBottom = bv_->workHeight();
+       else
+               middleBottom = bv_funcs::getPos(*bv_, end, end.boundary()).y_ - row2.ascent();
+
+       // start and end in the same line?
+       if (!(clipAbove || clipBelow) && &row1 == &row2)
+               // then only draw this row's selection
+               drawRowSelection(pi, x, row1, beg, end, false, false);
+       else {
+               if (!clipAbove) {
+                       // get row end
+                       DocIterator begRowEnd = beg;
+                       begRowEnd.pos() = row1.endpos();
+                       begRowEnd.boundary(true);
+                       
+                       // draw upper rectangle
+                       drawRowSelection(pi, x, row1, beg, begRowEnd, false, true);
+               }
+                       
+               if (middleTop < middleBottom) {
+                       // draw middle rectangle
+                       pi.pain.fillRectangle(x, middleTop, width(), middleBottom - middleTop,
+                               Color::selection);
+               }
+
+               if (!clipBelow) {
+                       // get row begin
+                       DocIterator endRowBeg = end;
+                       endRowBeg.pos() = row2.pos();
+                       endRowBeg.boundary(false);
+                       
+                       // draw low rectangle
+                       drawRowSelection(pi, x, row2, endRowBeg, end, true, false);
+               }
+       }
+}
+
+
+void TextMetrics::drawRowSelection(PainterInfo & pi, int x, Row const & row,
+               DocIterator const & beg, DocIterator const & end,
+               bool drawOnBegMargin, bool drawOnEndMargin) const
+{
+       Buffer & buffer = bv_->buffer();
+       DocIterator cur = beg;
+       int x1 = text_->cursorX(*bv_, beg.top(), beg.boundary());
+       int x2 = text_->cursorX(*bv_, end.top(), end.boundary());
+       int y1 = bv_funcs::getPos(*bv_, cur, cur.boundary()).y_ - row.ascent();
+       int y2 = y1 + row.height();
+       
+       // draw the margins
+       if (drawOnBegMargin) {
+               if (text_->isRTL(buffer, beg.paragraph()))
+                       pi.pain.fillRectangle(x + x1, y1, width() - x1, y2 - y1, Color::selection);
+               else
+                       pi.pain.fillRectangle(x, y1, x1, y2 - y1, Color::selection);
+       }
+       
+       if (drawOnEndMargin) {
+               if (text_->isRTL(buffer, beg.paragraph()))
+                       pi.pain.fillRectangle(x, y1, x2, y2 - y1, Color::selection);
+               else
+                       pi.pain.fillRectangle(x + x2, y1, width() - x2, y2 - y1, Color::selection);
+       }
+       
+       // if we are on a boundary from the beginning, it's probably
+       // a RTL boundary and we jump to the other side directly as this
+       // segement is 0-size and confuses the logic below
+       if (cur.boundary())
+               cur.boundary(false);
+       
+       // go through row and draw from RTL boundary to RTL boundary
+       while (cur < end) {
+               bool drawNow = false;
+               
+               // simplified cursorRight code below which does not
+               // descend into insets and which does not go into the
+               // next line. Compare the logic with the original cursorRight
+               
+               // if left of boundary -> just jump to right side
+               // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
+               if (cur.boundary()) {
+                       cur.boundary(false);
+               }       else if (text_->isRTLBoundary(buffer, cur.paragraph(), cur.pos() + 1)) {
+                       // in front of RTL boundary -> Stay on this side of the boundary because:
+                       //   ab|cDDEEFFghi -> abc|DDEEFFghi
+                       ++cur.pos();
+                       cur.boundary(true);
+                       drawNow = true;
+               } else {
+                       // move right
+                       ++cur.pos();
+                       
+                       // line end?
+                       if (cur.pos() == row.endpos())
+                               cur.boundary(true);
+               }
+                       
+               if (x1 == -1) {
+                       // the previous segment was just drawn, now the next starts
+                       x1 = text_->cursorX(*bv_, cur.top(), cur.boundary());
+               }
+               
+               if (!(cur < end) || drawNow) {
+                       x2 = text_->cursorX(*bv_, cur.top(), cur.boundary());
+                       pi.pain.fillRectangle(x + min(x1,x2), y1, abs(x2 - x1), y2 - y1,
+                               Color::selection);
+                       
+                       // reset x1, so it is set again next round (which will be on the 
+                       // right side of a boundary or at the selection end)
+                       x1 = -1;
+               }
+       }
 }
 
 //int Text::pos2x(pit_type pit, pos_type pos) const