#include "BranchList.h"
#include "Buffer.h"
-#include "buffer_funcs.h"
#include "BufferList.h"
#include "BufferParams.h"
#include "CoordCache.h"
#include "CutAndPaste.h"
#include "DispatchResult.h"
#include "ErrorList.h"
-#include "factory.h"
-#include "FloatList.h"
#include "FuncRequest.h"
#include "FuncStatus.h"
#include "Intl.h"
-#include "InsetIterator.h"
#include "Language.h"
-#include "LaTeXFeatures.h"
#include "LayoutFile.h"
#include "Lexer.h"
#include "LyX.h"
#include "LyXAction.h"
#include "lyxfind.h"
-#include "Layout.h"
#include "LyXRC.h"
#include "MetricsInfo.h"
#include "Paragraph.h"
-#include "ParagraphParameters.h"
-#include "ParIterator.h"
-#include "RowPainter.h"
#include "Session.h"
#include "Text.h"
-#include "TextClass.h"
#include "TextMetrics.h"
#include "TexRow.h"
#include "TocBackend.h"
-#include "WordLangTuple.h"
#include "insets/InsetBibtex.h"
#include "insets/InsetCitation.h"
#include "insets/InsetCommand.h" // ChangeRefs
-#include "insets/InsetExternal.h"
#include "insets/InsetGraphics.h"
-#include "insets/InsetNote.h"
#include "insets/InsetRef.h"
#include "insets/InsetText.h"
-#include "mathed/MathData.h"
#include "mathed/InsetMathNest.h"
+#include "mathed/InsetMathRef.h"
+#include "mathed/MathData.h"
+#include "mathed/MathRow.h"
#include "frontends/alert.h"
-#include "frontends/Application.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"
-#include "support/ExceptionMessage.h"
+#include "support/docstring.h"
#include "support/filetools.h"
#include "support/gettext.h"
#include "support/lassert.h"
#include "support/Length.h"
#include "support/lstrings.h"
#include "support/lyxlib.h"
-#include "support/Package.h"
#include "support/types.h"
+#include <algorithm>
#include <cerrno>
+#include <cstring>
#include <fstream>
#include <functional>
#include <iterator>
/// Moves cursor to the next inset with one of the given codes.
-void gotoInset(BufferView * bv, vector<InsetCode> const & codes,
+bool gotoInset(BufferView * bv, vector<InsetCode> 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);
}
///
CoordCache coord_cache_;
///
- typedef map<MathData const *, MathRow> MathRows;
+ typedef unordered_map<MathData const *, MathRow> MathRows;
MathRows math_rows_;
/// this is used to handle XSelection events in the right manner.
*/
frontend::GuiBufferViewDelegate * gui_;
- /// Cache for Find Next
- FuncRequest search_request_cache_;
-
///
map<string, Inset *> edited_insets_;
CursorSlice current_row_slice_;
/// are we hovering something that we can click
bool clickable_inset_;
+ /// shape of the caret
+ frontend::CaretGeometry caret_geometry_;
};
}
-int BufferView::rightMargin() const
+int BufferView::defaultMargin() const
{
// The value used to be hardcoded to 10
- int const default_margin = zoomedPixels(10);
+ return zoomedPixels(20);
+}
+
+
+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 + 2 * default_margin)
- return default_margin;
+ if (full_screen_ && lyxrc.full_screen_limit)
+ return max(defaultMargin(), (width_ - lyxrc.full_screen_width) / 2);
- return (width_ - lyxrc.full_screen_width) / 2;
+ return defaultMargin();
}
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();
}
}
+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) {
// 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);
}
}
-void BufferView::updateScrollbar()
+void BufferView::updateScrollbarParameters()
{
if (height_ == 0 && width_ == 0)
return;
// 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 value, bool update)
+void BufferView::scrollDocView(int const pixels, bool update)
{
// The scrollbar values are relative to the top of the screen, therefore the
// offset is equal to the target value.
// No scrolling at all? No need to redraw anything
- if (value == 0)
+ if (pixels == 0)
return;
// If the offset is less than 2 screen height, prefer to scroll instead.
- if (abs(value) <= 2 * height_) {
- d->anchor_ypos_ -= value;
+ if (abs(pixels) <= 2 * height_) {
+ d->anchor_ypos_ -= pixels;
processUpdateFlags(Update::Force);
return;
}
// cut off at the top
- if (value <= d->scrollbarParameters_.min) {
+ 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;
}
// cut off at the bottom
- if (value >= d->scrollbarParameters_.max) {
+ 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;
}
pit_type i = 0;
for (; i != int(d->par_height_.size()); ++i) {
par_pos += d->par_height_[i];
- if (par_pos >= value)
+ if (par_pos >= pixels)
break;
}
- if (par_pos < value) {
+ if (par_pos < pixels) {
// It seems we didn't find the correct pit so stay on the safe side and
// scroll to bottom.
LYXERR0("scrolling position not found!");
DocIterator dit = doc_iterator_begin(&buffer_);
dit.pit() = i;
- LYXERR(Debug::SCROLLING, "value = " << value << " -> scroll to pit " << i);
- showCursor(dit, false, update);
+ LYXERR(Debug::SCROLLING, "pixels = " << pixels << " -> scroll to pit " << i);
+ showCursor(dit, false, false, update);
}
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"));
}
// 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;
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)
-{
- if (scrollToCursor(dit, recenter) && update)
- processUpdateFlags(Update::Force);
-}
-
-
-void BufferView::scrollToCursor()
+ bool recenter, bool force, bool update)
{
- if (scrollToCursor(d->cursor_, false))
+ if (scrollToCursor(dit, recenter, force) && update)
processUpdateFlags(Update::Force);
}
-bool BufferView::scrollToCursor(DocIterator const & dit, bool const recenter)
+bool BufferView::scrollToCursor(DocIterator const & dit, bool const recenter, bool force)
{
- // 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();
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);
// 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);
}
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;
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;
}
void BufferView::makeDocumentClass()
{
DocumentClassConstPtr olddc = buffer_.params().documentClassPtr();
- buffer_.params().makeDocumentClass();
+ buffer_.params().makeDocumentClass(buffer_.isClone(), buffer_.isInternal());
updateDocumentClass(olddc);
}
break;
case LFUN_FILE_INSERT_PLAINTEXT_PARA:
case LFUN_FILE_INSERT_PLAINTEXT: {
- docstring const fname = cmd.argument();
+ docstring const & fname = cmd.argument();
if (!FileName::isAbsolute(to_utf8(fname))) {
flag.message(_("Absolute filename expected."));
return false;
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:
case LFUN_LABEL_GOTO:
flag.setEnabled(!cmd.argument().empty()
- || getInsetByCode<InsetRef>(cur, REF_CODE));
+ || getInsetByCode<InsetRef>(cur, REF_CODE)
+ || getInsetByCode<InsetMathRef>(cur, MATH_REF_CODE));
break;
case LFUN_CHANGES_MERGE:
break;
}
+ case LFUN_COPY:
+ flag.setEnabled(cur.selection());
+ break;
+
default:
return false;
}
else {
dr.screenUpdate(Update::Force | Update::FitCursor);
dr.forceBufferUpdate();
+ resetInlineCompletionPos();
if (buffer().params().citeEngine() != engine ||
buffer().params().citeEngineType() != enginetype)
buffer().invalidateCiteLabels();
else {
dr.screenUpdate(Update::Force | Update::FitCursor);
dr.forceBufferUpdate();
+ resetInlineCompletionPos();
if (buffer().params().citeEngine() != engine ||
buffer().params().citeEngineType() != enginetype)
buffer().invalidateCiteLabels();
break;
case LFUN_BOOKMARK_SAVE:
+ dr.screenUpdate(Update::Force);
saveBookmark(convert<unsigned int>(to_utf8(cmd.argument())));
break;
// eventually call LFUN_PARAGRAPH_GOTO, but it seems best
// to have it here.
dr.screenUpdate(Update::Force | Update::FitCursor);
+ } else {
+ InsetMathRef * minset =
+ getInsetByCode<InsetMathRef>(cur, MATH_REF_CODE);
+ if (minset)
+ lyx::dispatch(FuncRequest(LFUN_LABEL_GOTO,
+ minset->getTarget()));
}
break;
}
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(
}
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;
}
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 (cur.selection())
+ pattern = cur.selectionAsString(false);
+ else {
+ pos_type spos = cur.pos();
+ cur.innerText()->selectWord(cur, WHOLE_WORD);
+ pattern = cur.selectionAsString(false);
+ cur.selection(false);
+ cur.pos() = spos;
}
- if (lyxreplace(this, cmd, has_deleted)) {
+ 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;
}
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);
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);
}
+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);
// Do we have a selection?
theSelection().haveSelection(cursor().selection());
- if (cur.needBufferUpdate()) {
+ if (cur.needBufferUpdate() || buffer().needUpdate()) {
cur.clearBufferUpdate();
buffer().updateBuffer();
}
}
-int BufferView::scroll(int y)
+int BufferView::scroll(int pixels)
{
- if (y > 0)
- return scrollDown(y);
- if (y < 0)
- return scrollUp(-y);
+ if (pixels > 0)
+ return scrollDown(pixels);
+ if (pixels < 0)
+ return scrollUp(-pixels);
return 0;
}
-int BufferView::scrollDown(int offset)
+int BufferView::scrollDown(int pixels)
{
Text * text = &buffer_.text();
TextMetrics & tm = d->text_metrics_[text];
- int const ymax = height_ + offset;
+ int const ymax = height_ + pixels;
while (true) {
pair<pit_type, ParagraphMetrics const *> last = tm.last();
int bottom_pos = last.second->position() + last.second->descent();
if (last.first + 1 == int(text->paragraphs().size())) {
if (bottom_pos <= height_)
return 0;
- offset = min(offset, bottom_pos - height_);
+ pixels = min(pixels, bottom_pos - height_);
break;
}
if (bottom_pos > ymax)
break;
tm.newParMetricsDown();
}
- d->anchor_ypos_ -= offset;
- return -offset;
+ d->anchor_ypos_ -= pixels;
+ return -pixels;
}
-int BufferView::scrollUp(int offset)
+int BufferView::scrollUp(int pixels)
{
Text * text = &buffer_.text();
TextMetrics & tm = d->text_metrics_[text];
- int ymin = - offset;
+ int ymin = - pixels;
while (true) {
pair<pit_type, ParagraphMetrics const *> first = tm.first();
int top_pos = first.second->position() - first.second->ascent();
if (first.first == 0) {
if (top_pos >= 0)
return 0;
- offset = min(offset, - top_pos);
+ pixels = min(pixels, - top_pos);
break;
}
if (top_pos < ymin)
break;
tm.newParMetricsUp();
}
- d->anchor_ypos_ += offset;
- return offset;
+ d->anchor_ypos_ += pixels;
+ return pixels;
}
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");
} 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();
}
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()))
// The scrollbar needs an update.
// FIXME: does it always? see ticket #11947.
- updateScrollbar();
+ updateScrollbarParameters();
// Normalize anchor for next time
pair<pit_type, ParagraphMetrics const *> firstpm = tm.first();
* 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);
}
}