X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;ds=sidebyside;f=src%2FBufferView.cpp;h=6089756f9bbae041d28cc8322a1b4ff34712a579;hb=d1e3d75da226311cb290fc4be3686d6deef7b967;hp=dbe396c0704ab5c678757c3ca2ab37f037497e72;hpb=d8a6b5bfd0baa02a4ba03f8c9e9c618baf41b03f;p=lyx.git diff --git a/src/BufferView.cpp b/src/BufferView.cpp index dbe396c070..6089756f9b 100644 --- a/src/BufferView.cpp +++ b/src/BufferView.cpp @@ -4,10 +4,10 @@ * Licence details can be found in the file COPYING. * * \author Alfredo Braunstein - * \author Lars Gullik Bjønnes + * \author Lars Gullik Bjønnes * \author John Levon - * \author André Pönitz - * \author Jürgen Vigna + * \author André Pönitz + * \author Jürgen Vigna * * Full author contact details are available in file CREDITS. */ @@ -16,6 +16,7 @@ #include "BufferView.h" +#include "BranchList.h" #include "Buffer.h" #include "buffer_funcs.h" #include "BufferList.h" @@ -24,7 +25,6 @@ #include "Cursor.h" #include "CutAndPaste.h" #include "DispatchResult.h" -#include "EmbeddedFiles.h" #include "ErrorList.h" #include "factory.h" #include "FloatList.h" @@ -49,13 +49,17 @@ #include "TextClass.h" #include "TextMetrics.h" #include "TexRow.h" +#include "TocBackend.h" #include "VSpace.h" #include "WordLangTuple.h" #include "insets/InsetBibtex.h" #include "insets/InsetCommand.h" // ChangeRefs +#include "insets/InsetExternal.h" +#include "insets/InsetGraphics.h" #include "insets/InsetRef.h" #include "insets/InsetText.h" +#include "insets/InsetNote.h" #include "frontends/alert.h" #include "frontends/Application.h" @@ -146,11 +150,13 @@ bool findInset(DocIterator & dit, vector const & codes, if (!findNextInset(tmpdit, codes, contents)) { if (dit.depth() != 1 || dit.pit() != 0 || dit.pos() != 0) { - tmpdit = doc_iterator_begin(tmpdit.bottom().inset()); + Inset * inset = &tmpdit.bottom().inset(); + tmpdit = doc_iterator_begin(&inset->buffer(), inset); if (!findNextInset(tmpdit, codes, contents)) return false; - } else + } else { return false; + } } dit = tmpdit; @@ -211,7 +217,8 @@ struct BufferView::Private { Private(BufferView & bv): wh_(0), cursor_(bv), anchor_pit_(0), anchor_ypos_(0), - last_inset_(0), gui_(0) + inlineCompletionUniqueChars_(0), + last_inset_(0), bookmark_edit_position_(0), gui_(0) {} /// @@ -239,11 +246,11 @@ struct BufferView::Private vector par_height_; /// - DocIterator inlineCompletionPos; + DocIterator inlineCompletionPos_; /// - docstring inlineCompletion; + docstring inlineCompletion_; /// - size_t inlineCompletionUniqueChars; + size_t inlineCompletionUniqueChars_; /// keyboard mapping object. Intl intl_; @@ -254,27 +261,35 @@ struct BufferView::Private */ Inset * last_inset_; + // cache for id of the paragraph which was edited the last time + int bookmark_edit_position_; + mutable TextMetricsCache text_metrics_; /// Whom to notify. /** Not owned, so don't delete. */ frontend::GuiBufferViewDelegate * gui_; + + /// Cache for Find Next + FuncRequest search_request_cache_; }; BufferView::BufferView(Buffer & buf) - : width_(0), height_(0), full_screen_(false), buffer_(buf), d(new Private(*this)) + : width_(0), height_(0), full_screen_(false), buffer_(buf), + d(new Private(*this)) { d->xsel_cache_.set = false; d->intl_.initKeyMapper(lyxrc.use_kbmap); + d->cursor_.setBuffer(&buf); d->cursor_.push(buffer_.inset()); d->cursor_.resetAnchor(); d->cursor_.setCurrentFont(); if (graphics::Previews::status() != LyXRC::PREVIEW_OFF) - graphics::Previews::get().generateBufferPreviews(buffer_); + thePreviews().generateBufferPreviews(buffer_); } @@ -288,7 +303,7 @@ BufferView::~BufferView() LastFilePosSection::FilePos fp; fp.pit = d->cursor_.bottom().pit(); fp.pos = d->cursor_.bottom().pos(); - LyX::ref().session().lastFilePos().save(buffer_.fileName(), fp); + theSession().lastFilePos().save(buffer_.fileName(), fp); delete d; } @@ -312,6 +327,18 @@ int BufferView::leftMargin() const } +bool BufferView::isTopScreen() const +{ + return d->scrollbarParameters_.position == d->scrollbarParameters_.min; +} + + +bool BufferView::isBottomScreen() const +{ + return d->scrollbarParameters_.position == d->scrollbarParameters_.max; +} + + Intl & BufferView::getIntl() { return d->intl_; @@ -438,7 +465,7 @@ void BufferView::processUpdateFlags(Update::flags flags) void BufferView::updateScrollbar() { - if (height_ == 0) + if (height_ == 0 && width_ == 0) return; // We prefer fixed size line scrolling. @@ -490,7 +517,10 @@ void BufferView::updateScrollbar() d->scrollbarParameters_.position = 0; // The reference is the top position so we remove one page. - d->scrollbarParameters_.max -= d->scrollbarParameters_.page_step; + if (lyxrc.scroll_below_document) + d->scrollbarParameters_.max -= minVisiblePart(); + else + d->scrollbarParameters_.max -= d->scrollbarParameters_.page_step; } @@ -513,28 +543,38 @@ docstring BufferView::toolTip(int x, int y) const docstring BufferView::contextMenu(int x, int y) const { + //If there is a selection, return the containing inset menu + if (d->cursor_.selection()) + return d->cursor_.inset().contextMenu(*this, x, y); + // Get inset under mouse, if there is one. Inset const * covering_inset = getCoveringInset(buffer_.text(), x, y); if (covering_inset) return covering_inset->contextMenu(*this, x, y); - // FIXME: Do something more elaborate here. - return from_ascii("edit"); + return buffer_.inset().contextMenu(*this, x, y); } void BufferView::scrollDocView(int value) { int const offset = value - d->scrollbarParameters_.position; + + // No scrolling at all? No need to redraw anything + if (offset == 0) + return; + // If the offset is less than 2 screen height, prefer to scroll instead. if (abs(offset) <= 2 * height_) { - scroll(offset); + d->anchor_ypos_ -= offset; + updateMetrics(); + buffer_.changed(); return; } // cut off at the top if (value <= d->scrollbarParameters_.min) { - DocIterator dit = doc_iterator_begin(buffer_.inset()); + DocIterator dit = doc_iterator_begin(&buffer_); showCursor(dit); LYXERR(Debug::SCROLLING, "scroll to top"); return; @@ -542,7 +582,7 @@ void BufferView::scrollDocView(int value) // cut off at the bottom if (value >= d->scrollbarParameters_.max) { - DocIterator dit = doc_iterator_end(buffer_.inset()); + DocIterator dit = doc_iterator_end(&buffer_); dit.backwardPos(); showCursor(dit); LYXERR(Debug::SCROLLING, "scroll to bottom"); @@ -566,7 +606,7 @@ void BufferView::scrollDocView(int value) return; } - DocIterator dit = doc_iterator_begin(buffer_.inset()); + DocIterator dit = doc_iterator_begin(&buffer_); dit.pit() = i; LYXERR(Debug::SCROLLING, "value = " << value << " -> scroll to pit " << i); showCursor(dit); @@ -581,31 +621,35 @@ void BufferView::setCursorFromScrollbar() int const height = 2 * defaultRowHeight(); int const first = height; int const last = height_ - height; - Cursor & cur = d->cursor_; + int newy = 0; + Cursor const & oldcur = d->cursor_; - switch (cursorStatus(cur)) { + switch (cursorStatus(oldcur)) { case CUR_ABOVE: - // We reset the cursor because cursorStatus() does not - // work when the cursor is within mathed. - cur.reset(buffer_.inset()); - tm.setCursorFromCoordinates(cur, 0, first); - cur.clearSelection(); + newy = first; break; case CUR_BELOW: - // We reset the cursor because cursorStatus() does not - // work when the cursor is within mathed. - cur.reset(buffer_.inset()); - tm.setCursorFromCoordinates(cur, 0, last); - cur.clearSelection(); + newy = last; break; case CUR_INSIDE: - int const y = getPos(cur, cur.boundary()).y_; - int const newy = min(last, max(y, first)); - if (y != newy) { - cur.reset(buffer_.inset()); - tm.setCursorFromCoordinates(cur, 0, newy); - } + int const y = getPos(oldcur, oldcur.boundary()).y_; + newy = min(last, max(y, first)); + if (y == newy) + return; } + // We reset the cursor because cursorStatus() does not + // work when the cursor is within mathed. + Cursor cur(*this); + cur.reset(buffer_.inset()); + tm.setCursorFromCoordinates(cur, 0, newy); + + // update the bufferview cursor and notify insets + // FIXME: Care about the d->cursor_ flags to redraw if needed + Cursor old = d->cursor_; + mouseSetCursor(cur); + bool badcursor = notifyCursorLeavesOrEnters(old, d->cursor_); + if (badcursor) + d->cursor_.fixIfBroken(); } @@ -632,13 +676,23 @@ CursorStatus BufferView::cursorStatus(DocIterator const & dit) const } +void BufferView::bookmarkEditPosition() +{ + // Don't eat cpu time for each keystroke + if (d->cursor_.paragraph().id() == d->bookmark_edit_position_) + return; + saveBookmark(0); + d->bookmark_edit_position_ = d->cursor_.paragraph().id(); +} + + void BufferView::saveBookmark(unsigned int idx) { - // tenatively save bookmark, id and pos will be used to + // tentatively save bookmark, id and pos will be used to // acturately locate a bookmark in a 'live' lyx session. // pit and pos will be updated with bottom level pit/pos // when lyx exits. - LyX::ref().session().bookmarks().save( + theSession().bookmarks().save( buffer_.fileName(), d->cursor_.bottom().pit(), d->cursor_.bottom().pos(), @@ -675,7 +729,7 @@ bool BufferView::moveToPosition(pit_type bottom_pit, pos_type bottom_pos, // insets. size_t const n = dit.depth(); for (size_t i = 0; i < n; ++i) - if (dit[i].inset().editable() != Inset::HIGHLY_EDITABLE) { + if (!dit[i].inset().editable()) { dit.resize(i); break; } @@ -690,7 +744,7 @@ bool BufferView::moveToPosition(pit_type bottom_pit, pos_type bottom_pos, // it will be restored to the left of the outmost inset that contains // the bookmark. if (bottom_pit < int(buffer_.paragraphs().size())) { - dit = doc_iterator_begin(buffer_.inset()); + dit = doc_iterator_begin(&buffer_); dit.pit() = bottom_pit; dit.pos() = min(bottom_pos, dit.paragraph().size()); @@ -736,18 +790,37 @@ int BufferView::workWidth() const } +void BufferView::recenter() +{ + showCursor(d->cursor_, true); +} + + void BufferView::showCursor() { - showCursor(d->cursor_); + showCursor(d->cursor_, false); +} + + +void BufferView::showCursor(DocIterator const & dit, bool recenter) +{ + if (scrollToCursor(dit, recenter)) + buffer_.changed(); } -void BufferView::showCursor(DocIterator const & dit) +void BufferView::scrollToCursor() +{ + scrollToCursor(d->cursor_, false); +} + + +bool BufferView::scrollToCursor(DocIterator const & dit, bool recenter) { // We are not properly started yet, delay until resizing is // done. if (height_ == 0) - return; + return false; LYXERR(Debug::SCROLLING, "recentering!"); @@ -770,24 +843,32 @@ void BufferView::showCursor(DocIterator const & dit) if (tm.contains(bot_pit)) { ParagraphMetrics const & pm = tm.parMetrics(bot_pit); - BOOST_ASSERT(!pm.rows().empty()); + LASSERT(!pm.rows().empty(), /**/); // FIXME: smooth scrolling doesn't work in mathed. CursorSlice const & cs = dit.innerTextSlice(); int offset = coordOffset(dit, dit.boundary()).y_; int ypos = pm.position() + offset; Dimension const & row_dim = pm.getRow(cs.pos(), dit.boundary()).dimension(); - if (ypos - row_dim.ascent() < 0) - scrollUp(- ypos + row_dim.ascent()); + 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_) - scrollDown(ypos - height_ + row_dim.descent()); + scrolled = scrollDown(ypos - height_ + defaultRowHeight() ); + // else, nothing to do, the cursor is already visible so we just return. - return; + if (scrolled != 0) { + updateMetrics(); + return true; + } + return false; } // fix inline completion position - if (d->inlineCompletionPos.fixIfBroken()) - d->inlineCompletionPos = DocIterator(); + if (d->inlineCompletionPos_.fixIfBroken()) + d->inlineCompletionPos_ = DocIterator(); tm.redoParagraph(bot_pit); ParagraphMetrics const & pm = tm.parMetrics(bot_pit); @@ -798,83 +879,117 @@ void BufferView::showCursor(DocIterator const & dit) Dimension const & row_dim = pm.getRow(cs.pos(), dit.boundary()).dimension(); - if (d->anchor_pit_ == 0) + if (recenter) + d->anchor_ypos_ = height_/2; + else if (d->anchor_pit_ == 0) d->anchor_ypos_ = offset + pm.ascent(); else if (d->anchor_pit_ == max_pit) d->anchor_ypos_ = height_ - offset - row_dim.descent(); + else if (offset > height_) + d->anchor_ypos_ = height_ - offset - defaultRowHeight(); else - d->anchor_ypos_ = defaultRowHeight() * 2 - offset - row_dim.descent(); + d->anchor_ypos_ = defaultRowHeight() * 2; updateMetrics(); - buffer_.changed(); + return true; } -FuncStatus BufferView::getStatus(FuncRequest const & cmd) +bool BufferView::getStatus(FuncRequest const & cmd, FuncStatus & flag) { - FuncStatus flag; - Cursor & cur = d->cursor_; switch (cmd.action) { case LFUN_UNDO: - flag.enabled(buffer_.undo().hasUndoStack()); + flag.setEnabled(buffer_.undo().hasUndoStack()); break; case LFUN_REDO: - flag.enabled(buffer_.undo().hasRedoStack()); + flag.setEnabled(buffer_.undo().hasRedoStack()); break; case LFUN_FILE_INSERT: case LFUN_FILE_INSERT_PLAINTEXT_PARA: case LFUN_FILE_INSERT_PLAINTEXT: case LFUN_BOOKMARK_SAVE: // FIXME: Actually, these LFUNS should be moved to Text - flag.enabled(cur.inTexted()); + flag.setEnabled(cur.inTexted()); break; + 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_FINDADV: case LFUN_WORD_REPLACE: case LFUN_MARK_OFF: case LFUN_MARK_ON: case LFUN_MARK_TOGGLE: case LFUN_SCREEN_RECENTER: + 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_NEXT_INSET_TOGGLE: - flag.enabled(true); + flag.setEnabled(true); + break; + + // @todo Test if current WorkArea is the search WorkArea + case LFUN_REGEXP_MODE: + flag.setEnabled(! this->cursor().inRegexped()); + break; + + case LFUN_COPY_LABEL_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); break; + } case LFUN_LABEL_GOTO: { - flag.enabled(!cmd.argument().empty() + flag.setEnabled(!cmd.argument().empty() || getInsetByCode(cur, REF_CODE)); break; } case LFUN_CHANGES_TRACK: - flag.enabled(true); + flag.setEnabled(true); flag.setOnOff(buffer_.params().trackChanges); break; case LFUN_CHANGES_OUTPUT: - flag.enabled(true); + 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.enabled(true); + flag.setEnabled(true); break; case LFUN_BUFFER_TOGGLE_COMPRESSION: { @@ -882,83 +997,39 @@ FuncStatus BufferView::getStatus(FuncRequest const & cmd) break; } - case LFUN_BUFFER_TOGGLE_EMBEDDING: { - flag.setOnOff(buffer_.params().embedded); - break; - } - case LFUN_SCREEN_UP: case LFUN_SCREEN_DOWN: case LFUN_SCROLL: - flag.enabled(true); - break; - - // FIXME: LFUN_SCREEN_DOWN_SELECT should be removed from - // everywhere else before this can enabled: case LFUN_SCREEN_UP_SELECT: case LFUN_SCREEN_DOWN_SELECT: - flag.enabled(false); + flag.setEnabled(true); break; case LFUN_LAYOUT_TABULAR: - flag.enabled(cur.innerInsetOfType(TABULAR_CODE)); + flag.setEnabled(cur.innerInsetOfType(TABULAR_CODE)); break; case LFUN_LAYOUT: - case LFUN_LAYOUT_PARAGRAPH: - flag.enabled(cur.inset().allowParagraphCustomization(cur.idx())); + flag.setEnabled(!cur.inset().forcePlainLayout(cur.idx())); break; - case LFUN_INSET_SETTINGS: { - InsetCode code = cur.inset().lyxCode(); - bool enable = false; - switch (code) { - case TABULAR_CODE: - enable = cmd.argument() == "tabular"; - break; - case ERT_CODE: - enable = cmd.argument() == "ert"; - break; - case FLOAT_CODE: - enable = cmd.argument() == "float"; - break; - case WRAP_CODE: - enable = cmd.argument() == "wrap"; - break; - case NOTE_CODE: - enable = cmd.argument() == "note"; - break; - case BRANCH_CODE: - enable = cmd.argument() == "branch"; - break; - case BOX_CODE: - enable = cmd.argument() == "box"; - break; - case LISTINGS_CODE: - enable = cmd.argument() == "listings"; - break; - default: - break; - } - flag.enabled(enable); + case LFUN_LAYOUT_PARAGRAPH: + flag.setEnabled(cur.inset().allowParagraphCustomization(cur.idx())); break; - } case LFUN_DIALOG_SHOW_NEW_INSET: - flag.enabled(cur.inset().lyxCode() != ERT_CODE && + if (cur.inset().lyxCode() == CAPTION_CODE) + return cur.inset().getStatus(cur, cmd, flag); + flag.setEnabled(cur.inset().lyxCode() != ERT_CODE && cur.inset().lyxCode() != LISTINGS_CODE); - if (cur.inset().lyxCode() == CAPTION_CODE) { - FuncStatus flag; - if (cur.inset().getStatus(cur, cmd, flag)) - return flag; - } break; default: - flag.enabled(false); + flag.setEnabled(false); + return false; } - return flag; + return true; } @@ -1007,22 +1078,40 @@ bool BufferView::dispatch(FuncRequest const & cmd) docstring label = cmd.argument(); if (label.empty()) { InsetRef * inset = - getInsetByCode(d->cursor_, - REF_CODE); + getInsetByCode(cur, REF_CODE); if (inset) { label = inset->getParam("reference"); // persistent=false: use temp_bookmark 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; + break; + } case LFUN_PARAGRAPH_GOTO: { - int const id = convert(to_utf8(cmd.argument())); + int const id = convert(cmd.getArg(0)); + int const pos = convert(cmd.getArg(1)); int i = 0; for (Buffer * b = &buffer_; i == 0 || b != &buffer_; b = theBufferList().next(b)) { @@ -1039,6 +1128,7 @@ bool BufferView::dispatch(FuncRequest const & cmd) if (b == &buffer_) { // Set the cursor + dit.pos() = pos; setCursor(dit); processUpdateFlags(Update::Force | Update::FitCursor); } else { @@ -1072,19 +1162,19 @@ bool BufferView::dispatch(FuncRequest const & cmd) buffer_.params().outputChanges = !buffer_.params().outputChanges; if (buffer_.params().outputChanges) { bool dvipost = LaTeXFeatures::isAvailable("dvipost"); - bool xcolorsoul = LaTeXFeatures::isAvailable("soul") && + bool xcolorulem = LaTeXFeatures::isAvailable("ulem") && LaTeXFeatures::isAvailable("xcolor"); - if (!dvipost && !xcolorsoul) { + if (!dvipost && !xcolorulem) { Alert::warning(_("Changes not shown in LaTeX output"), _("Changes will not be highlighted in LaTeX output, " - "because neither dvipost nor xcolor/soul are installed.\n" + "because neither dvipost nor xcolor/ulem are installed.\n" "Please install these packages or redefine " "\\lyxadded and \\lyxdeleted in the LaTeX preamble.")); - } else if (!xcolorsoul) { + } 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 soul are not installed.\n" + "when using pdflatex, because xcolor and ulem are not installed.\n" "Please install both packages or redefine " "\\lyxadded and \\lyxdeleted in the LaTeX preamble.")); } @@ -1093,38 +1183,61 @@ bool BufferView::dispatch(FuncRequest const & cmd) 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); + 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); break; case LFUN_CHANGES_MERGE: - if (findNextChange(this)) + if (findNextChange(this) || findPreviousChange(this)) { + processUpdateFlags(Update::Force | Update::FitCursor); showDialog("changes"); + } break; case LFUN_ALL_CHANGES_ACCEPT: // select complete document - d->cursor_.reset(buffer_.inset()); - d->cursor_.selHandle(true); - buffer_.text().cursorBottom(d->cursor_); + cur.reset(buffer_.inset()); + cur.selHandle(true); + buffer_.text().cursorBottom(cur); // accept everything in a single step to support atomic undo - buffer_.text().acceptOrRejectChanges(d->cursor_, Text::ACCEPT); + buffer_.text().acceptOrRejectChanges(cur, Text::ACCEPT); + // FIXME: Move this LFUN to Buffer so that we don't have to do this: + processUpdateFlags(Update::Force | Update::FitCursor); break; case LFUN_ALL_CHANGES_REJECT: // select complete document - d->cursor_.reset(buffer_.inset()); - d->cursor_.selHandle(true); - buffer_.text().cursorBottom(d->cursor_); + cur.reset(buffer_.inset()); + cur.selHandle(true); + buffer_.text().cursorBottom(cur); // 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(d->cursor_, Text::REJECT); + buffer_.text().acceptOrRejectChanges(cur, Text::REJECT); + // FIXME: Move this LFUN to Buffer so that we don't have to do this: + processUpdateFlags(Update::Force | Update::FitCursor); break; - case LFUN_WORD_FIND: - if (find(this, cmd)) + case LFUN_WORD_FIND: { + FuncRequest req = 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")); + break; + } + if (find(this, req)) showCursor(); else message(_("String not found!")); + d->search_request_cache_ = req; break; + } case LFUN_WORD_REPLACE: { bool has_deleted = false; @@ -1142,54 +1255,60 @@ bool BufferView::dispatch(FuncRequest const & cmd) break; } + case LFUN_WORD_FINDADV: + findAdv(this, cmd); + break; + case LFUN_MARK_OFF: cur.clearSelection(); - cur.resetAnchor(); cur.message(from_utf8(N_("Mark off"))); break; case LFUN_MARK_ON: cur.clearSelection(); - cur.mark() = true; - cur.resetAnchor(); + cur.setMark(true); cur.message(from_utf8(N_("Mark on"))); break; case LFUN_MARK_TOGGLE: - cur.clearSelection(); + cur.setSelection(false); if (cur.mark()) { - cur.mark() = false; + cur.setMark(false); cur.message(from_utf8(N_("Mark removed"))); } else { - cur.mark() = true; + cur.setMark(true); cur.message(from_utf8(N_("Mark set"))); } cur.resetAnchor(); break; - case LFUN_SCREEN_RECENTER: + case LFUN_SCREEN_SHOW_CURSOR: showCursor(); break; + + case LFUN_SCREEN_RECENTER: + recenter(); + break; case LFUN_BIBTEX_DATABASE_ADD: { - Cursor tmpcur = d->cursor_; + Cursor tmpcur = cur; findInset(tmpcur, BIBTEX_CODE, false); InsetBibtex * inset = getInsetByCode(tmpcur, BIBTEX_CODE); if (inset) { - if (inset->addDatabase(to_utf8(cmd.argument()))) + if (inset->addDatabase(cmd.argument())) buffer_.updateBibfilesCache(); } break; } case LFUN_BIBTEX_DATABASE_DEL: { - Cursor tmpcur = d->cursor_; + Cursor tmpcur = cur; findInset(tmpcur, BIBTEX_CODE, false); InsetBibtex * inset = getInsetByCode(tmpcur, BIBTEX_CODE); if (inset) { - if (inset->delDatabase(to_utf8(cmd.argument()))) + if (inset->delDatabase(cmd.argument())) buffer_.updateBibfilesCache(); } break; @@ -1201,8 +1320,8 @@ bool BufferView::dispatch(FuncRequest const & cmd) from = cur.selectionBegin(); to = cur.selectionEnd(); } else { - from = doc_iterator_begin(buffer_.inset()); - to = doc_iterator_end(buffer_.inset()); + from = doc_iterator_begin(&buffer_); + to = doc_iterator_end(&buffer_); } int const words = countWords(from, to); int const chars = countChars(from, to, false); @@ -1238,44 +1357,42 @@ bool BufferView::dispatch(FuncRequest const & cmd) // turn compression on/off buffer_.params().compressed = !buffer_.params().compressed; break; - - case LFUN_BUFFER_TOGGLE_EMBEDDING: { - // turn embedding on/off - try { - buffer_.embeddedFiles().enable(!buffer_.params().embedded, buffer_); - } catch (ExceptionMessage const & message) { - Alert::error(message.title_, message.details_); + case LFUN_COPY_LABEL_AS_REF: { + // if there is an inset at cursor, try to copy it + Inset * 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_TOGGLE: { - // this is the real function we want to invoke - FuncRequest tmpcmd = FuncRequest(LFUN_INSET_TOGGLE, cmd.origin); + 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 - // wants to toggle. + // can be modified. Inset * inset = cur.nextInset(); if (inset) { - if (inset->isActive()) { - Cursor tmpcur = cur; - tmpcur.pushBackward(*inset); - inset->dispatch(tmpcur, tmpcmd); - if (tmpcur.result().dispatched()) { - cur.dispatched(); - } - } else if (inset->editable() == Inset::IS_EDITABLE) { - inset->edit(cur, true); - } + cur.recordUndo(); + inset->dispatch(cur, tmpcmd); } // if it did not work, try the underlying inset. - if (!cur.result().dispatched()) + 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::SinglePar | Update::FitCursor); + processUpdateFlags(Update::Force | Update::FitCursor); break; } @@ -1287,11 +1404,23 @@ bool BufferView::dispatch(FuncRequest const & cmd) showCursor(); p = getPos(cur, cur.boundary()); } - scroll(cmd.action == LFUN_SCREEN_UP? - height_ : height_); + int const scrolled = scroll(cmd.action == LFUN_SCREEN_UP + ? - height_ : height_); + if (cmd.action == LFUN_SCREEN_UP && scrolled > - height_) + p = Point(0, 0); + if (cmd.action == LFUN_SCREEN_DOWN && scrolled < height_) + p = Point(width_, height_); + Cursor old = cur; + bool const in_texted = cur.inTexted(); cur.reset(buffer_.inset()); + updateMetrics(); + buffer_.changed(); d->text_metrics_[&buffer_.text()].editXY(cur, p.x_, p.y_); //FIXME: what to do with cur.x_target()? + bool update = in_texted && cur.bv().checkDepm(cur, old); cur.finishUndo(); + if (update) + processUpdateFlags(Update::Force | Update::FitCursor); break; } @@ -1299,26 +1428,72 @@ bool BufferView::dispatch(FuncRequest const & cmd) lfunScroll(cmd); break; - case LFUN_SCREEN_UP_SELECT: - case LFUN_SCREEN_DOWN_SELECT: { - // Those two are not ready yet for consumption. - return false; + case LFUN_SCREEN_UP_SELECT: { + cur.selHandle(true); + if (isTopScreen()) { + lyx::dispatch(FuncRequest(LFUN_BUFFER_BEGIN_SELECT)); + cur.finishUndo(); + break; + } + int y = getPos(cur, cur.boundary()).y_; + int const ymin = y - height_ + defaultRowHeight(); + while (y > ymin && cur.up()) + y = getPos(cur, cur.boundary()).y_; + cur.finishUndo(); + processUpdateFlags(Update::SinglePar | Update::FitCursor); + break; + } + + case LFUN_SCREEN_DOWN_SELECT: { cur.selHandle(true); - size_t initial_depth = cur.depth(); - Point const p = getPos(cur, cur.boundary()); - scroll(cmd.action == LFUN_SCREEN_UP_SELECT? - height_ : height_); - // FIXME: We need to verify if the cursor stayed within an inset... - //cur.reset(buffer_.inset()); - d->text_metrics_[&buffer_.text()].editXY(cur, p.x_, p.y_); + if (isBottomScreen()) { + lyx::dispatch(FuncRequest(LFUN_BUFFER_END_SELECT)); + cur.finishUndo(); + break; + } + int y = getPos(cur, cur.boundary()).y_; + int const ymax = y + height_ - defaultRowHeight(); + while (y < ymax && cur.down()) + y = getPos(cur, cur.boundary()).y_; + cur.finishUndo(); - while (cur.depth() > initial_depth) { - cur.forwardInset(); + 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); } - // FIXME: we need to do a redraw again because of the selection - // But no screen update is needed. - d->update_strategy_ = NoScreenUpdate; - buffer_.changed(); + 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; } @@ -1334,11 +1509,13 @@ docstring const BufferView::requestSelection() { Cursor & cur = d->cursor_; + LYXERR(Debug::SELECTION, "requestSelection: cur.selection: " << cur.selection()); if (!cur.selection()) { d->xsel_cache_.set = false; return docstring(); } + 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) @@ -1420,7 +1597,7 @@ void BufferView::mouseEventDispatch(FuncRequest const & cmd0) Cursor old = cursor(); Cursor cur(*this); cur.push(buffer_.inset()); - cur.selection() = d->cursor_.selection(); + cur.setSelection(d->cursor_.selection()); // Either the inset under the cursor or the // surrounding Text will handle this event. @@ -1467,6 +1644,8 @@ void BufferView::mouseEventDispatch(FuncRequest const & cmd0) // Put anchor at the same position. cur.resetAnchor(); + cur.beginUndoGroup(); + // Try to dispatch to an non-editable inset near this position // via the temp cursor. If the inset wishes to change the real // cursor it has to do so explicitly by using @@ -1476,13 +1655,15 @@ void BufferView::mouseEventDispatch(FuncRequest const & cmd0) // Now dispatch to the temporary cursor. If the real cursor should // be modified, the inset's dispatch has to do so explicitly. - if (!cur.result().dispatched()) + if (!inset || !cur.result().dispatched()) cur.dispatch(cmd); + cur.endUndoGroup(); + // Notify left insets if (cur != old) { old.fixIfBroken(); - bool badcursor = notifyCursorLeaves(old, cur); + bool badcursor = notifyCursorLeavesOrEnters(old, cur); if (badcursor) cursor().fixIfBroken(); } @@ -1500,8 +1681,8 @@ 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; + (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); @@ -1514,29 +1695,40 @@ void BufferView::lfunScroll(FuncRequest const & cmd) if (scroll_value) scroll(scroll_step * scroll_value); } + updateMetrics(); + buffer_.changed(); } -void BufferView::scroll(int y) +int BufferView::minVisiblePart() +{ + return 2 * defaultRowHeight(); +} + + +int BufferView::scroll(int y) { if (y > 0) - scrollDown(y); - else if (y < 0) - scrollUp(-y); + return scrollDown(y); + if (y < 0) + return scrollUp(-y); + return 0; } -void BufferView::scrollDown(int offset) +int BufferView::scrollDown(int offset) { Text * text = &buffer_.text(); TextMetrics & tm = d->text_metrics_[text]; - int ymax = height_ + offset; + int const ymax = height_ + offset; while (true) { pair last = tm.last(); int bottom_pos = last.second->position() + last.second->descent(); + if (lyxrc.scroll_below_document) + bottom_pos += height_ - minVisiblePart(); if (last.first + 1 == int(text->paragraphs().size())) { if (bottom_pos <= height_) - return; + return 0; offset = min(offset, bottom_pos - height_); break; } @@ -1545,12 +1737,11 @@ void BufferView::scrollDown(int offset) tm.newParMetricsDown(); } d->anchor_ypos_ -= offset; - updateMetrics(); - buffer_.changed(); + return -offset; } -void BufferView::scrollUp(int offset) +int BufferView::scrollUp(int offset) { Text * text = &buffer_.text(); TextMetrics & tm = d->text_metrics_[text]; @@ -1560,7 +1751,7 @@ void BufferView::scrollUp(int offset) int top_pos = first.second->position() - first.second->ascent(); if (first.first == 0) { if (top_pos >= 0) - return; + return 0; offset = min(offset, - top_pos); break; } @@ -1569,8 +1760,7 @@ void BufferView::scrollUp(int offset) tm.newParMetricsUp(); } d->anchor_ypos_ += offset; - updateMetrics(); - buffer_.changed(); + return offset; } @@ -1589,15 +1779,42 @@ void BufferView::setCursorFromRow(int row) } +bool BufferView::setCursorFromInset(Inset const * inset) +{ + // are we already there? + if (cursor().nextInset() == inset) + return true; + + // Inset is not at cursor position. Find it in the document. + Cursor cur(*this); + cur.reset(buffer().inset()); + while (cur && cur.nextInset() != inset) + cur.forwardInset(); + + if (cur) { + setCursor(cur); + return true; + } + return false; +} + + void BufferView::gotoLabel(docstring const & label) { - for (InsetIterator it = inset_iterator_begin(buffer_.inset()); it; ++it) { - vector labels; - it->getLabelList(labels); - if (std::find(labels.begin(), labels.end(), label) != labels.end()) { - setCursor(it); - showCursor(); - return; + std::vector bufs = buffer().allRelatives(); + std::vector::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(); + for (; toc_it != end; ++toc_it) { + if (label == toc_it->str()) { + dispatch(toc_it->action()); + return; + } } } } @@ -1640,7 +1857,7 @@ void BufferView::setCursor(DocIterator const & dit) dit[i].inset().edit(d->cursor_, true); d->cursor_.setCursor(dit); - d->cursor_.selection() = false; + d->cursor_.setSelection(false); } @@ -1662,7 +1879,7 @@ bool BufferView::checkDepm(Cursor & cur, Cursor & old) d->cursor_ = cur; - updateLabels(buffer_); + buffer_.updateLabels(); updateMetrics(); buffer_.changed(); @@ -1672,7 +1889,7 @@ bool BufferView::checkDepm(Cursor & cur, Cursor & old) bool BufferView::mouseSetCursor(Cursor & cur, bool select) { - BOOST_ASSERT(&cur.bv() == this); + LASSERT(&cur.bv() == this, /**/); if (!select) // this event will clear selection so we save selection for @@ -1680,14 +1897,9 @@ bool BufferView::mouseSetCursor(Cursor & cur, bool select) cap::saveSelection(cursor()); // Has the cursor just left the inset? - bool badcursor = false; bool leftinset = (&d->cursor_.inset() != &cur.inset()); - if (leftinset) { + if (leftinset) d->cursor_.fixIfBroken(); - badcursor = notifyCursorLeaves(d->cursor_, cur); - if (badcursor) - cur.fixIfBroken(); - } // FIXME: shift-mouse selection doesn't work well across insets. bool do_selection = select && &d->cursor_.anchor().inset() == &cur.inset(); @@ -1697,7 +1909,7 @@ bool BufferView::mouseSetCursor(Cursor & cur, bool select) // FIXME: (2) if we had a working InsetText::notifyCursorLeaves, // the leftinset bool would not be necessary (badcursor instead). bool update = leftinset; - if (!do_selection && !badcursor && d->cursor_.inTexted()) + if (!do_selection && d->cursor_.inTexted()) update |= checkDepm(cur, d->cursor_); d->cursor_.setCursor(cur); @@ -1759,8 +1971,8 @@ bool BufferView::singleParUpdate() int old_height = tm.parMetrics(bottom_pit).height(); // make sure inline completion pointer is ok - if (d->inlineCompletionPos.fixIfBroken()) - d->inlineCompletionPos = DocIterator(); + if (d->inlineCompletionPos_.fixIfBroken()) + d->inlineCompletionPos_ = DocIterator(); // In Single Paragraph mode, rebreak only // the (main text, not inset!) paragraph containing the cursor. @@ -1785,6 +1997,9 @@ bool BufferView::singleParUpdate() void BufferView::updateMetrics() { + if (height_ == 0 || width_ == 0) + return; + Text & buftext = buffer_.text(); pit_type const npit = int(buftext.paragraphs().size()); @@ -1799,9 +2014,13 @@ void BufferView::updateMetrics() TextMetrics & tm = textMetrics(&buftext); // make sure inline completion pointer is ok - if (d->inlineCompletionPos.fixIfBroken()) - d->inlineCompletionPos = DocIterator(); + if (d->inlineCompletionPos_.fixIfBroken()) + d->inlineCompletionPos_ = DocIterator(); + if (d->anchor_pit_ >= npit) + // The anchor pit must have been deleted... + d->anchor_pit_ = npit - 1; + // Rebreak anchor paragraph. tm.redoParagraph(d->anchor_pit_); ParagraphMetrics & anchor_pm = tm.par_metrics_[d->anchor_pit_]; @@ -1869,7 +2088,7 @@ void BufferView::updateMetrics() void BufferView::insertLyXFile(FileName const & fname) { - BOOST_ASSERT(d->cursor_.inTexted()); + LASSERT(d->cursor_.inTexted(), /**/); // Get absolute path of file and add ".lyx" // to the filename if necessary @@ -1954,7 +2173,7 @@ 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()); - BOOST_ASSERT(!pm.rows().empty()); + LASSERT(!pm.rows().empty(), /**/); y -= pm.rows()[0].ascent(); #if 1 // FIXME: document this mess @@ -1993,10 +2212,11 @@ Point BufferView::coordOffset(DocIterator const & dit, bool boundary) const Point BufferView::getPos(DocIterator const & dit, bool boundary) const { + if (!paragraphVisible(dit)) + return Point(-1, -1); + CursorSlice const & bot = dit.bottom(); TextMetrics const & tm = textMetrics(bot.text()); - if (!tm.contains(bot.pit())) - return Point(-1, -1); Point p = coordOffset(dit, boundary); // offset from outer paragraph p.y_ += tm.parMetrics(bot.pit()).position(); @@ -2004,9 +2224,44 @@ Point BufferView::getPos(DocIterator const & dit, bool boundary) const } +bool BufferView::paragraphVisible(DocIterator const & dit) const +{ + CursorSlice const & bot = dit.bottom(); + TextMetrics const & tm = textMetrics(bot.text()); + + return tm.contains(bot.pit()); +} + + +void BufferView::cursorPosAndHeight(Point & p, int & h) const +{ + Cursor const & cur = cursor(); + Font const font = cur.getFont(); + frontend::FontMetrics const & fm = theFontMetrics(font); + int const asc = fm.maxAscent(); + int const des = fm.maxDescent(); + h = asc + des; + p = getPos(cur, cur.boundary()); + p.y_ -= asc; +} + + +bool BufferView::cursorInView(Point const & p, int h) const +{ + Cursor const & cur = cursor(); + // does the cursor touch the screen ? + if (p.y_ + h < 0 || p.y_ >= workHeight() || !paragraphVisible(cur)) + return false; + return true; +} + + void BufferView::draw(frontend::Painter & pain) { + if (height_ == 0 || width_ == 0) + return; LYXERR(Debug::PAINTING, "\t\t*** START DRAWING ***"); + Text & text = buffer_.text(); TextMetrics const & tm = d->text_metrics_[&text]; int const y = tm.first().second->position(); @@ -2041,7 +2296,7 @@ void BufferView::draw(frontend::Painter & pain) // Clear background. pain.fillRectangle(0, 0, width_, height_, - buffer_.inset().backgroundColor()); + pi.backgroundColor(&buffer_.inset())); // Draw everything. tm.draw(pi, 0, y); @@ -2168,19 +2423,19 @@ void BufferView::insertPlaintextFile(FileName const & f, bool asParagraph) docstring const & BufferView::inlineCompletion() const { - return d->inlineCompletion; + return d->inlineCompletion_; } size_t const & BufferView::inlineCompletionUniqueChars() const { - return d->inlineCompletionUniqueChars; + return d->inlineCompletionUniqueChars_; } DocIterator const & BufferView::inlineCompletionPos() const { - return d->inlineCompletionPos; + return d->inlineCompletionPos_; } @@ -2190,6 +2445,8 @@ bool samePar(DocIterator const & a, DocIterator const & b) return true; if (a.empty() || b.empty()) return false; + if (a.depth() != b.depth()) + return false; return &a.innerParagraph() == &b.innerParagraph(); } @@ -2198,16 +2455,16 @@ void BufferView::setInlineCompletion(Cursor & cur, DocIterator const & pos, docstring const & completion, size_t uniqueChars) { uniqueChars = min(completion.size(), uniqueChars); - bool changed = d->inlineCompletion != completion - || d->inlineCompletionUniqueChars != uniqueChars; + bool changed = d->inlineCompletion_ != completion + || d->inlineCompletionUniqueChars_ != uniqueChars; bool singlePar = true; - d->inlineCompletion = completion; - d->inlineCompletionUniqueChars = min(completion.size(), uniqueChars); + 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; + DocIterator const & old = d->inlineCompletionPos_; if (old != pos) { //lyxerr << "inlineCompletionPos changed" << std::endl; // old or pos are in another paragraph? @@ -2216,12 +2473,12 @@ void BufferView::setInlineCompletion(Cursor & cur, DocIterator const & pos, singlePar = false; //lyxerr << "different paragraph" << std::endl; } - d->inlineCompletionPos = pos; + d->inlineCompletionPos_ = pos; } // set update flags if (changed) { - if (singlePar && !(cur.disp_.update() | Update::Force)) + if (singlePar && !(cur.disp_.update() & Update::Force)) cur.updateFlags(cur.disp_.update() | Update::SinglePar); else cur.updateFlags(cur.disp_.update() | Update::Force);