X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FBufferView.cpp;h=b36b8ee5b6a694fc226862fead4a719e370946bb;hb=293b8dbe67dc025d03d0523d0079f71f5ab62ce3;hp=4e704e3249fac83c947672941961eaef892926a4;hpb=d38eddb397de982a379d8c0644c6ca2190c67fe9;p=features.git diff --git a/src/BufferView.cpp b/src/BufferView.cpp index 4e704e3249..b36b8ee5b6 100644 --- a/src/BufferView.cpp +++ b/src/BufferView.cpp @@ -50,14 +50,19 @@ #include "insets/InsetRef.h" #include "insets/InsetText.h" +#include "mathed/InsetMathNest.h" +#include "mathed/InsetMathRef.h" #include "mathed/MathData.h" +#include "mathed/MathRow.h" #include "frontends/alert.h" +#include "frontends/CaretGeometry.h" #include "frontends/Delegates.h" #include "frontends/FontMetrics.h" #include "frontends/NullPainter.h" #include "frontends/Painter.h" #include "frontends/Selection.h" +#include "frontends/Clipboard.h" #include "support/convert.h" #include "support/debug.h" @@ -166,18 +171,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(), false, true); } @@ -223,7 +228,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. @@ -271,9 +276,6 @@ struct BufferView::Private */ frontend::GuiBufferViewDelegate * gui_; - /// Cache for Find Next - FuncRequest search_request_cache_; - /// map edited_insets_; @@ -288,6 +290,10 @@ struct BufferView::Private CursorSlice current_row_slice_; /// are we hovering something that we can click bool clickable_inset_; + /// shape of the caret + frontend::CaretGeometry caret_geometry_; + /// + bool mouse_selecting_ = false; }; @@ -327,16 +333,23 @@ BufferView::~BufferView() } -int BufferView::rightMargin() const +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; + } } @@ -348,15 +361,15 @@ int BufferView::leftMargin() const int BufferView::topMargin() const { - // original value was 20px, which is 0.2in at 100dpi - return zoomedPixels(20); + // Original value was 20px at 100dpi. For internal buffers like in + // advanced search and replace, a value of 5px is enough. + return zoomedPixels(buffer().isInternal() ? 5 : 20); } int BufferView::bottomMargin() const { - // original value was 20px, which is 0.2in at 100dpi - return zoomedPixels(20); + return topMargin(); } @@ -444,6 +457,26 @@ Buffer const & BufferView::buffer() const } +docstring const & BufferView::searchRequestCache() const +{ + return theClipboard().getFindBuffer(); +} + + +void BufferView::setSearchRequestCache(docstring const & text) +{ + bool casesensitive; + bool matchword; + bool forward; + bool wrap; + bool instant; + bool onlysel; + docstring const search = string2find(text, casesensitive, matchword, + forward, wrap, instant, onlysel); + theClipboard().setFindBuffer(search); +} + + bool BufferView::needsFitCursor() const { if (cursorStatus(d->cursor_) == CUR_INSIDE) { @@ -504,22 +537,22 @@ void BufferView::processUpdateFlags(Update::flags flags) // We handle this before FitCursor because the later will require // correct metrics at cursor position. if (!(flags & Update::ForceDraw) - && (flags & Update::SinglePar) - && !singleParUpdate()) - updateMetrics(flags); + && (flags & Update::SinglePar) + && !singleParUpdate()) + updateMetrics(flags); // 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(), false, false); // Metrics have to be recomputed (maybe again) updateMetrics(); // 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_, false, false); // Metrics have to be recomputed (maybe again) updateMetrics(flags); } @@ -555,7 +588,7 @@ void BufferView::processUpdateFlags(Update::flags flags) } -void BufferView::updateScrollbar() +void BufferView::updateScrollbarParameters() { if (height_ == 0 && width_ == 0) return; @@ -646,13 +679,53 @@ string BufferView::contextMenu(int x, int y) const // Get inset under mouse, if there is one. Inset const * covering_inset = getCoveringInset(buffer_.text(), x, y); - if (covering_inset) + if (covering_inset) { + if (covering_inset->asInsetMath()) { + CoordCache::Insets const & inset_cache = + coordCache().getInsets(); + Inset const * inner_inset = mathContextMenu( + covering_inset->asInsetMath()->asNestInset(), + inset_cache, x, y); + if (inner_inset) + return inner_inset->contextMenu(*this, x, y); + } return covering_inset->contextMenu(*this, x, y); + } return buffer_.inset().contextMenu(*this, x, y); } +Inset const * BufferView::mathContextMenu(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, "Examining inset: " << name); + if (!ar[j].nucleus()->contextMenuName().empty()) { + if (inset_cache.covers(ar[j].nucleus(), x, y)) { + LYXERR(Debug::MATHED, "Hit inset: " + << name); + return ar[j].nucleus(); + } + } + InsetMathNest const * imn = + ar[j].nucleus()->asNestInset(); + if (imn) { + Inset const * inner = + mathContextMenu(imn, inset_cache, x, y); + if (inner) + return inner; + } + } + } + return nullptr; +} + void BufferView::scrollDocView(int const pixels, bool update) { @@ -673,7 +746,7 @@ void BufferView::scrollDocView(int const pixels, bool update) // cut off at the top if (pixels <= d->scrollbarParameters_.min) { DocIterator dit = doc_iterator_begin(&buffer_); - showCursor(dit, false, update); + showCursor(dit, false, false, update); LYXERR(Debug::SCROLLING, "scroll to top"); return; } @@ -682,7 +755,7 @@ 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, false, false, update); LYXERR(Debug::SCROLLING, "scroll to bottom"); return; } @@ -707,7 +780,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, false, false, update); } @@ -791,23 +864,24 @@ void BufferView::bookmarkEditPosition() void BufferView::saveBookmark(unsigned int idx) { + if (buffer().isInternal()) + return; + // 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. - if (!buffer_.isInternal()) { - theSession().bookmarks().save( - buffer_.fileName(), - d->cursor_.bottom().pit(), - d->cursor_.bottom().pos(), - d->cursor_.paragraph().id(), - d->cursor_.pos(), - idx - ); - if (idx) - // emit message signal. - message(_("Save bookmark")); - } + theSession().bookmarks().save( + buffer_.fileName(), + d->cursor_.bottom().pit(), + d->cursor_.bottom().pos(), + d->cursor_.paragraph().id(), + d->cursor_.pos(), + idx + ); + if (idx) + // emit message signal. + message(_("Save bookmark")); } @@ -848,7 +922,7 @@ 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 (bottom_pit < int(buffer_.paragraphs().size())) { + if (!success && bottom_pit < int(buffer_.paragraphs().size())) { dit = doc_iterator_begin(&buffer_); dit.pit() = bottom_pit; @@ -892,45 +966,37 @@ int BufferView::workWidth() const void BufferView::recenter() { - showCursor(d->cursor_, true, true); + showCursor(d->cursor_, true, false, true); } void BufferView::showCursor() { - showCursor(d->cursor_, false, true); + showCursor(d->cursor_, false, false, true); } void BufferView::showCursor(DocIterator const & dit, - bool recenter, bool update) + bool recenter, bool force, bool update) { - if (scrollToCursor(dit, recenter) && update) + if (scrollToCursor(dit, recenter, force) && update) processUpdateFlags(Update::Force); } -void BufferView::scrollToCursor() +bool BufferView::scrollToCursor(DocIterator const & dit, bool const recenter, bool force) { - if (scrollToCursor(d->cursor_, false)) - processUpdateFlags(Update::Force); -} - - -bool BufferView::scrollToCursor(DocIterator const & dit, bool const recenter) -{ - // We are not properly started yet, delay until resizing is - // done. + // 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"); + LYXERR(Debug::SCROLLING, "recentering and scrolling to cursor"); else - LYXERR(Debug::SCROLLING, "scrolling to cursor"); + LYXERR(Debug::SCROLLING, "scrolling to cursor"); CursorSlice const & bot = dit.bottom(); - TextMetrics & tm = d->text_metrics_[bot.text()]; + TextMetrics & tm = textMetrics(bot.text()); pos_type const max_pit = pos_type(bot.text()->paragraphs().size() - 1); pos_type bot_pit = bot.pit(); @@ -946,15 +1012,16 @@ 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) && !force) { ParagraphMetrics const & pm = tm.parMetrics(bot_pit); LBUFERR(!pm.rows().empty()); // FIXME: smooth scrolling doesn't work in mathed. CursorSlice const & cs = dit.innerTextSlice(); - int offset = coordOffset(dit).y_; - int ypos = pm.position() + offset; + int const ypos = pm.position() + coordOffset(dit).y_; + ParagraphMetrics const & inner_pm = + textMetrics(cs.text()).parMetrics(cs.pit()); Dimension const & row_dim = - pm.getRow(cs.pos(), dit.boundary()).dim(); + inner_pm.getRow(cs.pos(), dit.boundary()).dim(); int scrolled = 0; if (recenter) scrolled = scroll(ypos - height_/2); @@ -973,13 +1040,13 @@ bool BufferView::scrollToCursor(DocIterator const & dit, bool const recenter) // If the top part of the row falls of the screen, we scroll // up to align the top of the row with the top of the screen. else if (ypos - row_dim.ascent() < 0 && ypos < height_) { - int ynew = row_dim.ascent(); + int const ynew = row_dim.ascent(); scrolled = scrollUp(ynew - ypos); } // If the bottom of the row falls of the screen, we scroll down. else if (ypos + row_dim.descent() > height_ && ypos > 0) { - int ynew = height_ - row_dim.descent(); + int const ynew = height_ - row_dim.descent(); scrolled = scrollDown(ypos - ynew); } @@ -993,12 +1060,14 @@ bool BufferView::scrollToCursor(DocIterator const & dit, bool const recenter) tm.redoParagraph(bot_pit); ParagraphMetrics const & pm = tm.parMetrics(bot_pit); - int offset = coordOffset(dit).y_; + int const offset = coordOffset(dit).y_; d->anchor_pit_ = bot_pit; CursorSlice const & cs = dit.innerTextSlice(); + ParagraphMetrics const & inner_pm = + textMetrics(cs.text()).parMetrics(cs.pit()); Dimension const & row_dim = - pm.getRow(cs.pos(), dit.boundary()).dim(); + inner_pm.getRow(cs.pos(), dit.boundary()).dim(); if (recenter) d->anchor_ypos_ = height_/2; @@ -1007,9 +1076,9 @@ bool BufferView::scrollToCursor(DocIterator const & dit, bool const recenter) 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(); + d->anchor_ypos_ = height_ - offset - row_dim.descent(); else - d->anchor_ypos_ = defaultRowHeight() * 2; + d->anchor_ypos_ = row_dim.ascent(); return true; } @@ -1018,15 +1087,13 @@ bool BufferView::scrollToCursor(DocIterator const & dit, bool const recenter) void BufferView::makeDocumentClass() { DocumentClassConstPtr olddc = buffer_.params().documentClassPtr(); - buffer_.params().makeDocumentClass(); + buffer_.params().makeDocumentClass(buffer_.isClone(), buffer_.isInternal()); updateDocumentClass(olddc); } void BufferView::updateDocumentClass(DocumentClassConstPtr olddc) { - message(_("Converting document to new document class...")); - StableDocIterator backcur(d->cursor_); ErrorList & el = buffer_.errorList("Class Switch"); cap::switchBetweenClasses( @@ -1149,6 +1216,7 @@ bool BufferView::getStatus(FuncRequest const & cmd, FuncStatus & flag) case LFUN_MARK_OFF: case LFUN_MARK_ON: case LFUN_MARK_TOGGLE: + case LFUN_SEARCH_STRING_SET: case LFUN_SCREEN_RECENTER: case LFUN_SCREEN_SHOW_CURSOR: case LFUN_BIBTEX_DATABASE_ADD: @@ -1177,7 +1245,8 @@ bool BufferView::getStatus(FuncRequest const & cmd, FuncStatus & flag) case LFUN_LABEL_GOTO: flag.setEnabled(!cmd.argument().empty() - || getInsetByCode(cur, REF_CODE)); + || getInsetByCode(cur, REF_CODE) + || getInsetByCode(cur, MATH_REF_CODE)); break; case LFUN_CHANGES_MERGE: @@ -1245,6 +1314,10 @@ bool BufferView::getStatus(FuncRequest const & cmd, FuncStatus & flag) break; } + case LFUN_COPY: + flag.setEnabled(cur.selection()); + break; + default: return false; } @@ -1411,6 +1484,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(); @@ -1431,6 +1505,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(); @@ -1443,6 +1518,7 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) break; case LFUN_BOOKMARK_SAVE: + dr.screenUpdate(Update::Force); saveBookmark(convert(to_utf8(cmd.argument()))); break; @@ -1463,6 +1539,12 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) // eventually call LFUN_PARAGRAPH_GOTO, but it seems best // to have it here. dr.screenUpdate(Update::Force | Update::FitCursor); + } else { + InsetMathRef * minset = + getInsetByCode(cur, MATH_REF_CODE); + if (minset) + lyx::dispatch(FuncRequest(LFUN_LABEL_GOTO, + minset->getTarget())); } break; } @@ -1502,8 +1584,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_, false, true)) + dr.screenUpdate(Update::Force); } else { // Switch to other buffer view and resend cmd lyx::dispatch(FuncRequest( @@ -1516,19 +1598,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; } @@ -1598,63 +1674,74 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) case LFUN_WORD_FIND_FORWARD: case LFUN_WORD_FIND_BACKWARD: { - // FIXME THREAD - // Would it maybe be better if this variable were view specific anyway? - static docstring last_search; docstring searched_string; if (!cmd.argument().empty()) { - last_search = cmd.argument(); + setSearchRequestCache(cmd.argument()); searched_string = cmd.argument(); } else { - searched_string = last_search; + searched_string = searchRequestCache(); } if (searched_string.empty()) break; - bool const fw = act == LFUN_WORD_FIND_FORWARD; docstring const data = - find2string(searched_string, true, false, fw); + find2string(searched_string, false, false, + act == LFUN_WORD_FIND_FORWARD, false, false, false); bool found = lyxfind(this, FuncRequest(LFUN_WORD_FIND, data)); if (found) dr.screenUpdate(Update::Force | Update::FitCursor); + else + dr.setMessage(_("Search string not found!")); break; } 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()) { + docstring arg = cmd.argument(); + if (arg.empty()) + arg = searchRequestCache(); + if (arg.empty()) { lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "findreplace")); break; } - if (lyxfind(this, req)) + if (lyxfind(this, FuncRequest(act, arg))) dr.screenUpdate(Update::Force | Update::FitCursor); + else + dr.setMessage(_("Search string not found!")); - d->search_request_cache_ = req; + setSearchRequestCache(arg); break; } - case LFUN_WORD_REPLACE: { - bool has_deleted = false; - if (cur.selection()) { - DocIterator beg = cur.selectionBegin(); - DocIterator end = cur.selectionEnd(); - if (beg.pit() == end.pit()) { - for (pos_type p = beg.pos() ; p < end.pos() ; ++p) { - if (!cur.inMathed() && cur.paragraph().isDeleted(p)) { - has_deleted = true; - break; - } - } - } + case LFUN_SEARCH_STRING_SET: { + docstring pattern = cmd.argument(); + if (!pattern.empty()) { + setSearchRequestCache(pattern); + break; } - if (lyxreplace(this, cmd, has_deleted)) { + 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); + pattern = cur.selectionAsString(false); + cur.selection(false); + cur.pos() = spos; + } + setSearchRequestCache(pattern); + break; + } + + case LFUN_WORD_REPLACE: { + if (lyxreplace(this, cmd)) { dr.forceBufferUpdate(); dr.screenUpdate(Update::Force | Update::FitCursor); } + else + dr.setMessage(_("Search string not found!")); break; } @@ -1792,16 +1879,14 @@ 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); } @@ -1827,7 +1912,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 - processUpdateFlags(Update::Force); + if (scrolled) + processUpdateFlags(Update::Force); d->text_metrics_[&buffer_.text()].editXY(cur, p.x_, p.y_, true, act == LFUN_SCREEN_UP); @@ -1983,21 +2069,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; } @@ -2007,11 +2094,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) @@ -2019,8 +2106,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(); @@ -2174,6 +2261,18 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) break; } + case LFUN_COPY: + // With multi-cell table content, we pass down to the inset + if (cur.inTexted() && cur.selection() + && cur.selectionBegin().idx() != cur.selectionEnd().idx()) { + buffer_.dispatch(cmd, dr); + dispatched = dr.dispatched(); + break; + } + cap::copySelection(cur); + cur.message(_("Copy")); + break; + default: // OK, so try the Buffer itself... buffer_.dispatch(cmd, dr); @@ -2282,12 +2381,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); @@ -2334,6 +2471,12 @@ void BufferView::clearLastInset(Inset * inset) const } +bool BufferView::mouseSelecting() const +{ + return d->mouse_selecting_; +} + + void BufferView::mouseEventDispatch(FuncRequest const & cmd0) { //lyxerr << "[ cmd0 " << cmd0 << "]" << endl; @@ -2356,6 +2499,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; @@ -2397,8 +2543,8 @@ 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(); } @@ -2408,7 +2554,7 @@ void BufferView::mouseEventDispatch(FuncRequest const & cmd0) // Do we have a selection? theSelection().haveSelection(cursor().selection()); - if (cur.needBufferUpdate()) { + if (cur.needBufferUpdate() || buffer().needUpdate()) { cur.clearBufferUpdate(); buffer().updateBuffer(); } @@ -2487,7 +2633,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 << ")"); @@ -2934,7 +3080,8 @@ void BufferView::insertLyXFile(FileName const & fname, bool const ignorelang) buf.changeLanguage(buf.language(), d->cursor_.getFont().language()); buffer_.undo().recordUndo(d->cursor_); cap::pasteParagraphList(d->cursor_, pars, - buf.params().documentClassPtr(), el); + buf.params().documentClassPtr(), + buf.params().authors(), el); res = _("Document %1$s inserted."); } else { res = _("Could not insert document %1$s"); @@ -3057,7 +3204,9 @@ void BufferView::caretPosAndDim(Point & p, Dimension & dim) const } else { Font const font = cur.real_current_font; frontend::FontMetrics const & fm = theFontMetrics(font); - dim.wid = fm.lineWidth(); + // lineWidth() can be 0 to mean 'thin line' on HiDpi, but the + // caret drawing code is not prepared for that. + dim.wid = max(fm.lineWidth(), 1); dim.asc = fm.maxAscent(); dim.des = fm.maxDescent(); } @@ -3065,10 +3214,93 @@ void BufferView::caretPosAndDim(Point & p, Dimension & dim) const dim.wid = lyxrc.cursor_width; p = getPos(cur); + // center fat carets horizontally + p.x_ -= dim.wid / 2; + // p is top-left p.y_ -= dim.asc; } +void BufferView::buildCaretGeometry(bool complet) +{ + Point p; + Dimension dim; + caretPosAndDim(p, dim); + + Cursor const & cur = d->cursor_; + Font const & realfont = cur.real_current_font; + frontend::FontMetrics const & fm = theFontMetrics(realfont.fontInfo()); + bool const isrtl = realfont.isVisibleRightToLeft(); + int const dir = isrtl ? -1 : 1; + + frontend::CaretGeometry & cg = d->caret_geometry_; + cg.shapes.clear(); + + // The caret itself, slanted for italics in text edit mode except + // for selections because the selection rect does not slant + 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_ + dir * dim.wid - dim.des * slope), p.y_ + dim.height()}, + {iround(p.x_ + dir * dim.wid + dim.asc * slope), p.y_}} + ); + + // The language indicator _| (if needed) + Language const * doclang = buffer().params().language; + if (!((realfont.language() == doclang && isrtl == doclang->rightToLeft()) + || realfont.language() == latex_language)) { + int const lx = dim.height() / 3; + 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 + dir * (dim.wid + lx - 1), yy - dim.wid}, + {xx + dir * (dim.wid + lx - 1), yy}, + {xx, yy}} + ); + } + + // The completion triangle |> (if needed) + if (complet) { + int const m = p.y_ + dim.height() / 2; + int const d = dim.height() / 8; + // offset for slanted carret + int const sx = iround((dim.asc - (dim.height() / 2 - d)) * slope); + // 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 + dir * d - dim.wid, m}, + {xx, m - d + dim.wid}} + ); + } + + // compute extremal x values + cg.left = 1000000; + cg.right = -1000000; + cg.top = 1000000; + cg.bottom = -1000000; + for (auto const & shape : cg.shapes) + for (Point const & p : shape) { + cg.left = min(cg.left, p.x_); + cg.right = max(cg.right, p.x_); + cg.top = min(cg.top, p.y_); + cg.bottom = max(cg.bottom, p.y_); + } +} + + +frontend::CaretGeometry const & BufferView::caretGeometry() const +{ + return d->caret_geometry_; +} + + bool BufferView::caretInView() const { if (!paragraphVisible(cursor())) @@ -3172,15 +3404,12 @@ 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; @@ -3265,7 +3494,7 @@ 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 pair firstpm = tm.first(); @@ -3293,8 +3522,17 @@ void BufferView::draw(frontend::Painter & pain, bool paint_caret) * move at all */ if (paint_caret) { - Row const & caret_row = d->cursor_.textRow(); - caret_row.changed(true); + Cursor cur(d->cursor_); + while (cur.depth() > 1) { + if (!cur.inTexted()) + break; + TextMetrics const & tm = textMetrics(cur.text()); + if (d->caret_geometry_.left >= tm.origin().x_ + && d->caret_geometry_.right <= tm.origin().x_ + tm.dim().width()) + break; + cur.pop(); + } + cur.textRow().changed(true); } }