X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FBufferView.cpp;h=acf80d7871ee27895f9ee615637b78497c99f639;hb=26ba2a65838731ce639a09539f617cb0f0be3b22;hp=308cf7f5ba14cf6988dae206b55ff86dc4bd2bc7;hpb=0429b580e6f81cca3ff8e7da609ca868f39e5709;p=lyx.git diff --git a/src/BufferView.cpp b/src/BufferView.cpp index 308cf7f5ba..acf80d7871 100644 --- a/src/BufferView.cpp +++ b/src/BufferView.cpp @@ -20,6 +20,7 @@ #include "Buffer.h" #include "BufferList.h" #include "BufferParams.h" +#include "BiblioInfo.h" #include "CoordCache.h" #include "Cursor.h" #include "CutAndPaste.h" @@ -38,6 +39,7 @@ #include "MetricsInfo.h" #include "Paragraph.h" #include "Session.h" +#include "texstream.h" #include "Text.h" #include "TextMetrics.h" #include "TexRow.h" @@ -47,6 +49,7 @@ #include "insets/InsetCitation.h" #include "insets/InsetCommand.h" // ChangeRefs #include "insets/InsetGraphics.h" +#include "insets/InsetIndex.h" #include "insets/InsetRef.h" #include "insets/InsetText.h" @@ -67,6 +70,7 @@ #include "support/convert.h" #include "support/debug.h" #include "support/docstring.h" +#include "support/docstring_list.h" #include "support/filetools.h" #include "support/gettext.h" #include "support/lassert.h" @@ -171,18 +175,18 @@ bool findInset(DocIterator & dit, vector const & codes, /// Moves cursor to the next inset with one of the given codes. -void gotoInset(BufferView * bv, vector const & codes, +bool gotoInset(BufferView * bv, vector const & codes, bool same_content) { Cursor tmpcur = bv->cursor(); if (!findInset(tmpcur, codes, same_content)) { bv->cursor().message(_("No more insets")); - return; + return false; } tmpcur.clearSelection(); bv->setCursor(tmpcur); - bv->showCursor(); + return bv->scrollToCursor(bv->cursor(), SCROLL_TOP); } @@ -228,7 +232,7 @@ struct BufferView::Private /// CoordCache coord_cache_; /// - typedef map MathRows; + typedef unordered_map MathRows; MathRows math_rows_; /// this is used to handle XSelection events in the right manner. @@ -279,11 +283,11 @@ struct BufferView::Private /// map edited_insets_; - /// When the row where the cursor lies is scrolled, this - /// contains the scroll offset // cache for id of the paragraph which was edited the last time int bookmark_edit_position_; + /// 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) @@ -292,6 +296,15 @@ struct BufferView::Private bool clickable_inset_; /// shape of the caret frontend::CaretGeometry caret_geometry_; + /// + bool mouse_selecting_ = false; + /// Reference value for statistics (essentially subtract this from the actual value to see relative counts) + /// (words/chars/chars no blanks) + int stats_ref_value_w_ = 0; + int stats_ref_value_c_ = 0; + int stats_ref_value_nb_ = 0; + bool stats_update_trigger_ = false; + }; @@ -331,16 +344,31 @@ BufferView::~BufferView() } -int BufferView::rightMargin() const +void BufferView::copySettingsFrom(BufferView const & bv) +{ + setCursor(bv.cursor()); + d->anchor_pit_ = bv.d->anchor_pit_; + d->anchor_ypos_ = bv.d->anchor_ypos_; +} + + +int BufferView::defaultMargin() const { // The value used to be hardcoded to 10 - int const default_margin = zoomedPixels(10); - // The additional test for the case the outliner is opened. - if (!full_screen_ || !lyxrc.full_screen_limit - || width_ < lyxrc.full_screen_width + 2 * default_margin) - return default_margin; + return zoomedPixels(20); +} + - return (width_ - lyxrc.full_screen_width) / 2; +int BufferView::rightMargin() const +{ + const int screen_width = inPixels(lyxrc.screen_width); + + // The additional test for the case the outliner is opened. + if (!lyxrc.screen_limit || width_ < screen_width + 2 * defaultMargin()) { + return defaultMargin(); + } else { + return (width_ - screen_width) / 2; + } } @@ -422,6 +450,12 @@ CoordCache const & BufferView::coordCache() const } +bool BufferView::hasMathRow(MathData const * cell) const +{ + return d->math_rows_.find(cell) != d->math_rows_.end(); +} + + MathRow const & BufferView::mathRow(MathData const * cell) const { auto it = d->math_rows_.find(cell); @@ -505,7 +539,7 @@ void BufferView::processUpdateFlags(Update::flags flags) << flagsAsString(flags) << ") buffer: " << &buffer_); // Case when no explicit update is requested. - if (flags == Update::None) + if (flags == Update::None || !ready()) return; /* FIXME We would like to avoid doing this here, since it is very @@ -518,10 +552,13 @@ void BufferView::processUpdateFlags(Update::flags flags) // First check whether the metrics and inset positions should be updated if (flags & Update::Force) { - // This will update the CoordCache items and replace Force - // with ForceDraw in flags. - updateMetrics(flags); - } + // This will compute all metrics and positions. + updateMetrics(true); + // metrics is done, full drawing is necessary now + flags = (flags & ~Update::Force) | Update::ForceDraw; + } else if (flags & Update::ForceDraw) + // This will compute only the needed metrics and update positions. + updateMetrics(false); // Detect whether we can only repaint a single paragraph (if we // are not already redrawing all). @@ -530,22 +567,22 @@ void BufferView::processUpdateFlags(Update::flags flags) if (!(flags & Update::ForceDraw) && (flags & Update::SinglePar) && !singleParUpdate()) - updateMetrics(flags); + updateMetrics(true); // Then make sure that the screen contains the cursor if needed if (flags & Update::FitCursor) { if (needsFitCursor()) { // First try to make the selection start visible // (which is just the cursor when there is no selection) - scrollToCursor(d->cursor_.selectionBegin(), false); + scrollToCursor(d->cursor_.selectionBegin(), SCROLL_VISIBLE); // Metrics have to be recomputed (maybe again) - updateMetrics(); + updateMetrics(true); // Is the cursor visible? (only useful if cursor is at end of selection) if (needsFitCursor()) { // then try to make cursor visible instead - scrollToCursor(d->cursor_, false); + scrollToCursor(d->cursor_, SCROLL_VISIBLE); // Metrics have to be recomputed (maybe again) - updateMetrics(flags); + updateMetrics(true); } } flags = flags & ~Update::FitCursor; @@ -579,9 +616,9 @@ void BufferView::processUpdateFlags(Update::flags flags) } -void BufferView::updateScrollbar() +void BufferView::updateScrollbarParameters() { - if (height_ == 0 && width_ == 0) + if (!ready()) return; // We prefer fixed size line scrolling. @@ -592,7 +629,7 @@ void BufferView::updateScrollbar() Text & t = buffer_.text(); TextMetrics & tm = d->text_metrics_[&t]; - LYXERR(Debug::GUI, " Updating scrollbar: height: " + LYXERR(Debug::SCROLLING, " Updating scrollbar: height: " << t.paragraphs().size() << " curr par: " << d->cursor_.bottom().pit() << " default height " << defaultRowHeight()); @@ -614,8 +651,8 @@ void BufferView::updateScrollbar() << d->par_height_[pit]); } - int top_pos = first.second->position() - first.second->ascent(); - int bottom_pos = last.second->position() + last.second->descent(); + int top_pos = first.second->top(); + int bottom_pos = last.second->bottom(); 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) { @@ -727,17 +764,21 @@ void BufferView::scrollDocView(int const pixels, bool update) if (pixels == 0) return; - // If the offset is less than 2 screen height, prefer to scroll instead. - if (abs(pixels) <= 2 * height_) { + // If part of the existing paragraphs will remain visible, prefer to + // scroll + TextMetrics const & tm = textMetrics(&buffer_.text()); + if (tm.first().second->top() - pixels <= height_ + && tm.last().second->bottom() - pixels >= 0) { + LYXERR(Debug::SCROLLING, "small skip"); d->anchor_ypos_ -= pixels; - processUpdateFlags(Update::Force); + processUpdateFlags(Update::ForceDraw); return; } // cut off at the top if (pixels <= d->scrollbarParameters_.min) { DocIterator dit = doc_iterator_begin(&buffer_); - showCursor(dit, false, update); + showCursor(dit, SCROLL_VISIBLE, update); LYXERR(Debug::SCROLLING, "scroll to top"); return; } @@ -746,11 +787,12 @@ void BufferView::scrollDocView(int const pixels, bool update) if (pixels >= d->scrollbarParameters_.max) { DocIterator dit = doc_iterator_end(&buffer_); dit.backwardPos(); - showCursor(dit, false, update); + showCursor(dit, SCROLL_VISIBLE, update); LYXERR(Debug::SCROLLING, "scroll to bottom"); return; } + LYXERR(Debug::SCROLLING, "search paragraph"); // find paragraph at target position int par_pos = d->scrollbarParameters_.min; pit_type i = 0; @@ -771,7 +813,7 @@ void BufferView::scrollDocView(int const pixels, bool update) DocIterator dit = doc_iterator_begin(&buffer_); dit.pit() = i; LYXERR(Debug::SCROLLING, "pixels = " << pixels << " -> scroll to pit " << i); - showCursor(dit, false, update); + showCursor(dit, SCROLL_VISIBLE, update); } @@ -957,41 +999,36 @@ int BufferView::workWidth() const void BufferView::recenter() { - showCursor(d->cursor_, true, true); + showCursor(d->cursor_, SCROLL_CENTER, true); } void BufferView::showCursor() { - showCursor(d->cursor_, false, true); -} - - -void BufferView::showCursor(DocIterator const & dit, - bool recenter, bool update) -{ - if (scrollToCursor(dit, recenter) && update) - processUpdateFlags(Update::Force); + showCursor(d->cursor_, SCROLL_VISIBLE, true); } -void BufferView::scrollToCursor() +void BufferView::showCursor(DocIterator const & dit, ScrollType how, + bool update) { - if (scrollToCursor(d->cursor_, false)) + if (scrollToCursor(dit, how) && update) processUpdateFlags(Update::Force); } -bool BufferView::scrollToCursor(DocIterator const & dit, bool const recenter) +bool BufferView::scrollToCursor(DocIterator const & dit, ScrollType how) { // We are not properly started yet, delay until resizing is done. if (height_ == 0) return false; - if (recenter) - LYXERR(Debug::SCROLLING, "recentering and scrolling to cursor"); + if (how == SCROLL_CENTER) + LYXERR(Debug::SCROLLING, "Centering cursor in workarea"); + else if (how == SCROLL_TOP) + LYXERR(Debug::SCROLLING, "Setting cursor to top of workarea"); else - LYXERR(Debug::SCROLLING, "scrolling to cursor"); + LYXERR(Debug::SCROLLING, "Making sure cursor is visible in workarea"); CursorSlice const & bot = dit.bottom(); TextMetrics & tm = textMetrics(bot.text()); @@ -1010,7 +1047,7 @@ bool BufferView::scrollToCursor(DocIterator const & dit, bool const recenter) else if (bot_pit == tm.last().first + 1) tm.newParMetricsDown(); - if (tm.contains(bot_pit)) { + if (tm.contains(bot_pit) && how == SCROLL_VISIBLE) { ParagraphMetrics const & pm = tm.parMetrics(bot_pit); LBUFERR(!pm.rows().empty()); // FIXME: smooth scrolling doesn't work in mathed. @@ -1021,14 +1058,12 @@ bool BufferView::scrollToCursor(DocIterator const & dit, bool const recenter) Dimension const & row_dim = inner_pm.getRow(cs.pos(), dit.boundary()).dim(); int scrolled = 0; - if (recenter) - scrolled = scroll(ypos - height_/2); // 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 (row_dim.height() > height_) { if (ypos < defaultRowHeight()) scrolled = scroll(ypos - height_ / 4); else if (ypos > height_ - defaultRowHeight()) @@ -1057,28 +1092,26 @@ bool BufferView::scrollToCursor(DocIterator const & dit, bool const recenter) d->inlineCompletionPos_ = DocIterator(); tm.redoParagraph(bot_pit); - ParagraphMetrics const & pm = tm.parMetrics(bot_pit); int const offset = coordOffset(dit).y_; - + pit_type const old_pit = d->anchor_pit_; d->anchor_pit_ = bot_pit; + CursorSlice const & cs = dit.innerTextSlice(); ParagraphMetrics const & inner_pm = textMetrics(cs.text()).parMetrics(cs.pit()); Dimension const & row_dim = inner_pm.getRow(cs.pos(), dit.boundary()).dim(); - 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(); + int const old_ypos = d->anchor_ypos_; + d->anchor_ypos_ = - offset + row_dim.ascent(); + if (how == SCROLL_CENTER) + d->anchor_ypos_ += height_/2 - row_dim.height() / 2; else if (offset > height_) d->anchor_ypos_ = height_ - offset - defaultRowHeight(); else d->anchor_ypos_ = defaultRowHeight() * 2; - return true; + return d->anchor_ypos_ != old_ypos || d->anchor_pit_ != old_pit; } @@ -1092,8 +1125,6 @@ void BufferView::makeDocumentClass() void BufferView::updateDocumentClass(DocumentClassConstPtr olddc) { - message(_("Converting document to new document class...")); - StableDocIterator backcur(d->cursor_); ErrorList & el = buffer_.errorList("Class Switch"); cap::switchBetweenClasses( @@ -1221,6 +1252,7 @@ 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_BIBTEX_DATABASE_LIST: case LFUN_STATISTICS: case LFUN_KEYMAP_OFF: case LFUN_KEYMAP_PRIMARY: @@ -1318,6 +1350,17 @@ bool BufferView::getStatus(FuncRequest const & cmd, FuncStatus & flag) flag.setEnabled(cur.selection()); break; + case LFUN_STATISTICS_REFERENCE_CLAMP: { + // disable optitem reset if clamp not used + if (cmd.argument() == "reset" && d->stats_ref_value_c_ == 0) { + flag.setEnabled(false); + break; + } + flag.setEnabled(true); + break; + + } + default: return false; } @@ -1484,6 +1527,7 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) else { dr.screenUpdate(Update::Force | Update::FitCursor); dr.forceBufferUpdate(); + resetInlineCompletionPos(); if (buffer().params().citeEngine() != engine || buffer().params().citeEngineType() != enginetype) buffer().invalidateCiteLabels(); @@ -1504,6 +1548,7 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) else { dr.screenUpdate(Update::Force | Update::FitCursor); dr.forceBufferUpdate(); + resetInlineCompletionPos(); if (buffer().params().citeEngine() != engine || buffer().params().citeEngineType() != enginetype) buffer().invalidateCiteLabels(); @@ -1582,8 +1627,8 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) success = setCursorFromEntries({id, pos}, {id_end, pos_end}); } - if (success) - dr.screenUpdate(Update::Force | Update::FitCursor); + if (success && scrollToCursor(d->cursor_, SCROLL_TOP)) + dr.screenUpdate(Update::Force); } else { // Switch to other buffer view and resend cmd lyx::dispatch(FuncRequest( @@ -1596,19 +1641,13 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) } case LFUN_NOTE_NEXT: - gotoInset(this, { NOTE_CODE }, false); - // FIXME: if SinglePar is changed to act on the inner - // paragraph, this will not be OK anymore. The update is - // useful for auto-open collapsible insets. - dr.screenUpdate(Update::SinglePar | Update::FitCursor); + if (gotoInset(this, { NOTE_CODE }, false)) + dr.screenUpdate(Update::Force); break; case LFUN_REFERENCE_NEXT: { - gotoInset(this, { LABEL_CODE, REF_CODE }, true); - // FIXME: if SinglePar is changed to act on the inner - // paragraph, this will not be OK anymore. The update is - // useful for auto-open collapsible insets. - dr.screenUpdate(Update::SinglePar | Update::FitCursor); + if (gotoInset(this, { LABEL_CODE, REF_CODE }, true)) + dr.screenUpdate(Update::Force); break; } @@ -1726,6 +1765,8 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) } if (cur.selection()) pattern = cur.selectionAsString(false); + else if (!cur.inTexted()) + break; // not suitable for selectWord at cursor else { pos_type spos = cur.pos(); cur.innerText()->selectWord(cur, WHOLE_WORD); @@ -1762,6 +1803,84 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) break; } + case LFUN_INDEX_TAG_ALL: { + if (cur.pos() == 0) + // nothing precedes + break; + + Inset * ins = cur.nextInset(); + if (!ins || ins->lyxCode() != INDEX_CODE) + // not at index inset + break; + + // clone the index inset + InsetIndex * cins = + new InsetIndex(static_cast(*cur.nextInset())); + // In order to avoid duplication, we compare the + // LaTeX output if we find another index inset after + // the word + odocstringstream oilatex; + otexstream oits(oilatex); + OutputParams rp(&cur.buffer()->params().encoding()); + ins->latex(oits, rp); + cap::copyInsetToTemp(cur, cins); + + // move backwards into preceding word + // skip over other index insets + cur.backwardPosIgnoreCollapsed(); + while (true) { + if (cur.inset().lyxCode() == INDEX_CODE) + cur.pop_back(); + else if (cur.prevInset() && cur.prevInset()->lyxCode() == INDEX_CODE) + cur.backwardPosIgnoreCollapsed(); + else + break; + } + if (!cur.inTexted()) { + // Nothing to do here. + setCursorFromInset(ins); + break; + } + // Get word or selection + cur.text()->selectWord(cur, WHOLE_WORD); + docstring const searched_string = cur.selectionAsString(false); + if (searched_string.empty()) + break; + // Start from the beginning + lyx::dispatch(FuncRequest(LFUN_BUFFER_BEGIN)); + while (findOne(this, searched_string, + false,// case sensitive + true,// match whole word only + true,// forward + false,//find deleted + false,//check wrap + false,// auto-wrap + false,// instant + false// only selection + )) { + cur.clearSelection(); + Inset * ains = cur.nextInset(); + if (ains && ains->lyxCode() == INDEX_CODE) { + // We have an index inset. + // Check whether it has the same + // LaTeX content and move on if so. + odocstringstream filatex; + otexstream fits(filatex); + ains->latex(fits, rp); + if (oilatex.str() == filatex.str()) + continue; + } + // Paste the inset and possibly continue + cap::pasteFromTemp(cursor(), cursor().buffer()->errorList("Paste")); + } + // Go back to start position. + setCursorFromInset(ins); + dr.screenUpdate(cur.result().screenUpdate()); + if (cur.result().needBufferUpdate()) + dr.forceBufferUpdate(); + break; + } + case LFUN_MARK_OFF: cur.clearSelection(); dr.setMessage(from_utf8(N_("Mark off"))); @@ -1856,6 +1975,25 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) break; } + case LFUN_BIBTEX_DATABASE_LIST: { + docstring_list const & files = buffer_.getBibfiles(); + bool first = true; + docstring result; + char const separator(os::path_separator()); + for (auto const & file : files) { + if (first) + first = false; + else + result += separator; + + FileName const fn = buffer_.getBibfilePath(file); + string const path = fn.realPath(); + result += from_utf8(os::external_path(path)); + } + dr.setMessage(result); + break; + } + case LFUN_STATISTICS: { DocIterator from, to; if (cur.selection()) { @@ -1881,21 +2019,38 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) message += _("One word"); message += "\n"; if (chars_blanks != 1) - message += bformat(_("%1$d characters (including blanks)"), - chars_blanks); + message += bformat(_("%1$d characters"), chars_blanks); else - message += _("One character (including blanks)"); + message += _("One character"); message += "\n"; if (chars != 1) - message += bformat(_("%1$d characters (excluding blanks)"), - chars); + message += bformat(_("%1$d characters (no blanks)"), chars); else - message += _("One character (excluding blanks)"); + message += _("One character (no blanks)"); Alert::information(_("Statistics"), message); } break; + case LFUN_STATISTICS_REFERENCE_CLAMP: { + d->stats_update_trigger_ = true; + if (cmd.argument() == "reset") { + d->stats_ref_value_w_ = d->stats_ref_value_c_ = d->stats_ref_value_nb_ = 0; + break; + } + + DocIterator from, to; + from = doc_iterator_begin(&buffer_); + to = doc_iterator_end(&buffer_); + buffer_.updateStatistics(from, to); + + d->stats_ref_value_w_ = buffer_.wordCount(); + d->stats_ref_value_c_ = buffer_.charCount(true); + d->stats_ref_value_nb_ = buffer_.charCount(false); + break; + } + + case LFUN_SCREEN_UP: case LFUN_SCREEN_DOWN: { Point p = getPos(cur); @@ -1916,7 +2071,8 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) cur.setCursor(doc_iterator_begin(cur.buffer())); cur.selHandle(false); // Force an immediate computation of metrics because we need it below - updateMetrics(); + if (scrolled) + processUpdateFlags(Update::Force); d->text_metrics_[&buffer_.text()].editXY(cur, p.x_, p.y_, true, act == LFUN_SCREEN_UP); @@ -2072,21 +2228,22 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) // an arbitrary number to limit number of iterations const int max_iter = 100000; int iterations = 0; - Cursor & curs = d->cursor_; - Cursor const savecur = curs; - curs.reset(); - if (!curs.nextInset()) - curs.forwardInset(); - curs.beginUndoGroup(); - while(curs && iterations < max_iter) { - Inset * const ins = curs.nextInset(); + Cursor & bvcur = d->cursor_; + Cursor const savecur = bvcur; + bvcur.reset(); + if (!bvcur.nextInset()) + bvcur.forwardInset(); + bvcur.beginUndoGroup(); + while(bvcur && iterations < max_iter) { + Inset * const ins = bvcur.nextInset(); if (!ins) break; docstring insname = ins->layoutName(); while (!insname.empty()) { if (insname == name || name == from_utf8("*")) { - curs.recordUndo(); lyx::dispatch(fr, dr); + // we do not want to remember selection here + bvcur.clearSelection(); ++iterations; break; } @@ -2096,11 +2253,11 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) insname = insname.substr(0, i); } // if we did not delete the inset, skip it - if (!curs.nextInset() || curs.nextInset() == ins) - curs.forwardInset(); + if (!bvcur.nextInset() || bvcur.nextInset() == ins) + bvcur.forwardInset(); } - curs = savecur; - curs.fixIfBroken(); + bvcur = savecur; + bvcur.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) @@ -2108,8 +2265,8 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) * 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. */ - curs.recordUndo(); - curs.endUndoGroup(); + bvcur.recordUndo(); + bvcur.endUndoGroup(); dr.screenUpdate(Update::Force); dr.forceBufferUpdate(); @@ -2209,6 +2366,28 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) string icstr = InsetCommand::params2string(icp); FuncRequest fr(LFUN_INSET_INSERT, icstr); lyx::dispatch(fr); + + // if the request comes from the LyX server, then we + // return a list of the undefined keys, in case some + // action could be taken. + if (cmd.origin() != FuncRequest::LYXSERVER) + break; + + vector keys = getVectorFromString(from_utf8(arg)); + vector::iterator it = keys.begin(); + vector::const_iterator end = keys.end(); + + BiblioInfo const & bibInfo = buffer_.masterBibInfo(); + const BiblioInfo::const_iterator bibEnd = bibInfo.end(); + while (it != end) { + if (bibInfo.find(*it) != bibEnd) { + it = keys.erase(it); + end = keys.end(); + } else + ++it; + } + dr.setMessage(getStringFromVector(keys)); + break; } @@ -2342,14 +2521,19 @@ void BufferView::clearSelection() void BufferView::resize(int width, int height) { - // Update from work area - width_ = width; height_ = height; + // Update metrics only if width has changed + if (width != width_) { + width_ = width; - // Clear the paragraph height cache. - d->par_height_.clear(); - // Redo the metrics. - updateMetrics(); + // Clear the paragraph height cache. + d->par_height_.clear(); + // Redo the metrics. + updateMetrics(true); + } + // metrics is OK, full drawing is necessary now + d->update_flags_ = (d->update_flags_ & ~Update::Force) | Update::ForceDraw; + d->update_strategy_ = FullScreenUpdate; } @@ -2383,12 +2567,50 @@ Inset const * BufferView::getCoveringInset(Text const & text, } +Inset const * BufferView::clickableMathInset(InsetMathNest const * inset, + CoordCache::Insets const & inset_cache, int x, int y) const +{ + for (size_t i = 0; i < inset->nargs(); ++i) { + MathData const & ar = inset->cell(i); + for (size_t j = 0; j < ar.size(); ++j) { + string const name = lyxerr.debugging(Debug::MATHED) + ? insetName(ar[j].nucleus()->lyxCode()) + : string(); + LYXERR(Debug::MATHED, "Checking inset: " << name); + if (ar[j].nucleus()->clickable(*this, x, y)) { + if (inset_cache.covers(ar[j].nucleus(), x, y)) { + LYXERR(Debug::MATHED, "Clickable inset: " + << name); + return ar[j].nucleus(); + } + } + InsetMathNest const * imn = + ar[j].nucleus()->asNestInset(); + if (imn) { + Inset const * inner = + clickableMathInset(imn, inset_cache, x, y); + if (inner) + return inner; + } + } + } + return nullptr; +} + + 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); + if (covering_inset && covering_inset->asInsetMath()) { + Inset const * inner_inset = clickableMathInset( + covering_inset->asInsetMath()->asNestInset(), + coordCache().getInsets(), x, y); + if (inner_inset) + covering_inset = inner_inset; + } d->clickable_inset_ = covering_inset && covering_inset->clickable(*this, x, y); @@ -2435,10 +2657,37 @@ void BufferView::clearLastInset(Inset * inset) const } +bool BufferView::mouseSelecting() const +{ + return d->mouse_selecting_; +} + + +int BufferView::stats_ref_value_w() const +{ + return d->stats_ref_value_w_; +} + + +int BufferView::stats_ref_value_c() const +{ + return d->stats_ref_value_c_; +} + + +int BufferView::stats_ref_value_nb() const +{ + return d->stats_ref_value_nb_; +} + + void BufferView::mouseEventDispatch(FuncRequest const & cmd0) { //lyxerr << "[ cmd0 " << cmd0 << "]" << endl; + if (!ready()) + return; + // This is only called for mouse related events including // LFUN_FILE_OPEN generated by drag-and-drop. FuncRequest cmd = cmd0; @@ -2457,6 +2706,9 @@ void BufferView::mouseEventDispatch(FuncRequest const & cmd0) d->mouse_position_cache_.x_ = cmd.x(); d->mouse_position_cache_.y_ = cmd.y(); + d->mouse_selecting_ = + cmd.action() == LFUN_MOUSE_MOTION && cmd.button() == mouse_button::button1; + if (cmd.action() == LFUN_MOUSE_MOTION && cmd.button() == mouse_button::none) { updateHoveredInset(); return; @@ -2482,7 +2734,7 @@ void BufferView::mouseEventDispatch(FuncRequest const & cmd0) // Put anchor at the same position. cur.resetAnchor(); - cur.beginUndoGroup(); + old.beginUndoGroup(); // Try to dispatch to an non-editable inset near this position // via the temp cursor. If the inset wishes to change the real @@ -2498,13 +2750,13 @@ void BufferView::mouseEventDispatch(FuncRequest const & cmd0) // Notify left insets if (cur != old) { - bool badcursor = old.fixIfBroken() | cur.fixIfBroken(); - badcursor |= notifyCursorLeavesOrEnters(old, cur); + bool badcursor = old.fixIfBroken() || cur.fixIfBroken(); + badcursor = badcursor || notifyCursorLeavesOrEnters(old, cur); if (badcursor) cursor().fixIfBroken(); } - cur.endUndoGroup(); + old.endUndoGroup(); // Do we have a selection? theSelection().haveSelection(cursor().selection()); @@ -2543,7 +2795,7 @@ int BufferView::scrollDown(int pixels) int const ymax = height_ + pixels; while (true) { pair last = tm.last(); - int bottom_pos = last.second->position() + last.second->descent(); + int bottom_pos = last.second->bottom(); if (lyxrc.scroll_below_document) bottom_pos += height_ - minVisiblePart(); if (last.first + 1 == int(text->paragraphs().size())) { @@ -2568,7 +2820,7 @@ int BufferView::scrollUp(int pixels) int ymin = - pixels; while (true) { pair first = tm.first(); - int top_pos = first.second->position() - first.second->ascent(); + int top_pos = first.second->top(); if (first.first == 0) { if (top_pos >= 0) return 0; @@ -2588,7 +2840,7 @@ bool BufferView::setCursorFromRow(int row) { TexRow::TextEntry start, end; tie(start,end) = buffer_.texrow().getEntriesFromRow(row); - LYXERR(Debug::LATEX, + LYXERR(Debug::OUTFILE, "setCursorFromRow: for row " << row << ", TexRow has found " "start (id=" << start.id << ",pos=" << start.pos << "), " "end (id=" << end.id << ",pos=" << end.pos << ")"); @@ -2810,6 +3062,25 @@ void BufferView::putSelectionAt(DocIterator const & cur, } +void BufferView::setSelection(DocIterator const & from, + DocIterator const & to) +{ + if (from.pit() != to.pit()) { + // there are multiple paragraphs in selection + cursor().setCursor(from); + cursor().clearSelection(); + cursor().selection(true); + cursor().setCursor(to); + cursor().selection(true); + } else { + // only single paragraph + int const size = to.pos() - from.pos(); + putSelectionAt(from, size, false); + } + processUpdateFlags(Update::Force | Update::FitCursor); +} + + bool BufferView::selectIfEmpty(DocIterator & cur) { if ((cur.inTexted() && !cur.paragraph().empty()) @@ -2861,24 +3132,36 @@ Cursor const & BufferView::cursor() const bool BufferView::singleParUpdate() { - Text & buftext = buffer_.text(); - pit_type const bottom_pit = d->cursor_.bottom().pit(); - TextMetrics & tm = textMetrics(&buftext); - Dimension const old_dim = tm.parMetrics(bottom_pit).dim(); + CursorSlice const & its = d->cursor_.innerTextSlice(); + pit_type const pit = its.pit(); + TextMetrics & tm = textMetrics(its.text()); + Dimension const old_dim = tm.parMetrics(pit).dim(); // 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 - // recursively descend) - tm.redoParagraph(bottom_pit); - ParagraphMetrics & pm = tm.parMetrics(bottom_pit); - if (pm.height() != old_dim.height()) { - // Paragraph height has changed so we cannot proceed to - // the singlePar optimisation. + /* Try to rebreak only the paragraph containing the cursor (if + * this paragraph contains insets etc., rebreaking will + * recursively descend). We need a full redraw if either + * 1/ the height has changed + * or + * 2/ the width has changed and it was equal to the textmetrics + * width; the goal is to catch the case of a one-row inset that + * grows with its contents, but optimize the case of typing at + * the end of a mmultiple-row paragraph. + * + * NOTE: if only the height has changed, then it should be + * possible to update all metrics at minimal cost. However, + * since this is risky, we do not try that right now. + */ + tm.redoParagraph(pit); + ParagraphMetrics & pm = tm.parMetrics(pit); + if (pm.height() != old_dim.height() + || (pm.width() != old_dim.width() && old_dim.width() == tm.width())) { + // Paragraph height or width has changed so we cannot proceed + // to the singlePar optimisation. + LYXERR(Debug::PAINTING, "SinglePar optimization failed."); return false; } // Since position() points to the baseline of the first row, we @@ -2886,39 +3169,43 @@ bool BufferView::singleParUpdate() // the height does not change but the ascent does. pm.setPosition(pm.position() - old_dim.ascent() + pm.ascent()); - tm.updatePosCache(bottom_pit); + tm.updatePosCache(pit); - LYXERR(Debug::PAINTING, "\ny1: " << pm.position() - pm.ascent() - << " y2: " << pm.position() + pm.descent() - << " pit: " << bottom_pit - << " singlepar: 1"); + LYXERR(Debug::PAINTING, "\ny1: " << pm.top() << " y2: " << pm.bottom() + << " pit: " << pit << " singlepar: 1"); return true; } void BufferView::updateMetrics() { - updateMetrics(d->update_flags_); + updateMetrics(true); + // metrics is done, full drawing is necessary now + d->update_flags_ = (d->update_flags_ & ~Update::Force) | Update::ForceDraw; d->update_strategy_ = FullScreenUpdate; } -void BufferView::updateMetrics(Update::flags & update_flags) +void BufferView::updateMetrics(bool force) { - if (height_ == 0 || width_ == 0) + if (!ready()) return; + //LYXERR0("updateMetrics " << _v_(force)); + Text & buftext = buffer_.text(); - pit_type const npit = int(buftext.paragraphs().size()); + pit_type const lastpit = int(buftext.paragraphs().size()) - 1; - // Clear out the position cache in case of full screen redraw, - d->coord_cache_.clear(); - d->math_rows_.clear(); + if (force) { + // Clear out the position cache in case of full screen redraw, + d->coord_cache_.clear(); + d->math_rows_.clear(); - // Clear out paragraph metrics to avoid having invalid metrics - // in the cache from paragraphs not relayouted below - // The complete text metrics will be redone. - d->text_metrics_.clear(); + // Clear out paragraph metrics to avoid having invalid metrics + // in the cache from paragraphs not relayouted below. The + // complete text metrics will be redone. + d->text_metrics_.clear(); + } TextMetrics & tm = textMetrics(&buftext); @@ -2926,71 +3213,55 @@ void BufferView::updateMetrics(Update::flags & update_flags) if (d->inlineCompletionPos_.fixIfBroken()) d->inlineCompletionPos_ = DocIterator(); - if (d->anchor_pit_ >= npit) + if (d->anchor_pit_ > lastpit) // The anchor pit must have been deleted... - d->anchor_pit_ = npit - 1; + d->anchor_pit_ = lastpit; - // Rebreak anchor paragraph. - tm.redoParagraph(d->anchor_pit_); - ParagraphMetrics & anchor_pm = tm.parMetrics(d->anchor_pit_); + // Update metrics around the anchor + tm.updateMetrics(d->anchor_pit_, d->anchor_ypos_, height_); - // position anchor - if (d->anchor_pit_ == 0) { - int scrollRange = d->scrollbarParameters_.max - d->scrollbarParameters_.min; + // Check that the end of the document is not too high + int const min_visible = lyxrc.scroll_below_document ? minVisiblePart() : height_; + if (tm.last().first == lastpit && tm.last().second->bottom() < min_visible) { + d->anchor_ypos_ += min_visible - tm.last().second->bottom(); + LYXERR(Debug::SCROLLING, "Too high, adjusting anchor ypos to " << d->anchor_ypos_); + tm.updateMetrics(d->anchor_pit_, d->anchor_ypos_, height_); + } - // Complete buffer visible? Then it's easy. - if (scrollRange == 0) - d->anchor_ypos_ = anchor_pm.ascent(); - else { - // avoid empty space above the first row - d->anchor_ypos_ = min(d->anchor_ypos_, anchor_pm.ascent()); - } - } - anchor_pm.setPosition(d->anchor_ypos_); - tm.updatePosCache(d->anchor_pit_); - - LYXERR(Debug::PAINTING, "metrics: " - << " anchor pit = " << d->anchor_pit_ - << " anchor ypos = " << d->anchor_ypos_); - - // Redo paragraphs above anchor if necessary. - int y1 = d->anchor_ypos_ - anchor_pm.ascent(); - // We are now just above the anchor paragraph. - pit_type pit1 = d->anchor_pit_ - 1; - for (; pit1 >= 0 && y1 >= 0; --pit1) { - tm.redoParagraph(pit1); - ParagraphMetrics & pm = tm.parMetrics(pit1); - y1 -= pm.descent(); - // Save the paragraph position in the cache. - pm.setPosition(y1); - tm.updatePosCache(pit1); - y1 -= pm.ascent(); - } - - // Redo paragraphs below the anchor if necessary. - int y2 = d->anchor_ypos_ + anchor_pm.descent(); - // We are now just below the anchor paragraph. - pit_type pit2 = d->anchor_pit_ + 1; - for (; pit2 < npit && y2 <= height_; ++pit2) { - tm.redoParagraph(pit2); - ParagraphMetrics & pm = tm.parMetrics(pit2); - y2 += pm.ascent(); - // Save the paragraph position in the cache. - pm.setPosition(y2); - tm.updatePosCache(pit2); - y2 += pm.descent(); - } - - LYXERR(Debug::PAINTING, "Metrics: " - << " anchor pit = " << d->anchor_pit_ - << " anchor ypos = " << d->anchor_ypos_ - << " y1 = " << y1 - << " y2 = " << y2 - << " pit1 = " << pit1 - << " pit2 = " << pit2); + // Check that the start of the document is not too low + if (tm.first().first == 0 && tm.first().second->top() > 0) { + d->anchor_ypos_ -= tm.first().second->top(); + LYXERR(Debug::SCROLLING, "Too low, adjusting anchor ypos to " << d->anchor_ypos_); + tm.updateMetrics(d->anchor_pit_, d->anchor_ypos_, height_); + } - // metrics is done, full drawing is necessary now - update_flags = (update_flags & ~Update::Force) | Update::ForceDraw; + /* FIXME: do we want that? It avoids potential issues with old + * paragraphs that should have been recomputed but have not, at + * the price of potential extra metrics computaiton. I do not + * think that the performance gain is high, so that for now the + * extra paragraphs are removed + */ + // Remove paragraphs that are outside of screen + while(tm.first().second->bottom() <= 0) { + //LYXERR0("Forget pit: " << tm.first().first); + tm.forget(tm.first().first); + } + while(tm.last().second->top() > height_) { + //LYXERR0("Forget pit: " << tm.first().first); + tm.forget(tm.last().first); + } + + /* FIXME: if paragraphs outside of the screen are not removed + * above, one has to search for the first visible one here */ + // Normalize anchor for next time + if (d->anchor_pit_ != tm.first().first + || d->anchor_ypos_ != tm.first().second->position()) { + LYXERR(Debug::PAINTING, __func__ << ": Found new anchor pit = " << tm.first().first + << " anchor ypos = " << tm.first().second->position() + << " (was " << d->anchor_pit_ << ", " << d->anchor_ypos_ << ")"); + d->anchor_pit_ = tm.first().first; + d->anchor_ypos_ = tm.first().second->position(); + } // Now update the positions of insets in the cache. updatePosCache(); @@ -3034,6 +3305,7 @@ void BufferView::insertLyXFile(FileName const & fname, bool const ignorelang) // set main language of imported file to context language buf.changeLanguage(buf.language(), d->cursor_.getFont().language()); buffer_.undo().recordUndo(d->cursor_); + cap::replaceSelection(d->cursor_); cap::pasteParagraphList(d->cursor_, pars, buf.params().documentClassPtr(), buf.params().authors(), el); @@ -3153,7 +3425,7 @@ bool BufferView::paragraphVisible(DocIterator const & dit) const void BufferView::caretPosAndDim(Point & p, Dimension & dim) const { Cursor const & cur = cursor(); - if (cur.inMathed()) { + if (cur.inMathed() && hasMathRow(&cur.cell())) { MathRow const & mrow = mathRow(&cur.cell()); dim = mrow.caret_dim; } else { @@ -3196,8 +3468,8 @@ void BufferView::buildCaretGeometry(bool complet) bool const slant = fm.italic() && cur.inTexted() && !cur.selection(); double const slope = slant ? fm.italicSlope() : 0; cg.shapes.push_back( - {{iround(p.x_ + dim.asc * slope), p.y_}, - {iround(p.x_ - dim.des * slope), p.y_ + dim.height()}, + {{iround(p.x_ + dim.asc * slope), p.y_}, + {iround(p.x_ - dim.des * slope), p.y_ + dim.height()}, {iround(p.x_ + dir * dim.wid - dim.des * slope), p.y_ + dim.height()}, {iround(p.x_ + dir * dim.wid + dim.asc * slope), p.y_}} ); @@ -3210,10 +3482,10 @@ void BufferView::buildCaretGeometry(bool complet) int const xx = iround(p.x_ - dim.des * slope); int const yy = p.y_ + dim.height(); cg.shapes.push_back( - {{xx, yy - dim.wid}, + {{xx, yy - dim.wid}, {xx + dir * (dim.wid + lx - 1), yy - dim.wid}, {xx + dir * (dim.wid + lx - 1), yy}, - {xx, yy}} + {xx, yy}} ); } @@ -3226,12 +3498,12 @@ void BufferView::buildCaretGeometry(bool complet) // starting position x int const xx = p.x_ + dir * dim.wid + sx; cg.shapes.push_back( - {{xx, m - d}, - {xx + dir * d, m}, - {xx, m + d}, - {xx, m + d - dim.wid}, + {{xx, m - d}, + {xx + dir * d, m}, + {xx, m + d}, + {xx, m + d - dim.wid}, {xx + dir * d - dim.wid, m}, - {xx, m - d + dim.wid}} + {xx, m - d + dim.wid}} ); } @@ -3359,24 +3631,27 @@ void BufferView::checkCursorScrollOffset() //lyxerr << "cur_x=" << cur_x << ", offset=" << offset << ", row.wid=" << row.width() << ", margin=" << MARGIN << endl; - if (offset != d->horiz_scroll_offset_) + 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_) { - // FIXME: if one uses SingleParUpdate, then home/end - // will not work on long rows. Why? - d->update_strategy_ = FullScreenUpdate; + row.changed(true); + if (d->update_strategy_ == NoScreenUpdate) + d->update_strategy_ = SingleParUpdate; } d->horiz_scroll_offset_ = offset; } +bool BufferView::busy() const +{ + return buffer().undo().activeUndoGroup(); +} + + void BufferView::draw(frontend::Painter & pain, bool paint_caret) { - if (height_ == 0 || width_ == 0) + if (!ready()) return; LYXERR(Debug::PAINTING, (pain.isNull() ? "\t\t--- START NODRAW ---" : "\t\t*** START DRAWING ***")); @@ -3438,7 +3713,7 @@ void BufferView::draw(frontend::Painter & pain, bool paint_caret) // and possibly grey out below pair lastpm = tm.last(); - int const y2 = lastpm.second->position() + lastpm.second->descent(); + int const y2 = lastpm.second->bottom(); if (y2 < height_) { Color color = buffer().isInternal() @@ -3452,23 +3727,27 @@ void BufferView::draw(frontend::Painter & pain, bool paint_caret) // The scrollbar needs an update. // FIXME: does it always? see ticket #11947. - updateScrollbar(); + updateScrollbarParameters(); - // Normalize anchor for next time + // Normalize anchor for next time (in case updateMetrics did not do it yet) + // FIXME: is this useful? pair firstpm = tm.first(); pair lastpm = tm.last(); for (pit_type pit = firstpm.first; pit <= lastpm.first; ++pit) { ParagraphMetrics const & pm = tm.parMetrics(pit); - if (pm.position() + pm.descent() > 0) { + if (pm.bottom() > 0) { if (d->anchor_pit_ != pit || d->anchor_ypos_ != pm.position()) - LYXERR(Debug::PAINTING, "Found new anchor pit = " << d->anchor_pit_ - << " anchor ypos = " << d->anchor_ypos_); + LYXERR0(__func__ << ": Found new anchor pit = " << pit + << " anchor ypos = " << pm.position() + << " (was " << d->anchor_pit_ << ", " << d->anchor_ypos_ << ")" + "\nIf you see this message, please report."); d->anchor_pit_ = pit; d->anchor_ypos_ = pm.position(); break; } } + if (!pain.isNull()) { // reset the update flags, everything has been done d->update_flags_ = Update::None; @@ -3662,4 +3941,14 @@ bool BufferView::clickableInset() const return d->clickable_inset_; } + +bool BufferView::stats_update_trigger() +{ + if (d->stats_update_trigger_) { + d->stats_update_trigger_ = false; + return true; + } + return false; +} + } // namespace lyx