X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FBufferView.cpp;h=6922770f2966c558fe8d28f867c094f43eeeed0d;hb=57b69a5efddf9f3c148007322f00dad6c253a2ed;hp=bf2d220b56ff45b4de6049a13a1311757e653b53;hpb=570f96bc6e9af6c488234c7f261b320844f97dc0;p=lyx.git diff --git a/src/BufferView.cpp b/src/BufferView.cpp index bf2d220b56..6922770f29 100644 --- a/src/BufferView.cpp +++ b/src/BufferView.cpp @@ -35,33 +35,36 @@ #include "Language.h" #include "LaTeXFeatures.h" #include "LayoutFile.h" +#include "Length.h" #include "Lexer.h" #include "LyX.h" #include "LyXAction.h" #include "lyxfind.h" -#include "LyXFunc.h" #include "Layout.h" #include "LyXRC.h" #include "MetricsInfo.h" #include "Paragraph.h" #include "ParagraphParameters.h" #include "ParIterator.h" +#include "RowPainter.h" #include "Session.h" #include "Text.h" #include "TextClass.h" #include "TextMetrics.h" #include "TexRow.h" #include "TocBackend.h" -#include "VSpace.h" #include "WordLangTuple.h" #include "insets/InsetBibtex.h" +#include "insets/InsetCitation.h" #include "insets/InsetCommand.h" // ChangeRefs #include "insets/InsetExternal.h" #include "insets/InsetGraphics.h" +#include "insets/InsetNote.h" #include "insets/InsetRef.h" #include "insets/InsetText.h" -#include "insets/InsetNote.h" + +#include "mathed/MathData.h" #include "frontends/alert.h" #include "frontends/Application.h" @@ -70,13 +73,12 @@ #include "frontends/Painter.h" #include "frontends/Selection.h" -#include "graphics/Previews.h" - #include "support/convert.h" #include "support/debug.h" #include "support/ExceptionMessage.h" #include "support/filetools.h" #include "support/gettext.h" +#include "support/lassert.h" #include "support/lstrings.h" #include "support/Package.h" #include "support/types.h" @@ -109,9 +111,7 @@ T * getInsetByCode(Cursor const & cur, InsetCode code) } -bool findInset(DocIterator & dit, vector const & codes, - bool same_content); - +/// Note that comparing contents can only be used for InsetCommand bool findNextInset(DocIterator & dit, vector const & codes, docstring const & contents) { @@ -119,12 +119,17 @@ bool findNextInset(DocIterator & dit, vector const & codes, while (tmpdit) { Inset const * inset = tmpdit.nextInset(); - if (inset - && std::find(codes.begin(), codes.end(), inset->lyxCode()) != codes.end() - && (contents.empty() || - static_cast(inset)->getFirstNonOptParam() == contents)) { - dit = tmpdit; - return true; + if (inset) { + bool const valid_code = std::find(codes.begin(), codes.end(), + inset->lyxCode()) != codes.end(); + InsetCommand const * ic = inset->asInsetCommand(); + bool const same_or_no_contents = contents.empty() + || (ic && (ic->getFirstNonOptParam() == contents)); + + if (valid_code && same_or_no_contents) { + dit = tmpdit; + return true; + } } tmpdit.forwardInset(); } @@ -134,6 +139,7 @@ bool findNextInset(DocIterator & dit, vector const & codes, /// Looks for next inset with one of the given codes. +/// Note that same_content can only be used for InsetCommand bool findInset(DocIterator & dit, vector const & codes, bool same_content) { @@ -143,11 +149,14 @@ bool findInset(DocIterator & dit, vector const & codes, if (!tmpdit) return false; - if (same_content) { - Inset const * inset = tmpdit.nextInset(); - if (inset - && std::find(codes.begin(), codes.end(), inset->lyxCode()) != codes.end()) { - contents = static_cast(inset)->getFirstNonOptParam(); + Inset const * inset = tmpdit.nextInset(); + if (same_content && inset) { + InsetCommand const * ic = inset->asInsetCommand(); + if (ic) { + bool const valid_code = std::find(codes.begin(), codes.end(), + ic->lyxCode()) != codes.end(); + if (valid_code) + contents = ic->getFirstNonOptParam(); } } @@ -218,11 +227,17 @@ enum ScreenUpdateStrategy { 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), bookmark_edit_position_(0), gui_(0) - {} + last_inset_(0), clickable_inset_(false), + mouse_position_cache_(), + bookmark_edit_position_(-1), gui_(0), + horiz_scroll_offset_(0) + { + xsel_cache_.set = false; + } /// ScrollbarParameters scrollbarParameters_; @@ -262,7 +277,14 @@ struct BufferView::Private /** kept to send setMouseHover(false). * Not owned, so don't delete. */ - Inset * last_inset_; + Inset const * last_inset_; + /// are we hovering something that we can click + bool clickable_inset_; + + /// position of the mouse at the time of the last mouse move + /// This is used to update the hovering status of inset in + /// cases where the buffer is scrolled, but the mouse didn't move. + Point mouse_position_cache_; // cache for id of the paragraph which was edited the last time int bookmark_edit_position_; @@ -279,6 +301,16 @@ struct BufferView::Private /// map 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_; }; @@ -294,8 +326,7 @@ BufferView::BufferView(Buffer & buf) d->cursor_.resetAnchor(); d->cursor_.setCurrentFont(); - if (graphics::Previews::status() != LyXRC::PREVIEW_OFF) - thePreviews().generateBufferPreviews(buffer_); + buffer_.updatePreviews(); } @@ -311,17 +342,21 @@ BufferView::~BufferView() fp.pos = d->cursor_.bottom().pos(); theSession().lastFilePos().save(buffer_.fileName(), fp); + if (d->last_inset_) + d->last_inset_->setMouseHover(this, false); + delete d; } int BufferView::rightMargin() const { + // The value used to be hardcoded to 10, which is 2.5mm at 100dpi + int const default_margin = Length(2.5, Length::MM).inPixels(0); // The additional test for the case the outliner is opened. - if (!full_screen_ || - !lyxrc.full_screen_limit || - width_ < lyxrc.full_screen_width + 20) - return 10; + if (!full_screen_ || !lyxrc.full_screen_limit + || width_ < lyxrc.full_screen_width + 2 * default_margin) + return default_margin; return (width_ - lyxrc.full_screen_width) / 2; } @@ -335,13 +370,13 @@ int BufferView::leftMargin() const bool BufferView::isTopScreen() const { - return d->scrollbarParameters_.position == d->scrollbarParameters_.min; + return 0 == d->scrollbarParameters_.min; } bool BufferView::isBottomScreen() const { - return d->scrollbarParameters_.position == d->scrollbarParameters_.max; + return 0 == d->scrollbarParameters_.max; } @@ -381,14 +416,14 @@ Buffer const & BufferView::buffer() const } -bool BufferView::fitCursor() +bool BufferView::needsFitCursor() const { if (cursorStatus(d->cursor_) == CUR_INSIDE) { frontend::FontMetrics const & fm = theFontMetrics(d->cursor_.getFont().fontInfo()); int const asc = fm.maxAscent(); int const des = fm.maxDescent(); - Point const p = getPos(d->cursor_, d->cursor_.boundary()); + Point const p = getPos(d->cursor_); if (p.y_ - asc >= 0 && p.y_ + des < height_) return false; } @@ -398,22 +433,21 @@ bool BufferView::fitCursor() void BufferView::processUpdateFlags(Update::flags flags) { - // last_inset_ points to the last visited inset. This pointer may become - // invalid because of keyboard editing. Since all such operations - // causes screen update(), I reset last_inset_ to avoid such a problem. - d->last_inset_ = 0; // This is close to a hot-path. - LYXERR(Debug::DEBUG, "BufferView::processUpdateFlags()" + LYXERR(Debug::PAINTING, "BufferView::processUpdateFlags()" << "[fitcursor = " << (flags & Update::FitCursor) << ", forceupdate = " << (flags & Update::Force) << ", singlepar = " << (flags & Update::SinglePar) << "] buffer: " << &buffer_); + // FIXME Does this really need doing here? It's done in updateBuffer, and + // if the Buffer doesn't need updating, then do the macros? buffer_.updateMacros(); // 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) { @@ -431,7 +465,7 @@ void BufferView::processUpdateFlags(Update::flags 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; } @@ -440,8 +474,10 @@ void BufferView::processUpdateFlags(Update::flags flags) 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; } @@ -461,11 +497,13 @@ void BufferView::processUpdateFlags(Update::flags flags) // 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(); } + + updateHoveredInset(); } @@ -480,7 +518,7 @@ void BufferView::updateScrollbar() d->scrollbarParameters_.page_step = height_; Text & t = buffer_.text(); - TextMetrics & tm = d->text_metrics_[&t]; + TextMetrics & tm = d->text_metrics_[&t]; LYXERR(Debug::GUI, " Updating scrollbar: height: " << t.paragraphs().size() @@ -521,7 +559,6 @@ void BufferView::updateScrollbar() for (size_t i = last.first + 1; i != parsize; ++i) d->scrollbarParameters_.max += d->par_height_[i]; - d->scrollbarParameters_.position = 0; // The reference is the top position so we remove one page. if (lyxrc.scroll_below_document) d->scrollbarParameters_.max -= minVisiblePart(); @@ -547,7 +584,7 @@ docstring BufferView::toolTip(int x, int y) const } -docstring BufferView::contextMenu(int x, int y) const +string BufferView::contextMenu(int x, int y) const { //If there is a selection, return the containing inset menu if (d->cursor_.selection()) @@ -562,25 +599,28 @@ docstring BufferView::contextMenu(int x, int y) const } -void BufferView::scrollDocView(int value) + +void BufferView::scrollDocView(int const value, bool update) { - int const offset = value - d->scrollbarParameters_.position; + // The scrollbar values are relative to the top of the screen, therefore the + // offset is equal to the target value. // No scrolling at all? No need to redraw anything - if (offset == 0) + if (value == 0) return; // If the offset is less than 2 screen height, prefer to scroll instead. - if (abs(offset) <= 2 * height_) { - d->anchor_ypos_ -= offset; + if (abs(value) <= 2 * height_) { + d->anchor_ypos_ -= value; buffer_.changed(true); + updateHoveredInset(); return; } // cut off at the top if (value <= d->scrollbarParameters_.min) { DocIterator dit = doc_iterator_begin(&buffer_); - showCursor(dit); + showCursor(dit, false, update); LYXERR(Debug::SCROLLING, "scroll to top"); return; } @@ -589,7 +629,7 @@ void BufferView::scrollDocView(int value) if (value >= d->scrollbarParameters_.max) { DocIterator dit = doc_iterator_end(&buffer_); dit.backwardPos(); - showCursor(dit); + showCursor(dit, false, update); LYXERR(Debug::SCROLLING, "scroll to bottom"); return; } @@ -607,14 +647,14 @@ void BufferView::scrollDocView(int value) // It seems we didn't find the correct pit so stay on the safe side and // scroll to bottom. LYXERR0("scrolling position not found!"); - scrollDocView(d->scrollbarParameters_.max); + scrollDocView(d->scrollbarParameters_.max, update); return; } DocIterator dit = doc_iterator_begin(&buffer_); dit.pit() = i; LYXERR(Debug::SCROLLING, "value = " << value << " -> scroll to pit " << i); - showCursor(dit); + showCursor(dit, false, update); } @@ -637,9 +677,9 @@ void BufferView::setCursorFromScrollbar() newy = last; break; case CUR_INSIDE: - int const y = getPos(oldcur, oldcur.boundary()).y_; + int const y = getPos(oldcur).y_; newy = min(last, max(y, first)); - if (y == newy) + if (y == newy) return; } // We reset the cursor because cursorStatus() does not @@ -652,7 +692,10 @@ void BufferView::setCursorFromScrollbar() // FIXME: Care about the d->cursor_ flags to redraw if needed Cursor old = d->cursor_; mouseSetCursor(cur); - bool badcursor = notifyCursorLeavesOrEnters(old, d->cursor_); + // the DEPM call in mouseSetCursor() might have destroyed the + // paragraph the cursor is in. + bool badcursor = old.fixIfBroken(); + badcursor |= notifyCursorLeavesOrEnters(old, d->cursor_); if (badcursor) d->cursor_.fixIfBroken(); } @@ -664,6 +707,8 @@ Change const BufferView::getCurrentChange() const return Change(Change::UNCHANGED); DocIterator dit = d->cursor_.selectionBegin(); + // The selected content might have been changed (see #7685) + dit = dit.getInnerText(); return dit.paragraph().lookupChange(dit.pos()); } @@ -672,7 +717,7 @@ Change const BufferView::getCurrentChange() const // FIXME: This does not work within mathed! CursorStatus BufferView::cursorStatus(DocIterator const & dit) const { - Point const p = getPos(dit, dit.boundary()); + Point const p = getPos(dit); if (p.y_ < 0) return CUR_ABOVE; if (p.y_ > workHeight()) @@ -697,17 +742,19 @@ void BufferView::saveBookmark(unsigned int idx) // acturately locate a bookmark in a 'live' lyx session. // pit and pos will be updated with bottom level pit/pos // when lyx exits. - theSession().bookmarks().save( - buffer_.fileName(), - d->cursor_.bottom().pit(), - d->cursor_.bottom().pos(), - d->cursor_.paragraph().id(), - d->cursor_.pos(), - idx - ); - if (idx) - // emit message signal. - message(_("Save bookmark")); + if (!buffer_.isInternal()) { + theSession().bookmarks().save( + buffer_.fileName(), + d->cursor_.bottom().pit(), + d->cursor_.bottom().pos(), + d->cursor_.paragraph().id(), + d->cursor_.pos(), + idx + ); + if (idx) + // emit message signal. + message(_("Save bookmark")); + } } @@ -750,7 +797,7 @@ bool BufferView::moveToPosition(pit_type bottom_pit, pos_type bottom_pos, // the bookmark. if (bottom_pit < int(buffer_.paragraphs().size())) { dit = doc_iterator_begin(&buffer_); - + dit.pit() = bottom_pit; dit.pos() = min(bottom_pos, dit.paragraph().size()); success = true; @@ -761,11 +808,13 @@ bool BufferView::moveToPosition(pit_type bottom_pit, pos_type bottom_pos, setCursor(dit); // set the current font. d->cursor_.setCurrentFont(); + // Do not forget to reset the anchor (see #9912) + d->cursor_.resetAnchor(); // To center the screen on this new position we need the // paragraph position which is computed at draw() time. // So we need a redraw! buffer_.changed(false); - if (fitCursor()) + if (needsFitCursor()) showCursor(); } @@ -775,14 +824,12 @@ bool BufferView::moveToPosition(pit_type bottom_pit, pos_type bottom_pos, void BufferView::translateAndInsert(char_type c, Text * t, Cursor & cur) { - if (lyxrc.rtl_support) { - if (d->cursor_.real_current_font.isRightToLeft()) { - if (d->intl_.keymap == Intl::PRIMARY) - d->intl_.keyMapSec(); - } else { - if (d->intl_.keymap == Intl::SECONDARY) - d->intl_.keyMapPrim(); - } + if (d->cursor_.real_current_font.isRightToLeft()) { + if (d->intl_.keymap == Intl::PRIMARY) + d->intl_.keyMapSec(); + } else { + if (d->intl_.keymap == Intl::SECONDARY) + d->intl_.keyMapPrim(); } d->intl_.getTransManager().translateAndInsert(c, t, cur); @@ -797,26 +844,32 @@ int BufferView::workWidth() const void BufferView::recenter() { - showCursor(d->cursor_, true); + showCursor(d->cursor_, true, true); } void BufferView::showCursor() { - showCursor(d->cursor_, false); + showCursor(d->cursor_, false, true); } -void BufferView::showCursor(DocIterator const & dit, bool recenter) +void BufferView::showCursor(DocIterator const & dit, + bool recenter, bool update) { - if (scrollToCursor(dit, recenter)) - buffer_.changed(false); + if (scrollToCursor(dit, recenter) && update) { + buffer_.changed(true); + updateHoveredInset(); + } } void BufferView::scrollToCursor() { - scrollToCursor(d->cursor_, false); + if (scrollToCursor(d->cursor_, false)) { + buffer_.changed(true); + updateHoveredInset(); + } } @@ -848,27 +901,43 @@ bool BufferView::scrollToCursor(DocIterator const & dit, bool recenter) if (tm.contains(bot_pit)) { ParagraphMetrics const & pm = tm.parMetrics(bot_pit); - LASSERT(!pm.rows().empty(), /**/); + LBUFERR(!pm.rows().empty()); // FIXME: smooth scrolling doesn't work in mathed. CursorSlice const & cs = dit.innerTextSlice(); - int offset = coordOffset(dit, dit.boundary()).y_; + int offset = coordOffset(dit).y_; int ypos = pm.position() + offset; Dimension const & row_dim = pm.getRow(cs.pos(), dit.boundary()).dimension(); int scrolled = 0; if (recenter) scrolled = scroll(ypos - height_/2); - else if (ypos - row_dim.ascent() < 0) - scrolled = scrollUp(- ypos + row_dim.ascent()); - else if (ypos + row_dim.descent() > height_) - scrolled = scrollDown(ypos - height_ + defaultRowHeight() ); - // else, nothing to do, the cursor is already visible so we just return. - if (scrolled != 0) { - updateMetrics(); - return true; + // We try to visualize the whole row, if the row height is larger than + // the screen height, we scroll to a heuristic value of height_ / 4. + // FIXME: This heuristic value should be replaced by a recursive search + // for a row in the inset that can be visualized completely. + else if (row_dim.height() > height_) { + if (ypos < defaultRowHeight()) + scrolled = scroll(ypos - height_ / 4); + else if (ypos > height_ - defaultRowHeight()) + scrolled = scroll(ypos - 3 * height_ / 4); } - return false; + + // If the top part of the row falls of the screen, we scroll + // up to align the top of the row with the top of the screen. + else if (ypos - row_dim.ascent() < 0 && ypos < height_) { + int ynew = row_dim.ascent(); + scrolled = scrollUp(ynew - ypos); + } + + // If the bottom of the row falls of the screen, we scroll down. + else if (ypos + row_dim.descent() > height_ && ypos > 0) { + int ynew = height_ - row_dim.descent(); + scrolled = scrollDown(ypos - ynew); + } + + // else, nothing to do, the cursor is already visible so we just return. + return scrolled != 0; } // fix inline completion position @@ -877,7 +946,7 @@ bool BufferView::scrollToCursor(DocIterator const & dit, bool recenter) tm.redoParagraph(bot_pit); ParagraphMetrics const & pm = tm.parMetrics(bot_pit); - int offset = coordOffset(dit, dit.boundary()).y_; + int offset = coordOffset(dit).y_; d->anchor_pit_ = bot_pit; CursorSlice const & cs = dit.innerTextSlice(); @@ -895,28 +964,35 @@ bool BufferView::scrollToCursor(DocIterator const & dit, bool recenter) else d->anchor_ypos_ = defaultRowHeight() * 2; - updateMetrics(); return true; } -void BufferView::updateLayout(DocumentClass const * const oldlayout) +void BufferView::makeDocumentClass() +{ + DocumentClassConstPtr olddc = buffer_.params().documentClassPtr(); + buffer_.params().makeDocumentClass(); + updateDocumentClass(olddc); +} + + +void BufferView::updateDocumentClass(DocumentClassConstPtr olddc) { message(_("Converting document to new document class...")); - + StableDocIterator backcur(d->cursor_); ErrorList & el = buffer_.errorList("Class Switch"); cap::switchBetweenClasses( - oldlayout, buffer_.params().documentClassPtr(), + olddc, buffer_.params().documentClassPtr(), static_cast(buffer_.inset()), el); setCursor(backcur.asDocIterator(&buffer_)); buffer_.errors("Class Switch"); - buffer_.updateLabels(); } -/** 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. @@ -940,18 +1016,21 @@ static Change::Type lookupChangeType(DocIterator const & dit, bool outer = false bool BufferView::getStatus(FuncRequest const & cmd, FuncStatus & flag) { + FuncCode const act = cmd.action(); + // Can we use a readonly buffer? if (buffer_.isReadonly() - && !lyxaction.funcHasFlag(cmd.action, LyXAction::ReadOnly) - && !lyxaction.funcHasFlag(cmd.action, LyXAction::NoBuffer)) { + && !lyxaction.funcHasFlag(act, LyXAction::ReadOnly) + && !lyxaction.funcHasFlag(act, LyXAction::NoBuffer)) { flag.message(from_utf8(N_("Document is read-only"))); flag.setEnabled(false); return true; } + // Are we in a DELETED change-tracking region? if (lookupChangeType(d->cursor_, true) == Change::DELETED - && !lyxaction.funcHasFlag(cmd.action, LyXAction::ReadOnly) - && !lyxaction.funcHasFlag(cmd.action, LyXAction::NoBuffer)) { + && !lyxaction.funcHasFlag(act, LyXAction::ReadOnly) + && !lyxaction.funcHasFlag(act, LyXAction::NoBuffer)) { flag.message(from_utf8(N_("This portion of the document is deleted."))); flag.setEnabled(false); return true; @@ -962,13 +1041,13 @@ bool BufferView::getStatus(FuncRequest const & cmd, FuncStatus & flag) if (cur.getStatus(cmd, flag)) return true; - switch (cmd.action) { + switch (act) { - // FIXME: This is a bit problematic because we don't check is this is a - // document BufferView or not for these LFUNs. We probably have to + // FIXME: This is a bit problematic because we don't check if this is + // a document BufferView or not for these LFUNs. We probably have to // dispatch both to currentBufferView() and, if that fails, - // to documentBufferView(); same as we do know for current Buffer and - // document Buffer. Ideally those LFUN should go to Buffer as they* + // to documentBufferView(); same as we do now for current Buffer and + // document Buffer. Ideally those LFUN should go to Buffer as they // operate on the full Buffer and the cursor is only needed either for // an Undo record or to restore a cursor position. But we don't know // how to do that inside Buffer of course. @@ -982,14 +1061,26 @@ bool BufferView::getStatus(FuncRequest const & cmd, FuncStatus & flag) break; case LFUN_UNDO: - flag.setEnabled(buffer_.undo().hasUndoStack()); + // We do not use the LyXAction flag for readonly because Undo sets the + // buffer clean/dirty status by itself. + flag.setEnabled(!buffer_.isReadonly() && buffer_.undo().hasUndoStack()); break; case LFUN_REDO: - flag.setEnabled(buffer_.undo().hasRedoStack()); + // We do not use the LyXAction flag for readonly because Redo sets the + // buffer clean/dirty status by itself. + flag.setEnabled(!buffer_.isReadonly() && buffer_.undo().hasRedoStack()); break; - case LFUN_FILE_INSERT: case LFUN_FILE_INSERT_PLAINTEXT_PARA: - case LFUN_FILE_INSERT_PLAINTEXT: + case LFUN_FILE_INSERT_PLAINTEXT: { + docstring const fname = cmd.argument(); + if (!FileName::isAbsolute(to_utf8(fname))) { + flag.message(_("Absolute filename expected.")); + return false; + } + flag.setEnabled(cur.inTexted()); + break; + } + case LFUN_FILE_INSERT: case LFUN_BOOKMARK_SAVE: // FIXME: Actually, these LFUNS should be moved to Text flag.setEnabled(cur.inTexted()); @@ -998,14 +1089,12 @@ bool BufferView::getStatus(FuncRequest const & cmd, FuncStatus & flag) case LFUN_FONT_STATE: case LFUN_LABEL_INSERT: case LFUN_INFO_INSERT: - case LFUN_INSET_EDIT: case LFUN_PARAGRAPH_GOTO: case LFUN_NOTE_NEXT: case LFUN_REFERENCE_NEXT: case LFUN_WORD_FIND: case LFUN_WORD_FIND_FORWARD: case LFUN_WORD_FIND_BACKWARD: - case LFUN_WORD_FINDADV: case LFUN_WORD_REPLACE: case LFUN_MARK_OFF: case LFUN_MARK_ON: @@ -1014,76 +1103,37 @@ bool BufferView::getStatus(FuncRequest const & cmd, FuncStatus & flag) case LFUN_SCREEN_SHOW_CURSOR: case LFUN_BIBTEX_DATABASE_ADD: case LFUN_BIBTEX_DATABASE_DEL: - case LFUN_NOTES_MUTATE: - case LFUN_ALL_INSETS_TOGGLE: case LFUN_STATISTICS: - case LFUN_BRANCH_ADD_INSERT: case LFUN_KEYMAP_OFF: case LFUN_KEYMAP_PRIMARY: case LFUN_KEYMAP_SECONDARY: case LFUN_KEYMAP_TOGGLE: + case LFUN_INSET_SELECT_ALL: flag.setEnabled(true); break; - case LFUN_REGEXP_MODE: - // FIXME: Test if current WorkArea is the search WorkArea - flag.setEnabled(buffer().isInternal() && !cur.inRegexped()); - break; - - case LFUN_LABEL_COPY_AS_REF: { - // if there is an inset at cursor, see whether it - // handles the lfun - Inset * inset = cur.nextInset(); - if (!inset || !inset->getStatus(cur, cmd, flag)) - flag.setEnabled(false); - break; - } - - case LFUN_NEXT_INSET_MODIFY: { - // this is the real function we want to invoke - FuncRequest tmpcmd = cmd; - tmpcmd.action = LFUN_INSET_MODIFY; - // if there is an inset at cursor, see whether it - // handles the lfun, other start from scratch - Inset * inset = cur.nextInset(); - if (!inset || !inset->getStatus(cur, tmpcmd, flag)) - flag = lyx::getStatus(tmpcmd); + case LFUN_WORD_FINDADV: { + FindAndReplaceOptions opt; + istringstream iss(to_utf8(cmd.argument())); + 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(cur, REF_CODE)); break; - } - - case LFUN_CHANGES_TRACK: - flag.setEnabled(true); - flag.setOnOff(buffer_.params().trackChanges); - break; - - case LFUN_CHANGES_OUTPUT: - flag.setEnabled(true); - flag.setOnOff(buffer_.params().outputChanges); - break; case LFUN_CHANGES_MERGE: case LFUN_CHANGE_NEXT: case LFUN_CHANGE_PREVIOUS: case LFUN_ALL_CHANGES_ACCEPT: case LFUN_ALL_CHANGES_REJECT: - // TODO: context-sensitive enabling of LFUNs - // In principle, these command should only be enabled if there - // is a change in the document. However, without proper - // optimizations, this will inevitably result in poor performance. - flag.setEnabled(true); + flag.setEnabled(buffer_.areChangesPresent()); break; - case LFUN_BUFFER_TOGGLE_COMPRESSION: { - flag.setOnOff(buffer_.params().compressed); - break; - } - case LFUN_SCREEN_UP: case LFUN_SCREEN_DOWN: case LFUN_SCROLL: @@ -1105,13 +1155,17 @@ bool BufferView::getStatus(FuncRequest const & cmd, FuncStatus & flag) flag.setEnabled(cur.inset().allowParagraphCustomization(cur.idx())); break; + case LFUN_BRANCH_ADD_INSERT: + flag.setEnabled(!(cur.inTexted() && cur.paragraph().isPassThru())); + break; + case LFUN_DIALOG_SHOW_NEW_INSET: // FIXME: this is wrong, but I do not understand the // intent (JMarc) if (cur.inset().lyxCode() == CAPTION_CODE) return cur.inset().getStatus(cur, cmd, flag); // FIXME we should consider passthru paragraphs too. - flag.setEnabled(!cur.inset().getLayout().isPassThru()); + flag.setEnabled(!(cur.inTexted() && cur.paragraph().isPassThru())); break; case LFUN_CITATION_INSERT: { @@ -1126,21 +1180,18 @@ bool BufferView::getStatus(FuncRequest const & cmd, FuncStatus & flag) Inset * inset = editedInset(name); if (inset) { FuncRequest fr(LFUN_INSET_MODIFY, cmd.argument()); - FuncStatus fs; - if (!inset->getStatus(cur, fr, fs)) { + if (!inset->getStatus(cur, fr, flag)) { // Every inset is supposed to handle this LASSERT(false, break); } - flag |= fs; } else { FuncRequest fr(LFUN_INSET_INSERT, cmd.argument()); - flag |= lyx::getStatus(fr); + flag = lyx::getStatus(fr); } break; } default: - flag.setEnabled(false); return false; } @@ -1161,34 +1212,29 @@ void BufferView::editInset(string const & name, Inset * inset) } -bool BufferView::dispatch(FuncRequest const & cmd) +void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) { - //lyxerr << [ cmd = " << cmd << "]" << endl; - - // Make sure that the cached BufferView is correct. - LYXERR(Debug::ACTION, " action[" << cmd.action << ']' - << " arg[" << to_utf8(cmd.argument()) << ']' - << " x[" << cmd.x << ']' - << " y[" << cmd.y << ']' - << " button[" << cmd.button() << ']'); + LYXERR(Debug::ACTION, "BufferView::dispatch: cmd: " << cmd); 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() - && lyxaction.funcHasFlag(cmd.action, LyXAction::NoInternal)) - return false; + if (buffer_.isInternal() + && lyxaction.funcHasFlag(cmd.action(), LyXAction::NoInternal)) + return; // We'll set this back to false if need be. bool dispatched = true; buffer_.undo().beginUndoGroup(); - switch (cmd.action) { + FuncCode const act = cmd.action(); + switch (act) { case LFUN_BUFFER_PARAMS_APPLY: { - DocumentClass const * const oldClass = buffer_.params().documentClassPtr(); - cur.recordUndoFullDocument(); + DocumentClassConstPtr olddc = buffer_.params().documentClassPtr(); + cur.recordUndoBufferParams(); istringstream ss(to_utf8(cmd.argument())); Lexer lex; lex.setStream(ss); @@ -1198,47 +1244,59 @@ bool BufferView::dispatch(FuncRequest const & cmd) << unknown_tokens << " unknown token" << (unknown_tokens == 1 ? "" : "s")); } - updateLayout(oldClass); - + updateDocumentClass(olddc); + // We are most certainly here because of a change in the document // It is then better to make sure that all dialogs are in sync with // current document settings. - processUpdateFlags(Update::Force | Update::FitCursor); + dr.screenUpdate(Update::Force | Update::FitCursor); + dr.forceBufferUpdate(); break; } - + case LFUN_LAYOUT_MODULES_CLEAR: { - DocumentClass const * const oldClass = - buffer_.params().documentClassPtr(); - cur.recordUndoFullDocument(); + // FIXME: this modifies the document in cap::switchBetweenClasses + // without calling recordUndo. Fix this before using + // recordUndoBufferParams(). + cur.recordUndoFullBuffer(); buffer_.params().clearLayoutModules(); - buffer_.params().makeDocumentClass(); - updateLayout(oldClass); - processUpdateFlags(Update::Force | Update::FitCursor); + makeDocumentClass(); + dr.screenUpdate(Update::Force); + dr.forceBufferUpdate(); break; } case LFUN_LAYOUT_MODULE_ADD: { BufferParams const & params = buffer_.params(); - if (!params.moduleCanBeAdded(argument)) { - LYXERR0("Module `" << argument << + if (!params.layoutModuleCanBeAdded(argument)) { + LYXERR0("Module `" << argument << "' cannot be added due to failed requirements or " "conflicts with installed modules."); break; } - DocumentClass const * const oldClass = params.documentClassPtr(); - cur.recordUndoFullDocument(); + // FIXME: this modifies the document in cap::switchBetweenClasses + // without calling recordUndo. Fix this before using + // recordUndoBufferParams(). + cur.recordUndoFullBuffer(); buffer_.params().addLayoutModule(argument); - buffer_.params().makeDocumentClass(); - updateLayout(oldClass); - processUpdateFlags(Update::Force | Update::FitCursor); + makeDocumentClass(); + dr.screenUpdate(Update::Force); + dr.forceBufferUpdate(); break; } case LFUN_TEXTCLASS_APPLY: { - if (!LayoutFileList::get().load(argument, buffer_.temppath()) && - !LayoutFileList::get().load(argument, buffer_.filePath())) + // since this shortcircuits, the second call is made only if + // the first fails + bool const success = + LayoutFileList::get().load(argument, buffer_.temppath()) || + LayoutFileList::get().load(argument, buffer_.filePath()); + if (!success) { + docstring s = bformat(_("The document class `%1$s' " + "could not be loaded."), from_utf8(argument)); + frontend::Alert::error(_("Could not load class"), s); break; + } LayoutFile const * old_layout = buffer_.params().baseClass(); LayoutFile const * new_layout = &(LayoutFileList::get()[argument]); @@ -1247,53 +1305,64 @@ bool BufferView::dispatch(FuncRequest const & cmd) // nothing to do break; - //Save the old, possibly modular, layout for use in conversion. - DocumentClass const * const oldDocClass = - buffer_.params().documentClassPtr(); - cur.recordUndoFullDocument(); + // Save the old, possibly modular, layout for use in conversion. + // FIXME: this modifies the document in cap::switchBetweenClasses + // without calling recordUndo. Fix this before using + // recordUndoBufferParams(). + cur.recordUndoFullBuffer(); buffer_.params().setBaseClass(argument); - buffer_.params().makeDocumentClass(); - updateLayout(oldDocClass); - processUpdateFlags(Update::Force | Update::FitCursor); + makeDocumentClass(); + dr.screenUpdate(Update::Force); + dr.forceBufferUpdate(); + break; + } + + case LFUN_TEXTCLASS_LOAD: { + // since this shortcircuits, the second call is made only if + // the first fails + bool const success = + LayoutFileList::get().load(argument, buffer_.temppath()) || + LayoutFileList::get().load(argument, buffer_.filePath()); + if (!success) { + docstring s = bformat(_("The document class `%1$s' " + "could not be loaded."), from_utf8(argument)); + frontend::Alert::error(_("Could not load class"), s); + } break; } - case LFUN_TEXTCLASS_LOAD: - LayoutFileList::get().load(argument, buffer_.temppath()) || - LayoutFileList::get().load(argument, buffer_.filePath()); - break; - case LFUN_LAYOUT_RELOAD: { - DocumentClass const * const oldClass = buffer_.params().documentClassPtr(); LayoutFileIndex bc = buffer_.params().baseClassID(); LayoutFileList::get().reset(bc); buffer_.params().setBaseClass(bc); - buffer_.params().makeDocumentClass(); - updateLayout(oldClass); - processUpdateFlags(Update::Force | Update::FitCursor); + makeDocumentClass(); + dr.screenUpdate(Update::Force); + dr.forceBufferUpdate(); break; } case LFUN_UNDO: - cur.message(_("Undo")); + dr.setMessage(_("Undo")); cur.clearSelection(); if (!cur.textUndo()) - cur.message(_("No further undo information")); + dr.setMessage(_("No further undo information")); else - processUpdateFlags(Update::Force | Update::FitCursor); + dr.screenUpdate(Update::Force | Update::FitCursor); + dr.forceBufferUpdate(); break; case LFUN_REDO: - cur.message(_("Redo")); + dr.setMessage(_("Redo")); cur.clearSelection(); if (!cur.textRedo()) - cur.message(_("No further redo information")); + dr.setMessage(_("No further redo information")); else - processUpdateFlags(Update::Force | Update::FitCursor); + dr.screenUpdate(Update::Force | Update::FitCursor); + dr.forceBufferUpdate(); break; case LFUN_FONT_STATE: - cur.message(cur.currentState()); + dr.setMessage(cur.currentState()); break; case LFUN_BOOKMARK_SAVE: @@ -1311,27 +1380,13 @@ bool BufferView::dispatch(FuncRequest const & cmd) saveBookmark(0); } } - if (!label.empty()) + if (!label.empty()) { gotoLabel(label); - break; - } - - case LFUN_INSET_EDIT: { - FuncRequest fr(cmd); - // if there is an inset at cursor, see whether it - // can be modified. - Inset * inset = cur.nextInset(); - if (inset) - inset->dispatch(cur, fr); - // if it did not work, try the underlying inset. - if (!inset || !cur.result().dispatched()) - cur.dispatch(cmd); - - // FIXME I'm adding the last break to solve a crash, - // but that is obviously not right. - if (!cur.result().dispatched()) - // It did not work too; no action needed. - break; + // at the moment, this is redundant, since gotoLabel will + // eventually call LFUN_PARAGRAPH_GOTO, but it seems best + // to have it here. + dr.screenUpdate(Update::Force | Update::FitCursor); + } break; } @@ -1342,26 +1397,27 @@ bool BufferView::dispatch(FuncRequest const & cmd) 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); - processUpdateFlags(Update::Force | Update::FitCursor); + cur.pos() = pos; + mouseSetCursor(cur); + dr.screenUpdate(Update::Force | Update::FitCursor); } else { // Switch to other buffer view and resend cmd - theLyXFunc().dispatch(FuncRequest( + lyx::dispatch(FuncRequest( LFUN_BUFFER_SWITCH, b->absFileName())); - theLyXFunc().dispatch(cmd); + lyx::dispatch(cmd); } break; } @@ -1380,48 +1436,22 @@ bool BufferView::dispatch(FuncRequest const & cmd) break; } - case LFUN_CHANGES_TRACK: - buffer_.params().trackChanges = !buffer_.params().trackChanges; - break; - - case LFUN_CHANGES_OUTPUT: - buffer_.params().outputChanges = !buffer_.params().outputChanges; - if (buffer_.params().outputChanges) { - bool dvipost = LaTeXFeatures::isAvailable("dvipost"); - bool xcolorulem = LaTeXFeatures::isAvailable("ulem") && - LaTeXFeatures::isAvailable("xcolor"); - - if (!dvipost && !xcolorulem) { - Alert::warning(_("Changes not shown in LaTeX output"), - _("Changes will not be highlighted in LaTeX output, " - "because neither dvipost nor xcolor/ulem are installed.\n" - "Please install these packages or redefine " - "\\lyxadded and \\lyxdeleted in the LaTeX preamble.")); - } else if (!xcolorulem) { - Alert::warning(_("Changes not shown in LaTeX output"), - _("Changes will not be highlighted in LaTeX output " - "when using pdflatex, because xcolor and ulem are not installed.\n" - "Please install both packages or redefine " - "\\lyxadded and \\lyxdeleted in the LaTeX preamble.")); - } - } - break; - case LFUN_CHANGE_NEXT: findNextChange(this); // FIXME: Move this LFUN to Buffer so that we don't have to do this: - processUpdateFlags(Update::Force | Update::FitCursor); + dr.screenUpdate(Update::Force | Update::FitCursor); break; - + case LFUN_CHANGE_PREVIOUS: findPreviousChange(this); // FIXME: Move this LFUN to Buffer so that we don't have to do this: - processUpdateFlags(Update::Force | Update::FitCursor); + dr.screenUpdate(Update::Force | Update::FitCursor); break; case LFUN_CHANGES_MERGE: if (findNextChange(this) || findPreviousChange(this)) { - processUpdateFlags(Update::Force | Update::FitCursor); + dr.screenUpdate(Update::Force | Update::FitCursor); + dr.forceBufferUpdate(); showDialog("changes"); } break; @@ -1433,8 +1463,10 @@ bool BufferView::dispatch(FuncRequest const & cmd) buffer_.text().cursorBottom(cur); // accept everything in a single step to support atomic undo buffer_.text().acceptOrRejectChanges(cur, Text::ACCEPT); + cur.resetAnchor(); // FIXME: Move this LFUN to Buffer so that we don't have to do this: - processUpdateFlags(Update::Force | Update::FitCursor); + dr.screenUpdate(Update::Force | Update::FitCursor); + dr.forceBufferUpdate(); break; case LFUN_ALL_CHANGES_REJECT: @@ -1445,12 +1477,16 @@ bool BufferView::dispatch(FuncRequest const & cmd) // reject everything in a single step to support atomic undo // Note: reject does not work recursively; the user may have to repeat the operation buffer_.text().acceptOrRejectChanges(cur, Text::REJECT); + cur.resetAnchor(); // FIXME: Move this LFUN to Buffer so that we don't have to do this: - processUpdateFlags(Update::Force | Update::FitCursor); + dr.screenUpdate(Update::Force | Update::FitCursor); + dr.forceBufferUpdate(); break; case LFUN_WORD_FIND_FORWARD: case LFUN_WORD_FIND_BACKWARD: { + // FIXME THREAD + // Would it maybe be better if this variable were view specific anyway? static docstring last_search; docstring searched_string; @@ -1464,10 +1500,18 @@ bool BufferView::dispatch(FuncRequest const & cmd) if (searched_string.empty()) break; - bool const fw = cmd.action == LFUN_WORD_FIND_FORWARD; + bool const fw = act == LFUN_WORD_FIND_FORWARD; docstring const data = find2string(searched_string, true, false, fw); - find(this, FuncRequest(LFUN_WORD_FIND, data)); + bool found = lyxfind(this, FuncRequest(LFUN_WORD_FIND, data)); + if (found) { + dr.screenUpdate(Update::Force | Update::FitCursor); + cur.dispatched(); + dispatched = true; + } else { + cur.undispatched(); + dispatched = false; + } break; } @@ -1476,13 +1520,17 @@ bool BufferView::dispatch(FuncRequest const & cmd) if (cmd.argument().empty() && !d->search_request_cache_.argument().empty()) req = d->search_request_cache_; if (req.argument().empty()) { - theLyXFunc().dispatch(FuncRequest(LFUN_DIALOG_SHOW, "findreplace")); + lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "findreplace")); break; } - if (find(this, req)) - showCursor(); - else - message(_("String not found!")); + if (lyxfind(this, req)) { + dr.screenUpdate(Update::Force | Update::FitCursor); + cur.dispatched(); + dispatched = true; + } else { + cur.undispatched(); + dispatched = false; + } d->search_request_cache_ = req; break; } @@ -1494,12 +1542,22 @@ bool BufferView::dispatch(FuncRequest const & cmd) DocIterator end = cur.selectionEnd(); if (beg.pit() == end.pit()) { for (pos_type p = beg.pos() ; p < end.pos() ; ++p) { - if (cur.paragraph().isDeleted(p)) + if (!cur.inMathed() && cur.paragraph().isDeleted(p)) { has_deleted = true; + break; + } } } } - replace(this, cmd, has_deleted); + if (lyxreplace(this, cmd, has_deleted)) { + dr.forceBufferUpdate(); + dr.screenUpdate(Update::Force | Update::FitCursor); + cur.dispatched(); + dispatched = true; + } else { + cur.undispatched(); + dispatched = false; + } break; } @@ -1507,32 +1565,36 @@ bool BufferView::dispatch(FuncRequest const & cmd) FindAndReplaceOptions opt; istringstream iss(to_utf8(cmd.argument())); iss >> opt; - if (findAdv(this, opt)) + if (findAdv(this, opt)) { + dr.screenUpdate(Update::Force | Update::FitCursor); cur.dispatched(); - else + dispatched = true; + } else { cur.undispatched(); + dispatched = false; + } break; } case LFUN_MARK_OFF: cur.clearSelection(); - cur.message(from_utf8(N_("Mark off"))); + dr.setMessage(from_utf8(N_("Mark off"))); break; case LFUN_MARK_ON: cur.clearSelection(); cur.setMark(true); - cur.message(from_utf8(N_("Mark on"))); + dr.setMessage(from_utf8(N_("Mark on"))); break; case LFUN_MARK_TOGGLE: - cur.setSelection(false); + cur.selection(false); if (cur.mark()) { cur.setMark(false); - cur.message(from_utf8(N_("Mark removed"))); + dr.setMessage(from_utf8(N_("Mark removed"))); } else { cur.setMark(true); - cur.message(from_utf8(N_("Mark set"))); + dr.setMessage(from_utf8(N_("Mark set"))); } cur.resetAnchor(); break; @@ -1540,7 +1602,7 @@ bool BufferView::dispatch(FuncRequest const & cmd) case LFUN_SCREEN_SHOW_CURSOR: showCursor(); break; - + case LFUN_SCREEN_RECENTER: recenter(); break; @@ -1551,8 +1613,10 @@ bool BufferView::dispatch(FuncRequest const & cmd) InsetBibtex * inset = getInsetByCode(tmpcur, BIBTEX_CODE); if (inset) { - if (inset->addDatabase(cmd.argument())) - buffer_.updateBibfilesCache(); + if (inset->addDatabase(cmd.argument())) { + buffer_.invalidateBibfileCache(); + dr.forceBufferUpdate(); + } } break; } @@ -1563,8 +1627,10 @@ bool BufferView::dispatch(FuncRequest const & cmd) InsetBibtex * inset = getInsetByCode(tmpcur, BIBTEX_CODE); if (inset) { - if (inset->delDatabase(cmd.argument())) - buffer_.updateBibfilesCache(); + if (inset->delDatabase(cmd.argument())) { + buffer_.invalidateBibfileCache(); + dr.forceBufferUpdate(); + } } break; } @@ -1578,9 +1644,10 @@ bool BufferView::dispatch(FuncRequest const & cmd) from = doc_iterator_begin(&buffer_); to = doc_iterator_end(&buffer_); } - int const words = countWords(from, to); - int const chars = countChars(from, to, false); - int const chars_blanks = countChars(from, to, true); + buffer_.updateStatistics(from, to); + int const words = buffer_.wordCount(); + int const chars = buffer_.charCount(false); + int const chars_blanks = buffer_.charCount(true); docstring message; if (cur.selection()) message = _("Statistics for the selection:"); @@ -1608,130 +1675,140 @@ bool BufferView::dispatch(FuncRequest const & cmd) } break; - case LFUN_BUFFER_TOGGLE_COMPRESSION: - // turn compression on/off - buffer_.params().compressed = !buffer_.params().compressed; - break; - - case LFUN_LABEL_COPY_AS_REF: { - // if there is an inset at cursor, try to copy it - Inset * inset = &cur.inset(); - if (!inset || !inset->asInsetMath()) - inset = cur.nextInset(); - if (inset) { - FuncRequest tmpcmd = cmd; - inset->dispatch(cur, tmpcmd); - } - if (!cur.result().dispatched()) - // It did not work too; no action needed. - break; - cur.clearSelection(); - processUpdateFlags(Update::SinglePar | Update::FitCursor); - break; - } - - case LFUN_NEXT_INSET_MODIFY: { - // create the the real function we want to invoke - FuncRequest tmpcmd = cmd; - tmpcmd.action = LFUN_INSET_MODIFY; - // if there is an inset at cursor, see whether it - // can be modified. - Inset * inset = cur.nextInset(); - if (inset) { - cur.recordUndo(); - inset->dispatch(cur, tmpcmd); - } - // if it did not work, try the underlying inset. - if (!inset || !cur.result().dispatched()) { - cur.recordUndo(); - cur.dispatch(tmpcmd); - } - - if (!cur.result().dispatched()) - // It did not work too; no action needed. - break; - cur.clearSelection(); - processUpdateFlags(Update::Force | Update::FitCursor); - break; - } - case LFUN_SCREEN_UP: case LFUN_SCREEN_DOWN: { - Point p = getPos(cur, cur.boundary()); + Point p = getPos(cur); // This code has been commented out to enable to scroll down a // document, even if there are large insets in it (see bug #5465). /*if (p.y_ < 0 || p.y_ > height_) { // The cursor is off-screen so recenter before proceeding. showCursor(); - p = getPos(cur, cur.boundary()); + p = getPos(cur); }*/ - int const scrolled = scroll(cmd.action == LFUN_SCREEN_UP + int const scrolled = scroll(act == LFUN_SCREEN_UP ? -height_ : height_); - if (cmd.action == LFUN_SCREEN_UP && scrolled > -height_) + if (act == LFUN_SCREEN_UP && scrolled > -height_) p = Point(0, 0); - if (cmd.action == LFUN_SCREEN_DOWN && scrolled < height_) + if (act == LFUN_SCREEN_DOWN && scrolled < height_) p = Point(width_, height_); - Cursor old = cur; bool const in_texted = cur.inTexted(); - cur.reset(); + cur.setCursor(doc_iterator_begin(cur.buffer())); + cur.selHandle(false); buffer_.changed(true); + updateHoveredInset(); + d->text_metrics_[&buffer_.text()].editXY(cur, p.x_, p.y_, - true, cmd.action == LFUN_SCREEN_UP); + true, act == LFUN_SCREEN_UP); //FIXME: what to do with cur.x_target()? bool update = in_texted && cur.bv().checkDepm(cur, old); cur.finishUndo(); + + if (update || cur.mark()) + dr.screenUpdate(Update::Force | Update::FitCursor); if (update) - processUpdateFlags(Update::Force | Update::FitCursor); + dr.forceBufferUpdate(); break; } - case LFUN_SCROLL: - lfunScroll(cmd); + case LFUN_SCROLL: { + string const scroll_type = cmd.getArg(0); + int scroll_step = 0; + if (scroll_type == "line") + scroll_step = d->scrollbarParameters_.single_step; + else if (scroll_type == "page") + scroll_step = d->scrollbarParameters_.page_step; + else + return; + string const scroll_quantity = cmd.getArg(1); + if (scroll_quantity == "up") + scrollUp(scroll_step); + else if (scroll_quantity == "down") + scrollDown(scroll_step); + else { + int const scroll_value = convert(scroll_quantity); + if (scroll_value) + scroll(scroll_step * scroll_value); + } + buffer_.changed(true); + updateHoveredInset(); + dr.forceBufferUpdate(); break; + } case LFUN_SCREEN_UP_SELECT: { + // FIXME: why is the algorithm different from LFUN_SCREEN_UP? cur.selHandle(true); if (isTopScreen()) { lyx::dispatch(FuncRequest(LFUN_BUFFER_BEGIN_SELECT)); cur.finishUndo(); break; } - int y = getPos(cur, cur.boundary()).y_; + int y = getPos(cur).y_; int const ymin = y - height_ + defaultRowHeight(); while (y > ymin && cur.up()) - y = getPos(cur, cur.boundary()).y_; + y = getPos(cur).y_; cur.finishUndo(); - processUpdateFlags(Update::SinglePar | Update::FitCursor); + dr.screenUpdate(Update::SinglePar | Update::FitCursor); break; } case LFUN_SCREEN_DOWN_SELECT: { + // FIXME: why is the algorithm different from LFUN_SCREEN_DOWN? cur.selHandle(true); if (isBottomScreen()) { lyx::dispatch(FuncRequest(LFUN_BUFFER_END_SELECT)); cur.finishUndo(); break; } - int y = getPos(cur, cur.boundary()).y_; + int y = getPos(cur).y_; int const ymax = y + height_ - defaultRowHeight(); while (y < ymax && cur.down()) - y = getPos(cur, cur.boundary()).y_; + y = getPos(cur).y_; cur.finishUndo(); - processUpdateFlags(Update::SinglePar | Update::FitCursor); - break; - } - - // This could be rewriten using some command like forall - // once the insets refactoring is done. - case LFUN_NOTES_MUTATE: { - if (cmd.argument().empty()) - break; - - if (mutateNotes(cur, cmd.getArg(0), cmd.getArg(1))) { - processUpdateFlags(Update::Force); + dr.screenUpdate(Update::SinglePar | Update::FitCursor); + break; + } + + + case LFUN_INSET_SELECT_ALL: { + // true if all cells are selected + bool const all_selected = cur.depth() > 1 + && cur.selBegin().at_begin() + && cur.selEnd().at_end(); + // true if some cells are selected + bool const cells_selected = cur.depth() > 1 + && cur.selBegin().at_cell_begin() + && cur.selEnd().at_cell_end(); + if (all_selected || (cells_selected && !cur.inset().isTable())) { + // All the contents of the inset if selected, or only at + // least one cell but inset is not a table. + // Select the inset from outside. + cur.pop(); + cur.resetAnchor(); + cur.selection(true); + cur.posForward(); + } else if (cells_selected) { + // At least one complete cell is selected and inset is a table. + // Select all cells + cur.idx() = 0; + cur.pos() = 0; + cur.resetAnchor(); + cur.selection(true); + cur.idx() = cur.lastidx(); + cur.pos() = cur.lastpos(); + } else { + // select current cell + cur.pit() = 0; + cur.pos() = 0; + cur.resetAnchor(); + cur.selection(true); + cur.pit() = cur.lastpit(); + cur.pos() = cur.lastpos(); } + cur.setCurrentFont(); + dr.screenUpdate(Update::Force); break; } @@ -1744,22 +1821,23 @@ bool BufferView::dispatch(FuncRequest const & cmd) FuncRequest const fr = lyxaction.lookupFunc(commandstr); // an arbitrary number to limit number of iterations - const int max_iter = 1000; + const int max_iter = 100000; int iterations = 0; Cursor & cur = d->cursor_; + Cursor const savecur = cur; cur.reset(); if (!cur.nextInset()) cur.forwardInset(); cur.beginUndoGroup(); while(cur && iterations < max_iter) { - Inset * ins = cur.nextInset(); + Inset * const ins = cur.nextInset(); if (!ins) break; - docstring insname = ins->name(); + docstring insname = ins->layoutName(); while (!insname.empty()) { if (insname == name || name == from_utf8("*")) { cur.recordUndo(); - lyx::dispatch(fr); + lyx::dispatch(fr, dr); ++iterations; break; } @@ -1768,43 +1846,33 @@ bool BufferView::dispatch(FuncRequest const & cmd) break; insname = insname.substr(0, i); } - cur.forwardInset(); + // if we did not delete the inset, skip it + if (!cur.nextInset() || cur.nextInset() == ins) + cur.forwardInset(); } + cur = savecur; + cur.fixIfBroken(); + /** This is a dummy undo record only to remember the cursor + * that has just been set; this will be used on a redo action + * (see ticket #10097) + + * FIXME: a better fix would be to have a way to set the + * cursor value directly, but I am not sure it is worth it. + */ + cur.recordUndo(); cur.endUndoGroup(); - cur.reset(); - processUpdateFlags(Update::Force); + dr.screenUpdate(Update::Force); + dr.forceBufferUpdate(); - if (iterations >= max_iter) - cur.errorMessage(bformat(_("`inset-forall' interrupted because number of actions is larger than %1$d"), max_iter)); - else - cur.message(bformat(_("Applied \"%1$s\" to %2$d insets"), from_utf8(commandstr), iterations)); + if (iterations >= max_iter) { + dr.setError(true); + dr.setMessage(bformat(_("`inset-forall' interrupted because number of actions is larger than %1$d"), max_iter)); + } else + dr.setMessage(bformat(_("Applied \"%1$s\" to %2$d insets"), from_utf8(commandstr), iterations)); break; } - case LFUN_ALL_INSETS_TOGGLE: { - string action; - string const name = split(to_utf8(cmd.argument()), action, ' '); - InsetCode const inset_code = insetCode(name); - - FuncRequest fr(LFUN_INSET_TOGGLE, action); - - Inset & inset = cur.buffer()->inset(); - InsetIterator it = inset_iterator_begin(inset); - InsetIterator const end = inset_iterator_end(inset); - for (; it != end; ++it) { - if (it->asInsetCollapsable() - && (inset_code == NO_CODE - || inset_code == it->lyxCode())) { - Cursor tmpcur = cur; - tmpcur.pushBackward(*it); - it->dispatch(tmpcur, fr); - } - } - processUpdateFlags(Update::Force | Update::FitCursor); - break; - } - case LFUN_BRANCH_ADD_INSERT: { docstring branch_name = from_utf8(cmd.getArg(0)); if (branch_name.empty()) @@ -1818,7 +1886,14 @@ bool BufferView::dispatch(FuncRequest const & cmd) Alert::warning(_("Branch already exists"), drtmp.message()); break; } - lyx::dispatch(FuncRequest(LFUN_BRANCH_INSERT, branch_name)); + BranchList & branch_list = buffer_.params().branchlist(); + vector const branches = + getVectorFromString(branch_name, branch_list.separator()); + for (vector::const_iterator it = branches.begin(); + it != branches.end(); ++it) { + branch_name = *it; + lyx::dispatch(FuncRequest(LFUN_BRANCH_INSERT, branch_name)); + } break; } @@ -1844,7 +1919,7 @@ bool BufferView::dispatch(FuncRequest const & cmd) if (decodeInsetParam(name, data, buffer_)) lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, name + " " + data)); else - lyxerr << "Inset type '" << name << + lyxerr << "Inset type '" << name << "' not recognized in LFUN_DIALOG_SHOW_NEW_INSET" << endl; break; } @@ -1864,11 +1939,28 @@ bool BufferView::dispatch(FuncRequest const & cmd) arg = token(argument, '|', 0); opt1 = token(argument, '|', 1); } + + // if our cursor is directly in front of or behind a citation inset, + // we will instead add the new key to it. + Inset * inset = cur.nextInset(); + if (!inset || inset->lyxCode() != CITE_CODE) + inset = cur.prevInset(); + if (inset && inset->lyxCode() == CITE_CODE) { + InsetCitation * icite = static_cast(inset); + if (icite->addKey(arg)) { + dr.forceBufferUpdate(); + dr.screenUpdate(Update::FitCursor | Update::SinglePar); + if (!opt1.empty()) + LYXERR0("Discarding optional argument to citation-insert."); + } + dispatched = true; + break; + } InsetCommandParams icp(CITE_CODE); icp["key"] = from_utf8(arg); if (!opt1.empty()) icp["before"] = from_utf8(opt1); - string icstr = InsetCommand::params2string("citation", icp); + string icstr = InsetCommand::params2string(icp); FuncRequest fr(LFUN_INSET_INSERT, icstr); lyx::dispatch(fr); break; @@ -1889,17 +1981,66 @@ bool BufferView::dispatch(FuncRequest const & cmd) cur.recordUndo(); FuncRequest fr(LFUN_INSET_MODIFY, cmd.argument()); inset->dispatch(cur, fr); - processUpdateFlags(Update::SinglePar | Update::FitCursor); + dr.screenUpdate(cur.result().screenUpdate()); + if (cur.result().needBufferUpdate()) + dr.forceBufferUpdate(); + break; + } + + // FIXME: + // The change of language of buffer belongs to the Buffer class. + // We have to do it here because we need a cursor for Undo. + // When Undo::recordUndoBufferParams() is implemented someday + // LFUN_BUFFER_LANGUAGE should be handled by the Buffer class. + case LFUN_BUFFER_LANGUAGE: { + Language const * oldL = buffer_.params().language; + Language const * newL = languages.getLanguage(argument); + if (!newL || oldL == newL) + break; + if (oldL->rightToLeft() == newL->rightToLeft()) { + cur.recordUndoFullBuffer(); + buffer_.changeLanguage(oldL, newL); + cur.setCurrentFont(); + dr.forceBufferUpdate(); + } + break; + } + + case LFUN_FILE_INSERT_PLAINTEXT_PARA: + case LFUN_FILE_INSERT_PLAINTEXT: { + bool const as_paragraph = (act == LFUN_FILE_INSERT_PLAINTEXT_PARA); + string const fname = to_utf8(cmd.argument()); + if (!FileName::isAbsolute(fname)) + dr.setMessage(_("Absolute filename expected.")); + else + insertPlaintextFile(FileName(fname), as_paragraph); break; } default: - dispatched = false; + // OK, so try the Buffer itself... + buffer_.dispatch(cmd, dr); + dispatched = dr.dispatched(); break; } buffer_.undo().endUndoGroup(); - return dispatched; + 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(); + } } @@ -1916,10 +2057,10 @@ docstring const BufferView::requestSelection() LYXERR(Debug::SELECTION, "requestSelection: xsel_cache.set: " << d->xsel_cache_.set); if (!d->xsel_cache_.set || cur.top() != d->xsel_cache_.cursor || - cur.anchor_.top() != d->xsel_cache_.anchor) + cur.realAnchor().top() != d->xsel_cache_.anchor) { d->xsel_cache_.cursor = cur.top(); - d->xsel_cache_.anchor = cur.anchor_.top(); + d->xsel_cache_.anchor = cur.realAnchor().top(); d->xsel_cache_.set = cur.selection(); return cur.selectionAsString(false); } @@ -1984,6 +2125,58 @@ Inset const * BufferView::getCoveringInset(Text const & text, } +void BufferView::updateHoveredInset() const +{ + // Get inset under mouse, if there is one. + int const x = d->mouse_position_cache_.x_; + int const y = d->mouse_position_cache_.y_; + Inset const * covering_inset = getCoveringInset(buffer_.text(), x, y); + + d->clickable_inset_ = covering_inset && covering_inset->clickable(*this, x, y); + + if (covering_inset == d->last_inset_) + // Same inset, no need to do anything... + return; + + bool need_redraw = false; + if (d->last_inset_) { + // Remove the hint on the last hovered inset (if any). + need_redraw |= d->last_inset_->setMouseHover(this, false); + d->last_inset_ = 0; + } + + if (covering_inset && covering_inset->setMouseHover(this, true)) { + need_redraw = true; + // Only the insets that accept the hover state, do + // clear the last_inset_, so only set the last_inset_ + // member if the hovered setting is accepted. + d->last_inset_ = covering_inset; + } + + if (need_redraw) { + LYXERR(Debug::PAINTING, "Mouse hover detected at: (" + << d->mouse_position_cache_.x_ << ", " + << d->mouse_position_cache_.y_ << ")"); + + d->update_strategy_ = DecorationUpdate; + + // This event (moving without mouse click) is not passed further. + // This should be changed if it is further utilized. + buffer_.changed(false); + } +} + + +void BufferView::clearLastInset(Inset * inset) const +{ + if (d->last_inset_ != inset) { + LYXERR0("Wrong last_inset!"); + LATTEST(false); + } + d->last_inset_ = 0; +} + + void BufferView::mouseEventDispatch(FuncRequest const & cmd0) { //lyxerr << "[ cmd0 " << cmd0 << "]" << endl; @@ -1995,49 +2188,38 @@ void BufferView::mouseEventDispatch(FuncRequest const & cmd0) Cursor old = cursor(); Cursor cur(*this); cur.push(buffer_.inset()); - cur.setSelection(d->cursor_.selection()); + cur.selection(d->cursor_.selection()); // Either the inset under the cursor or the // surrounding Text will handle this event. // make sure we stay within the screen... - cmd.y = min(max(cmd.y, -1), height_); - - if (cmd.action == LFUN_MOUSE_MOTION && cmd.button() == mouse_button::none) { - - // Get inset under mouse, if there is one. - Inset const * covering_inset = - getCoveringInset(buffer_.text(), cmd.x, cmd.y); - if (covering_inset == d->last_inset_) - // Same inset, no need to do anything... - return; - - bool need_redraw = false; - // const_cast because of setMouseHover(). - Inset * inset = const_cast(covering_inset); - if (d->last_inset_) - // Remove the hint on the last hovered inset (if any). - need_redraw |= d->last_inset_->setMouseHover(false); - if (inset) - // Highlighted the newly hovered inset (if any). - need_redraw |= inset->setMouseHover(true); - d->last_inset_ = inset; - if (!need_redraw) - return; - - LYXERR(Debug::PAINTING, "Mouse hover detected at: (" - << cmd.x << ", " << cmd.y << ")"); + cmd.set_y(min(max(cmd.y(), -1), height_)); - d->update_strategy_ = DecorationUpdate; + d->mouse_position_cache_.x_ = cmd.x(); + d->mouse_position_cache_.y_ = cmd.y(); - // This event (moving without mouse click) is not passed further. - // This should be changed if it is further utilized. - buffer_.changed(false); + if (cmd.action() == LFUN_MOUSE_MOTION && cmd.button() == mouse_button::none) { + updateHoveredInset(); return; } // Build temporary cursor. - Inset * inset = d->text_metrics_[&buffer_.text()].editXY(cur, cmd.x, cmd.y); + 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() || !inset->contextMenuName().empty() + || inset->lyxCode() == SEPARATOR_CODE) && + cur.nextInset() != inset && cur.prevInset() == inset) + cur.posBackward(); + } else if (cur.inTexted() && cur.pos() + && cur.paragraph().isEnvSeparator(cur.pos() - 1)) { + // Always place cursor in front of a separator inset. + cur.posBackward(); + } // Put anchor at the same position. cur.resetAnchor(); @@ -2056,44 +2238,27 @@ void BufferView::mouseEventDispatch(FuncRequest const & cmd0) if (!inset || !cur.result().dispatched()) cur.dispatch(cmd); - cur.endUndoGroup(); - // Notify left insets if (cur != old) { - old.fixIfBroken(); - bool badcursor = notifyCursorLeavesOrEnters(old, cur); + bool badcursor = old.fixIfBroken() | cur.fixIfBroken(); + badcursor |= notifyCursorLeavesOrEnters(old, cur); if (badcursor) cursor().fixIfBroken(); } - - // Do we have a selection? - theSelection().haveSelection(cursor().selection()); - // If the command has been dispatched, - if (cur.result().dispatched() || cur.result().update()) - processUpdateFlags(cur.result().update()); -} + cur.endUndoGroup(); + // Do we have a selection? + theSelection().haveSelection(cursor().selection()); -void BufferView::lfunScroll(FuncRequest const & cmd) -{ - string const scroll_type = cmd.getArg(0); - int const scroll_step = - (scroll_type == "line") ? d->scrollbarParameters_.single_step - : (scroll_type == "page") ? d->scrollbarParameters_.page_step : 0; - if (scroll_step == 0) - return; - string const scroll_quantity = cmd.getArg(1); - if (scroll_quantity == "up") - scrollUp(scroll_step); - else if (scroll_quantity == "down") - scrollDown(scroll_step); - else { - int const scroll_value = convert(scroll_quantity); - if (scroll_value) - scroll(scroll_step * scroll_value); + if (cur.needBufferUpdate()) { + cur.clearBufferUpdate(); + buffer().updateBuffer(); } - buffer_.changed(true); + + // If the command has been dispatched, + if (cur.result().dispatched() || cur.result().screenUpdate()) + processUpdateFlags(cur.result().screenUpdate()); } @@ -2163,16 +2328,55 @@ int BufferView::scrollUp(int offset) void BufferView::setCursorFromRow(int row) { - int tmpid = -1; - int tmppos = -1; + setCursorFromRow(row, buffer_.texrow()); +} - buffer_.texrow().getIdFromRow(row, tmpid, tmppos); +void BufferView::setCursorFromRow(int row, TexRow const & texrow) +{ + int tmpid; + int tmppos; + pit_type newpit = 0; + pos_type newpos = 0; + + texrow.getIdFromRow(row, tmpid, tmppos); + + bool posvalid = (tmpid != -1); + if (posvalid) { + // we need to make sure that the row and position + // we got back are valid, because the buffer may well + // have changed since we last generated the LaTeX. + DocIterator dit = buffer_.getParFromID(tmpid); + if (dit == doc_iterator_end(&buffer_)) + posvalid = false; + else if (dit.depth() > 1) { + // We are in an inset. + pos_type lastpos = dit.lastpos(); + dit.pos() = tmppos > lastpos ? lastpos : tmppos; + setCursor(dit); + recenter(); + return; + } else { + newpit = dit.pit(); + // now have to check pos. + newpos = tmppos; + Paragraph const & par = buffer_.text().getPar(newpit); + if (newpos > par.size()) { + LYXERR0("Requested position no longer valid."); + newpos = par.size() - 1; + } + } + } + if (!posvalid) { + frontend::Alert::error(_("Inverse Search Failed"), + _("Invalid position requested by inverse search.\n" + "You need to update the viewed document.")); + return; + } d->cursor_.reset(); - if (tmpid == -1) - buffer_.text().setCursor(d->cursor_, 0, 0); - else - buffer_.text().setCursor(d->cursor_, buffer_.getParFromID(tmpid).pit(), tmppos); + buffer_.text().setCursor(d->cursor_, newpit, newpos); + d->cursor_.selection(false); + d->cursor_.resetAnchor(); recenter(); } @@ -2199,18 +2403,18 @@ bool BufferView::setCursorFromInset(Inset const * inset) void BufferView::gotoLabel(docstring const & label) { - std::vector bufs = buffer().allRelatives(); - std::vector::iterator it = bufs.begin(); + ListOfBuffers bufs = buffer().allRelatives(); + ListOfBuffers::iterator it = bufs.begin(); for (; it != bufs.end(); ++it) { Buffer const * buf = *it; // find label - Toc & toc = buf->tocBackend().toc("label"); - TocIterator toc_it = toc.begin(); - TocIterator end = toc.end(); + shared_ptr toc = buf->tocBackend().toc("label"); + Toc::const_iterator toc_it = toc->begin(); + Toc::const_iterator end = toc->end(); for (; toc_it != end; ++toc_it) { if (label == toc_it->str()) { - dispatch(toc_it->action()); + lyx::dispatch(toc_it->action()); return; } } @@ -2226,6 +2430,7 @@ TextMetrics const & BufferView::textMetrics(Text const * t) const TextMetrics & BufferView::textMetrics(Text const * t) { + LBUFERR(t); TextMetricsCache::iterator tmc_it = d->text_metrics_.find(t); if (tmc_it == d->text_metrics_.end()) { tmc_it = d->text_metrics_.insert( @@ -2256,7 +2461,13 @@ void BufferView::setCursor(DocIterator const & dit) dit[i].inset().edit(d->cursor_, true); d->cursor_.setCursor(dit); - d->cursor_.setSelection(false); + d->cursor_.selection(false); + d->cursor_.setCurrentFont(); + // FIXME + // It seems on general grounds as if this is probably needed, but + // it is not yet clear. + // See bug #7394 and r38388. + // d->cursor.resetAnchor(); } @@ -2278,7 +2489,10 @@ bool BufferView::checkDepm(Cursor & cur, Cursor & old) d->cursor_ = cur; - buffer_.updateLabels(); + // we would rather not do this here, but it needs to be done before + // the changed() signal is sent. + buffer_.updateBuffer(); + buffer_.changed(true); return true; } @@ -2286,7 +2500,7 @@ bool BufferView::checkDepm(Cursor & cur, Cursor & old) bool BufferView::mouseSetCursor(Cursor & cur, bool select) { - LASSERT(&cur.bv() == this, /**/); + LASSERT(&cur.bv() == this, return false); if (!select) // this event will clear selection so we save selection for @@ -2294,14 +2508,17 @@ bool BufferView::mouseSetCursor(Cursor & cur, bool select) cap::saveSelection(cursor()); d->cursor_.macroModeClose(); + // If a macro has been finalized, the cursor might have been broken + cur.fixIfBroken(); // Has the cursor just left the inset? - bool leftinset = (&d->cursor_.inset() != &cur.inset()); + bool const leftinset = (&d->cursor_.inset() != &cur.inset()); if (leftinset) d->cursor_.fixIfBroken(); // FIXME: shift-mouse selection doesn't work well across insets. - bool do_selection = select && &d->cursor_.anchor().inset() == &cur.inset(); + bool const do_selection = + select && &d->cursor_.normalAnchor().inset() == &cur.inset(); // do the dEPM magic if needed // FIXME: (1) move this to InsetText::notifyCursorLeaves? @@ -2322,6 +2539,8 @@ bool BufferView::mouseSetCursor(Cursor & cur, bool select) d->cursor_.finishUndo(); d->cursor_.setCurrentFont(); + if (update) + cur.forceBufferUpdate(); return update; } @@ -2340,9 +2559,43 @@ void BufferView::putSelectionAt(DocIterator const & cur, } else d->cursor_.setSelection(d->cursor_, length); } - // Ensure a redraw happens in any case because the new selection could - // possibly be on the same screen as the previous selection. - processUpdateFlags(Update::Force | Update::FitCursor); +} + + +bool BufferView::selectIfEmpty(DocIterator & cur) +{ + if ((cur.inTexted() && !cur.paragraph().empty()) + || (cur.inMathed() && !cur.cell().empty())) + return false; + + pit_type const beg_pit = cur.pit(); + if (beg_pit > 0) { + // The paragraph associated to this item isn't + // the first one, so it can be selected + cur.backwardPos(); + } else { + // We have to resort to select the space between the + // end of this item and the begin of the next one + cur.forwardPos(); + } + if (cur.empty()) { + // If it is the only item in the document, + // nothing can be selected + return false; + } + pit_type const end_pit = cur.pit(); + pos_type const end_pos = cur.pos(); + d->cursor_.clearSelection(); + d->cursor_.reset(); + d->cursor_.setCursor(cur); + d->cursor_.pit() = beg_pit; + d->cursor_.pos() = 0; + d->cursor_.selection(false); + d->cursor_.resetAnchor(); + d->cursor_.pit() = end_pit; + d->cursor_.pos() = end_pos; + d->cursor_.setSelection(); + return true; } @@ -2380,7 +2633,7 @@ bool BufferView::singleParUpdate() // (if this paragraph contains insets etc., rebreaking will // recursively descend) tm.redoParagraph(bottom_pit); - ParagraphMetrics const & pm = tm.parMetrics(bottom_pit); + ParagraphMetrics const & pm = tm.parMetrics(bottom_pit); if (pm.height() != old_height) // Paragraph height has changed so we cannot proceed to // the singlePar optimisation. @@ -2417,7 +2670,7 @@ void BufferView::updateMetrics() // make sure inline completion pointer is ok if (d->inlineCompletionPos_.fixIfBroken()) d->inlineCompletionPos_ = DocIterator(); - + if (d->anchor_pit_ >= npit) // The anchor pit must have been deleted... d->anchor_pit_ = npit - 1; @@ -2425,19 +2678,19 @@ void BufferView::updateMetrics() // Rebreak anchor paragraph. tm.redoParagraph(d->anchor_pit_); ParagraphMetrics & anchor_pm = tm.par_metrics_[d->anchor_pit_]; - + // position anchor if (d->anchor_pit_ == 0) { int scrollRange = d->scrollbarParameters_.max - d->scrollbarParameters_.min; - + // Complete buffer visible? Then it's easy. if (scrollRange == 0) d->anchor_ypos_ = anchor_pm.ascent(); - + // FIXME: Some clever handling needed to show // the _first_ paragraph up to the top if the cursor is // in the first line. - } + } anchor_pm.setPosition(d->anchor_ypos_); LYXERR(Debug::PAINTING, "metrics: " @@ -2489,19 +2742,19 @@ void BufferView::updateMetrics() void BufferView::insertLyXFile(FileName const & fname) { - LASSERT(d->cursor_.inTexted(), /**/); + LASSERT(d->cursor_.inTexted(), return); // Get absolute path of file and add ".lyx" // to the filename if necessary - FileName filename = fileSearch(string(), fname.absFilename(), "lyx"); + FileName filename = fileSearch(string(), fname.absFileName(), "lyx"); - docstring const disp_fn = makeDisplayPath(filename.absFilename()); + docstring const disp_fn = makeDisplayPath(filename.absFileName()); // emit message signal. message(bformat(_("Inserting document %1$s..."), disp_fn)); docstring res; - Buffer buf("", false); - if (buf.loadLyXFile(filename)) { + Buffer buf(filename.absFileName(), false); + if (buf.loadLyXFile() == Buffer::ReadSuccess) { ErrorList & el = buffer_.errorList("Parse"); // Copy the inserted document error list into the current buffer one. el = buf.errorList("Parse"); @@ -2516,34 +2769,33 @@ void BufferView::insertLyXFile(FileName const & fname) buffer_.changed(true); // emit message signal. message(bformat(res, disp_fn)); - buffer_.errors("Parse"); } -Point BufferView::coordOffset(DocIterator const & dit, bool boundary) const +Point BufferView::coordOffset(DocIterator const & dit) const { int x = 0; int y = 0; 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; int yy = 0; - + // get relative position inside sl.inset() - sl.inset().cursorPos(*this, sl, boundary && (i + 1 == dit.depth()), xx, yy); - + sl.inset().cursorPos(*this, sl, dit.boundary() && (i + 1 == dit.depth()), xx, yy); + // Make relative position inside of the edited inset relative to sl.inset() x += xx; y += yy; - + // In case of an RTL inset, the edited inset will be positioned to the left // of xx:yy if (sl.text()) { - bool boundary_i = boundary && i + 1 == dit.depth(); + bool boundary_i = dit.boundary() && i + 1 == dit.depth(); bool rtl = textMetrics(sl.text()).isRTL(sl, boundary_i); if (rtl) x -= lastw; @@ -2557,14 +2809,13 @@ Point BufferView::coordOffset(DocIterator const & dit, bool boundary) const // 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); lastw = dim.wid; } - + //lyxerr << "Cursor::getPos, i: " // << i << " x: " << xx << " y: " << y << endl; } @@ -2573,16 +2824,17 @@ Point BufferView::coordOffset(DocIterator const & dit, bool boundary) const CursorSlice const & sl = dit[0]; TextMetrics const & tm = textMetrics(sl.text()); ParagraphMetrics const & pm = tm.parMetrics(sl.pit()); - LASSERT(!pm.rows().empty(), /**/); + + LBUFERR(!pm.rows().empty()); y -= pm.rows()[0].ascent(); #if 1 // FIXME: document this mess size_t rend; if (sl.pos() > 0 && dit.depth() == 1) { int pos = sl.pos(); - if (pos && boundary) + if (pos && dit.boundary()) --pos; -// lyxerr << "coordOffset: boundary:" << boundary << " depth:" << dit.depth() << " pos:" << pos << " sl.pos:" << sl.pos() << endl; +// lyxerr << "coordOffset: boundary:" << dit.boundary() << " depth:" << dit.depth() << " pos:" << pos << " sl.pos:" << sl.pos() << endl; rend = pm.pos2row(pos); } else rend = pm.pos2row(sl.pos()); @@ -2592,25 +2844,25 @@ Point BufferView::coordOffset(DocIterator const & dit, bool boundary) const for (size_t rit = 0; rit != rend; ++rit) y += pm.rows()[rit].height(); y += pm.rows()[rend].ascent(); - + TextMetrics const & bottom_tm = textMetrics(dit.bottom().text()); - + // Make relative position from the nested inset now bufferview absolute. - int xx = bottom_tm.cursorX(dit.bottom(), boundary && dit.depth() == 1); + int xx = bottom_tm.cursorX(dit.bottom(), dit.boundary() && dit.depth() == 1); x += xx; - - // In the RTL case place the nested inset at the left of the cursor in + + // In the RTL case place the nested inset at the left of the cursor in // the outer paragraph - bool boundary_1 = boundary && 1 == dit.depth(); + bool boundary_1 = dit.boundary() && 1 == dit.depth(); bool rtl = bottom_tm.isRTL(dit.bottom(), boundary_1); if (rtl) x -= lastw; - + return Point(x, y); } -Point BufferView::getPos(DocIterator const & dit, bool boundary) const +Point BufferView::getPos(DocIterator const & dit) const { if (!paragraphVisible(dit)) return Point(-1, -1); @@ -2618,7 +2870,8 @@ Point BufferView::getPos(DocIterator const & dit, bool boundary) const CursorSlice const & bot = dit.bottom(); TextMetrics const & tm = textMetrics(bot.text()); - Point p = coordOffset(dit, boundary); // offset from outer paragraph + // offset from outer paragraph + Point p = coordOffset(dit); p.y_ += tm.parMetrics(bot.pit()).position(); return p; } @@ -2641,7 +2894,7 @@ void BufferView::cursorPosAndHeight(Point & p, int & h) const int const asc = fm.maxAscent(); int const des = fm.maxDescent(); h = asc + des; - p = getPos(cur, cur.boundary()); + p = getPos(cur); p.y_ -= asc; } @@ -2656,6 +2909,145 @@ bool BufferView::cursorInView(Point const & p, int h) const } +int BufferView::horizScrollOffset() const +{ + return d->horiz_scroll_offset_; +} + + +int BufferView::horizScrollOffset(Text const * text, + pit_type pit, pos_type pos) const +{ + // Is this a row that is currently scrolled? + if (!d->current_row_slice_.empty() + && &text->inset() == d->current_row_slice_.inset().asInsetText() + && pit == d->current_row_slice_.pit() + && pos == d->current_row_slice_.pos()) + return d->horiz_scroll_offset_; + return 0; +} + + +bool BufferView::hadHorizScrollOffset(Text const * text, + pit_type pit, pos_type pos) const +{ + return !d->last_row_slice_.empty() + && &text->inset() == d->last_row_slice_.inset().asInsetText() + && pit == d->last_row_slice_.pit() + && pos == d->last_row_slice_.pos(); +} + + +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(PainterInfo & pi) +{ + 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); + + // If insets referred to by cursor are not all in the cache, the positions + // need to be recomputed. + if (!d->cursor_.inCoordCache()) { + /** FIXME: the code below adds an extraneous computation of + * inset positions, and can therefore be bad for performance + * (think for example about a very large tabular inset. + * Redawing the row where it is means redrawing the whole + * screen). + * + * The bug that this fixes is the following: assume that there + * is a very large math inset. Upon entering the inset, when + * pressing `End', the row is not scrolled and the cursor is + * not visible. The extra row computation makes sure that the + * inset positions are correctly computed and set in the + * cache. This would not happen if we did not have two-stage + * drawing. + * + * A proper fix would be to always have proper inset positions + * at this point. + */ + // Force the recomputation of inset positions + bool const drawing = pi.pain.isDrawingEnabled(); + pi.pain.setDrawingEnabled(false); + // No need to care about vertical position. + RowPainter rp(pi, buffer().text(), row, -d->horiz_scroll_offset_, 0); + rp.paintText(); + pi.pain.setDrawingEnabled(drawing); + } + + // Current x position of the cursor in pixels + int 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() + + row.right_margin; + if (row.right_x() <= workWidth() - row.right_margin) { + // Row is narrower than the work area, no offset needed. + offset = 0; + } else { + if (cur_x - offset < MARGIN) { + // cursor would be too far right + offset = cur_x - MARGIN; + } else if (cur_x - offset > workWidth() - MARGIN) { + // cursor would be too far left + offset = cur_x - workWidth() + MARGIN; + } + // Correct the offset to make sure that we do not scroll too much + if (offset < 0) + offset = 0; + if (row.right_x() - offset < workWidth() - row.right_margin) + offset = row.right_x() - workWidth() + row.right_margin; + } + + //lyxerr << "cur_x=" << cur_x << ", offset=" << offset << ", row.wid=" << row.width() << ", margin=" << MARGIN << endl; + + 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) @@ -2667,22 +3059,28 @@ void BufferView::draw(frontend::Painter & pain) 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(pi); + 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: @@ -2691,6 +3089,12 @@ void BufferView::draw(frontend::Painter & pain) // 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; @@ -2704,9 +3108,9 @@ void BufferView::draw(frontend::Painter & pain) // and possibly grey out below pair lastpm = tm.last(); int const y2 = lastpm.second->position() + lastpm.second->descent(); - + if (y2 < height_) { - Color color = buffer().isInternal() + Color color = buffer().isInternal() ? Color_background : Color_bottomarea; pain.fillRectangle(0, y2, width_, height_ - y2, color); } @@ -2773,7 +3177,7 @@ docstring BufferView::contentsOfPlaintextFile(FileName const & fname) { if (!fname.isReadableFile()) { docstring const error = from_ascii(strerror(errno)); - docstring const file = makeDisplayPath(fname.absFilename(), 50); + docstring const file = makeDisplayPath(fname.absFileName(), 50); docstring const text = bformat(_("Could not read the specified document\n" "%1$s\ndue to the error: %2$s"), file, error); @@ -2782,7 +3186,7 @@ docstring BufferView::contentsOfPlaintextFile(FileName const & fname) } if (!fname.isReadableFile()) { - docstring const file = makeDisplayPath(fname.absFilename(), 50); + docstring const file = makeDisplayPath(fname.absFileName(), 50); docstring const text = bformat(_("%1$s\n is not readable."), file); Alert::error(_("Could not open file"), text); @@ -2842,6 +3246,12 @@ DocIterator const & BufferView::inlineCompletionPos() const } +void BufferView::resetInlineCompletionPos() +{ + d->inlineCompletionPos_ = DocIterator(); +} + + bool samePar(DocIterator const & a, DocIterator const & b) { if (a.empty() && b.empty()) @@ -2854,7 +3264,7 @@ bool samePar(DocIterator const & a, DocIterator const & b) } -void BufferView::setInlineCompletion(Cursor & cur, DocIterator const & pos, +void BufferView::setInlineCompletion(Cursor const & cur, DocIterator const & pos, docstring const & completion, size_t uniqueChars) { uniqueChars = min(completion.size(), uniqueChars); @@ -2863,9 +3273,9 @@ void BufferView::setInlineCompletion(Cursor & cur, DocIterator const & pos, bool singlePar = true; d->inlineCompletion_ = completion; d->inlineCompletionUniqueChars_ = min(completion.size(), uniqueChars); - + //lyxerr << "setInlineCompletion pos=" << pos << " completion=" << completion << " uniqueChars=" << uniqueChars << std::endl; - + // at new position? DocIterator const & old = d->inlineCompletionPos_; if (old != pos) { @@ -2878,14 +3288,20 @@ void BufferView::setInlineCompletion(Cursor & cur, DocIterator const & pos, } d->inlineCompletionPos_ = pos; } - + // set update flags if (changed) { - if (singlePar && !(cur.disp_.update() & Update::Force)) - cur.updateFlags(cur.disp_.update() | Update::SinglePar); + if (singlePar && !(cur.result().screenUpdate() & Update::Force)) + cur.screenUpdateFlags(cur.result().screenUpdate() | Update::SinglePar); else - cur.updateFlags(cur.disp_.update() | Update::Force); + cur.screenUpdateFlags(cur.result().screenUpdate() | Update::Force); } } + +bool BufferView::clickableInset() const +{ + return d->clickable_inset_; +} + } // namespace lyx