#include "Language.h"
#include "LaTeXFeatures.h"
#include "LayoutFile.h"
+#include "Length.h"
#include "Lexer.h"
#include "LyX.h"
#include "LyXAction.h"
struct BufferView::Private
{
- Private(BufferView & bv): wh_(0), cursor_(bv),
+ Private(BufferView & bv) : update_strategy_(NoScreenUpdate),
+ wh_(0), cursor_(bv),
anchor_pit_(0), anchor_ypos_(0),
inlineCompletionUniqueChars_(0),
last_inset_(0), clickable_inset_(false),
mouse_position_cache_(),
- bookmark_edit_position_(-1), gui_(0)
- {}
+ bookmark_edit_position_(-1), gui_(0),
+ horiz_scroll_offset_(0)
+ {
+ xsel_cache_.set = false;
+ }
///
ScrollbarParameters scrollbarParameters_;
///
map<string, Inset *> edited_insets_;
+
+ /// When the row where the cursor lies is scrolled, this
+ /// contains the scroll offset
+ int horiz_scroll_offset_;
+ /// a slice pointing to the start of the row where the cursor
+ /// is (at last draw time)
+ CursorSlice current_row_slice_;
+ /// a slice pointing to the start of the row where cursor was
+ /// at previous draw event
+ CursorSlice last_row_slice_;
};
}
-bool BufferView::fitCursor()
+bool BufferView::needsFitCursor() const
{
if (cursorStatus(d->cursor_) == CUR_INSIDE) {
frontend::FontMetrics const & fm =
// Now do the first drawing step if needed. This consists on updating
// the CoordCache in updateMetrics().
// The second drawing step is done in WorkArea::redraw() if needed.
+ // FIXME: is this still true now that Buffer::changed() is used all over?
// Case when no explicit update is requested.
if (!flags) {
if (flags == Update::FitCursor
|| flags == (Update::Decoration | Update::FitCursor)) {
// tell the frontend to update the screen if needed.
- if (fitCursor()) {
+ if (needsFitCursor()) {
showCursor();
return;
}
buffer_.changed(false);
return;
}
- // no screen update is needed.
+ // no screen update is needed in principle, but this
+ // could change if cursor row needs horizontal scrolling.
d->update_strategy_ = NoScreenUpdate;
+ buffer_.changed(false);
return;
}
// This is done at draw() time. So we need a redraw!
buffer_.changed(false);
- if (fitCursor()) {
+ if (needsFitCursor()) {
// The cursor is off screen so ensure it is visible.
// refresh it:
showCursor();
// paragraph position which is computed at draw() time.
// So we need a redraw!
buffer_.changed(false);
- if (fitCursor())
+ if (needsFitCursor())
showCursor();
}
}
-/** Return the change status at cursor position, taking in account the
+/** Return the change status at cursor position, taking into account the
* status at each level of the document iterator (a table in a deleted
* footnote is deleted).
* When \param outer is true, the top slice is not looked at.
iss >> opt;
flag.setEnabled(opt.repl_buf_name.empty()
|| !buffer_.isReadonly());
+ break;
}
- case LFUN_LABEL_GOTO: {
+ case LFUN_LABEL_GOTO:
flag.setEnabled(!cmd.argument().empty()
|| getInsetByCode<InsetRef>(cur, REF_CODE));
break;
- }
case LFUN_CHANGES_TRACK:
flag.setEnabled(true);
string const argument = to_utf8(cmd.argument());
Cursor & cur = d->cursor_;
+ Cursor old = cur;
// Don't dispatch function that does not apply to internal buffers.
if (buffer_.isInternal()
case LFUN_BUFFER_PARAMS_APPLY: {
DocumentClassConstPtr olddc = buffer_.params().documentClassPtr();
- cur.recordUndoFullDocument();
+ cur.recordUndoBufferParams();
istringstream ss(to_utf8(cmd.argument()));
Lexer lex;
lex.setStream(ss);
}
case LFUN_LAYOUT_MODULES_CLEAR: {
- cur.recordUndoFullDocument();
+ // FIXME: this modifies the document in cap::switchBetweenClasses
+ // without calling recordUndo. Fix this before using
+ // recordUndoBufferParams().
+ cur.recordUndoFullBuffer();
buffer_.params().clearLayoutModules();
makeDocumentClass();
dr.screenUpdate(Update::Force);
"conflicts with installed modules.");
break;
}
- cur.recordUndoFullDocument();
+ // FIXME: this modifies the document in cap::switchBetweenClasses
+ // without calling recordUndo. Fix this before using
+ // recordUndoBufferParams().
+ cur.recordUndoFullBuffer();
buffer_.params().addLayoutModule(argument);
makeDocumentClass();
dr.screenUpdate(Update::Force);
break;
// Save the old, possibly modular, layout for use in conversion.
- cur.recordUndoFullDocument();
+ // FIXME: this modifies the document in cap::switchBetweenClasses
+ // without calling recordUndo. Fix this before using
+ // recordUndoBufferParams().
+ cur.recordUndoFullBuffer();
buffer_.params().setBaseClass(argument);
makeDocumentClass();
dr.screenUpdate(Update::Force);
for (Buffer * b = &buffer_; i == 0 || b != &buffer_;
b = theBufferList().next(b)) {
- DocIterator dit = b->getParFromID(id);
- if (dit.atEnd()) {
+ Cursor cur(*this);
+ cur.setCursor(b->getParFromID(id));
+ if (cur.atEnd()) {
LYXERR(Debug::INFO, "No matching paragraph found! [" << id << "].");
++i;
continue;
}
- LYXERR(Debug::INFO, "Paragraph " << dit.paragraph().id()
+ LYXERR(Debug::INFO, "Paragraph " << cur.paragraph().id()
<< " found in buffer `"
<< b->absFileName() << "'.");
if (b == &buffer_) {
// Set the cursor
- dit.pos() = pos;
- setCursor(dit);
+ cur.pos() = pos;
+ mouseSetCursor(cur);
dr.screenUpdate(Update::Force | Update::FitCursor);
} else {
// Switch to other buffer view and resend cmd
p = Point(0, 0);
if (act == LFUN_SCREEN_DOWN && scrolled < height_)
p = Point(width_, height_);
- Cursor old = cur;
bool const in_texted = cur.inTexted();
cur.setCursor(doc_iterator_begin(cur.buffer()));
cur.selHandle(false);
cur.setSelection(true);
cur.posForward();
} else if (cur.selBegin().idx() != cur.selEnd().idx()
- || (cur.selBegin().at_cell_begin()
+ || (cur.depth() > 1
+ && cur.selBegin().at_cell_begin()
&& cur.selEnd().at_cell_end())) {
// At least one complete cell is selected.
// Select all cells
- cur.pos() = 0;
cur.idx() = 0;
+ cur.pos() = 0;
cur.resetAnchor();
cur.setSelection(true);
cur.idx() = cur.lastidx();
cur.pos() = cur.lastpos();
} else {
// select current cell
- cur.pos() = 0;
cur.pit() = 0;
+ cur.pos() = 0;
cur.resetAnchor();
cur.setSelection(true);
- cur.pos() = cur.lastpos();
cur.pit() = cur.lastpit();
+ cur.pos() = cur.lastpos();
}
dr.screenUpdate(Update::Force);
break;
if (!newL || oldL == newL)
break;
if (oldL->rightToLeft() == newL->rightToLeft()) {
- cur.recordUndoFullDocument();
+ cur.recordUndoFullBuffer();
buffer_.changeLanguage(oldL, newL);
cur.setCurrentFont();
dr.forceBufferUpdate();
buffer_.undo().endUndoGroup();
dr.dispatched(dispatched);
+
+ // NOTE: The code below is copied from Cursor::dispatch. If you
+ // need to modify this, please update the other one too.
+
+ // notify insets we just entered/left
+ if (cursor() != old) {
+ old.beginUndoGroup();
+ old.fixIfBroken();
+ bool badcursor = notifyCursorLeavesOrEnters(old, cursor());
+ if (badcursor) {
+ cursor().fixIfBroken();
+ resetInlineCompletionPos();
+ }
+ old.endUndoGroup();
+ }
}
// Build temporary cursor.
Inset * inset = d->text_metrics_[&buffer_.text()].editXY(cur, cmd.x(), cmd.y());
+ if (inset) {
+ // If inset is not editable, cur.pos() might point behind the
+ // inset (depending on cmd.x(), cmd.y()). This is needed for
+ // editing to fix bug 9628, but e.g. the context menu needs a
+ // cursor in front of the inset.
+ if (inset->hasSettings() &&
+ cur.nextInset() != inset && cur.prevInset() == inset)
+ cur.backwardPos();
+ }
// Put anchor at the same position.
cur.resetAnchor();
void BufferView::setCursorFromRow(int row)
+{
+ setCursorFromRow(row, buffer_.texrow());
+}
+
+
+void BufferView::setCursorFromRow(int row, TexRow const & texrow)
{
int tmpid;
int tmppos;
pit_type newpit = 0;
pos_type newpos = 0;
- buffer_.texrow().getIdFromRow(row, tmpid, tmppos);
+ texrow.getIdFromRow(row, tmpid, tmppos);
bool posvalid = (tmpid != -1);
if (posvalid) {
Buffer const * buf = *it;
// find label
- Toc & toc = buf->tocBackend().toc("label");
- TocIterator toc_it = toc.begin();
- TocIterator end = toc.end();
+ shared_ptr<Toc> toc = buf->tocBackend().toc("label");
+ TocIterator toc_it = toc->begin();
+ TocIterator end = toc->end();
for (; toc_it != end; ++toc_it) {
if (label == toc_it->str()) {
lyx::dispatch(toc_it->action());
d->cursor_.setCursor(dit);
d->cursor_.setSelection(false);
+ d->cursor_.setCurrentFont();
// FIXME
// It seems on general grounds as if this is probably needed, but
// it is not yet clear.
d->cursor_ = cur;
// we would rather not do this here, but it needs to be done before
- // the changed() signal is sent.
+ // the changed() signal is sent.
buffer_.updateBuffer();
buffer_.changed(true);
int lastw = 0;
// Addup contribution of nested insets, from inside to outside,
- // keeping the outer paragraph for a special handling below
+ // keeping the outer paragraph for a special handling below
for (size_t i = dit.depth() - 1; i >= 1; --i) {
CursorSlice const & sl = dit[i];
int xx = 0;
// FIXME (Abdel 23/09/2007): this is a bit messy because of the
// elimination of Inset::dim_ cache. This coordOffset() method needs
// to be rewritten in light of the new design.
- Dimension const & dim = parMetrics(dit[i - 1].text(),
- dit[i - 1].pit()).insetDimension(&sl.inset());
+ Dimension const & dim = coordCache().getInsets().dim(&sl.inset());
lastw = dim.wid;
} else {
Dimension const dim = sl.inset().dimension(*this);
}
+int BufferView::horizScrollOffset() const
+{
+ return d->horiz_scroll_offset_;
+}
+
+
+CursorSlice const & BufferView::currentRowSlice() const
+{
+ return d->current_row_slice_;
+}
+
+
+CursorSlice const & BufferView::lastRowSlice() const
+{
+ return d->last_row_slice_;
+}
+
+
+void BufferView::setCurrentRowSlice(CursorSlice const & rowSlice)
+{
+ // nothing to do if the cursor was already on this row
+ if (d->current_row_slice_ == rowSlice) {
+ d->last_row_slice_ = CursorSlice();
+ return;
+ }
+
+ // if the (previous) current row was scrolled, we have to
+ // remember it in order to repaint it next time.
+ if (d->horiz_scroll_offset_ != 0)
+ d->last_row_slice_ = d->current_row_slice_;
+ else
+ d->last_row_slice_ = CursorSlice();
+
+ // Since we changed row, the scroll offset is not valid anymore
+ d->horiz_scroll_offset_ = 0;
+ d->current_row_slice_ = rowSlice;
+}
+
+
+void BufferView::checkCursorScrollOffset()
+{
+ CursorSlice rowSlice = d->cursor_.bottom();
+ TextMetrics const & tm = textMetrics(rowSlice.text());
+
+ // Stop if metrics have not been computed yet, since it means
+ // that there is nothing to do.
+ if (!tm.contains(rowSlice.pit()))
+ return;
+ ParagraphMetrics const & pm = tm.parMetrics(rowSlice.pit());
+ Row const & row = pm.getRow(rowSlice.pos(),
+ d->cursor_.boundary() && rowSlice == d->cursor_.top());
+ rowSlice.pos() = row.pos();
+
+ // Set the row on which the cursor lives.
+ setCurrentRowSlice(rowSlice);
+
+ // Current x position of the cursor in pixels
+ int const cur_x = getPos(d->cursor_).x_;
+
+ // Horizontal scroll offset of the cursor row in pixels
+ int offset = d->horiz_scroll_offset_;
+ int const MARGIN = 2 * theFontMetrics(d->cursor_.real_current_font).em();
+ //lyxerr << "cur_x=" << cur_x << ", offset=" << offset << ", margin=" << MARGIN << endl;
+ if (cur_x < offset + MARGIN) {
+ // scroll right
+ offset = cur_x - MARGIN;
+ } else if (cur_x > offset + workWidth() - MARGIN) {
+ // scroll left
+ offset = cur_x - workWidth() + MARGIN;
+ }
+
+ if (offset < row.left_margin || row.width() <= workWidth())
+ offset = 0;
+
+ if (offset != d->horiz_scroll_offset_)
+ LYXERR(Debug::PAINTING, "Horiz. scroll offset changed from "
+ << d->horiz_scroll_offset_ << " to " << offset);
+
+ if (d->update_strategy_ == NoScreenUpdate
+ && (offset != d->horiz_scroll_offset_
+ || !d->last_row_slice_.empty())) {
+ // FIXME: if one uses SingleParUpdate, then home/end
+ // will not work on long rows. Why?
+ d->update_strategy_ = FullScreenUpdate;
+ }
+
+ d->horiz_scroll_offset_ = offset;
+}
+
+
void BufferView::draw(frontend::Painter & pain)
{
if (height_ == 0 || width_ == 0)
int const y = tm.first().second->position();
PainterInfo pi(this, pain);
+ // Check whether the row where the cursor lives needs to be scrolled.
+ // Update the drawing strategy if needed.
+ checkCursorScrollOffset();
+
switch (d->update_strategy_) {
case NoScreenUpdate:
// If no screen painting is actually needed, only some the different
// coordinates of insets and paragraphs needs to be updated.
+ LYXERR(Debug::PAINTING, "Strategy: NoScreenUpdate");
pi.full_repaint = true;
pi.pain.setDrawingEnabled(false);
- tm.draw(pi, 0, y);
+ tm.draw(pi, 0, y);
break;
case SingleParUpdate:
pi.full_repaint = false;
+ LYXERR(Debug::PAINTING, "Strategy: SingleParUpdate");
// In general, only the current row of the outermost paragraph
// will be redrawn. Particular cases where selection spans
// multiple paragraph are correctly detected in TextMetrics.
- tm.draw(pi, 0, y);
+ tm.draw(pi, 0, y);
break;
case DecorationUpdate:
// because of the single backing pixmap.
case FullScreenUpdate:
+
+ LYXERR(Debug::PAINTING,
+ ((d->update_strategy_ == FullScreenUpdate)
+ ? "Strategy: FullScreenUpdate"
+ : "Strategy: DecorationUpdate"));
+
// The whole screen, including insets, will be refreshed.
pi.full_repaint = true;