X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FBufferView.cpp;h=b46be350b850010964b3c6322381b7dd86cda0ba;hb=2455bc258f3b62c29ec4a41cb88070ff8518e842;hp=19e16da5f012c65d101361d3829bae3c0294a4e0;hpb=ed2b1631db33ba8265c2df141d77f207dca6c967;p=lyx.git diff --git a/src/BufferView.cpp b/src/BufferView.cpp index 19e16da5f0..b46be350b8 100644 --- a/src/BufferView.cpp +++ b/src/BufferView.cpp @@ -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" @@ -69,7 +73,6 @@ #include "support/convert.h" #include "support/debug.h" #include "support/ExceptionMessage.h" -#include "support/FileFilterList.h" #include "support/filetools.h" #include "support/gettext.h" #include "support/lstrings.h" @@ -114,7 +117,7 @@ bool findNextInset(DocIterator & dit, vector const & codes, while (tmpdit) { Inset const * inset = tmpdit.nextInset(); if (inset - && find(codes.begin(), codes.end(), inset->lyxCode()) != codes.end() + && std::find(codes.begin(), codes.end(), inset->lyxCode()) != codes.end() && (contents.empty() || static_cast(inset)->getFirstNonOptParam() == contents)) { dit = tmpdit; @@ -140,7 +143,7 @@ bool findInset(DocIterator & dit, vector const & codes, if (same_content) { Inset const * inset = tmpdit.nextInset(); if (inset - && find(codes.begin(), codes.end(), inset->lyxCode()) != codes.end()) { + && std::find(codes.begin(), codes.end(), inset->lyxCode()) != codes.end()) { contents = static_cast(inset)->getFirstNonOptParam(); } } @@ -212,6 +215,7 @@ struct BufferView::Private { Private(BufferView & bv): wh_(0), cursor_(bv), anchor_pit_(0), anchor_ypos_(0), + inlineCompletionUniqueChars_(0), last_inset_(0), gui_(0) {} @@ -239,6 +243,13 @@ struct BufferView::Private /// vector par_height_; + /// + DocIterator inlineCompletionPos_; + /// + docstring inlineCompletion_; + /// + size_t inlineCompletionUniqueChars_; + /// keyboard mapping object. Intl intl_; @@ -254,11 +265,14 @@ struct BufferView::Private /** 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), 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); @@ -268,7 +282,7 @@ BufferView::BufferView(Buffer & buf) d->cursor_.setCurrentFont(); if (graphics::Previews::status() != LyXRC::PREVIEW_OFF) - graphics::Previews::get().generateBufferPreviews(buffer_); + thePreviews().generateBufferPreviews(buffer_); } @@ -282,12 +296,42 @@ 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; } +int BufferView::rightMargin() const +{ + // 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; + + return (width_ - lyxrc.full_screen_width) / 2; +} + + +int BufferView::leftMargin() const +{ + return rightMargin(); +} + + +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_; @@ -414,9 +458,14 @@ void BufferView::processUpdateFlags(Update::flags flags) void BufferView::updateScrollbar() { - if (height_ == 0) + if (height_ == 0 && width_ == 0) return; + // We prefer fixed size line scrolling. + d->scrollbarParameters_.single_step = defaultRowHeight(); + // We prefer full screen page scrolling. + d->scrollbarParameters_.page_step = height_; + Text & t = buffer_.text(); TextMetrics & tm = d->text_metrics_[&t]; @@ -425,7 +474,7 @@ void BufferView::updateScrollbar() << " curr par: " << d->cursor_.bottom().pit() << " default height " << defaultRowHeight()); - int const parsize = int(t.paragraphs().size()); + size_t const parsize = t.paragraphs().size(); if (d->par_height_.size() != parsize) { d->par_height_.clear(); // FIXME: We assume a default paragraph height of 2 rows. This @@ -433,45 +482,35 @@ void BufferView::updateScrollbar() d->par_height_.resize(parsize, defaultRowHeight() * 2); } - // It would be better to fix the scrollbar to understand - // values in [0..1] and divide everything by wh - // Look at paragraph heights on-screen - pit_type first_visible_pit = -1; pair first = tm.first(); pair last = tm.last(); for (pit_type pit = first.first; pit <= last.first; ++pit) { - ParagraphMetrics const & pm = tm.parMetrics(pit); - d->par_height_[pit] = pm.height(); - if (first_visible_pit >= 0 || pm.position() + pm.descent() <= 0) - continue; - first_visible_pit = pit; - LYXERR(Debug::SCROLLING, "first visible pit " << first_visible_pit); - // FIXME: we should look for the first visible row within - // the deepest inset! - int row_pos = pm.position(); - size_t const nrows = pm.rows().size(); - for (size_t i = 0; i != nrows; ++i) { - Row const & row = pm.rows()[i]; - if (row_pos >= 0) { - LYXERR(Debug::SCROLLING, "first visible row " << i - << "(row pos = " << row_pos << ");"); - break; - } - row_pos += row.height(); - } - d->scrollbarParameters_.position = row_pos; + d->par_height_[pit] = tm.parMetrics(pit).height(); + LYXERR(Debug::SCROLLING, "storing height for pit " << pit << " : " + << d->par_height_[pit]); } - d->scrollbarParameters_.height = 0; - for (size_t i = 0; i != d->par_height_.size(); ++i) { - if (i == first_visible_pit) - d->scrollbarParameters_.position += d->scrollbarParameters_.height; - d->scrollbarParameters_.height += d->par_height_[i]; + int top_pos = first.second->position() - first.second->ascent(); + int bottom_pos = last.second->position() + last.second->descent(); + bool first_visible = first.first == 0 && top_pos >= 0; + bool last_visible = last.first + 1 == int(parsize) && bottom_pos <= height_; + if (first_visible && last_visible) { + d->scrollbarParameters_.min = 0; + d->scrollbarParameters_.max = 0; + return; } - // We prefer fixed size line scrolling. - d->scrollbarParameters_.lineScrollHeight = defaultRowHeight(); + d->scrollbarParameters_.min = top_pos; + for (size_t i = 0; i != size_t(first.first); ++i) + d->scrollbarParameters_.min -= d->par_height_[i]; + d->scrollbarParameters_.max = bottom_pos; + 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. + d->scrollbarParameters_.max -= d->scrollbarParameters_.page_step; } @@ -499,8 +538,7 @@ docstring BufferView::contextMenu(int x, int y) const 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); } @@ -510,25 +548,49 @@ void BufferView::scrollDocView(int value) // If the offset is less than 2 screen height, prefer to scroll instead. if (abs(offset) <= 2 * height_) { scroll(offset); + updateMetrics(); + buffer_.changed(); return; } - int par_pos = 0; - for (size_t i = 0; i != d->par_height_.size(); ++i) { + // cut off at the top + if (value <= d->scrollbarParameters_.min) { + DocIterator dit = doc_iterator_begin(buffer_.inset()); + showCursor(dit); + LYXERR(Debug::SCROLLING, "scroll to top"); + return; + } + + // cut off at the bottom + if (value >= d->scrollbarParameters_.max) { + DocIterator dit = doc_iterator_end(buffer_.inset()); + dit.backwardPos(); + showCursor(dit); + LYXERR(Debug::SCROLLING, "scroll to bottom"); + return; + } + + // find paragraph at target position + int par_pos = d->scrollbarParameters_.min; + pit_type i = 0; + for (; i != int(d->par_height_.size()); ++i) { par_pos += d->par_height_[i]; - if (par_pos >= value) { - d->anchor_pit_ = pit_type(i); + if (par_pos >= value) break; - } } - LYXERR(Debug::SCROLLING, "value = " << value - << "\tanchor_ref_ = " << d->anchor_pit_ - << "\tpar_pos = " << par_pos); + if (par_pos < 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); + return; + } - d->anchor_ypos_ = par_pos - value; - updateMetrics(); - buffer_.changed(); + DocIterator dit = doc_iterator_begin(buffer_.inset()); + dit.pit() = i; + LYXERR(Debug::SCROLLING, "value = " << value << " -> scroll to pit " << i); + showCursor(dit); } @@ -597,7 +659,7 @@ 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. - LyX::ref().session().bookmarks().save( + theSession().bookmarks().save( buffer_.fileName(), d->cursor_.bottom().pit(), d->cursor_.bottom().pos(), @@ -615,7 +677,7 @@ bool BufferView::moveToPosition(pit_type bottom_pit, pos_type bottom_pos, int top_id, pos_type top_pos) { bool success = false; - DocIterator doc_it; + DocIterator dit; d->cursor_.clearSelection(); @@ -623,19 +685,19 @@ bool BufferView::moveToPosition(pit_type bottom_pit, pos_type bottom_pos, // This is the case for a 'live' bookmark when unique paragraph ID // is used to track bookmarks. if (top_id > 0) { - ParIterator par = buffer_.getParFromID(top_id); - if (par != buffer_.par_iterator_end()) { - doc_it = makeDocIterator(par, min(par->size(), top_pos)); + dit = buffer_.getParFromID(top_id); + if (!dit.atEnd()) { + dit.pos() = min(dit.paragraph().size(), top_pos); // Some slices of the iterator may not be // reachable (e.g. closed collapsable inset) // so the dociterator may need to be // shortened. Otherwise, setCursor may crash // lyx when the cursor can not be set to these // insets. - size_t const n = doc_it.depth(); + size_t const n = dit.depth(); for (size_t i = 0; i < n; ++i) - if (doc_it[i].inset().editable() != Inset::HIGHLY_EDITABLE) { - doc_it.resize(i); + if (dit[i].inset().editable() != Inset::HIGHLY_EDITABLE) { + dit.resize(i); break; } success = true; @@ -648,16 +710,17 @@ bool BufferView::moveToPosition(pit_type bottom_pit, pos_type bottom_pos, // restoration is inaccurate. If a bookmark was within an inset, // it will be restored to the left of the outmost inset that contains // the bookmark. - if (static_cast(bottom_pit) < buffer_.paragraphs().size()) { - doc_it = doc_iterator_begin(buffer_.inset()); - doc_it.pit() = bottom_pit; - doc_it.pos() = min(bottom_pos, doc_it.paragraph().size()); + if (bottom_pit < int(buffer_.paragraphs().size())) { + dit = doc_iterator_begin(buffer_.inset()); + + dit.pit() = bottom_pit; + dit.pos() = min(bottom_pos, dit.paragraph().size()); success = true; } if (success) { // Note: only bottom (document) level pit is set. - setCursor(doc_it); + setCursor(dit); // set the current font. d->cursor_.setCurrentFont(); // To center the screen on this new position we need the @@ -695,6 +758,12 @@ int BufferView::workWidth() const void BufferView::showCursor() +{ + showCursor(d->cursor_); +} + + +void BufferView::showCursor(DocIterator const & dit) { // We are not properly started yet, delay until resizing is // done. @@ -703,11 +772,11 @@ void BufferView::showCursor() LYXERR(Debug::SCROLLING, "recentering!"); - CursorSlice & bot = d->cursor_.bottom(); + CursorSlice const & bot = dit.bottom(); TextMetrics & tm = d->text_metrics_[bot.text()]; pos_type const max_pit = pos_type(bot.text()->paragraphs().size() - 1); - int bot_pit = d->cursor_.bottom().pit(); + int bot_pit = bot.pit(); if (bot_pit > max_pit) { // FIXME: Why does this happen? LYXERR0("bottom pit is greater that max pit: " @@ -720,32 +789,47 @@ void BufferView::showCursor() else if (bot_pit == tm.last().first + 1) tm.newParMetricsDown(); - if (tm.has(bot_pit)) { + if (tm.contains(bot_pit)) { ParagraphMetrics const & pm = tm.parMetrics(bot_pit); - int offset = coordOffset(d->cursor_, d->cursor_.boundary()).y_; + 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 = d->cursor_.textRow().dimension(); + Dimension const & row_dim = + pm.getRow(cs.pos(), dit.boundary()).dimension(); + int scrolled = 0; if (ypos - row_dim.ascent() < 0) - scrollUp(- ypos + row_dim.ascent()); + scrolled = scrollUp(- ypos + row_dim.ascent()); else if (ypos + row_dim.descent() > height_) - scrollDown(ypos - height_ + row_dim.descent()); + scrolled = scrollDown(ypos - height_ + row_dim.descent()); // else, nothing to do, the cursor is already visible so we just return. + if (scrolled != 0) { + updateMetrics(); + buffer_.changed(); + } return; } + // fix inline completion position + if (d->inlineCompletionPos_.fixIfBroken()) + d->inlineCompletionPos_ = DocIterator(); + tm.redoParagraph(bot_pit); ParagraphMetrics const & pm = tm.parMetrics(bot_pit); - int offset = coordOffset(d->cursor_, d->cursor_.boundary()).y_; + int offset = coordOffset(dit, dit.boundary()).y_; d->anchor_pit_ = bot_pit; - Dimension const & row_dim = d->cursor_.textRow().dimension(); + CursorSlice const & cs = dit.innerTextSlice(); + Dimension const & row_dim = + pm.getRow(cs.pos(), dit.boundary()).dimension(); 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 - d->anchor_ypos_ = offset + pm.ascent() - height_ / 2; + d->anchor_ypos_ = defaultRowHeight() * 2 - offset - row_dim.descent(); updateMetrics(); buffer_.changed(); @@ -761,21 +845,22 @@ FuncStatus BufferView::getStatus(FuncRequest const & cmd) 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: @@ -787,24 +872,40 @@ FuncStatus BufferView::getStatus(FuncRequest const & cmd) case LFUN_SCREEN_RECENTER: case LFUN_BIBTEX_DATABASE_ADD: case LFUN_BIBTEX_DATABASE_DEL: - case LFUN_WORDS_COUNT: - case LFUN_NEXT_INSET_TOGGLE: - flag.enabled(true); + case LFUN_GRAPHICS_GROUPS_UNIFY: + case LFUN_NOTES_MUTATE: + case LFUN_ALL_INSETS_TOGGLE: + case LFUN_STATISTICS: + flag.setEnabled(true); + break; + + case LFUN_NEXT_INSET_TOGGLE: + case LFUN_NEXT_INSET_MODIFY: { + // this is the real function we want to invoke + FuncRequest tmpcmd = cmd; + tmpcmd.action = (cmd.action == LFUN_NEXT_INSET_TOGGLE) + ? LFUN_INSET_TOGGLE : 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; @@ -816,7 +917,7 @@ FuncStatus BufferView::getStatus(FuncRequest const & cmd) // 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: { @@ -824,70 +925,57 @@ 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: + flag.setEnabled(!cur.inset().forcePlainLayout(cur.idx())); + break; + case LFUN_LAYOUT_PARAGRAPH: - flag.enabled(cur.inset().forceDefaultParagraphs(cur.idx())); + flag.setEnabled(cur.inset().allowParagraphCustomization(cur.idx())); break; case LFUN_INSET_SETTINGS: { InsetCode code = cur.inset().lyxCode(); + if (cmd.getArg(0) == insetName(code)) { + flag.setEnabled(true); + break; + } bool enable = false; - switch (code) { + InsetCode next_code = cur.nextInset() + ? cur.nextInset()->lyxCode() : NO_CODE; + //FIXME: remove these special cases: + switch (next_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"; + enable = (cmd.argument().empty() || + cmd.getArg(0) == insetName(next_code)); break; default: break; } - flag.enabled(enable); + flag.setEnabled(enable); break; } case LFUN_DIALOG_SHOW_NEW_INSET: - flag.enabled(cur.inset().lyxCode() != ERT_CODE && + flag.setEnabled(cur.inset().lyxCode() != ERT_CODE && cur.inset().lyxCode() != LISTINGS_CODE); if (cur.inset().lyxCode() == CAPTION_CODE) { FuncStatus flag; @@ -896,8 +984,18 @@ FuncStatus BufferView::getStatus(FuncRequest const & cmd) } break; + case LFUN_BRANCH_ACTIVATE: + case LFUN_BRANCH_DEACTIVATE: { + bool enable = false; + docstring const branchName = cmd.argument(); + if (!branchName.empty()) + enable = buffer_.params().branchlist().find(branchName); + flag.setEnabled(enable); + break; + } + default: - flag.enabled(false); + flag.setEnabled(false); } return flag; @@ -962,34 +1060,55 @@ bool BufferView::dispatch(FuncRequest const & cmd) 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)) { - ParIterator par = b->getParFromID(id); - if (par == b->par_iterator_end()) { + DocIterator dit = b->getParFromID(id); + if (dit.atEnd()) { LYXERR(Debug::INFO, "No matching paragraph found! [" << id << "]."); + ++i; + continue; + } + LYXERR(Debug::INFO, "Paragraph " << dit.paragraph().id() + << " found in buffer `" + << b->absFileName() << "'."); + + if (b == &buffer_) { + // Set the cursor + dit.pos() = pos; + setCursor(dit); + processUpdateFlags(Update::Force | Update::FitCursor); } else { - LYXERR(Debug::INFO, "Paragraph " << par->id() - << " found in buffer `" - << b->absFileName() << "'."); - - if (b == &buffer_) { - // Set the cursor - setCursor(makeDocIterator(par, 0)); - showCursor(); - } else { - // Switch to other buffer view and resend cmd - theLyXFunc().dispatch(FuncRequest( - LFUN_BUFFER_SWITCH, b->absFileName())); - theLyXFunc().dispatch(cmd); - } - break; + // Switch to other buffer view and resend cmd + theLyXFunc().dispatch(FuncRequest( + LFUN_BUFFER_SWITCH, b->absFileName())); + theLyXFunc().dispatch(cmd); } - ++i; + break; } break; } @@ -1049,6 +1168,8 @@ bool BufferView::dispatch(FuncRequest const & cmd) buffer_.text().cursorBottom(d->cursor_); // accept everything in a single step to support atomic undo buffer_.text().acceptOrRejectChanges(d->cursor_, 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: @@ -1059,11 +1180,21 @@ 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(d->cursor_, 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: - 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 (find(this, req)) + showCursor(); + else + message(_("String not found!")); + d->search_request_cache_ = req; break; + } case LFUN_WORD_REPLACE: { bool has_deleted = false; @@ -1116,7 +1247,7 @@ bool BufferView::dispatch(FuncRequest const & cmd) InsetBibtex * inset = getInsetByCode(tmpcur, BIBTEX_CODE); if (inset) { - if (inset->addDatabase(to_utf8(cmd.argument()))) + if (inset->addDatabase(cmd.argument())) buffer_.updateBibfilesCache(); } break; @@ -1128,13 +1259,13 @@ bool BufferView::dispatch(FuncRequest const & cmd) InsetBibtex * inset = getInsetByCode(tmpcur, BIBTEX_CODE); if (inset) { - if (inset->delDatabase(to_utf8(cmd.argument()))) + if (inset->delDatabase(cmd.argument())) buffer_.updateBibfilesCache(); } break; } - case LFUN_WORDS_COUNT: { + case LFUN_STATISTICS: { DocIterator from, to; if (cur.selection()) { from = cur.selectionBegin(); @@ -1143,24 +1274,33 @@ bool BufferView::dispatch(FuncRequest const & cmd) from = doc_iterator_begin(buffer_.inset()); to = doc_iterator_end(buffer_.inset()); } - int const count = countWords(from, to); + int const words = countWords(from, to); + int const chars = countChars(from, to, false); + int const chars_blanks = countChars(from, to, true); docstring message; - if (count != 1) { - if (cur.selection()) - message = bformat(_("%1$d words in selection."), - count); - else - message = bformat(_("%1$d words in document."), - count); - } - else { - if (cur.selection()) - message = _("One word in selection."); - else - message = _("One word in document."); - } + if (cur.selection()) + message = _("Statistics for the selection:"); + else + message = _("Statistics for the document:"); + message += "\n\n"; + if (words != 1) + message += bformat(_("%1$d words"), words); + else + message += _("One word"); + message += "\n"; + if (chars_blanks != 1) + message += bformat(_("%1$d characters (including blanks)"), + chars_blanks); + else + message += _("One character (including blanks)"); + message += "\n"; + if (chars != 1) + message += bformat(_("%1$d characters (excluding blanks)"), + chars); + else + message += _("One character (excluding blanks)"); - Alert::information(_("Count words"), message); + Alert::information(_("Statistics"), message); } break; @@ -1169,19 +1309,10 @@ bool BufferView::dispatch(FuncRequest const & cmd) buffer_.params().compressed = !buffer_.params().compressed; break; - case LFUN_BUFFER_TOGGLE_EMBEDDING: { - // turn embedding on/off - try { - buffer_.embeddedFiles().enable(!buffer_.params().embedded); - } catch (ExceptionMessage const & message) { - Alert::error(message.title_, message.details_); - } - break; - } - case LFUN_NEXT_INSET_TOGGLE: { - // this is the real function we want to invoke - FuncRequest tmpcmd = FuncRequest(LFUN_INSET_TOGGLE, cmd.origin); + // create the the real function we want to invoke + FuncRequest tmpcmd = cmd; + tmpcmd.action = LFUN_INSET_TOGGLE; // if there is an inset at cursor, see whether it // wants to toggle. Inset * inset = cur.nextInset(); @@ -1190,15 +1321,13 @@ bool BufferView::dispatch(FuncRequest const & cmd) Cursor tmpcur = cur; tmpcur.pushBackward(*inset); inset->dispatch(tmpcur, tmpcmd); - if (tmpcur.result().dispatched()) { + if (tmpcur.result().dispatched()) cur.dispatched(); - } - } else if (inset->editable() == Inset::IS_EDITABLE) { - inset->edit(cur, true); - } + } else + inset->dispatch(cur, tmpcmd); } // if it did not work, try the underlying inset. - if (!cur.result().dispatched()) + if (!inset || !cur.result().dispatched()) cur.dispatch(tmpcmd); if (!cur.result().dispatched()) @@ -1209,6 +1338,27 @@ bool BufferView::dispatch(FuncRequest const & cmd) 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) + inset->dispatch(cur, tmpcmd); + // if it did not work, try the underlying inset. + if (!inset || !cur.result().dispatched()) + 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()); @@ -1217,8 +1367,15 @@ 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_); 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()? cur.finishUndo(); @@ -1229,26 +1386,88 @@ 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; + } + + case LFUN_BRANCH_ACTIVATE: + case LFUN_BRANCH_DEACTIVATE: + buffer_.dispatch(cmd); + processUpdateFlags(Update::Force); + break; + + // These two could be rewriten using some command like forall + // once the insets refactoring is done. + case LFUN_GRAPHICS_GROUPS_UNIFY: { + if (cmd.argument().empty()) + break; + //view()->cursor().recordUndoFullDocument(); let inset-apply do that job + graphics::unifyGraphicsGroups(cur.buffer(), to_utf8(cmd.argument())); + processUpdateFlags(Update::Force | Update::FitCursor); + break; + } + + case LFUN_NOTES_MUTATE: { + if (cmd.argument().empty()) + break; + cur.recordUndoFullDocument(); + + 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; } @@ -1264,11 +1483,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) @@ -1304,7 +1525,7 @@ void BufferView::resize(int width, int height) // Clear the paragraph height cache. d->par_height_.clear(); - + // Redo the metrics. updateMetrics(); } @@ -1347,6 +1568,7 @@ void BufferView::mouseEventDispatch(FuncRequest const & cmd0) // LFUN_FILE_OPEN generated by drag-and-drop. FuncRequest cmd = cmd0; + Cursor old = cursor(); Cursor cur(*this); cur.push(buffer_.inset()); cur.selection() = d->cursor_.selection(); @@ -1405,16 +1627,22 @@ 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); - //Do we have a selection? + // Notify left insets + if (cur != old) { + old.fixIfBroken(); + bool badcursor = notifyCursorLeaves(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() - // an update is asked, - && cur.result().update()) + if (cur.result().dispatched() || cur.result().update()) processUpdateFlags(cur.result().update()); } @@ -1423,8 +1651,8 @@ void BufferView::lfunScroll(FuncRequest const & cmd) { string const scroll_type = cmd.getArg(0); int const scroll_step = - (scroll_type == "line")? d->scrollbarParameters_.lineScrollHeight - : (scroll_type == "page")? height_ : 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); @@ -1437,19 +1665,22 @@ void BufferView::lfunScroll(FuncRequest const & cmd) if (scroll_value) scroll(scroll_step * scroll_value); } + updateMetrics(); + buffer_.changed(); } -void BufferView::scroll(int y) +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]; @@ -1459,7 +1690,7 @@ void BufferView::scrollDown(int offset) int bottom_pos = last.second->position() + last.second->descent(); if (last.first + 1 == int(text->paragraphs().size())) { if (bottom_pos <= height_) - return; + return 0; offset = min(offset, bottom_pos - height_); break; } @@ -1468,12 +1699,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]; @@ -1483,7 +1713,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; } @@ -1492,8 +1722,7 @@ void BufferView::scrollUp(int offset) tm.newParMetricsUp(); } d->anchor_ypos_ += offset; - updateMetrics(); - buffer_.changed(); + return offset; } @@ -1514,15 +1743,15 @@ void BufferView::setCursorFromRow(int row) void BufferView::gotoLabel(docstring const & label) { - for (InsetIterator it = inset_iterator_begin(buffer_.inset()); it; ++it) { - vector labels; - it->getLabelList(buffer_, labels); - if (find(labels.begin(), labels.end(), label) != labels.end()) { - setCursor(it); - showCursor(); - return; - } + Toc & toc = buffer().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()); } + //FIXME: We could do a bit more searching thanks to this: + //InsetLabel const * inset = buffer_.insetLabel(label); } @@ -1595,7 +1824,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 @@ -1605,8 +1834,12 @@ bool BufferView::mouseSetCursor(Cursor & cur, bool select) // 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(); @@ -1619,24 +1852,7 @@ bool BufferView::mouseSetCursor(Cursor & cur, bool select) if (!do_selection && !badcursor && d->cursor_.inTexted()) update |= checkDepm(cur, d->cursor_); - // if the cursor was in an empty script inset and the new - // position is in the nucleus of the inset, notifyCursorLeaves - // will kill the script inset itself. So we check all the - // elements of the cursor to make sure that they are correct. - // For an example, see bug 2933: - // http://bugzilla.lyx.org/show_bug.cgi?id=2933 - // The code below could maybe be moved to a DocIterator method. - //lyxerr << "cur before " << cur <cursor_.setCursor(dit); + d->cursor_.setCursor(cur); d->cursor_.boundary(cur.boundary()); if (do_selection) d->cursor_.setSelection(); @@ -1663,6 +1879,9 @@ 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); } @@ -1691,6 +1910,10 @@ bool BufferView::singleParUpdate() TextMetrics & tm = textMetrics(&buftext); int old_height = tm.parMetrics(bottom_pit).height(); + // make sure inline completion pointer is ok + if (d->inlineCompletionPos_.fixIfBroken()) + d->inlineCompletionPos_ = DocIterator(); + // In Single Paragraph mode, rebreak only // the (main text, not inset!) paragraph containing the cursor. // (if this paragraph contains insets etc., rebreaking will @@ -1714,6 +1937,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()); @@ -1727,9 +1953,30 @@ void BufferView::updateMetrics() TextMetrics & tm = textMetrics(&buftext); + // 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; + // 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: " @@ -1781,7 +2028,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 @@ -1799,7 +2046,7 @@ void BufferView::insertLyXFile(FileName const & fname) el = buf.errorList("Parse"); buffer_.undo().recordUndo(d->cursor_); cap::pasteParagraphList(d->cursor_, buf.paragraphs(), - buf.params().getTextClassPtr(), el); + buf.params().documentClassPtr(), el); res = _("Document %1$s inserted."); } else { res = _("Could not insert document %1$s"); @@ -1866,7 +2113,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 @@ -1907,7 +2154,7 @@ Point BufferView::getPos(DocIterator const & dit, bool boundary) const { CursorSlice const & bot = dit.bottom(); TextMetrics const & tm = textMetrics(bot.text()); - if (!tm.has(bot.pit())) + if (!tm.contains(bot.pit())) return Point(-1, -1); Point p = coordOffset(dit, boundary); // offset from outer paragraph @@ -1918,6 +2165,8 @@ Point BufferView::getPos(DocIterator const & dit, bool boundary) const 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]; @@ -2077,4 +2326,67 @@ void BufferView::insertPlaintextFile(FileName const & f, bool asParagraph) buffer_.changed(); } + +docstring const & BufferView::inlineCompletion() const +{ + return d->inlineCompletion_; +} + + +size_t const & BufferView::inlineCompletionUniqueChars() const +{ + return d->inlineCompletionUniqueChars_; +} + + +DocIterator const & BufferView::inlineCompletionPos() const +{ + return d->inlineCompletionPos_; +} + + +bool samePar(DocIterator const & a, DocIterator const & b) +{ + if (a.empty() && b.empty()) + return true; + if (a.empty() || b.empty()) + return false; + return &a.innerParagraph() == &b.innerParagraph(); +} + + +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 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) { + //lyxerr << "inlineCompletionPos changed" << std::endl; + // old or pos are in another paragraph? + if ((!samePar(cur, pos) && !pos.empty()) + || (!samePar(cur, old) && !old.empty())) { + singlePar = false; + //lyxerr << "different paragraph" << std::endl; + } + d->inlineCompletionPos_ = pos; + } + + // set update flags + if (changed) { + if (singlePar && !(cur.disp_.update() & Update::Force)) + cur.updateFlags(cur.disp_.update() | Update::SinglePar); + else + cur.updateFlags(cur.disp_.update() | Update::Force); + } +} + } // namespace lyx