]> git.lyx.org Git - lyx.git/blob - src/BufferView.cpp
Fix cursor when undoing accept/reject all changes
[lyx.git] / src / BufferView.cpp
1 /**
2  * \file BufferView.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Alfredo Braunstein
7  * \author Lars Gullik Bjønnes
8  * \author John Levon
9  * \author André Pönitz
10  * \author Jürgen Vigna
11  *
12  * Full author contact details are available in file CREDITS.
13  */
14
15 #include <config.h>
16
17 #include "BufferView.h"
18
19 #include "BranchList.h"
20 #include "Buffer.h"
21 #include "BufferList.h"
22 #include "BufferParams.h"
23 #include "BiblioInfo.h"
24 #include "CoordCache.h"
25 #include "Cursor.h"
26 #include "CutAndPaste.h"
27 #include "DispatchResult.h"
28 #include "ErrorList.h"
29 #include "FuncRequest.h"
30 #include "FuncStatus.h"
31 #include "Intl.h"
32 #include "Language.h"
33 #include "LayoutFile.h"
34 #include "LyX.h"
35 #include "LyXAction.h"
36 #include "lyxfind.h"
37 #include "LyXRC.h"
38 #include "MetricsInfo.h"
39 #include "Paragraph.h"
40 #include "Session.h"
41 #include "texstream.h"
42 #include "Text.h"
43 #include "TextMetrics.h"
44 #include "TexRow.h"
45 #include "TocBackend.h"
46
47 #include "insets/InsetBibtex.h"
48 #include "insets/InsetCitation.h"
49 #include "insets/InsetCommand.h" // ChangeRefs
50 #include "insets/InsetGraphics.h"
51 #include "insets/InsetIndex.h"
52 #include "insets/InsetRef.h"
53 #include "insets/InsetText.h"
54
55 #include "mathed/InsetMathNest.h"
56 #include "mathed/InsetMathRef.h"
57 #include "mathed/MathData.h"
58 #include "mathed/MathRow.h"
59
60 #include "frontends/alert.h"
61 #include "frontends/Application.h"
62 #include "frontends/CaretGeometry.h"
63 #include "frontends/Delegates.h"
64 #include "frontends/FontMetrics.h"
65 #include "frontends/NullPainter.h"
66 #include "frontends/Painter.h"
67 #include "frontends/Selection.h"
68 #include "frontends/Clipboard.h"
69
70 #include "support/convert.h"
71 #include "support/debug.h"
72 #include "support/docstring.h"
73 #include "support/docstring_list.h"
74 #include "support/filetools.h"
75 #include "support/gettext.h"
76 #include "support/lassert.h"
77 #include "support/Length.h"
78 #include "support/Lexer.h"
79 #include "support/lstrings.h"
80 #include "support/lyxlib.h"
81 #include "support/types.h"
82
83 #include <algorithm>
84 #include <cerrno>
85 #include <cstring>
86 #include <fstream>
87 #include <functional>
88 #include <iterator>
89 #include <sstream>
90 #include <vector>
91
92 using namespace std;
93 using namespace lyx::support;
94
95 namespace lyx {
96
97 namespace Alert = frontend::Alert;
98
99 namespace {
100
101 /// Return an inset of this class if it exists at the current cursor position
102 template <class T>
103 T * getInsetByCode(Cursor const & cur, InsetCode code)
104 {
105         DocIterator it = cur;
106         Inset * inset = it.nextInset();
107         if (inset && inset->lyxCode() == code)
108                 return static_cast<T*>(inset);
109         return nullptr;
110 }
111
112
113 /// Note that comparing contents can only be used for InsetCommand
114 bool findNextInset(DocIterator & dit, vector<InsetCode> const & codes,
115         docstring const & contents)
116 {
117         DocIterator tmpdit = dit;
118
119         while (tmpdit) {
120                 Inset const * inset = tmpdit.nextInset();
121                 if (inset) {
122                         bool const valid_code = std::find(codes.begin(), codes.end(),
123                                 inset->lyxCode()) != codes.end();
124                         InsetCommand const * ic = inset->asInsetCommand();
125                         bool const same_or_no_contents =  contents.empty()
126                                 || (ic && (ic->getFirstNonOptParam() == contents));
127
128                         if (valid_code && same_or_no_contents) {
129                                 dit = tmpdit;
130                                 return true;
131                         }
132                 }
133                 tmpdit.forwardInset();
134         }
135
136         return false;
137 }
138
139
140 /// Looks for next inset with one of the given codes.
141 /// Note that same_content can only be used for InsetCommand
142 bool findInset(DocIterator & dit, vector<InsetCode> const & codes,
143         bool same_content)
144 {
145         docstring contents;
146         DocIterator tmpdit = dit;
147         tmpdit.forwardInset();
148         if (!tmpdit)
149                 return false;
150
151         Inset const * inset = tmpdit.nextInset();
152         if (same_content && inset) {
153                 InsetCommand const * ic = inset->asInsetCommand();
154                 if (ic) {
155                         bool const valid_code = std::find(codes.begin(), codes.end(),
156                                 ic->lyxCode()) != codes.end();
157                         if (valid_code)
158                                 contents = ic->getFirstNonOptParam();
159                 }
160         }
161
162         if (!findNextInset(tmpdit, codes, contents)) {
163                 if (dit.depth() != 1 || dit.pit() != 0 || dit.pos() != 0) {
164                         inset = &tmpdit.bottom().inset();
165                         tmpdit = doc_iterator_begin(&inset->buffer(), inset);
166                         if (!findNextInset(tmpdit, codes, contents))
167                                 return false;
168                 } else {
169                         return false;
170                 }
171         }
172
173         dit = tmpdit;
174         return true;
175 }
176
177
178 /// Moves cursor to the next inset with one of the given codes.
179 bool gotoInset(BufferView * bv, vector<InsetCode> const & codes,
180                bool same_content)
181 {
182         Cursor tmpcur = bv->cursor();
183         if (!findInset(tmpcur, codes, same_content)) {
184                 bv->cursor().message(_("No more insets"));
185                 return false;
186         }
187
188         tmpcur.clearSelection();
189         bv->setCursor(tmpcur);
190         return bv->scrollToCursor(bv->cursor(), SCROLL_TOP);
191 }
192
193
194 /// A map from a Text to the associated text metrics
195 typedef map<Text const *, TextMetrics> TextMetricsCache;
196
197 enum ScreenUpdateStrategy {
198         NoScreenUpdate,
199         SingleParUpdate,
200         FullScreenUpdate,
201         DecorationUpdate
202 };
203
204 } // namespace
205
206
207 /////////////////////////////////////////////////////////////////////
208 //
209 // BufferView
210 //
211 /////////////////////////////////////////////////////////////////////
212
213 struct BufferView::Private
214 {
215         Private(BufferView & bv) :
216                 update_strategy_(FullScreenUpdate),
217                 update_flags_(Update::Force),
218                 cursor_(bv), anchor_pit_(0), anchor_ypos_(10000),
219                 wh_(0), inlineCompletionUniqueChars_(0),
220                 last_inset_(nullptr), mouse_position_cache_(),
221                 gui_(nullptr), bookmark_edit_position_(-1),
222                 horiz_scroll_offset_(0), clickable_inset_(false)
223         {
224                 xsel_cache_.set = false;
225         }
226
227         ///
228         ScrollbarParameters scrollbarParameters_;
229         ///
230         ScreenUpdateStrategy update_strategy_;
231         ///
232         Update::flags update_flags_;
233         ///
234         CoordCache coord_cache_;
235         ///
236         typedef unordered_map<MathData const *, MathRow> MathRows;
237         MathRows math_rows_;
238
239         /// this is used to handle XSelection events in the right manner.
240         struct {
241                 CursorSlice cursor;
242                 CursorSlice anchor;
243                 bool set;
244         } xsel_cache_;
245         ///
246         Cursor cursor_;
247         ///
248         pit_type anchor_pit_;
249         ///
250         int anchor_ypos_;
251         /// Estimated average par height for scrollbar.
252         int wh_;
253         ///
254         vector<int> par_height_;
255
256         ///
257         DocIterator inlineCompletionPos_;
258         ///
259         docstring inlineCompletion_;
260         ///
261         size_t inlineCompletionUniqueChars_;
262
263         /// keyboard mapping object.
264         Intl intl_;
265
266         /// last visited inset.
267         /** kept to send setMouseHover(false).
268           * Not owned, so don't delete.
269           */
270         Inset const * last_inset_;
271
272         /// position of the mouse at the time of the last mouse move
273         /// This is used to update the hovering status of inset in
274         /// cases where the buffer is scrolled, but the mouse didn't move.
275         Point mouse_position_cache_;
276
277         mutable TextMetricsCache text_metrics_;
278
279         /// Whom to notify.
280         /** Not owned, so don't delete.
281           */
282         frontend::GuiBufferViewDelegate * gui_;
283
284         ///
285         map<string, Inset *> edited_insets_;
286
287         // cache for id of the paragraph which was edited the last time
288         int bookmark_edit_position_;
289
290         /// When the row where the cursor lies is scrolled, this
291         /// contains the scroll offset
292         int horiz_scroll_offset_;
293         /// a slice pointing to the start of the row where the cursor
294         /// is (at last draw time)
295         CursorSlice current_row_slice_;
296         /// are we hovering something that we can click
297         bool clickable_inset_;
298         /// shape of the caret
299         frontend::CaretGeometry caret_geometry_;
300         ///
301         bool mouse_selecting_ = false;
302         /// Reference value for statistics (essentially subtract this from the actual value to see relative counts)
303         /// (words/chars/chars no blanks)
304         int stats_ref_value_w_ = 0;
305         int stats_ref_value_c_ = 0;
306         int stats_ref_value_nb_ = 0;
307         bool stats_update_trigger_ = false;
308
309 };
310
311
312 BufferView::BufferView(Buffer & buf)
313         : width_(0), height_(0), full_screen_(false), buffer_(buf),
314       d(new Private(*this))
315 {
316         d->xsel_cache_.set = false;
317         d->intl_.initKeyMapper(lyxrc.use_kbmap);
318
319         d->cursor_.setBuffer(&buf);
320         d->cursor_.push(buffer_.inset());
321         d->cursor_.resetAnchor();
322         d->cursor_.setCurrentFont();
323
324         buffer_.updatePreviews();
325 }
326
327
328 BufferView::~BufferView()
329 {
330         // current buffer is going to be switched-off, save cursor pos
331         // Ideally, the whole cursor stack should be saved, but session
332         // currently can only handle bottom (whole document) level pit and pos.
333         // That is to say, if a cursor is in a nested inset, it will be
334         // restore to the left of the top level inset.
335         LastFilePosSection::FilePos fp;
336         fp.file = buffer_.fileName();
337         fp.pit = d->cursor_.bottom().pit();
338         fp.pos = d->cursor_.bottom().pos();
339         theSession().lastFilePos().save(fp);
340
341         if (d->last_inset_)
342                 d->last_inset_->setMouseHover(this, false);
343
344         delete d;
345 }
346
347
348 void BufferView::copySettingsFrom(BufferView const & bv)
349 {
350         setCursor(bv.cursor());
351         d->anchor_pit_ = bv.d->anchor_pit_;
352         d->anchor_ypos_ = bv.d->anchor_ypos_;
353 }
354
355
356 int BufferView::defaultMargin() const
357 {
358         // The value used to be hardcoded to 10
359         return zoomedPixels(20);
360 }
361
362
363 int BufferView::rightMargin() const
364 {
365         const int screen_width = inPixels(lyxrc.screen_width);
366
367         // The additional test for the case the outliner is opened.
368         if (!lyxrc.screen_limit || width_ < screen_width + 2 * defaultMargin()) {
369                 return defaultMargin();
370         } else {
371                 return (width_ - screen_width) / 2;
372         }
373 }
374
375
376 int BufferView::leftMargin() const
377 {
378         return rightMargin();
379 }
380
381
382 int BufferView::topMargin() const
383 {
384         // Original value was 20px at 100dpi. For internal buffers like in
385         // advanced search and replace, a value of 5px is enough.
386         return zoomedPixels(buffer().isInternal() ? 5 : 20);
387 }
388
389
390 int BufferView::bottomMargin() const
391 {
392         return topMargin();
393 }
394
395
396 int BufferView::inPixels(Length const & len) const
397 {
398         Font const font = buffer().params().getFont();
399         return len.inPixels(workWidth(), theFontMetrics(font).em());
400 }
401
402
403 int BufferView::zoomedPixels(int pix) const
404 {
405         // FIXME: the dpi setting should really depend on the BufferView
406         // (think different monitors).
407
408         // Zoom factor specified by user in percent
409         double const zoom = lyxrc.currentZoom / 100.0; // [percent]
410
411         // DPI setting for monitor relative to 100dpi
412         double const dpizoom = lyxrc.dpi / 100.0; // [per 100dpi]
413
414         return support::iround(pix * zoom * dpizoom);
415 }
416
417
418 bool BufferView::isTopScreen() const
419 {
420         return 0 == d->scrollbarParameters_.min;
421 }
422
423
424 bool BufferView::isBottomScreen() const
425 {
426         return 0 == d->scrollbarParameters_.max;
427 }
428
429
430 Intl & BufferView::getIntl()
431 {
432         return d->intl_;
433 }
434
435
436 Intl const & BufferView::getIntl() const
437 {
438         return d->intl_;
439 }
440
441
442 CoordCache & BufferView::coordCache()
443 {
444         return d->coord_cache_;
445 }
446
447
448 CoordCache const & BufferView::coordCache() const
449 {
450         return d->coord_cache_;
451 }
452
453
454 bool BufferView::hasMathRow(MathData const * cell) const
455 {
456         return d->math_rows_.find(cell) != d->math_rows_.end();
457 }
458
459
460 MathRow const & BufferView::mathRow(MathData const * cell) const
461 {
462         auto it = d->math_rows_.find(cell);
463         LATTEST(it != d->math_rows_.end());
464         return it->second;
465 }
466
467
468 void BufferView::setMathRow(MathData const * cell, MathRow const & mrow)
469 {
470         d->math_rows_[cell] = mrow;
471 }
472
473
474 Buffer & BufferView::buffer()
475 {
476         return buffer_;
477 }
478
479
480 Buffer const & BufferView::buffer() const
481 {
482         return buffer_;
483 }
484
485
486 docstring const & BufferView::searchRequestCache() const
487 {
488         return theClipboard().getFindBuffer();
489 }
490
491
492 void BufferView::setSearchRequestCache(docstring const & text)
493 {
494         bool casesensitive;
495         bool matchword;
496         bool forward;
497         bool wrap;
498         bool instant;
499         bool onlysel;
500         docstring const search = string2find(text, casesensitive, matchword,
501                                              forward, wrap, instant, onlysel);
502         theClipboard().setFindBuffer(search);
503 }
504
505
506 bool BufferView::needsFitCursor() const
507 {
508         if (cursorStatus(d->cursor_) == CUR_INSIDE) {
509                 frontend::FontMetrics const & fm =
510                         theFontMetrics(d->cursor_.getFont().fontInfo());
511                 int const asc = fm.maxAscent();
512                 int const des = fm.maxDescent();
513                 Point const p = getPos(d->cursor_);
514                 if (p.y - asc >= 0 && p.y + des < height_)
515                         return false;
516         }
517         return true;
518 }
519
520
521 namespace {
522
523 // this is for debugging only.
524 string flagsAsString(Update::flags flags)
525 {
526         if (flags == Update::None)
527                 return "None ";
528         return string((flags & Update::FitCursor) ? "FitCursor " : "")
529                 + ((flags & Update::Force) ? "Force " : "")
530                 + ((flags & Update::ForceDraw) ? "ForceDraw " : "")
531                 + ((flags & Update::SinglePar) ? "SinglePar " : "")
532                 + ((flags & Update::Decoration) ? "Decoration " : "");
533 }
534
535 }
536
537 void BufferView::processUpdateFlags(Update::flags flags)
538 {
539         LYXERR(Debug::PAINTING, "BufferView::processUpdateFlags( "
540                    << flagsAsString(flags) << ")  buffer: " << &buffer_);
541
542         // Case when no explicit update is requested.
543         if (flags == Update::None || !ready())
544                 return;
545
546         /* FIXME We would like to avoid doing this here, since it is very
547          * expensive and is called in updateBuffer already. However, even
548          * inserting a plain character can invalidate the overly fragile
549          * tables of child documents built by updateMacros. Some work is
550          * needed to avoid doing that when not necessary.
551          */
552         buffer_.updateMacros();
553
554         // First check whether the metrics and inset positions should be updated
555         if (flags & Update::Force) {
556                 // This will compute all metrics and positions.
557                 updateMetrics(true);
558                 // metrics is done, full drawing is necessary now
559                 flags = (flags & ~Update::Force) | Update::ForceDraw;
560         }
561         /* If a single paragraph update has been requested and we are not
562          * already repainting all, check whether this update changes the
563          * paragraph metrics. If it does, then compute all metrics (in
564          * case the paragraph is in an inset)
565          *
566          * We handle this before FitCursor because the later will require
567          * correct metrics at cursor position.
568          */
569         else if ((flags & Update::SinglePar) && !(flags & Update::ForceDraw)) {
570                 if (!singleParUpdate())
571                         updateMetrics(true);
572         }
573         else if (flags & Update::ForceDraw)
574                 // This will compute only the needed metrics and update positions.
575                 updateMetrics(false);
576
577         // Then make sure that the screen contains the cursor if needed
578         if (flags & Update::FitCursor) {
579                 if (needsFitCursor()) {
580                         // First try to make the selection start visible
581                         // (which is just the cursor when there is no selection)
582                         scrollToCursor(d->cursor_.selectionBegin(), SCROLL_VISIBLE);
583                         // Metrics have to be updated
584                         updateMetrics(false);
585                         // Is the cursor visible? (only useful if cursor is at end of selection)
586                         if (needsFitCursor()) {
587                                 // then try to make cursor visible instead
588                                 scrollToCursor(d->cursor_, SCROLL_VISIBLE);
589                                 // Metrics have to be recomputed (maybe again)
590                                 updateMetrics(false);
591                         }
592                 }
593                 flags = (flags & ~Update::FitCursor) | Update::ForceDraw;
594         }
595
596         if (theApp()->drawStrategy() == DrawStrategy::Full)
597                 flags = flags | Update::ForceDraw;
598
599         // Add flags to the the update flags. These will be reset to None
600         // after the redraw is actually done
601         d->update_flags_ = d->update_flags_ | flags;
602         LYXERR(Debug::PAINTING, "Cumulative flags: " << flagsAsString(flags));
603
604         // Now compute the update strategy
605         // Possibly values in flag are None, SinglePar, Decoration, ForceDraw
606         LATTEST((d->update_flags_ & ~(Update::None | Update::SinglePar
607                                       | Update::Decoration | Update::ForceDraw)) == 0);
608
609         if (d->update_flags_ & Update::ForceDraw)
610                 d->update_strategy_ = FullScreenUpdate;
611         else if (d->update_flags_ & Update::Decoration)
612                 d->update_strategy_ = DecorationUpdate;
613         else if (d->update_flags_ & Update::SinglePar)
614                 d->update_strategy_ = SingleParUpdate;
615         else {
616                 // no need to redraw anything.
617                 d->update_strategy_ = NoScreenUpdate;
618         }
619
620         updateHoveredInset();
621
622         // Trigger a redraw.
623         buffer_.changed(false);
624 }
625
626
627 void BufferView::updateScrollbarParameters()
628 {
629         if (!ready())
630                 return;
631
632         // We prefer fixed size line scrolling.
633         d->scrollbarParameters_.single_step = defaultRowHeight();
634         // We prefer full screen page scrolling.
635         d->scrollbarParameters_.page_step = height_;
636
637         Text & t = buffer_.text();
638         TextMetrics & tm = d->text_metrics_[&t];
639
640         LYXERR(Debug::SCROLLING, " Updating scrollbar: height: "
641                 << t.paragraphs().size()
642                 << " curr par: " << d->cursor_.bottom().pit()
643                 << " default height " << defaultRowHeight());
644
645         size_t const parsize = t.paragraphs().size();
646         if (d->par_height_.size() != parsize) {
647                 d->par_height_.clear();
648                 // FIXME: We assume a default paragraph height of 2 rows. This
649                 // should probably be pondered with the screen width.
650                 d->par_height_.resize(parsize, defaultRowHeight() * 2);
651         }
652
653         // Look at paragraph heights on-screen
654         pair<pit_type, ParagraphMetrics const *> first = tm.first();
655         pair<pit_type, ParagraphMetrics const *> last = tm.last();
656         for (pit_type pit = first.first; pit <= last.first; ++pit) {
657                 d->par_height_[pit] = tm.parMetrics(pit).height();
658                 LYXERR(Debug::SCROLLING, "storing height for pit " << pit << " : "
659                         << d->par_height_[pit]);
660         }
661
662         int top_pos = first.second->top();
663         int bottom_pos = last.second->bottom();
664         bool first_visible = first.first == 0 && top_pos >= 0;
665         bool last_visible = last.first + 1 == int(parsize) && bottom_pos <= height_;
666         if (first_visible && last_visible) {
667                 d->scrollbarParameters_.min = 0;
668                 d->scrollbarParameters_.max = 0;
669                 return;
670         }
671
672         d->scrollbarParameters_.min = top_pos;
673         for (size_t i = 0; i != size_t(first.first); ++i)
674                 d->scrollbarParameters_.min -= d->par_height_[i];
675         d->scrollbarParameters_.max = bottom_pos;
676         for (size_t i = last.first + 1; i != parsize; ++i)
677                 d->scrollbarParameters_.max += d->par_height_[i];
678
679         // The reference is the top position so we remove one page.
680         if (lyxrc.scroll_below_document)
681                 d->scrollbarParameters_.max -= minVisiblePart();
682         else
683                 d->scrollbarParameters_.max -= d->scrollbarParameters_.page_step;
684
685         // 0 must be inside the range as it denotes the current position
686         if (d->scrollbarParameters_.max < 0)
687                 d->scrollbarParameters_.max = 0;
688         if (d->scrollbarParameters_.min > 0)
689                 d->scrollbarParameters_.min = 0;
690 }
691
692
693 ScrollbarParameters const & BufferView::scrollbarParameters() const
694 {
695         return d->scrollbarParameters_;
696 }
697
698
699 docstring BufferView::toolTip(int x, int y) const
700 {
701         // Get inset under mouse, if there is one.
702         Inset const * covering_inset = getCoveringInset(buffer_.text(), x, y);
703         if (!covering_inset)
704                 // No inset, no tooltip...
705                 return docstring();
706         return covering_inset->toolTip(*this, x, y);
707 }
708
709
710 string BufferView::contextMenu(int x, int y) const
711 {
712         //If there is a selection, return the containing inset menu
713         if (d->cursor_.selection())
714                 return d->cursor_.inset().contextMenu(*this, x, y);
715
716         // Get inset under mouse, if there is one.
717         Inset const * covering_inset = getCoveringInset(buffer_.text(), x, y);
718         if (covering_inset) {
719                 if (covering_inset->asInsetMath()) {
720                         CoordCache::Insets const & inset_cache =
721                                 coordCache().getInsets();
722                         Inset const * inner_inset = mathContextMenu(
723                                 covering_inset->asInsetMath()->asNestInset(),
724                                 inset_cache, x, y);
725                         if (inner_inset)
726                                 return inner_inset->contextMenu(*this, x, y);
727                 }
728                 return covering_inset->contextMenu(*this, x, y);
729         }
730
731         return buffer_.inset().contextMenu(*this, x, y);
732 }
733
734
735 Inset const * BufferView::mathContextMenu(InsetMathNest const * inset,
736                 CoordCache::Insets const & inset_cache, int x, int y) const
737 {
738         for (size_t i = 0; i < inset->nargs(); ++i) {
739                 MathData const & ar = inset->cell(i);
740                 for (size_t j = 0; j < ar.size(); ++j) {
741                         string const name = lyxerr.debugging(Debug::MATHED)
742                                 ? insetName(ar[j].nucleus()->lyxCode())
743                                 : string();
744                         LYXERR(Debug::MATHED, "Examining inset: " << name);
745                         if (!ar[j].nucleus()->contextMenuName().empty()) {
746                                 if (inset_cache.covers(ar[j].nucleus(), x, y)) {
747                                         LYXERR(Debug::MATHED, "Hit inset: "
748                                                << name);
749                                         return ar[j].nucleus();
750                                 }
751                         }
752                         InsetMathNest const * imn =
753                                 ar[j].nucleus()->asNestInset();
754                         if (imn) {
755                                 Inset const * inner =
756                                         mathContextMenu(imn, inset_cache, x, y);
757                                 if (inner)
758                                         return inner;
759                         }
760                 }
761         }
762         return nullptr;
763 }
764
765
766 void BufferView::scrollDocView(int const pixels, bool update)
767 {
768         // The scrollbar values are relative to the top of the screen, therefore the
769         // offset is equal to the target value.
770
771         // No scrolling at all? No need to redraw anything
772         if (pixels == 0)
773                 return;
774
775         // If part of the existing paragraphs will remain visible, prefer to
776         // scroll
777         TextMetrics const & tm = textMetrics(&buffer_.text());
778         if (tm.first().second->top() - pixels <= height_
779              &&  tm.last().second->bottom() - pixels >= 0) {
780                 LYXERR(Debug::SCROLLING, "small skip");
781                 d->anchor_ypos_ -= pixels;
782                 processUpdateFlags(Update::ForceDraw);
783                 return;
784         }
785
786         // cut off at the top
787         if (pixels <= d->scrollbarParameters_.min) {
788                 DocIterator dit = doc_iterator_begin(&buffer_);
789                 showCursor(dit, SCROLL_VISIBLE, update);
790                 LYXERR(Debug::SCROLLING, "scroll to top");
791                 return;
792         }
793
794         // cut off at the bottom
795         if (pixels >= d->scrollbarParameters_.max) {
796                 DocIterator dit = doc_iterator_end(&buffer_);
797                 dit.backwardPos();
798                 showCursor(dit, SCROLL_VISIBLE, update);
799                 LYXERR(Debug::SCROLLING, "scroll to bottom");
800                 return;
801         }
802
803         LYXERR(Debug::SCROLLING, "search paragraph");
804         // find paragraph at target position
805         int par_pos = d->scrollbarParameters_.min;
806         pit_type i = 0;
807         for (; i != int(d->par_height_.size()); ++i) {
808                 par_pos += d->par_height_[i];
809                 if (par_pos >= pixels)
810                         break;
811         }
812
813         if (par_pos < pixels) {
814                 // It seems we didn't find the correct pit so stay on the safe side and
815                 // scroll to bottom.
816                 LYXERR0("scrolling position not found!");
817                 scrollDocView(d->scrollbarParameters_.max, update);
818                 return;
819         }
820
821         DocIterator dit = doc_iterator_begin(&buffer_);
822         dit.pit() = i;
823         LYXERR(Debug::SCROLLING, "pixels = " << pixels << " -> scroll to pit " << i);
824         showCursor(dit, SCROLL_VISIBLE, update);
825 }
826
827
828 // FIXME: this method is not working well.
829 void BufferView::setCursorFromScrollbar()
830 {
831         TextMetrics & tm = d->text_metrics_[&buffer_.text()];
832
833         int const height = 2 * defaultRowHeight();
834         int const first = height;
835         int const last = height_ - height;
836         int newy = 0;
837         Cursor const & oldcur = d->cursor_;
838
839         switch (cursorStatus(oldcur)) {
840         case CUR_ABOVE:
841                 newy = first;
842                 break;
843         case CUR_BELOW:
844                 newy = last;
845                 break;
846         case CUR_INSIDE:
847                 int const y = getPos(oldcur).y;
848                 newy = min(last, max(y, first));
849                 if (y == newy)
850                         return;
851         }
852         // We reset the cursor because cursorStatus() does not
853         // work when the cursor is within mathed.
854         Cursor cur(*this);
855         cur.reset();
856         tm.setCursorFromCoordinates(cur, 0, newy);
857
858         // update the bufferview cursor and notify insets
859         // FIXME: Care about the d->cursor_ flags to redraw if needed
860         Cursor old = d->cursor_;
861         mouseSetCursor(cur);
862         // the DEPM call in mouseSetCursor() might have destroyed the
863         // paragraph the cursor is in.
864         bool badcursor = old.fixIfBroken();
865         badcursor |= notifyCursorLeavesOrEnters(old, d->cursor_);
866         if (badcursor)
867                 d->cursor_.fixIfBroken();
868 }
869
870
871 Change const BufferView::getCurrentChange() const
872 {
873         if (!d->cursor_.selection())
874                 return Change(Change::UNCHANGED);
875
876         DocIterator dit = d->cursor_.selectionBegin();
877         // The selected content might have been changed (see #7685)
878         dit = dit.getInnerText();
879         return dit.paragraph().lookupChange(dit.pos());
880 }
881
882
883 // this could be used elsewhere as well?
884 // FIXME: This does not work within mathed!
885 CursorStatus BufferView::cursorStatus(DocIterator const & dit) const
886 {
887         Point const p = getPos(dit);
888         if (p.y < 0)
889                 return CUR_ABOVE;
890         if (p.y > workHeight())
891                 return CUR_BELOW;
892         return CUR_INSIDE;
893 }
894
895
896 void BufferView::bookmarkEditPosition()
897 {
898         // Don't eat cpu time for each keystroke
899         if (d->cursor_.paragraph().id() == d->bookmark_edit_position_)
900                 return;
901         saveBookmark(0);
902         d->bookmark_edit_position_ = d->cursor_.paragraph().id();
903 }
904
905
906 void BufferView::saveBookmark(unsigned int idx)
907 {
908         if (buffer().isInternal())
909                 return;
910
911         // tentatively save bookmark, id and pos will be used to
912         // acturately locate a bookmark in a 'live' lyx session.
913         // pit and pos will be updated with bottom level pit/pos
914         // when lyx exits.
915         theSession().bookmarks().save(
916                 buffer_.fileName(),
917                 d->cursor_.bottom().pit(),
918                 d->cursor_.bottom().pos(),
919                 d->cursor_.paragraph().id(),
920                 d->cursor_.pos(),
921                 idx
922         );
923         if (idx)
924                 // emit message signal.
925                 message(_("Save bookmark"));
926 }
927
928
929 bool BufferView::moveToPosition(pit_type bottom_pit, pos_type bottom_pos,
930         int top_id, pos_type top_pos)
931 {
932         bool success = false;
933         DocIterator dit;
934
935         d->cursor_.clearSelection();
936
937         // if a valid par_id is given, try it first
938         // This is the case for a 'live' bookmark when unique paragraph ID
939         // is used to track bookmarks.
940         if (top_id > 0) {
941                 dit = buffer_.getParFromID(top_id);
942                 if (!dit.atEnd()) {
943                         dit.pos() = min(dit.paragraph().size(), top_pos);
944                         // Some slices of the iterator may not be
945                         // reachable (e.g. closed collapsible inset)
946                         // so the dociterator may need to be
947                         // shortened. Otherwise, setCursor may crash
948                         // lyx when the cursor can not be set to these
949                         // insets.
950                         size_t const n = dit.depth();
951                         for (size_t i = 0; i < n; ++i)
952                                 if (!dit[i].inset().editable()) {
953                                         dit.resize(i);
954                                         break;
955                                 }
956                         success = true;
957                 }
958         }
959
960         // if top_id == 0, or searching through top_id failed
961         // This is the case for a 'restored' bookmark when only bottom
962         // (document level) pit was saved. Because of this, bookmark
963         // restoration is inaccurate. If a bookmark was within an inset,
964         // it will be restored to the left of the outmost inset that contains
965         // the bookmark.
966         if (!success && bottom_pit < int(buffer_.paragraphs().size())) {
967                 dit = doc_iterator_begin(&buffer_);
968
969                 dit.pit() = bottom_pit;
970                 dit.pos() = min(bottom_pos, dit.paragraph().size());
971                 success = true;
972         }
973
974         if (success) {
975                 // Note: only bottom (document) level pit is set.
976                 setCursor(dit);
977                 // set the current font.
978                 d->cursor_.setCurrentFont();
979                 // Do not forget to reset the anchor (see #9912)
980                 d->cursor_.resetAnchor();
981                 processUpdateFlags(Update::Force | Update::FitCursor);
982         }
983
984         return success;
985 }
986
987
988 void BufferView::translateAndInsert(char_type c, Text * t, Cursor & cur)
989 {
990         if (d->cursor_.real_current_font.isRightToLeft()) {
991                 if (d->intl_.keymap == Intl::PRIMARY)
992                         d->intl_.keyMapSec();
993         } else {
994                 if (d->intl_.keymap == Intl::SECONDARY)
995                         d->intl_.keyMapPrim();
996         }
997
998         d->intl_.getTransManager().translateAndInsert(c, t, cur);
999 }
1000
1001
1002 int BufferView::workWidth() const
1003 {
1004         return width_;
1005 }
1006
1007
1008 void BufferView::recenter()
1009 {
1010         showCursor(d->cursor_, SCROLL_CENTER, true);
1011 }
1012
1013
1014 void BufferView::showCursor()
1015 {
1016         showCursor(d->cursor_, SCROLL_VISIBLE, true);
1017 }
1018
1019
1020 void BufferView::showCursor(DocIterator const & dit, ScrollType how,
1021         bool update)
1022 {
1023         if (scrollToCursor(dit, how) && update)
1024                 processUpdateFlags(Update::ForceDraw);
1025 }
1026
1027
1028 bool BufferView::scrollToCursor(DocIterator const & dit, ScrollType how)
1029 {
1030         // We are not properly started yet, delay until resizing is done.
1031         if (height_ == 0)
1032                 return false;
1033
1034         if (how == SCROLL_CENTER)
1035                 LYXERR(Debug::SCROLLING, "Centering cursor in workarea");
1036         else if (how == SCROLL_TOP)
1037                 LYXERR(Debug::SCROLLING, "Setting cursor to top of workarea");
1038         else
1039                 LYXERR(Debug::SCROLLING, "Making sure cursor is visible in workarea");
1040
1041         CursorSlice const & bot = dit.bottom();
1042         TextMetrics & tm = textMetrics(bot.text());
1043
1044         pos_type const max_pit = pos_type(bot.text()->paragraphs().size() - 1);
1045         pos_type bot_pit = bot.pit();
1046         if (bot_pit > max_pit) {
1047                 // FIXME: Why does this happen?
1048                 LYXERR0("bottom pit is greater that max pit: "
1049                         << bot_pit << " > " << max_pit);
1050                 bot_pit = max_pit;
1051         }
1052
1053         if (bot_pit == tm.first().first - 1)
1054                 tm.newParMetricsUp();
1055         else if (bot_pit == tm.last().first + 1)
1056                 tm.newParMetricsDown();
1057
1058         if (tm.contains(bot_pit) && how == SCROLL_VISIBLE) {
1059                 ParagraphMetrics const & pm = tm.parMetrics(bot_pit);
1060                 LBUFERR(!pm.rows().empty());
1061                 // FIXME: smooth scrolling doesn't work in mathed.
1062                 CursorSlice const & cs = dit.innerTextSlice();
1063                 int const ypos = pm.position() + coordOffset(dit).y;
1064                 ParagraphMetrics const & inner_pm =
1065                         textMetrics(cs.text()).parMetrics(cs.pit());
1066                 Dimension const & row_dim =
1067                         inner_pm.getRow(cs.pos(), dit.boundary()).dim();
1068                 int scrolled = 0;
1069
1070                 // We try to visualize the whole row, if the row height is larger than
1071                 // the screen height, we scroll to a heuristic value of height_ / 4.
1072                 // FIXME: This heuristic value should be replaced by a recursive search
1073                 // for a row in the inset that can be visualized completely.
1074                 if (row_dim.height() > height_) {
1075                         if (ypos < defaultRowHeight())
1076                                 scrolled = scroll(ypos - height_ / 4);
1077                         else if (ypos > height_ - defaultRowHeight())
1078                                 scrolled = scroll(ypos - 3 * height_ / 4);
1079                 }
1080
1081                 // If the top part of the row falls of the screen, we scroll
1082                 // up to align the top of the row with the top of the screen.
1083                 else if (ypos - row_dim.ascent() < 0 && ypos < height_) {
1084                         int const ynew = row_dim.ascent();
1085                         scrolled = scrollUp(ynew - ypos);
1086                 }
1087
1088                 // If the bottom of the row falls of the screen, we scroll down.
1089                 else if (ypos + row_dim.descent() > height_ && ypos > 0) {
1090                         int const ynew = height_ - row_dim.descent();
1091                         scrolled = scrollDown(ypos - ynew);
1092                 }
1093
1094                 // else, nothing to do, the cursor is already visible so we just return.
1095                 return scrolled != 0;
1096         }
1097
1098         // fix inline completion position
1099         if (d->inlineCompletionPos_.fixIfBroken())
1100                 d->inlineCompletionPos_ = DocIterator();
1101
1102         tm.redoParagraph(bot_pit);
1103         int const offset = coordOffset(dit).y;
1104         pit_type const old_pit = d->anchor_pit_;
1105         d->anchor_pit_ = bot_pit;
1106
1107         CursorSlice const & cs = dit.innerTextSlice();
1108         ParagraphMetrics const & inner_pm =
1109                 textMetrics(cs.text()).parMetrics(cs.pit());
1110         Dimension const & row_dim =
1111                 inner_pm.getRow(cs.pos(), dit.boundary()).dim();
1112
1113         int const old_ypos = d->anchor_ypos_;
1114         d->anchor_ypos_ = - offset + row_dim.ascent();
1115         if (how == SCROLL_CENTER)
1116                 d->anchor_ypos_ += height_/2 - row_dim.height() / 2;
1117         else if (offset > height_)
1118                 d->anchor_ypos_ = height_ - offset - defaultRowHeight();
1119         else
1120                 d->anchor_ypos_ = defaultRowHeight() * 2;
1121
1122         return d->anchor_ypos_ != old_ypos || d->anchor_pit_ != old_pit;
1123 }
1124
1125
1126 void BufferView::makeDocumentClass()
1127 {
1128         DocumentClassConstPtr olddc = buffer_.params().documentClassPtr();
1129         buffer_.params().makeDocumentClass(buffer_.isClone(), buffer_.isInternal());
1130         updateDocumentClass(olddc);
1131 }
1132
1133
1134 void BufferView::updateDocumentClass(DocumentClassConstPtr olddc)
1135 {
1136         StableDocIterator backcur(d->cursor_);
1137         ErrorList & el = buffer_.errorList("Class Switch");
1138         cap::switchBetweenClasses(
1139                         olddc, buffer_.params().documentClassPtr(),
1140                         static_cast<InsetText &>(buffer_.inset()), el);
1141
1142         setCursor(backcur.asDocIterator(&buffer_));
1143
1144         buffer_.errors("Class Switch");
1145 }
1146
1147
1148 /** Return the change status at cursor position, taking into account the
1149  * status at each level of the document iterator (a table in a deleted
1150  * footnote is deleted).
1151  * When \param outer is true, the top slice is not looked at.
1152  */
1153 static Change::Type lookupChangeType(DocIterator const & dit, bool outer = false)
1154 {
1155         size_t const depth = dit.depth() - (outer ? 1 : 0);
1156
1157         for (size_t i = 0 ; i < depth ; ++i) {
1158                 CursorSlice const & slice = dit[i];
1159                 if (!slice.inset().inMathed()
1160                     && slice.pos() < slice.paragraph().size()) {
1161                         Change::Type const ch = slice.paragraph().lookupChange(slice.pos()).type;
1162                         if (ch != Change::UNCHANGED)
1163                                 return ch;
1164                 }
1165         }
1166         return Change::UNCHANGED;
1167 }
1168
1169
1170 bool BufferView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
1171 {
1172         FuncCode const act = cmd.action();
1173
1174         // Can we use a readonly buffer?
1175         if (buffer_.isReadonly()
1176             && !lyxaction.funcHasFlag(act, LyXAction::ReadOnly)
1177             && !lyxaction.funcHasFlag(act, LyXAction::NoBuffer)) {
1178                 if (buffer_.hasReadonlyFlag())
1179                         flag.message(from_utf8(N_("Document is read-only")));
1180                 else
1181                         flag.message(from_utf8(N_("Document has been modified externally")));
1182                 flag.setEnabled(false);
1183                 return true;
1184         }
1185
1186         // Are we in a DELETED change-tracking region?
1187         if (lookupChangeType(d->cursor_, true) == Change::DELETED
1188             && !lyxaction.funcHasFlag(act, LyXAction::ReadOnly)
1189             && !lyxaction.funcHasFlag(act, LyXAction::NoBuffer)) {
1190                 flag.message(from_utf8(N_("This portion of the document is deleted.")));
1191                 flag.setEnabled(false);
1192                 return true;
1193         }
1194
1195         Cursor & cur = d->cursor_;
1196
1197         if (cur.getStatus(cmd, flag))
1198                 return true;
1199
1200         switch (act) {
1201
1202         // FIXME: This is a bit problematic because we don't check if this is
1203         // a document BufferView or not for these LFUNs. We probably have to
1204         // dispatch both to currentBufferView() and, if that fails,
1205         // to documentBufferView(); same as we do now for current Buffer and
1206         // document Buffer. Ideally those LFUN should go to Buffer as they
1207         // operate on the full Buffer and the cursor is only needed either for
1208         // an Undo record or to restore a cursor position. But we don't know
1209         // how to do that inside Buffer of course.
1210         case LFUN_BUFFER_PARAMS_APPLY:
1211         case LFUN_LAYOUT_MODULES_CLEAR:
1212         case LFUN_LAYOUT_MODULE_ADD:
1213         case LFUN_LAYOUT_RELOAD:
1214         case LFUN_TEXTCLASS_APPLY:
1215         case LFUN_TEXTCLASS_LOAD:
1216                 flag.setEnabled(!buffer_.isReadonly());
1217                 break;
1218
1219         case LFUN_UNDO:
1220                 // We do not use the LyXAction flag for readonly because Undo sets the
1221                 // buffer clean/dirty status by itself.
1222                 flag.setEnabled(!buffer_.isReadonly() && buffer_.undo().hasUndoStack());
1223                 break;
1224         case LFUN_REDO:
1225                 // We do not use the LyXAction flag for readonly because Redo sets the
1226                 // buffer clean/dirty status by itself.
1227                 flag.setEnabled(!buffer_.isReadonly() && buffer_.undo().hasRedoStack());
1228                 break;
1229         case LFUN_FILE_INSERT_PLAINTEXT_PARA:
1230         case LFUN_FILE_INSERT_PLAINTEXT: {
1231                 docstring const & fname = cmd.argument();
1232                 if (!FileName::isAbsolute(to_utf8(fname))) {
1233                         flag.message(_("Absolute filename expected."));
1234                         return false;
1235                 }
1236                 flag.setEnabled(cur.inTexted());
1237                 break;
1238         }
1239         case LFUN_FILE_INSERT:
1240         case LFUN_BOOKMARK_SAVE:
1241                 // FIXME: Actually, these LFUNS should be moved to Text
1242                 flag.setEnabled(cur.inTexted());
1243                 break;
1244
1245         case LFUN_FONT_STATE:
1246         case LFUN_LABEL_INSERT:
1247         case LFUN_INFO_INSERT:
1248         case LFUN_PARAGRAPH_GOTO:
1249         case LFUN_NOTE_NEXT:
1250         case LFUN_REFERENCE_NEXT:
1251         case LFUN_WORD_FIND:
1252         case LFUN_WORD_FIND_FORWARD:
1253         case LFUN_WORD_FIND_BACKWARD:
1254         case LFUN_WORD_REPLACE:
1255         case LFUN_MARK_OFF:
1256         case LFUN_MARK_ON:
1257         case LFUN_MARK_TOGGLE:
1258         case LFUN_SEARCH_STRING_SET:
1259         case LFUN_SCREEN_RECENTER:
1260         case LFUN_SCREEN_SHOW_CURSOR:
1261         case LFUN_BIBTEX_DATABASE_ADD:
1262         case LFUN_BIBTEX_DATABASE_DEL:
1263         case LFUN_BIBTEX_DATABASE_LIST:
1264         case LFUN_STATISTICS:
1265         case LFUN_KEYMAP_OFF:
1266         case LFUN_KEYMAP_PRIMARY:
1267         case LFUN_KEYMAP_SECONDARY:
1268         case LFUN_KEYMAP_TOGGLE:
1269         case LFUN_INSET_SELECT_ALL:
1270                 flag.setEnabled(true);
1271                 break;
1272
1273         case LFUN_GRAPHICS_UNIFY:
1274                 flag.setEnabled(cur.countInsetsInSelection(GRAPHICS_CODE)>1);
1275                 break;
1276
1277         case LFUN_WORD_FINDADV: {
1278                 FindAndReplaceOptions opt;
1279                 istringstream iss(to_utf8(cmd.argument()));
1280                 iss >> opt;
1281                 flag.setEnabled(opt.repl_buf_name.empty()
1282                                 || !buffer_.isReadonly());
1283                 break;
1284         }
1285
1286         case LFUN_LABEL_GOTO:
1287                 flag.setEnabled(!cmd.argument().empty()
1288                     || getInsetByCode<InsetRef>(cur, REF_CODE)
1289                     || getInsetByCode<InsetMathRef>(cur, MATH_REF_CODE));
1290                 break;
1291
1292         case LFUN_CHANGES_MERGE:
1293         case LFUN_CHANGE_NEXT:
1294         case LFUN_CHANGE_PREVIOUS:
1295         case LFUN_ALL_CHANGES_ACCEPT:
1296         case LFUN_ALL_CHANGES_REJECT:
1297                 flag.setEnabled(buffer_.areChangesPresent());
1298                 break;
1299
1300         case LFUN_SCREEN_UP:
1301         case LFUN_SCREEN_DOWN:
1302         case LFUN_SCROLL:
1303         case LFUN_SCREEN_UP_SELECT:
1304         case LFUN_SCREEN_DOWN_SELECT:
1305         case LFUN_INSET_FORALL:
1306                 flag.setEnabled(true);
1307                 break;
1308
1309         case LFUN_LAYOUT_TABULAR:
1310                 flag.setEnabled(cur.innerInsetOfType(TABULAR_CODE));
1311                 break;
1312
1313         case LFUN_LAYOUT:
1314                 flag.setEnabled(!cur.inset().forcePlainLayout(cur.idx()));
1315                 break;
1316
1317         case LFUN_LAYOUT_PARAGRAPH:
1318                 flag.setEnabled(cur.inset().allowParagraphCustomization(cur.idx()));
1319                 break;
1320
1321         case LFUN_BRANCH_ADD_INSERT:
1322                 flag.setEnabled(!(cur.inTexted() && cur.paragraph().isPassThru()));
1323                 break;
1324
1325         case LFUN_DIALOG_SHOW_NEW_INSET:
1326                 // FIXME: this is wrong, but I do not understand the
1327                 // intent (JMarc)
1328                 if (cur.inset().lyxCode() == CAPTION_CODE)
1329                         return cur.inset().getStatus(cur, cmd, flag);
1330                 // FIXME we should consider passthru paragraphs too.
1331                 flag.setEnabled(!(cur.inTexted() && cur.paragraph().isPassThru()));
1332                 break;
1333
1334         case LFUN_CITATION_INSERT: {
1335                 FuncRequest fr(LFUN_INSET_INSERT, "citation");
1336                 // FIXME: This could turn in a recursive hell.
1337                 // Shouldn't we use Buffer::getStatus() instead?
1338                 flag.setEnabled(lyx::getStatus(fr).enabled());
1339                 break;
1340         }
1341         case LFUN_INSET_APPLY: {
1342                 string const name = cmd.getArg(0);
1343                 Inset * inset = editedInset(name);
1344                 if (inset) {
1345                         FuncRequest fr(LFUN_INSET_MODIFY, cmd.argument());
1346                         if (!inset->getStatus(cur, fr, flag)) {
1347                                 // Every inset is supposed to handle this
1348                                 LASSERT(false, break);
1349                         }
1350                 } else {
1351                         FuncRequest fr(LFUN_INSET_INSERT, cmd.argument());
1352                         flag = lyx::getStatus(fr);
1353                 }
1354                 break;
1355         }
1356
1357         case LFUN_COPY:
1358                 flag.setEnabled(cur.selection());
1359                 break;
1360
1361         case LFUN_STATISTICS_REFERENCE_CLAMP: {
1362                 // disable optitem reset if clamp not used
1363                 if  (cmd.argument() == "reset" && d->stats_ref_value_c_ == 0) {
1364                                 flag.setEnabled(false);
1365                                 break;
1366                 }
1367                 flag.setEnabled(true);
1368                 break;
1369
1370         }
1371
1372         default:
1373                 return false;
1374         }
1375
1376         return true;
1377 }
1378
1379
1380 Inset * BufferView::editedInset(string const & name) const
1381 {
1382         map<string, Inset *>::const_iterator it = d->edited_insets_.find(name);
1383         return it == d->edited_insets_.end() ? nullptr : it->second;
1384 }
1385
1386
1387 void BufferView::editInset(string const & name, Inset * inset)
1388 {
1389         if (inset)
1390                 d->edited_insets_[name] = inset;
1391         else
1392                 d->edited_insets_.erase(name);
1393 }
1394
1395
1396 void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
1397 {
1398         LYXERR(Debug::ACTION, "BufferView::dispatch: cmd: " << cmd);
1399
1400         string const argument = to_utf8(cmd.argument());
1401         Cursor & cur = d->cursor_;
1402         Cursor old = cur;
1403
1404         // Don't dispatch function that does not apply to internal buffers.
1405         if (buffer_.isInternal()
1406             && lyxaction.funcHasFlag(cmd.action(), LyXAction::NoInternal))
1407                 return;
1408
1409         // We'll set this back to false if need be.
1410         bool dispatched = true;
1411         buffer_.undo().beginUndoGroup();
1412
1413         FuncCode const act = cmd.action();
1414         switch (act) {
1415
1416         case LFUN_BUFFER_PARAMS_APPLY: {
1417                 DocumentClassConstPtr olddc = buffer_.params().documentClassPtr();
1418                 cur.recordUndoBufferParams();
1419                 istringstream ss(to_utf8(cmd.argument()));
1420                 Lexer lex;
1421                 lex.setStream(ss);
1422                 int const unknown_tokens = buffer_.readHeader(lex);
1423                 if (unknown_tokens != 0) {
1424                         LYXERR0("Warning in LFUN_BUFFER_PARAMS_APPLY!\n"
1425                                                 << unknown_tokens << " unknown token"
1426                                                 << (unknown_tokens == 1 ? "" : "s"));
1427                 }
1428                 updateDocumentClass(olddc);
1429
1430                 // We are most certainly here because of a change in the document
1431                 // It is then better to make sure that all dialogs are in sync with
1432                 // current document settings.
1433                 dr.screenUpdate(Update::Force | Update::FitCursor);
1434                 dr.forceBufferUpdate();
1435                 break;
1436         }
1437
1438         case LFUN_LAYOUT_MODULES_CLEAR: {
1439                 // FIXME: this modifies the document in cap::switchBetweenClasses
1440                 //  without calling recordUndo. Fix this before using
1441                 //  recordUndoBufferParams().
1442                 cur.recordUndoFullBuffer();
1443                 buffer_.params().clearLayoutModules();
1444                 makeDocumentClass();
1445                 dr.screenUpdate(Update::Force);
1446                 dr.forceBufferUpdate();
1447                 break;
1448         }
1449
1450         case LFUN_LAYOUT_MODULE_ADD: {
1451                 BufferParams const & params = buffer_.params();
1452                 if (!params.layoutModuleCanBeAdded(argument)) {
1453                         LYXERR0("Module `" << argument <<
1454                                 "' cannot be added due to failed requirements or "
1455                                 "conflicts with installed modules.");
1456                         break;
1457                 }
1458                 // FIXME: this modifies the document in cap::switchBetweenClasses
1459                 //  without calling recordUndo. Fix this before using
1460                 //  recordUndoBufferParams().
1461                 cur.recordUndoFullBuffer();
1462                 buffer_.params().addLayoutModule(argument);
1463                 makeDocumentClass();
1464                 dr.screenUpdate(Update::Force);
1465                 dr.forceBufferUpdate();
1466                 break;
1467         }
1468
1469         case LFUN_TEXTCLASS_APPLY: {
1470                 // since this shortcircuits, the second call is made only if
1471                 // the first fails
1472                 bool const success =
1473                         LayoutFileList::get().load(argument, buffer_.temppath()) ||
1474                         LayoutFileList::get().load(argument, buffer_.filePath());
1475                 if (!success) {
1476                         docstring s = bformat(_("The document class `%1$s' "
1477                                                  "could not be loaded."), from_utf8(argument));
1478                         frontend::Alert::error(_("Could not load class"), s);
1479                         break;
1480                 }
1481
1482                 LayoutFile const * old_layout = buffer_.params().baseClass();
1483                 LayoutFile const * new_layout = &(LayoutFileList::get()[argument]);
1484
1485                 if (old_layout == new_layout)
1486                         // nothing to do
1487                         break;
1488
1489                 // Save the old, possibly modular, layout for use in conversion.
1490                 // FIXME: this modifies the document in cap::switchBetweenClasses
1491                 //  without calling recordUndo. Fix this before using
1492                 //  recordUndoBufferParams().
1493                 cur.recordUndoFullBuffer();
1494                 buffer_.params().setBaseClass(argument, buffer_.layoutPos());
1495                 makeDocumentClass();
1496                 dr.screenUpdate(Update::Force);
1497                 dr.forceBufferUpdate();
1498                 break;
1499         }
1500
1501         case LFUN_TEXTCLASS_LOAD: {
1502                 // since this shortcircuits, the second call is made only if
1503                 // the first fails
1504                 bool const success =
1505                         LayoutFileList::get().load(argument, buffer_.temppath()) ||
1506                         LayoutFileList::get().load(argument, buffer_.filePath());
1507                 if (!success) {
1508                         docstring s = bformat(_("The document class `%1$s' "
1509                                                  "could not be loaded."), from_utf8(argument));
1510                         frontend::Alert::error(_("Could not load class"), s);
1511                 }
1512                 break;
1513         }
1514
1515         case LFUN_LAYOUT_RELOAD: {
1516                 LayoutFileIndex bc = buffer_.params().baseClassID();
1517                 LayoutFileList::get().reset(bc);
1518                 buffer_.params().setBaseClass(bc, buffer_.layoutPos());
1519                 makeDocumentClass();
1520                 dr.screenUpdate(Update::Force);
1521                 dr.forceBufferUpdate();
1522                 break;
1523         }
1524
1525         case LFUN_UNDO: {
1526                 dr.setMessage(_("Undo"));
1527                 cur.clearSelection();
1528                 // We need to find out if the bibliography information
1529                 // has changed. See bug #11055.
1530                 // So these should not be references...
1531                 string const engine = buffer().params().citeEngine();
1532                 CiteEngineType const enginetype = buffer().params().citeEngineType();
1533                 if (!cur.undoAction())
1534                         dr.setMessage(_("No further undo information"));
1535                 else {
1536                         dr.screenUpdate(Update::Force | Update::FitCursor);
1537                         dr.forceBufferUpdate();
1538                         resetInlineCompletionPos();
1539                         if (buffer().params().citeEngine() != engine ||
1540                             buffer().params().citeEngineType() != enginetype)
1541                                 buffer().invalidateCiteLabels();
1542                 }
1543                 break;
1544         }
1545
1546         case LFUN_REDO: {
1547                 dr.setMessage(_("Redo"));
1548                 cur.clearSelection();
1549                 // We need to find out if the bibliography information
1550                 // has changed. See bug #11055.
1551                 // So these should not be references...
1552                 string const engine = buffer().params().citeEngine();
1553                 CiteEngineType const enginetype = buffer().params().citeEngineType();
1554                 if (!cur.redoAction())
1555                         dr.setMessage(_("No further redo information"));
1556                 else {
1557                         dr.screenUpdate(Update::Force | Update::FitCursor);
1558                         dr.forceBufferUpdate();
1559                         resetInlineCompletionPos();
1560                         if (buffer().params().citeEngine() != engine ||
1561                             buffer().params().citeEngineType() != enginetype)
1562                                 buffer().invalidateCiteLabels();
1563                 }
1564                 break;
1565         }
1566
1567         case LFUN_FONT_STATE:
1568                 dr.setMessage(cur.currentState(false));
1569                 break;
1570
1571         case LFUN_BOOKMARK_SAVE:
1572                 dr.screenUpdate(Update::Force);
1573                 saveBookmark(convert<unsigned int>(to_utf8(cmd.argument())));
1574                 break;
1575
1576         case LFUN_LABEL_GOTO: {
1577                 docstring label = cmd.argument();
1578                 if (label.empty()) {
1579                         InsetRef * inset =
1580                                 getInsetByCode<InsetRef>(cur, REF_CODE);
1581                         if (inset) {
1582                                 label = inset->getParam("reference");
1583                                 // persistent=false: use temp_bookmark
1584                                 saveBookmark(0);
1585                         }
1586                 }
1587                 if (!label.empty()) {
1588                         gotoLabel(label);
1589                         // at the moment, this is redundant, since gotoLabel will
1590                         // eventually call LFUN_PARAGRAPH_GOTO, but it seems best
1591                         // to have it here.
1592                         dr.screenUpdate(Update::Force | Update::FitCursor);
1593                 } else {
1594                         InsetMathRef * minset =
1595                                 getInsetByCode<InsetMathRef>(cur, MATH_REF_CODE);
1596                         if (minset)
1597                                 lyx::dispatch(FuncRequest(LFUN_LABEL_GOTO,
1598                                                         minset->getTarget()));
1599                 }
1600                 break;
1601         }
1602
1603         case LFUN_PARAGRAPH_GOTO: {
1604                 int const id = convert<int>(cmd.getArg(0));
1605                 pos_type const pos = convert<int>(cmd.getArg(1));
1606                 if (id < 0)
1607                         break;
1608                 string const str_id_end = cmd.getArg(2);
1609                 string const str_pos_end = cmd.getArg(3);
1610                 int i = 0;
1611                 for (Buffer * b = &buffer_; i == 0 || b != &buffer_;
1612                         b = theBufferList().next(b)) {
1613
1614                         Cursor curs(*this);
1615                         curs.setCursor(b->getParFromID(id));
1616                         if (curs.atEnd()) {
1617                                 LYXERR(Debug::INFO, "No matching paragraph found! [" << id << "].");
1618                                 ++i;
1619                                 continue;
1620                         }
1621                         LYXERR(Debug::INFO, "Paragraph " << curs.paragraph().id()
1622                                 << " found in buffer `"
1623                                 << b->absFileName() << "'.");
1624
1625                         if (b == &buffer_) {
1626                                 bool success;
1627                                 if (str_id_end.empty() || str_pos_end.empty()) {
1628                                         // Set the cursor
1629                                         curs.pos() = pos;
1630                                         mouseSetCursor(curs);
1631                                         success = true;
1632                                 } else {
1633                                         int const id_end = convert<int>(str_id_end);
1634                                         pos_type const pos_end = convert<int>(str_pos_end);
1635                                         success = setCursorFromEntries({id, pos},
1636                                                                        {id_end, pos_end});
1637                                 }
1638                                 if (success && scrollToCursor(d->cursor_, SCROLL_TOP))
1639                                                 dr.screenUpdate(Update::Force);
1640                         } else {
1641                                 // Switch to other buffer view and resend cmd
1642                                 lyx::dispatch(FuncRequest(
1643                                         LFUN_BUFFER_SWITCH, b->absFileName()));
1644                                 lyx::dispatch(cmd);
1645                         }
1646                         break;
1647                 }
1648                 break;
1649         }
1650
1651         case LFUN_NOTE_NEXT:
1652                 if (gotoInset(this, { NOTE_CODE }, false))
1653                         dr.screenUpdate(Update::Force);
1654                 break;
1655
1656         case LFUN_REFERENCE_NEXT: {
1657                 if (gotoInset(this, { LABEL_CODE, REF_CODE }, true))
1658                         dr.screenUpdate(Update::Force);
1659                 break;
1660         }
1661
1662         case LFUN_CHANGE_NEXT:
1663                 findNextChange(this);
1664                 if (cur.inset().isTable())
1665                         // In tables, there might be whole changed rows or columns
1666                         cur.dispatch(cmd);
1667                 // FIXME: Move this LFUN to Buffer so that we don't have to do this:
1668                 dr.screenUpdate(Update::Force | Update::FitCursor);
1669                 break;
1670
1671         case LFUN_CHANGE_PREVIOUS:
1672                 findPreviousChange(this);
1673                 if (cur.inset().isTable())
1674                         // In tables, there might be whole changed rows or columns
1675                         cur.dispatch(cmd);
1676                 // FIXME: Move this LFUN to Buffer so that we don't have to do this:
1677                 dr.screenUpdate(Update::Force | Update::FitCursor);
1678                 break;
1679
1680         case LFUN_CHANGES_MERGE:
1681                 if (findNextChange(this) || findPreviousChange(this)) {
1682                         dr.screenUpdate(Update::Force | Update::FitCursor);
1683                         dr.forceBufferUpdate();
1684                         showDialog("changes");
1685                 }
1686                 break;
1687
1688         case LFUN_ALL_CHANGES_ACCEPT: {
1689                 UndoGroupHelper helper(cur);
1690                 // select complete document
1691                 cur.reset();
1692                 cur.selHandle(true);
1693                 buffer_.text().cursorBottom(cur);
1694                 // accept everything in a single step to support atomic undo
1695                 // temporarily disable track changes in order to end with really
1696                 // no new (e.g., DPSM-caused) changes (see #7487)
1697                 bool const track = buffer_.params().track_changes;
1698                 buffer_.params().track_changes = false;
1699                 buffer_.text().acceptOrRejectChanges(cur, Text::ACCEPT);
1700                 buffer_.params().track_changes = track;
1701                 cur.resetAnchor();
1702                 // FIXME: Move this LFUN to Buffer so that we don't have to do this:
1703                 dr.screenUpdate(Update::Force | Update::FitCursor);
1704                 dr.forceBufferUpdate();
1705                 break;
1706         }
1707
1708         case LFUN_ALL_CHANGES_REJECT: {
1709                 UndoGroupHelper helper(cur);
1710                 // select complete document
1711                 cur.reset();
1712                 cur.selHandle(true);
1713                 buffer_.text().cursorBottom(cur);
1714                 // reject everything in a single step to support atomic undo
1715                 // temporarily disable track changes in order to end with really
1716                 // no new (e.g., DPSM-caused) changes (see #7487)
1717                 bool const track = buffer_.params().track_changes;
1718                 buffer_.params().track_changes = false;
1719                 buffer_.text().acceptOrRejectChanges(cur, Text::REJECT);
1720                 buffer_.params().track_changes = track;
1721                 cur.resetAnchor();
1722                 // FIXME: Move this LFUN to Buffer so that we don't have to do this:
1723                 dr.screenUpdate(Update::Force | Update::FitCursor);
1724                 dr.forceBufferUpdate();
1725                 break;
1726         }
1727
1728         case LFUN_WORD_FIND_FORWARD:
1729         case LFUN_WORD_FIND_BACKWARD: {
1730                 docstring searched_string;
1731
1732                 if (!cmd.argument().empty()) {
1733                         setSearchRequestCache(cmd.argument());
1734                         searched_string = cmd.argument();
1735                 } else {
1736                         searched_string = searchRequestCache();
1737                 }
1738
1739                 if (searched_string.empty())
1740                         break;
1741
1742                 docstring const data =
1743                         find2string(searched_string, false, false,
1744                                     act == LFUN_WORD_FIND_FORWARD, false, false, false);
1745                 bool found = lyxfind(this, FuncRequest(LFUN_WORD_FIND, data));
1746                 if (found)
1747                         dr.screenUpdate(Update::Force | Update::FitCursor);
1748                 else
1749                         dr.setMessage(_("Search string not found!"));
1750                 break;
1751         }
1752
1753         case LFUN_WORD_FIND: {
1754                 docstring arg = cmd.argument();
1755                 if (arg.empty())
1756                         arg = searchRequestCache();
1757                 if (arg.empty()) {
1758                         lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "findreplace"));
1759                         break;
1760                 }
1761                 if (lyxfind(this, FuncRequest(act, arg)))
1762                         dr.screenUpdate(Update::Force | Update::FitCursor);
1763                 else
1764                         dr.setMessage(_("Search string not found!"));
1765
1766                 setSearchRequestCache(arg);
1767                 break;
1768         }
1769
1770         case LFUN_SEARCH_STRING_SET: {
1771                 docstring pattern = cmd.argument();
1772                 if (!pattern.empty()) {
1773                         setSearchRequestCache(pattern);
1774                         break;
1775                 }
1776                 if (cur.selection())
1777                         pattern = cur.selectionAsString(false);
1778                 else if (!cur.inTexted())
1779                         break; // not suitable for selectWord at cursor
1780                 else {
1781                         pos_type spos = cur.pos();
1782                         cur.innerText()->selectWord(cur, WHOLE_WORD);
1783                         pattern = cur.selectionAsString(false);
1784                         cur.selection(false);
1785                         cur.pos() = spos;
1786                 }
1787                 setSearchRequestCache(pattern);
1788                 break;
1789         }
1790
1791         case LFUN_WORD_REPLACE: {
1792                 if (lyxreplace(this, cmd)) {
1793                         dr.forceBufferUpdate();
1794                         dr.screenUpdate(Update::Force | Update::FitCursor);
1795                 }
1796                 else
1797                         dr.setMessage(_("Search string not found!"));
1798                 break;
1799         }
1800
1801         case LFUN_WORD_FINDADV: {
1802                 FindAndReplaceOptions opt;
1803                 istringstream iss(to_utf8(cmd.argument()));
1804                 iss >> opt;
1805                 if (findAdv(this, opt)) {
1806                         dr.screenUpdate(Update::Force | Update::FitCursor);
1807                         cur.dispatched();
1808                         dispatched = true;
1809                 } else {
1810                         cur.undispatched();
1811                         dispatched = false;
1812                 }
1813                 break;
1814         }
1815
1816         case LFUN_INDEX_TAG_ALL: {
1817                 if (cur.pos() == 0)
1818                         // nothing precedes
1819                         break;
1820
1821                 Inset * ins = cur.nextInset();
1822                 if (!ins || ins->lyxCode() != INDEX_CODE)
1823                         // not at index inset
1824                         break;
1825
1826                 // clone the index inset
1827                 InsetIndex * cins =
1828                         new InsetIndex(static_cast<InsetIndex &>(*cur.nextInset()));
1829                 // In order to avoid duplication, we compare the
1830                 // LaTeX output if we find another index inset after
1831                 // the word
1832                 odocstringstream oilatex;
1833                 otexstream oits(oilatex);
1834                 OutputParams rp(&cur.buffer()->params().encoding());
1835                 ins->latex(oits, rp);
1836                 cap::copyInsetToTemp(cur, cins);
1837
1838                 // move backwards into preceding word
1839                 // skip over other index insets
1840                 cur.backwardPosIgnoreCollapsed();
1841                 while (true) {
1842                         if (cur.inset().lyxCode() == INDEX_CODE)
1843                                 cur.pop_back();
1844                         else if (cur.prevInset() && cur.prevInset()->lyxCode() == INDEX_CODE)
1845                                 cur.backwardPosIgnoreCollapsed();
1846                         else
1847                                 break;
1848                 }
1849                 if (!cur.inTexted()) {
1850                         // Nothing to do here.
1851                         setCursorFromInset(ins);
1852                         break;
1853                 }
1854                 // Get word or selection
1855                 cur.text()->selectWord(cur, WHOLE_WORD);
1856                 docstring const searched_string = cur.selectionAsString(false);
1857                 if (searched_string.empty())
1858                         break;
1859                 // Start from the beginning
1860                 lyx::dispatch(FuncRequest(LFUN_BUFFER_BEGIN));
1861                 while (findOne(this, searched_string,
1862                                false,// case sensitive
1863                                true,// match whole word only
1864                                true,// forward
1865                                false,//find deleted
1866                                false,//check wrap
1867                                false,// auto-wrap
1868                                false,// instant
1869                                false// only selection
1870                                )) {
1871                         cur.clearSelection();
1872                         Inset * ains = cur.nextInset();
1873                         if (ains && ains->lyxCode() == INDEX_CODE) {
1874                                 // We have an index inset.
1875                                 // Check whether it has the same
1876                                 // LaTeX content and move on if so.
1877                                 odocstringstream filatex;
1878                                 otexstream fits(filatex);
1879                                 ains->latex(fits, rp);
1880                                 if (oilatex.str() == filatex.str())
1881                                         continue;
1882                         }
1883                         // Paste the inset and possibly continue
1884                         cap::pasteFromTemp(cursor(), cursor().buffer()->errorList("Paste"));
1885                 }
1886                 // Go back to start position.
1887                 setCursorFromInset(ins);
1888                 dr.screenUpdate(cur.result().screenUpdate());
1889                 if (cur.result().needBufferUpdate())
1890                         dr.forceBufferUpdate();
1891                 break;
1892         }
1893
1894         case LFUN_MARK_OFF:
1895                 cur.clearSelection();
1896                 dr.setMessage(from_utf8(N_("Mark off")));
1897                 break;
1898
1899         case LFUN_MARK_ON:
1900                 cur.clearSelection();
1901                 cur.setMark(true);
1902                 dr.setMessage(from_utf8(N_("Mark on")));
1903                 break;
1904
1905         case LFUN_MARK_TOGGLE:
1906                 cur.selection(false);
1907                 if (cur.mark()) {
1908                         cur.setMark(false);
1909                         dr.setMessage(from_utf8(N_("Mark removed")));
1910                 } else {
1911                         cur.setMark(true);
1912                         dr.setMessage(from_utf8(N_("Mark set")));
1913                 }
1914                 cur.resetAnchor();
1915                 break;
1916
1917         case LFUN_SCREEN_SHOW_CURSOR:
1918                 showCursor();
1919                 break;
1920
1921         case LFUN_SCREEN_RECENTER:
1922                 recenter();
1923                 break;
1924
1925         case LFUN_BIBTEX_DATABASE_ADD: {
1926                 Cursor tmpcur = cur;
1927                 findInset(tmpcur, { BIBTEX_CODE }, false);
1928                 InsetBibtex * inset = getInsetByCode<InsetBibtex>(tmpcur,
1929                                                 BIBTEX_CODE);
1930                 if (inset) {
1931                         if (inset->addDatabase(cmd.argument()))
1932                                 dr.forceBufferUpdate();
1933                 }
1934                 break;
1935         }
1936
1937         case LFUN_BIBTEX_DATABASE_DEL: {
1938                 Cursor tmpcur = cur;
1939                 findInset(tmpcur, { BIBTEX_CODE }, false);
1940                 InsetBibtex * inset = getInsetByCode<InsetBibtex>(tmpcur,
1941                                                 BIBTEX_CODE);
1942                 if (inset) {
1943                         if (inset->delDatabase(cmd.argument()))
1944                                 dr.forceBufferUpdate();
1945                 }
1946                 break;
1947         }
1948
1949         case LFUN_GRAPHICS_UNIFY: {
1950
1951                 cur.recordUndoFullBuffer();
1952
1953                 DocIterator from, to;
1954                 from = cur.selectionBegin();
1955                 to = cur.selectionEnd();
1956
1957                 string const newId = cmd.getArg(0);
1958                 bool fetchId = newId.empty(); //if we wait for groupId from first graphics inset
1959
1960                 InsetGraphicsParams grp_par;
1961                 if (!fetchId)
1962                         InsetGraphics::string2params(graphics::getGroupParams(buffer_, newId), buffer_, grp_par);
1963
1964                 if (!from.nextInset())  //move to closest inset
1965                         from.forwardInset();
1966
1967                 while (!from.empty() && from < to) {
1968                         Inset * inset = from.nextInset();
1969                         if (!inset)
1970                                 break;
1971                         InsetGraphics * ig = inset->asInsetGraphics();
1972                         if (ig) {
1973                                 InsetGraphicsParams inspar = ig->getParams();
1974                                 if (fetchId) {
1975                                         grp_par = inspar;
1976                                         fetchId = false;
1977                                 } else {
1978                                         grp_par.filename = inspar.filename;
1979                                         ig->setParams(grp_par);
1980                                 }
1981                         }
1982                         from.forwardInset();
1983                 }
1984                 dr.screenUpdate(Update::Force); //needed if triggered from context menu
1985                 break;
1986         }
1987
1988         case LFUN_BIBTEX_DATABASE_LIST: {
1989                 docstring_list const & files = buffer_.getBibfiles();
1990                 bool first = true;
1991                 docstring result;
1992                 char const separator(os::path_separator());
1993                 for (auto const & file : files) {
1994                         if (first)
1995                                 first = false;
1996                         else
1997                                 result += separator;
1998
1999                         FileName const fn = buffer_.getBibfilePath(file);
2000                         string const path = fn.realPath();
2001                         result += from_utf8(os::external_path(path));
2002                 }
2003                 dr.setMessage(result);
2004                 break;
2005         }
2006
2007         case LFUN_STATISTICS: {
2008                 DocIterator from, to;
2009                 if (cur.selection()) {
2010                         from = cur.selectionBegin();
2011                         to = cur.selectionEnd();
2012                 } else {
2013                         from = doc_iterator_begin(&buffer_);
2014                         to = doc_iterator_end(&buffer_);
2015                 }
2016                 buffer_.updateStatistics(from, to);
2017                 int const words = buffer_.wordCount();
2018                 int const chars = buffer_.charCount(false);
2019                 int const chars_blanks = buffer_.charCount(true);
2020                 docstring message;
2021                 if (cur.selection())
2022                         message = _("Statistics for the selection:");
2023                 else
2024                         message = _("Statistics for the document:");
2025                 message += "\n\n";
2026                 if (words != 1)
2027                         message += bformat(_("%1$d words"), words);
2028                 else
2029                         message += _("One word");
2030                 message += "\n";
2031                 if (chars_blanks != 1)
2032                         message += bformat(_("%1$d characters"), chars_blanks);
2033                 else
2034                         message += _("One character");
2035                 message += "\n";
2036                 if (chars != 1)
2037                         message += bformat(_("%1$d characters (no blanks)"), chars);
2038                 else
2039                         message += _("One character (no blanks)");
2040
2041                 Alert::information(_("Statistics"), message);
2042         }
2043                 break;
2044
2045         case LFUN_STATISTICS_REFERENCE_CLAMP: {
2046                 d->stats_update_trigger_ = true;
2047                 if  (cmd.argument() == "reset") {
2048                         d->stats_ref_value_w_ = d->stats_ref_value_c_ = d->stats_ref_value_nb_ = 0;
2049                         break;
2050                 }
2051
2052                 DocIterator from, to;
2053                 from = doc_iterator_begin(&buffer_);
2054                 to = doc_iterator_end(&buffer_);
2055                 buffer_.updateStatistics(from, to);
2056
2057                 d->stats_ref_value_w_ = buffer_.wordCount();
2058                 d->stats_ref_value_c_ = buffer_.charCount(true);
2059                 d->stats_ref_value_nb_ = buffer_.charCount(false);
2060                 break;
2061         }
2062
2063
2064         case LFUN_SCREEN_UP:
2065         case LFUN_SCREEN_DOWN: {
2066                 Point p = getPos(cur);
2067                 // This code has been commented out to enable to scroll down a
2068                 // document, even if there are large insets in it (see bug #5465).
2069                 /*if (p.y < 0 || p.y > height_) {
2070                         // The cursor is off-screen so recenter before proceeding.
2071                         showCursor();
2072                         p = getPos(cur);
2073                 }*/
2074                 int const scrolled = scroll(act == LFUN_SCREEN_UP
2075                         ? -height_ : height_);
2076                 if (act == LFUN_SCREEN_UP && scrolled > -height_)
2077                         p = Point(0, 0);
2078                 if (act == LFUN_SCREEN_DOWN && scrolled < height_)
2079                         p = Point(width_, height_);
2080                 bool const in_texted = cur.inTexted();
2081                 cur.setCursor(doc_iterator_begin(cur.buffer()));
2082                 cur.selHandle(false);
2083                 // Force an immediate computation of metrics because we need it below
2084                 if (scrolled)
2085                         processUpdateFlags(Update::Force);
2086
2087                 d->text_metrics_[&buffer_.text()].editXY(cur, p.x, p.y,
2088                         true, act == LFUN_SCREEN_UP);
2089                 //FIXME: what to do with cur.x_target()?
2090                 bool update = in_texted && cur.bv().checkDepm(cur, old);
2091                 cur.finishUndo();
2092
2093                 if (update || cur.mark())
2094                         dr.screenUpdate(Update::Force | Update::FitCursor);
2095                 if (update)
2096                         dr.forceBufferUpdate();
2097                 break;
2098         }
2099
2100         case LFUN_SCROLL: {
2101                 string const scroll_type = cmd.getArg(0);
2102                 int scroll_step = 0;
2103                 if (scroll_type == "line")
2104                         scroll_step = d->scrollbarParameters_.single_step;
2105                 else if (scroll_type == "page")
2106                         scroll_step = d->scrollbarParameters_.page_step;
2107                 else
2108                         return;
2109                 string const scroll_quantity = cmd.getArg(1);
2110                 if (scroll_quantity == "up")
2111                         scrollUp(scroll_step);
2112                 else if (scroll_quantity == "down")
2113                         scrollDown(scroll_step);
2114                 else {
2115                         int const scroll_value = convert<int>(scroll_quantity);
2116                         if (scroll_value)
2117                                 scroll(scroll_step * scroll_value);
2118                 }
2119                 dr.screenUpdate(Update::ForceDraw);
2120                 dr.forceBufferUpdate();
2121                 break;
2122         }
2123
2124         case LFUN_SCREEN_UP_SELECT: {
2125                 // FIXME: why is the algorithm different from LFUN_SCREEN_UP?
2126                 cur.selHandle(true);
2127                 if (isTopScreen()) {
2128                         lyx::dispatch(FuncRequest(LFUN_BUFFER_BEGIN_SELECT));
2129                         cur.finishUndo();
2130                         break;
2131                 }
2132                 int y = getPos(cur).y;
2133                 int const ymin = y - height_ + defaultRowHeight();
2134                 while (y > ymin && cur.up())
2135                         y = getPos(cur).y;
2136
2137                 cur.finishUndo();
2138                 dr.screenUpdate(Update::SinglePar | Update::FitCursor);
2139                 break;
2140         }
2141
2142         case LFUN_SCREEN_DOWN_SELECT: {
2143                 // FIXME: why is the algorithm different from LFUN_SCREEN_DOWN?
2144                 cur.selHandle(true);
2145                 if (isBottomScreen()) {
2146                         lyx::dispatch(FuncRequest(LFUN_BUFFER_END_SELECT));
2147                         cur.finishUndo();
2148                         break;
2149                 }
2150                 int y = getPos(cur).y;
2151                 int const ymax = y + height_ - defaultRowHeight();
2152                 while (y < ymax && cur.down())
2153                         y = getPos(cur).y;
2154
2155                 cur.finishUndo();
2156                 dr.screenUpdate(Update::SinglePar | Update::FitCursor);
2157                 break;
2158         }
2159
2160
2161         case LFUN_INSET_SELECT_ALL: {
2162                 // true if all cells are selected
2163                 bool const all_selected = cur.depth() > 1
2164                     && cur.selBegin().at_begin()
2165                     && cur.selEnd().at_end();
2166                 // true if some cells are selected
2167                 bool const cells_selected = cur.depth() > 1
2168                     && cur.selBegin().at_cell_begin()
2169                         && cur.selEnd().at_cell_end();
2170                 if (all_selected || (cells_selected && !cur.inset().isTable())) {
2171                         // All the contents of the inset if selected, or only at
2172                         // least one cell but inset is not a table.
2173                         // Select the inset from outside.
2174                         cur.pop();
2175                         cur.resetAnchor();
2176                         cur.selection(true);
2177                         cur.posForward();
2178                 } else if (cells_selected) {
2179                         // At least one complete cell is selected and inset is a table.
2180                         // Select all cells
2181                         cur.idx() = 0;
2182                         cur.pit() = 0;
2183                         cur.pos() = 0;
2184                         cur.resetAnchor();
2185                         cur.selection(true);
2186                         cur.idx() = cur.lastidx();
2187                         cur.pit() = cur.lastpit();
2188                         cur.pos() = cur.lastpos();
2189                 } else {
2190                         // select current cell
2191                         cur.pit() = 0;
2192                         cur.pos() = 0;
2193                         cur.resetAnchor();
2194                         cur.selection(true);
2195                         cur.pit() = cur.lastpit();
2196                         cur.pos() = cur.lastpos();
2197                 }
2198                 cur.setCurrentFont();
2199                 dr.screenUpdate(Update::Force);
2200                 break;
2201         }
2202
2203
2204         case LFUN_UNICODE_INSERT: {
2205                 if (cmd.argument().empty())
2206                         break;
2207
2208                 FuncCode code = cur.inset().currentMode() == Inset::MATH_MODE ?
2209                         LFUN_MATH_INSERT : LFUN_SELF_INSERT;
2210                 int i = 0;
2211                 while (true) {
2212                         docstring const arg = from_utf8(cmd.getArg(i));
2213                         if (arg.empty())
2214                                 break;
2215                         if (!isHex(arg)) {
2216                                 LYXERR0("Not a hexstring: " << arg);
2217                                 ++i;
2218                                 continue;
2219                         }
2220                         char_type c = hexToInt(arg);
2221                         if (c >= 32 && c < 0x10ffff) {
2222                                 LYXERR(Debug::KEY, "Inserting c: " << c);
2223                                 lyx::dispatch(FuncRequest(code, docstring(1, c)));
2224                         }
2225                         ++i;
2226                 }
2227                 break;
2228         }
2229
2230
2231         // This would be in Buffer class if only Cursor did not
2232         // require a bufferview
2233         case LFUN_INSET_FORALL: {
2234                 docstring const name = from_utf8(cmd.getArg(0));
2235                 string const commandstr = cmd.getLongArg(1);
2236                 FuncRequest const fr = lyxaction.lookupFunc(commandstr);
2237
2238                 // an arbitrary number to limit number of iterations
2239                 const int max_iter = 100000;
2240                 int iterations = 0;
2241                 Cursor & bvcur = d->cursor_;
2242                 Cursor const savecur = bvcur;
2243                 bvcur.reset();
2244                 if (!bvcur.nextInset())
2245                         bvcur.forwardInset();
2246                 bvcur.beginUndoGroup();
2247                 while(bvcur && iterations < max_iter) {
2248                         Inset * const ins = bvcur.nextInset();
2249                         if (!ins)
2250                                 break;
2251                         docstring insname = ins->layoutName();
2252                         while (!insname.empty()) {
2253                                 if (insname == name || name == from_utf8("*")) {
2254                                         lyx::dispatch(fr, dr);
2255                                         // we do not want to remember selection here
2256                                         bvcur.clearSelection();
2257                                         ++iterations;
2258                                         break;
2259                                 }
2260                                 size_t const i = insname.rfind(':');
2261                                 if (i == string::npos)
2262                                         break;
2263                                 insname = insname.substr(0, i);
2264                         }
2265                         // if we did not delete the inset, skip it
2266                         if (!bvcur.nextInset() || bvcur.nextInset() == ins)
2267                                 bvcur.forwardInset();
2268                 }
2269                 bvcur = savecur;
2270                 bvcur.fixIfBroken();
2271                 /** This is a dummy undo record only to remember the cursor
2272                  * that has just been set; this will be used on a redo action
2273                  * (see ticket #10097)
2274
2275                  * FIXME: a better fix would be to have a way to set the
2276                  * cursor value directly, but I am not sure it is worth it.
2277                  */
2278                 bvcur.recordUndo();
2279                 bvcur.endUndoGroup();
2280                 dr.screenUpdate(Update::Force);
2281                 dr.forceBufferUpdate();
2282
2283                 if (iterations >= max_iter) {
2284                         dr.setError(true);
2285                         dr.setMessage(bformat(_("`inset-forall' interrupted because number of actions is larger than %1$d"), max_iter));
2286                 } else
2287                         dr.setMessage(bformat(_("Applied \"%1$s\" to %2$d insets"), from_utf8(commandstr), iterations));
2288                 break;
2289         }
2290
2291
2292         case LFUN_BRANCH_ADD_INSERT: {
2293                 docstring branch_name = from_utf8(cmd.getArg(0));
2294                 if (branch_name.empty())
2295                         if (!Alert::askForText(branch_name, _("Branch name")) ||
2296                                                 branch_name.empty())
2297                                 break;
2298
2299                 DispatchResult drtmp;
2300                 buffer_.dispatch(FuncRequest(LFUN_BRANCH_ADD, branch_name), drtmp);
2301                 if (drtmp.error()) {
2302                         Alert::warning(_("Branch already exists"), drtmp.message());
2303                         break;
2304                 }
2305                 docstring const sep = buffer_.params().branchlist().separator();
2306                 for (docstring const & branch : getVectorFromString(branch_name, sep))
2307                         lyx::dispatch(FuncRequest(LFUN_BRANCH_INSERT, branch));
2308                 break;
2309         }
2310
2311         case LFUN_KEYMAP_OFF:
2312                 getIntl().keyMapOn(false);
2313                 break;
2314
2315         case LFUN_KEYMAP_PRIMARY:
2316                 getIntl().keyMapPrim();
2317                 break;
2318
2319         case LFUN_KEYMAP_SECONDARY:
2320                 getIntl().keyMapSec();
2321                 break;
2322
2323         case LFUN_KEYMAP_TOGGLE:
2324                 getIntl().toggleKeyMap();
2325                 break;
2326
2327         case LFUN_DIALOG_SHOW_NEW_INSET: {
2328                 string const name = cmd.getArg(0);
2329                 string data = trim(to_utf8(cmd.argument()).substr(name.size()));
2330                 if (decodeInsetParam(name, data, buffer_))
2331                         lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, name + " " + data));
2332                 else
2333                         lyxerr << "Inset type '" << name <<
2334                         "' not recognized in LFUN_DIALOG_SHOW_NEW_INSET" <<  endl;
2335                 break;
2336         }
2337
2338         case LFUN_CITATION_INSERT: {
2339                 if (argument.empty()) {
2340                         lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW_NEW_INSET, "citation"));
2341                         break;
2342                 }
2343                 // we can have one optional argument, delimited by '|'
2344                 // citation-insert <key>|<text_before>
2345                 // this should be enhanced to also support text_after
2346                 // and citation style
2347                 string arg = argument;
2348                 string opt1;
2349                 if (contains(argument, "|")) {
2350                         arg = token(argument, '|', 0);
2351                         opt1 = token(argument, '|', 1);
2352                 }
2353
2354                 // if our cursor is directly in front of or behind a citation inset,
2355                 // we will instead add the new key to it.
2356                 Inset * inset = cur.nextInset();
2357                 if (!inset || inset->lyxCode() != CITE_CODE)
2358                         inset = cur.prevInset();
2359                 if (inset && inset->lyxCode() == CITE_CODE) {
2360                         InsetCitation * icite = static_cast<InsetCitation *>(inset);
2361                         if (icite->addKey(arg)) {
2362                                 dr.forceBufferUpdate();
2363                                 dr.screenUpdate(Update::FitCursor | Update::SinglePar);
2364                                 if (!opt1.empty())
2365                                         LYXERR0("Discarding optional argument to citation-insert.");
2366                         }
2367                         dispatched = true;
2368                         break;
2369                 }
2370                 InsetCommandParams icp(CITE_CODE);
2371                 icp["key"] = from_utf8(arg);
2372                 if (!opt1.empty())
2373                         icp["before"] = from_utf8(opt1);
2374                 icp["literal"] = 
2375                         from_ascii(InsetCitation::last_literal ? "true" : "false");
2376                 string icstr = InsetCommand::params2string(icp);
2377                 FuncRequest fr(LFUN_INSET_INSERT, icstr);
2378                 lyx::dispatch(fr);
2379
2380                 // if the request comes from the LyX server, then we
2381                 // return a list of the undefined keys, in case some
2382                 // action could be taken.
2383                 if (cmd.origin() != FuncRequest::LYXSERVER)
2384                         break;
2385
2386                 vector<docstring> keys = getVectorFromString(from_utf8(arg));
2387                 vector<docstring>::iterator it = keys.begin();
2388                 vector<docstring>::const_iterator end = keys.end();
2389
2390                 BiblioInfo const & bibInfo = buffer_.masterBibInfo();
2391                 const BiblioInfo::const_iterator bibEnd = bibInfo.end();
2392                 while (it != end) {
2393                         if (bibInfo.find(*it) != bibEnd) {
2394                                 it = keys.erase(it);
2395                                 end = keys.end();
2396                         } else
2397                                 ++it;
2398                 }
2399                 dr.setMessage(getStringFromVector(keys));
2400
2401                 break;
2402         }
2403
2404         case LFUN_INSET_APPLY: {
2405                 string const name = cmd.getArg(0);
2406                 Inset * inset = editedInset(name);
2407                 if (!inset) {
2408                         FuncRequest fr(LFUN_INSET_INSERT, cmd.argument());
2409                         lyx::dispatch(fr);
2410                         break;
2411                 }
2412                 // put cursor in front of inset.
2413                 if (!setCursorFromInset(inset)) {
2414                         LASSERT(false, break);
2415                 }
2416                 cur.recordUndo();
2417                 FuncRequest fr(LFUN_INSET_MODIFY, cmd.argument());
2418                 inset->dispatch(cur, fr);
2419                 dr.screenUpdate(cur.result().screenUpdate());
2420                 if (cur.result().needBufferUpdate())
2421                         dr.forceBufferUpdate();
2422                 break;
2423         }
2424
2425         // FIXME:
2426         // The change of language of buffer belongs to the Buffer class.
2427         // We have to do it here because we need a cursor for Undo.
2428         // When Undo::recordUndoBufferParams() is implemented someday
2429         // LFUN_BUFFER_LANGUAGE should be handled by the Buffer class.
2430         case LFUN_BUFFER_LANGUAGE: {
2431                 Language const * oldL = buffer_.params().language;
2432                 Language const * newL = languages.getLanguage(argument);
2433                 if (!newL || oldL == newL)
2434                         break;
2435                 if (oldL->rightToLeft() == newL->rightToLeft()) {
2436                         cur.recordUndoFullBuffer();
2437                         buffer_.changeLanguage(oldL, newL);
2438                         cur.setCurrentFont();
2439                         dr.forceBufferUpdate();
2440                 }
2441                 break;
2442         }
2443
2444         case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2445         case LFUN_FILE_INSERT_PLAINTEXT: {
2446                 bool const as_paragraph = (act == LFUN_FILE_INSERT_PLAINTEXT_PARA);
2447                 string const fname = to_utf8(cmd.argument());
2448                 if (!FileName::isAbsolute(fname))
2449                         dr.setMessage(_("Absolute filename expected."));
2450                 else
2451                         insertPlaintextFile(FileName(fname), as_paragraph);
2452                 break;
2453         }
2454
2455         case LFUN_COPY:
2456                 // With multi-cell table content, we pass down to the inset
2457                 if (cur.inTexted() && cur.selection()
2458                     && cur.selectionBegin().idx() != cur.selectionEnd().idx()) {
2459                         buffer_.dispatch(cmd, dr);
2460                         dispatched = dr.dispatched();
2461                         break;
2462                 }
2463                 cap::copySelection(cur);
2464                 cur.message(_("Copy"));
2465                 break;
2466
2467         default:
2468                 // OK, so try the Buffer itself...
2469                 buffer_.dispatch(cmd, dr);
2470                 dispatched = dr.dispatched();
2471                 break;
2472         }
2473
2474         buffer_.undo().endUndoGroup();
2475         dr.dispatched(dispatched);
2476
2477         // NOTE: The code below is copied from Cursor::dispatch. If you
2478         // need to modify this, please update the other one too.
2479
2480         // notify insets we just entered/left
2481         if (cursor() != old) {
2482                 old.beginUndoGroup();
2483                 old.fixIfBroken();
2484                 bool badcursor = notifyCursorLeavesOrEnters(old, cursor());
2485                 if (badcursor) {
2486                         cursor().fixIfBroken();
2487                         resetInlineCompletionPos();
2488                 }
2489                 old.endUndoGroup();
2490         }
2491 }
2492
2493
2494 docstring BufferView::requestSelection()
2495 {
2496         Cursor & cur = d->cursor_;
2497
2498         LYXERR(Debug::SELECTION, "requestSelection: cur.selection: " << cur.selection());
2499         if (!cur.selection()) {
2500                 d->xsel_cache_.set = false;
2501                 return docstring();
2502         }
2503
2504         LYXERR(Debug::SELECTION, "requestSelection: xsel_cache.set: " << d->xsel_cache_.set);
2505         if (!d->xsel_cache_.set ||
2506             cur.top() != d->xsel_cache_.cursor ||
2507             cur.realAnchor().top() != d->xsel_cache_.anchor)
2508         {
2509                 d->xsel_cache_.cursor = cur.top();
2510                 d->xsel_cache_.anchor = cur.realAnchor().top();
2511                 d->xsel_cache_.set = cur.selection();
2512                 return cur.selectionAsString(false);
2513         }
2514         return docstring();
2515 }
2516
2517
2518 void BufferView::clearSelection()
2519 {
2520         d->cursor_.clearSelection();
2521         // Clear the selection buffer. Otherwise a subsequent
2522         // middle-mouse-button paste would use the selection buffer,
2523         // not the more current external selection.
2524         cap::clearSelection();
2525         d->xsel_cache_.set = false;
2526         // The buffer did not really change, but this causes the
2527         // redraw we need because we cleared the selection above.
2528         buffer_.changed(false);
2529 }
2530
2531
2532 void BufferView::resize(int width, int height)
2533 {
2534         height_ = height;
2535         // Update metrics only if width has changed
2536         if (width != width_) {
2537                 width_ = width;
2538
2539                 // Clear the paragraph height cache.
2540                 d->par_height_.clear();
2541                 // Redo the metrics.
2542                 updateMetrics(true);
2543         }
2544         // metrics is OK, full drawing is necessary now
2545         d->update_flags_ = (d->update_flags_ & ~Update::Force) | Update::ForceDraw;
2546         d->update_strategy_ = FullScreenUpdate;
2547 }
2548
2549
2550 Inset const * BufferView::getCoveringInset(Text const & text,
2551                 int x, int y) const
2552 {
2553         TextMetrics & tm = d->text_metrics_[&text];
2554         Inset * inset = tm.checkInsetHit(x, y);
2555         if (!inset)
2556                 return nullptr;
2557
2558         if (!inset->descendable(*this))
2559                 // No need to go further down if the inset is not
2560                 // descendable.
2561                 return inset;
2562
2563         size_t cell_number = inset->nargs();
2564         // Check all the inner cell.
2565         for (size_t i = 0; i != cell_number; ++i) {
2566                 Text const * inner_text = inset->getText(i);
2567                 if (inner_text) {
2568                         // Try deeper.
2569                         Inset const * inset_deeper =
2570                                 getCoveringInset(*inner_text, x, y);
2571                         if (inset_deeper)
2572                                 return inset_deeper;
2573                 }
2574         }
2575
2576         return inset;
2577 }
2578
2579
2580 Inset const * BufferView::clickableMathInset(InsetMathNest const * inset,
2581                 CoordCache::Insets const & inset_cache, int x, int y) const
2582 {
2583         for (size_t i = 0; i < inset->nargs(); ++i) {
2584                 MathData const & ar = inset->cell(i);
2585                 for (size_t j = 0; j < ar.size(); ++j) {
2586                         string const name = lyxerr.debugging(Debug::MATHED)
2587                                 ? insetName(ar[j].nucleus()->lyxCode())
2588                                 : string();
2589                         LYXERR(Debug::MATHED, "Checking inset: " << name);
2590                         if (ar[j].nucleus()->clickable(*this, x, y)) {
2591                                 if (inset_cache.covers(ar[j].nucleus(), x, y)) {
2592                                         LYXERR(Debug::MATHED, "Clickable inset: "
2593                                                << name);
2594                                         return ar[j].nucleus();
2595                                 }
2596                         }
2597                         InsetMathNest const * imn =
2598                                 ar[j].nucleus()->asNestInset();
2599                         if (imn) {
2600                                 Inset const * inner =
2601                                         clickableMathInset(imn, inset_cache, x, y);
2602                                 if (inner)
2603                                         return inner;
2604                         }
2605                 }
2606         }
2607         return nullptr;
2608 }
2609
2610
2611 void BufferView::updateHoveredInset() const
2612 {
2613         // Get inset under mouse, if there is one.
2614         int const x = d->mouse_position_cache_.x;
2615         int const y = d->mouse_position_cache_.y;
2616         Inset const * covering_inset = getCoveringInset(buffer_.text(), x, y);
2617         if (covering_inset && covering_inset->asInsetMath()) {
2618                 Inset const * inner_inset = clickableMathInset(
2619                                 covering_inset->asInsetMath()->asNestInset(),
2620                                 coordCache().getInsets(), x, y);
2621                 if (inner_inset)
2622                         covering_inset = inner_inset;
2623         }
2624
2625         d->clickable_inset_ = covering_inset && covering_inset->clickable(*this, x, y);
2626
2627         if (covering_inset == d->last_inset_)
2628                 // Same inset, no need to do anything...
2629                 return;
2630
2631         bool need_redraw = false;
2632         if (d->last_inset_) {
2633                 // Remove the hint on the last hovered inset (if any).
2634                 need_redraw |= d->last_inset_->setMouseHover(this, false);
2635                 d->last_inset_ = nullptr;
2636         }
2637
2638         if (covering_inset && covering_inset->setMouseHover(this, true)) {
2639                 need_redraw = true;
2640                 // Only the insets that accept the hover state, do
2641                 // clear the last_inset_, so only set the last_inset_
2642                 // member if the hovered setting is accepted.
2643                 d->last_inset_ = covering_inset;
2644         }
2645
2646         if (need_redraw) {
2647                 LYXERR(Debug::PAINTING, "Mouse hover detected at: ("
2648                                 << d->mouse_position_cache_.x << ", "
2649                                 << d->mouse_position_cache_.y << ")");
2650
2651                 d->update_strategy_ = DecorationUpdate;
2652
2653                 // This event (moving without mouse click) is not passed further.
2654                 // This should be changed if it is further utilized.
2655                 buffer_.changed(false);
2656         }
2657 }
2658
2659
2660 void BufferView::clearLastInset(Inset * inset) const
2661 {
2662         if (d->last_inset_ != inset) {
2663                 LYXERR0("Wrong last_inset!");
2664                 LATTEST(false);
2665         }
2666         d->last_inset_ = nullptr;
2667 }
2668
2669
2670 bool BufferView::mouseSelecting() const
2671 {
2672         return d->mouse_selecting_;
2673 }
2674
2675
2676 int BufferView::stats_ref_value_w() const
2677 {
2678         return d->stats_ref_value_w_;
2679 }
2680
2681
2682 int BufferView::stats_ref_value_c() const
2683 {
2684         return d->stats_ref_value_c_;
2685 }
2686
2687
2688 int BufferView::stats_ref_value_nb() const
2689 {
2690         return d->stats_ref_value_nb_;
2691 }
2692
2693
2694 void BufferView::mouseEventDispatch(FuncRequest const & cmd0)
2695 {
2696         //lyxerr << "[ cmd0 " << cmd0 << "]" << endl;
2697
2698         if (!ready())
2699                 return;
2700
2701         // This is only called for mouse related events including
2702         // LFUN_FILE_OPEN generated by drag-and-drop.
2703         FuncRequest cmd = cmd0;
2704
2705         // Either the inset under the cursor or the
2706         // surrounding Text will handle this event.
2707
2708         // make sure we stay within the screen...
2709         cmd.set_y(min(max(cmd.y(), -1), height_));
2710
2711         d->mouse_position_cache_.x = cmd.x();
2712         d->mouse_position_cache_.y = cmd.y();
2713
2714         d->mouse_selecting_ =
2715                 cmd.action() == LFUN_MOUSE_MOTION && cmd.button() == mouse_button::button1;
2716
2717         if (cmd.action() == LFUN_MOUSE_MOTION && cmd.button() == mouse_button::none) {
2718                 updateHoveredInset();
2719                 return;
2720         }
2721
2722         Cursor old = cursor();
2723         Cursor cur(*this);
2724         cur.push(buffer_.inset());
2725         cur.selection(d->cursor_.selection());
2726
2727         // Build temporary cursor.
2728         Inset * inset = d->text_metrics_[&buffer_.text()].editXY(cur, cmd.x(), cmd.y());
2729         if (inset) {
2730                 // If inset is not editable, cur.pos() might point behind the
2731                 // inset (depending on cmd.x(), cmd.y()). This is needed for
2732                 // editing to fix bug 9628, but e.g. the context menu needs a
2733                 // cursor in front of the inset.
2734                 if ((inset->hasSettings() || !inset->contextMenuName().empty()
2735                      || inset->lyxCode() == SEPARATOR_CODE) &&
2736                     cur.nextInset() != inset && cur.prevInset() == inset)
2737                         cur.posBackward();
2738         } else if (cur.inTexted() && cur.pos()
2739                         && cur.paragraph().isEnvSeparator(cur.pos() - 1)) {
2740                 // Always place cursor in front of a separator inset.
2741                 cur.posBackward();
2742         }
2743
2744         // Put anchor at the same position.
2745         cur.resetAnchor();
2746
2747         old.beginUndoGroup();
2748
2749         // Try to dispatch to an non-editable inset near this position
2750         // via the temp cursor. If the inset wishes to change the real
2751         // cursor it has to do so explicitly by using
2752         //  cur.bv().cursor() = cur;  (or similar)
2753         if (inset)
2754                 inset->dispatch(cur, cmd);
2755
2756         // Now dispatch to the temporary cursor. If the real cursor should
2757         // be modified, the inset's dispatch has to do so explicitly.
2758         if (!inset || !cur.result().dispatched())
2759                 cur.dispatch(cmd);
2760
2761         // Notify left insets
2762         if (cur != old) {
2763                 bool badcursor = old.fixIfBroken() || cur.fixIfBroken();
2764                 badcursor = badcursor || notifyCursorLeavesOrEnters(old, cur);
2765                 if (badcursor)
2766                         cursor().fixIfBroken();
2767         }
2768
2769         old.endUndoGroup();
2770
2771         // Do we have a selection?
2772         theSelection().haveSelection(cursor().selection());
2773
2774         if (cur.needBufferUpdate() || buffer().needUpdate()) {
2775                 cur.clearBufferUpdate();
2776                 buffer().updateBuffer();
2777         }
2778
2779         // If the command has been dispatched,
2780         if (cur.result().dispatched() || cur.result().screenUpdate())
2781                 processUpdateFlags(cur.result().screenUpdate());
2782 }
2783
2784
2785 int BufferView::minVisiblePart()
2786 {
2787         return 2 * defaultRowHeight();
2788 }
2789
2790
2791 int BufferView::scroll(int pixels)
2792 {
2793         if (pixels > 0)
2794                 return scrollDown(pixels);
2795         if (pixels < 0)
2796                 return scrollUp(-pixels);
2797         return 0;
2798 }
2799
2800
2801 int BufferView::scrollDown(int pixels)
2802 {
2803         Text * text = &buffer_.text();
2804         TextMetrics & tm = d->text_metrics_[text];
2805         int const ymax = height_ + pixels;
2806         while (true) {
2807                 pair<pit_type, ParagraphMetrics const *> last = tm.last();
2808                 int bottom_pos = last.second->bottom();
2809                 if (lyxrc.scroll_below_document)
2810                         bottom_pos += height_ - minVisiblePart();
2811                 if (last.first + 1 == int(text->paragraphs().size())) {
2812                         if (bottom_pos <= height_)
2813                                 return 0;
2814                         pixels = min(pixels, bottom_pos - height_);
2815                         break;
2816                 }
2817                 if (bottom_pos > ymax)
2818                         break;
2819                 tm.newParMetricsDown();
2820         }
2821         d->anchor_ypos_ -= pixels;
2822         return -pixels;
2823 }
2824
2825
2826 int BufferView::scrollUp(int pixels)
2827 {
2828         Text * text = &buffer_.text();
2829         TextMetrics & tm = d->text_metrics_[text];
2830         int ymin = - pixels;
2831         while (true) {
2832                 pair<pit_type, ParagraphMetrics const *> first = tm.first();
2833                 int top_pos = first.second->top();
2834                 if (first.first == 0) {
2835                         if (top_pos >= 0)
2836                                 return 0;
2837                         pixels = min(pixels, - top_pos);
2838                         break;
2839                 }
2840                 if (top_pos < ymin)
2841                         break;
2842                 tm.newParMetricsUp();
2843         }
2844         d->anchor_ypos_ += pixels;
2845         return pixels;
2846 }
2847
2848
2849 bool BufferView::setCursorFromRow(int row)
2850 {
2851         TexRow::TextEntry start, end;
2852         tie(start,end) = buffer_.texrow().getEntriesFromRow(row);
2853         LYXERR(Debug::OUTFILE,
2854                "setCursorFromRow: for row " << row << ", TexRow has found "
2855                "start (id=" << start.id << ",pos=" << start.pos << "), "
2856                "end (id=" << end.id << ",pos=" << end.pos << ")");
2857         return setCursorFromEntries(start, end);
2858 }
2859
2860
2861 bool BufferView::setCursorFromEntries(TexRow::TextEntry start,
2862                                       TexRow::TextEntry end)
2863 {
2864         DocIterator dit_start, dit_end;
2865         tie(dit_start,dit_end) =
2866                 TexRow::getDocIteratorsFromEntries(start, end, buffer_);
2867         if (!dit_start)
2868                 return false;
2869         // Setting selection start
2870         d->cursor_.clearSelection();
2871         setCursor(dit_start);
2872         // Setting selection end
2873         if (dit_end) {
2874                 d->cursor_.resetAnchor();
2875                 setCursorSelectionTo(dit_end);
2876         }
2877         return true;
2878 }
2879
2880
2881 bool BufferView::setCursorFromInset(Inset const * inset)
2882 {
2883         // are we already there?
2884         if (cursor().nextInset() == inset)
2885                 return true;
2886
2887         // Inset is not at cursor position. Find it in the document.
2888         Cursor cur(*this);
2889         cur.reset();
2890         while (cur && cur.nextInset() != inset)
2891                 cur.forwardInset();
2892
2893         if (cur) {
2894                 setCursor(cur);
2895                 return true;
2896         }
2897         return false;
2898 }
2899
2900
2901 void BufferView::gotoLabel(docstring const & label)
2902 {
2903         FuncRequest action;
2904         bool have_inactive = false;
2905         for (Buffer const * buf : buffer().allRelatives()) {
2906                 // find label
2907                 for (TocItem const & item : *buf->tocBackend().toc("label")) {
2908                         if (label == item.str() && item.isOutput()) {
2909                                 lyx::dispatch(item.action());
2910                                 return;
2911                         }
2912                         // If we find an inactive label, save it for the case
2913                         // that no active one is there
2914                         if (label == item.str() && !have_inactive) {
2915                                 have_inactive = true;
2916                                 action = item.action();
2917                         }
2918                 }
2919         }
2920         // We only found an inactive label. Go there.
2921         if (have_inactive)
2922                 lyx::dispatch(action);
2923 }
2924
2925
2926 TextMetrics const & BufferView::textMetrics(Text const * t) const
2927 {
2928         return const_cast<BufferView *>(this)->textMetrics(t);
2929 }
2930
2931
2932 TextMetrics & BufferView::textMetrics(Text const * t)
2933 {
2934         LBUFERR(t);
2935         TextMetricsCache::iterator tmc_it  = d->text_metrics_.find(t);
2936         if (tmc_it == d->text_metrics_.end()) {
2937                 tmc_it = d->text_metrics_.emplace(std::piecewise_construct,
2938                                 std::forward_as_tuple(t),
2939                                 std::forward_as_tuple(this, const_cast<Text *>(t))).first;
2940         }
2941         return tmc_it->second;
2942 }
2943
2944
2945 ParagraphMetrics const & BufferView::parMetrics(Text const * t,
2946                 pit_type pit) const
2947 {
2948         return textMetrics(t).parMetrics(pit);
2949 }
2950
2951
2952 int BufferView::workHeight() const
2953 {
2954         return height_;
2955 }
2956
2957
2958 void BufferView::setCursor(DocIterator const & dit)
2959 {
2960         d->cursor_.reset();
2961         size_t const n = dit.depth();
2962         for (size_t i = 0; i < n; ++i)
2963                 dit[i].inset().edit(d->cursor_, true);
2964
2965         d->cursor_.setCursor(dit);
2966         d->cursor_.selection(false);
2967         d->cursor_.setCurrentFont();
2968         // FIXME
2969         // It seems on general grounds as if this is probably needed, but
2970         // it is not yet clear.
2971         // See bug #7394 and r38388.
2972         // d->cursor.resetAnchor();
2973 }
2974
2975
2976 void BufferView::setCursorSelectionTo(DocIterator const & dit)
2977 {
2978         size_t const n = dit.depth();
2979         for (size_t i = 0; i < n; ++i)
2980                 dit[i].inset().edit(d->cursor_, true);
2981
2982         d->cursor_.selection(true);
2983         d->cursor_.setCursorSelectionTo(dit);
2984         d->cursor_.setCurrentFont();
2985 }
2986
2987
2988 bool BufferView::checkDepm(Cursor & cur, Cursor & old)
2989 {
2990         // Would be wrong to delete anything if we have a selection.
2991         if (cur.selection())
2992                 return false;
2993
2994         bool need_anchor_change = false;
2995         bool changed = Text::deleteEmptyParagraphMechanism(cur, old,
2996                 need_anchor_change);
2997
2998         if (need_anchor_change)
2999                 cur.resetAnchor();
3000
3001         if (!changed)
3002                 return false;
3003
3004         d->cursor_ = cur;
3005
3006         // we would rather not do this here, but it needs to be done before
3007         // the changed() signal is sent.
3008         buffer_.updateBuffer();
3009
3010         buffer_.changed(true);
3011         return true;
3012 }
3013
3014
3015 bool BufferView::mouseSetCursor(Cursor & cur, bool const select)
3016 {
3017         LASSERT(&cur.bv() == this, return false);
3018
3019         if (!select)
3020                 // this event will clear selection so we save selection for
3021                 // persistent selection
3022                 cap::saveSelection(cursor());
3023
3024         d->cursor_.macroModeClose();
3025         // If a macro has been finalized, the cursor might have been broken
3026         cur.fixIfBroken();
3027
3028         // Has the cursor just left the inset?
3029         bool const leftinset = (&d->cursor_.inset() != &cur.inset());
3030         if (leftinset)
3031                 d->cursor_.fixIfBroken();
3032
3033         // do the dEPM magic if needed
3034         // FIXME: (1) move this to InsetText::notifyCursorLeaves?
3035         // FIXME: (2) if we had a working InsetText::notifyCursorLeaves,
3036         // the leftinset bool would not be necessary (badcursor instead).
3037         bool update = leftinset;
3038
3039         if (select) {
3040                 d->cursor_.setSelection();
3041                 d->cursor_.setCursorSelectionTo(cur);
3042         } else {
3043                 if (d->cursor_.inTexted())
3044                         update |= checkDepm(cur, d->cursor_);
3045                 d->cursor_.resetAnchor();
3046                 d->cursor_.setCursor(cur);
3047                 d->cursor_.clearSelection();
3048         }
3049         d->cursor_.boundary(cur.boundary());
3050         d->cursor_.finishUndo();
3051         d->cursor_.setCurrentFont();
3052         if (update)
3053                 cur.forceBufferUpdate();
3054         return update;
3055 }
3056
3057
3058 void BufferView::putSelectionAt(DocIterator const & cur,
3059                                 int length, bool backwards)
3060 {
3061         d->cursor_.clearSelection();
3062
3063         setCursor(cur);
3064
3065         if (length) {
3066                 if (backwards) {
3067                         d->cursor_.pos() += length;
3068                         d->cursor_.setSelection(d->cursor_, -length);
3069                 } else
3070                         d->cursor_.setSelection(d->cursor_, length);
3071         }
3072 }
3073
3074
3075 void BufferView::setSelection(DocIterator const & from,
3076                               DocIterator const & to)
3077 {
3078         if (from.pit() != to.pit()) {
3079                 // there are multiple paragraphs in selection
3080                 cursor().setCursor(from);
3081                 cursor().clearSelection();
3082                 cursor().selection(true);
3083                 cursor().setCursor(to);
3084                 cursor().selection(true);
3085         } else {
3086                 // only single paragraph
3087                 int const size = to.pos() - from.pos();
3088                 putSelectionAt(from, size, false);
3089         }
3090         processUpdateFlags(Update::Force | Update::FitCursor);
3091 }
3092
3093
3094 bool BufferView::selectIfEmpty(DocIterator & cur)
3095 {
3096         if ((cur.inTexted() && !cur.paragraph().empty())
3097             || (cur.inMathed() && !cur.cell().empty()))
3098                 return false;
3099
3100         pit_type const beg_pit = cur.pit();
3101         if (beg_pit > 0) {
3102                 // The paragraph associated to this item isn't
3103                 // the first one, so it can be selected
3104                 cur.backwardPos();
3105         } else {
3106                 // We have to resort to select the space between the
3107                 // end of this item and the begin of the next one
3108                 cur.forwardPos();
3109         }
3110         if (cur.empty()) {
3111                 // If it is the only item in the document,
3112                 // nothing can be selected
3113                 return false;
3114         }
3115         pit_type const end_pit = cur.pit();
3116         pos_type const end_pos = cur.pos();
3117         d->cursor_.clearSelection();
3118         d->cursor_.reset();
3119         d->cursor_.setCursor(cur);
3120         d->cursor_.pit() = beg_pit;
3121         d->cursor_.pos() = 0;
3122         d->cursor_.selection(false);
3123         d->cursor_.resetAnchor();
3124         d->cursor_.pit() = end_pit;
3125         d->cursor_.pos() = end_pos;
3126         d->cursor_.setSelection();
3127         return true;
3128 }
3129
3130
3131 Cursor & BufferView::cursor()
3132 {
3133         return d->cursor_;
3134 }
3135
3136
3137 Cursor const & BufferView::cursor() const
3138 {
3139         return d->cursor_;
3140 }
3141
3142
3143 bool BufferView::singleParUpdate()
3144 {
3145         CursorSlice const & its = d->cursor_.innerTextSlice();
3146         pit_type const pit = its.pit();
3147         TextMetrics & tm = textMetrics(its.text());
3148         Dimension const old_dim = tm.parMetrics(pit).dim();
3149
3150         // make sure inline completion pointer is ok
3151         if (d->inlineCompletionPos_.fixIfBroken())
3152                 d->inlineCompletionPos_ = DocIterator();
3153
3154         /* Try to rebreak only the paragraph containing the cursor (if
3155          * this paragraph contains insets etc., rebreaking will
3156          * recursively descend). We need a full redraw if either
3157          * 1/ the height has changed
3158          * or
3159          * 2/ the width has changed and it was equal to the textmetrics
3160          *    width; the goal is to catch the case of a one-row inset that
3161          *    grows with its contents, but optimize the case of typing at
3162          *    the end of a mmultiple-row paragraph.
3163          *
3164          * NOTE: if only the height has changed, then it should be
3165          *   possible to update all metrics at minimal cost. However,
3166          *   since this is risky, we do not try that right now.
3167          */
3168         tm.redoParagraph(pit);
3169         ParagraphMetrics & pm = tm.parMetrics(pit);
3170         if (pm.height() != old_dim.height()
3171                 || (pm.width() != old_dim.width() && old_dim.width() == tm.width())) {
3172                 // Paragraph height or width has changed so we cannot proceed
3173                 // to the singlePar optimisation.
3174                 LYXERR(Debug::PAINTING, "SinglePar optimization failed.");
3175                 return false;
3176         }
3177         // Since position() points to the baseline of the first row, we
3178         // may have to update it. See ticket #11601 for an example where
3179         // the height does not change but the ascent does.
3180         pm.setPosition(pm.position() - old_dim.ascent() + pm.ascent());
3181
3182         tm.updatePosCache(pit);
3183
3184         LYXERR(Debug::PAINTING, "\ny1: " << pm.top() << " y2: " << pm.bottom()
3185                 << " pit: " << pit << " singlepar: 1");
3186         return true;
3187 }
3188
3189
3190 void BufferView::updateMetrics()
3191 {
3192         updateMetrics(true);
3193         // metrics is done, full drawing is necessary now
3194         d->update_flags_ = (d->update_flags_ & ~Update::Force) | Update::ForceDraw;
3195         d->update_strategy_ = FullScreenUpdate;
3196 }
3197
3198
3199 void BufferView::updateMetrics(bool force)
3200 {
3201         if (!ready())
3202                 return;
3203
3204         //LYXERR0("updateMetrics " << _v_(force));
3205
3206         Text & buftext = buffer_.text();
3207         pit_type const lastpit = int(buftext.paragraphs().size()) - 1;
3208
3209         if (force) {
3210                 // Clear out the position cache in case of full screen redraw,
3211                 d->coord_cache_.clear();
3212                 d->math_rows_.clear();
3213
3214                 // Clear out paragraph metrics to avoid having invalid metrics
3215                 // in the cache from paragraphs not relayouted below. The
3216                 // complete text metrics will be redone.
3217                 d->text_metrics_.clear();
3218         }
3219
3220         // This should not be moved earlier
3221         TextMetrics & tm = textMetrics(&buftext);
3222
3223         // make sure inline completion pointer is ok
3224         if (d->inlineCompletionPos_.fixIfBroken())
3225                 d->inlineCompletionPos_ = DocIterator();
3226
3227         if (d->anchor_pit_ > lastpit)
3228                 // The anchor pit must have been deleted...
3229                 d->anchor_pit_ = lastpit;
3230
3231         // Update metrics around the anchor
3232         tm.updateMetrics(d->anchor_pit_, d->anchor_ypos_, height_);
3233
3234         // Check that the end of the document is not too high
3235         int const min_visible = lyxrc.scroll_below_document ? minVisiblePart() : height_;
3236         if (tm.last().first == lastpit && tm.last().second->hasPosition()
3237              && tm.last().second->bottom() < min_visible) {
3238                 d->anchor_ypos_ += min_visible - tm.last().second->bottom();
3239                 LYXERR(Debug::SCROLLING, "Too high, adjusting anchor ypos to " << d->anchor_ypos_);
3240                 tm.updateMetrics(d->anchor_pit_, d->anchor_ypos_, height_);
3241         }
3242
3243         // Check that the start of the document is not too low
3244         if (tm.first().first == 0 && tm.first().second->hasPosition()
3245              && tm.first().second->top() > 0) {
3246                 d->anchor_ypos_ -= tm.first().second->top();
3247                 LYXERR(Debug::SCROLLING, "Too low, adjusting anchor ypos to " << d->anchor_ypos_);
3248                 tm.updateMetrics(d->anchor_pit_, d->anchor_ypos_, height_);
3249         }
3250
3251         /* FIXME: do we want that? It avoids potential issues with old
3252          * paragraphs that should have been recomputed but have not, at
3253          * the price of potential extra metrics computation. I do not
3254          * think that the performance gain is high, so that for now the
3255          * extra paragraphs are removed
3256          */
3257         // Remove paragraphs that are outside of screen
3258         while(!tm.first().second->hasPosition() || tm.first().second->bottom() <= 0) {
3259                 //LYXERR0("Forget pit: " << tm.first().first);
3260                 tm.forget(tm.first().first);
3261         }
3262         while(!tm.last().second->hasPosition() || tm.last().second->top() > height_) {
3263                 //LYXERR0("Forget pit: " << tm.first().first);
3264                 tm.forget(tm.last().first);
3265         }
3266
3267         /* FIXME: if paragraphs outside of the screen are not removed
3268          * above, one has to search for the first visible one here */
3269         // Normalize anchor for next time
3270         if (d->anchor_pit_ != tm.first().first
3271             || d->anchor_ypos_ != tm.first().second->position()) {
3272                 LYXERR(Debug::PAINTING, __func__ << ": Found new anchor pit = " << tm.first().first
3273                                 << "  anchor ypos = " << tm.first().second->position()
3274                                 << " (was " << d->anchor_pit_ << ", " << d->anchor_ypos_ << ")");
3275                 d->anchor_pit_ = tm.first().first;
3276                 d->anchor_ypos_ = tm.first().second->position();
3277         }
3278
3279         // Now update the positions of insets in the cache.
3280         updatePosCache();
3281
3282         if (lyxerr.debugging(Debug::WORKAREA)) {
3283                 LYXERR(Debug::WORKAREA, "BufferView::updateMetrics");
3284                 d->coord_cache_.dump();
3285         }
3286 }
3287
3288
3289 void BufferView::updatePosCache()
3290 {
3291         // this is the "nodraw" drawing stage: only set the positions of the
3292         // insets in metrics cache.
3293         frontend::NullPainter np;
3294         draw(np, false);
3295 }
3296
3297
3298 void BufferView::insertLyXFile(FileName const & fname, bool const ignorelang)
3299 {
3300         LASSERT(d->cursor_.inTexted(), return);
3301
3302         // Get absolute path of file and add ".lyx"
3303         // to the filename if necessary
3304         FileName filename = fileSearch(string(), fname.absFileName(), "lyx");
3305
3306         docstring const disp_fn = makeDisplayPath(filename.absFileName());
3307         // emit message signal.
3308         message(bformat(_("Inserting document %1$s..."), disp_fn));
3309
3310         docstring res;
3311         Buffer buf(filename.absFileName(), false);
3312         if (buf.loadLyXFile() == Buffer::ReadSuccess) {
3313                 ErrorList & el = buffer_.errorList("Parse");
3314                 // Copy the inserted document error list into the current buffer one.
3315                 el = buf.errorList("Parse");
3316                 ParagraphList & pars = buf.paragraphs();
3317                 if (ignorelang)
3318                         // set main language of imported file to context language
3319                         buf.changeLanguage(buf.language(), d->cursor_.getFont().language());
3320                 buffer_.undo().recordUndo(d->cursor_);
3321                 cap::replaceSelection(d->cursor_);
3322                 cap::pasteParagraphList(d->cursor_, pars,
3323                                         buf.params().documentClassPtr(),
3324                                         buf.params().authors(), el);
3325                 res = _("Document %1$s inserted.");
3326         } else {
3327                 res = _("Could not insert document %1$s");
3328         }
3329
3330         buffer_.changed(true);
3331         // emit message signal.
3332         message(bformat(res, disp_fn));
3333 }
3334
3335
3336 Point BufferView::coordOffset(DocIterator const & dit) const
3337 {
3338         int x = 0;
3339         int y = 0;
3340         int lastw = 0;
3341
3342         // Addup contribution of nested insets, from inside to outside,
3343         // keeping the outer paragraph for a special handling below
3344         for (size_t i = dit.depth() - 1; i >= 1; --i) {
3345                 CursorSlice const & sl = dit[i];
3346                 int xx = 0;
3347                 int yy = 0;
3348
3349                 // get relative position inside sl.inset()
3350                 sl.inset().cursorPos(*this, sl, dit.boundary() && (i + 1 == dit.depth()), xx, yy);
3351
3352                 // Make relative position inside of the edited inset relative to sl.inset()
3353                 x += xx;
3354                 y += yy;
3355
3356                 // In case of an RTL inset, the edited inset will be positioned to the left
3357                 // of xx:yy
3358                 if (sl.text()) {
3359                         bool boundary_i = dit.boundary() && i + 1 == dit.depth();
3360                         bool rtl = textMetrics(sl.text()).isRTL(sl, boundary_i);
3361                         if (rtl)
3362                                 x -= lastw;
3363                 }
3364
3365                 // remember width for the case that sl.inset() is positioned in an RTL inset
3366                 lastw = sl.inset().dimension(*this).wid;
3367
3368                 //lyxerr << "Cursor::getPos, i: "
3369                 // << i << " x: " << xx << " y: " << y << endl;
3370         }
3371
3372         // Add contribution of initial rows of outermost paragraph
3373         CursorSlice const & sl = dit[0];
3374         TextMetrics const & tm = textMetrics(sl.text());
3375         ParagraphMetrics const & pm = tm.parMetrics(sl.pit());
3376
3377         LBUFERR(!pm.rows().empty());
3378         y -= pm.rows()[0].ascent();
3379 #if 1
3380         // FIXME: document this mess
3381         size_t rend;
3382         if (sl.pos() > 0 && dit.depth() == 1) {
3383                 int pos = sl.pos();
3384                 if (pos && dit.boundary())
3385                         --pos;
3386 //              lyxerr << "coordOffset: boundary:" << dit.boundary() << " depth:" << dit.depth() << " pos:" << pos << " sl.pos:" << sl.pos() << endl;
3387                 rend = pm.pos2row(pos);
3388         } else
3389                 rend = pm.pos2row(sl.pos());
3390 #else
3391         size_t rend = pm.pos2row(sl.pos());
3392 #endif
3393         for (size_t rit = 0; rit != rend; ++rit)
3394                 y += pm.rows()[rit].height();
3395         y += pm.rows()[rend].ascent();
3396
3397         TextMetrics const & bottom_tm = textMetrics(dit.bottom().text());
3398
3399         // Make relative position from the nested inset now bufferview absolute.
3400         int xx = bottom_tm.cursorX(dit.bottom(), dit.boundary() && dit.depth() == 1);
3401         x += xx;
3402
3403         // In the RTL case place the nested inset at the left of the cursor in
3404         // the outer paragraph
3405         bool boundary_1 = dit.boundary() && 1 == dit.depth();
3406         bool rtl = bottom_tm.isRTL(dit.bottom(), boundary_1);
3407         if (rtl)
3408                 x -= lastw;
3409
3410         return Point(x, y);
3411 }
3412
3413
3414 Point BufferView::getPos(DocIterator const & dit) const
3415 {
3416         if (!paragraphVisible(dit))
3417                 return Point(-1, -1);
3418
3419         CursorSlice const & bot = dit.bottom();
3420         TextMetrics const & tm = textMetrics(bot.text());
3421
3422         // offset from outer paragraph
3423         Point p = coordOffset(dit);
3424         p.y += tm.parMetrics(bot.pit()).position();
3425         return p;
3426 }
3427
3428
3429 bool BufferView::paragraphVisible(DocIterator const & dit) const
3430 {
3431         CursorSlice const & bot = dit.bottom();
3432         TextMetrics const & tm = textMetrics(bot.text());
3433
3434         return tm.contains(bot.pit());
3435 }
3436
3437
3438 void BufferView::caretPosAndDim(Point & p, Dimension & dim) const
3439 {
3440         Cursor const & cur = cursor();
3441         if (cur.inMathed() && hasMathRow(&cur.cell())) {
3442                 MathRow const & mrow = mathRow(&cur.cell());
3443                 dim = mrow.caret_dim;
3444         } else {
3445                 Font const font = cur.real_current_font;
3446                 frontend::FontMetrics const & fm = theFontMetrics(font);
3447                 // lineWidth() can be 0 to mean 'thin line' on HiDpi, but the
3448                 // caret drawing code is not prepared for that.
3449                 dim.wid = max(fm.lineWidth(), 1);
3450                 dim.asc = fm.maxAscent();
3451                 dim.des = fm.maxDescent();
3452         }
3453         if (lyxrc.cursor_width > 0)
3454                 dim.wid = lyxrc.cursor_width;
3455
3456         p = getPos(cur);
3457         // center fat carets horizontally
3458         p.x -= dim.wid / 2;
3459         // p is top-left
3460         p.y -= dim.asc;
3461 }
3462
3463
3464 void BufferView::buildCaretGeometry(bool complet)
3465 {
3466         Point p;
3467         Dimension dim;
3468         caretPosAndDim(p, dim);
3469
3470         Cursor const & cur = d->cursor_;
3471         Font const & realfont = cur.real_current_font;
3472         frontend::FontMetrics const & fm = theFontMetrics(realfont.fontInfo());
3473         bool const isrtl = realfont.isVisibleRightToLeft();
3474         int const dir = isrtl ? -1 : 1;
3475
3476         frontend::CaretGeometry & cg = d->caret_geometry_;
3477         cg.shapes.clear();
3478
3479         // The caret itself, slanted for italics in text edit mode except
3480         // for selections because the selection rect does not slant
3481         bool const slant = fm.italic() && cur.inTexted() && !cur.selection();
3482         double const slope = slant ? fm.italicSlope() : 0;
3483         cg.shapes.push_back(
3484                 {{iround(p.x + dim.asc * slope),                 p.y},
3485                  {iround(p.x - dim.des * slope),                 p.y + dim.height()},
3486                  {iround(p.x + dir * dim.wid - dim.des * slope), p.y + dim.height()},
3487                  {iround(p.x + dir * dim.wid + dim.asc * slope), p.y}}
3488                 );
3489
3490         // The language indicator _| (if needed)
3491         Language const * doclang = buffer().params().language;
3492         if (!((realfont.language() == doclang && isrtl == doclang->rightToLeft())
3493                   || realfont.language() == latex_language)) {
3494                 int const lx = dim.height() / 3;
3495                 int const xx = iround(p.x - dim.des * slope);
3496                 int const yy = p.y + dim.height();
3497                 cg.shapes.push_back(
3498                         {{xx,                            yy - dim.wid},
3499                          {xx + dir * (dim.wid + lx - 1), yy - dim.wid},
3500                          {xx + dir * (dim.wid + lx - 1), yy},
3501                          {xx,                            yy}}
3502                         );
3503         }
3504
3505         // The completion triangle |> (if needed)
3506         if (complet) {
3507                 int const m = p.y + dim.height() / 2;
3508                 int const d = dim.height() / 8;
3509                 // offset for slanted carret
3510                 int const sx = iround((dim.asc - (dim.height() / 2 - d)) * slope);
3511                 // starting position x
3512                 int const xx = p.x + dir * dim.wid + sx;
3513                 cg.shapes.push_back(
3514                         {{xx,                     m - d},
3515                          {xx + dir * d,           m},
3516                          {xx,                     m + d},
3517                          {xx,                     m + d - dim.wid},
3518                          {xx + dir * d - dim.wid, m},
3519                          {xx,                     m - d + dim.wid}}
3520                         );
3521         }
3522
3523         // compute extremal x values
3524         cg.left = 1000000;
3525         cg.right = -1000000;
3526         cg.top = 1000000;
3527         cg.bottom = -1000000;
3528         for (auto const & shape : cg.shapes)
3529                 for (Point const & p : shape) {
3530                         cg.left = min(cg.left, p.x);
3531                         cg.right = max(cg.right, p.x);
3532                         cg.top = min(cg.top, p.y);
3533                         cg.bottom = max(cg.bottom, p.y);
3534                 }
3535 }
3536
3537
3538 frontend::CaretGeometry const &  BufferView::caretGeometry() const
3539 {
3540         return d->caret_geometry_;
3541 }
3542
3543
3544 bool BufferView::caretInView() const
3545 {
3546         if (!paragraphVisible(cursor()))
3547                 return false;
3548         Point p;
3549         Dimension dim;
3550         caretPosAndDim(p, dim);
3551
3552         // does the cursor touch the screen ?
3553         if (p.y + dim.height() < 0 || p.y >= workHeight())
3554                 return false;
3555         return true;
3556 }
3557
3558
3559 int BufferView::horizScrollOffset() const
3560 {
3561         return d->horiz_scroll_offset_;
3562 }
3563
3564
3565 int BufferView::horizScrollOffset(Text const * text,
3566                                   pit_type pit, pos_type pos) const
3567 {
3568         // Is this a row that is currently scrolled?
3569         if (!d->current_row_slice_.empty()
3570             && &text->inset() == d->current_row_slice_.inset().asInsetText()
3571             && pit ==  d->current_row_slice_.pit()
3572             && pos ==  d->current_row_slice_.pos())
3573                 return d->horiz_scroll_offset_;
3574         return 0;
3575 }
3576
3577
3578 void BufferView::setCurrentRowSlice(CursorSlice const & rowSlice)
3579 {
3580         // nothing to do if the cursor was already on this row
3581         if (d->current_row_slice_ == rowSlice)
3582                 return;
3583
3584         // if the (previous) current row was scrolled, we have to
3585         // remember it in order to repaint it next time.
3586         if (d->horiz_scroll_offset_ != 0) {
3587                 // search the old row in cache and mark it changed
3588                 for (auto & tm_pair : d->text_metrics_) {
3589                         if (&tm_pair.first->inset() == rowSlice.inset().asInsetText()) {
3590                                 tm_pair.second.setRowChanged(rowSlice.pit(), rowSlice.pos());
3591                                 // We found it, no need to continue.
3592                                 break;
3593                         }
3594                 }
3595         }
3596
3597         // Since we changed row, the scroll offset is not valid anymore
3598         d->horiz_scroll_offset_ = 0;
3599         d->current_row_slice_ = rowSlice;
3600 }
3601
3602
3603 void BufferView::checkCursorScrollOffset()
3604 {
3605         CursorSlice rowSlice = d->cursor_.bottom();
3606         TextMetrics const & tm = textMetrics(rowSlice.text());
3607
3608         // Stop if metrics have not been computed yet, since it means
3609         // that there is nothing to do.
3610         if (!tm.contains(rowSlice.pit()))
3611                 return;
3612         ParagraphMetrics const & pm = tm.parMetrics(rowSlice.pit());
3613         Row const & row = pm.getRow(rowSlice.pos(),
3614                                     d->cursor_.boundary() && rowSlice == d->cursor_.top());
3615         rowSlice.pos() = row.pos();
3616
3617         // Set the row on which the cursor lives.
3618         setCurrentRowSlice(rowSlice);
3619
3620         // Current x position of the cursor in pixels
3621         int cur_x = getPos(d->cursor_).x;
3622
3623         // Horizontal scroll offset of the cursor row in pixels
3624         int offset = d->horiz_scroll_offset_;
3625         int const MARGIN = 2 * theFontMetrics(d->cursor_.real_current_font).em()
3626                            + row.right_margin;
3627         if (row.right_x() <= workWidth() - row.right_margin) {
3628                 // Row is narrower than the work area, no offset needed.
3629                 offset = 0;
3630         } else {
3631                 if (cur_x - offset < MARGIN) {
3632                         // cursor would be too far right
3633                         offset = cur_x - MARGIN;
3634                 } else if (cur_x - offset > workWidth() - MARGIN) {
3635                         // cursor would be too far left
3636                         offset = cur_x - workWidth() + MARGIN;
3637                 }
3638                 // Correct the offset to make sure that we do not scroll too much
3639                 if (offset < 0)
3640                         offset = 0;
3641                 if (row.right_x() - offset < workWidth() - row.right_margin)
3642                         offset = row.right_x() - workWidth() + row.right_margin;
3643         }
3644
3645         //lyxerr << "cur_x=" << cur_x << ", offset=" << offset << ", row.wid=" << row.width() << ", margin=" << MARGIN << endl;
3646
3647         if (offset != d->horiz_scroll_offset_) {
3648                 LYXERR(Debug::PAINTING, "Horiz. scroll offset changed from "
3649                        << d->horiz_scroll_offset_ << " to " << offset);
3650                 row.changed(true);
3651                 if (d->update_strategy_ == NoScreenUpdate)
3652                         d->update_strategy_ = SingleParUpdate;
3653         }
3654
3655         d->horiz_scroll_offset_ = offset;
3656 }
3657
3658
3659 bool BufferView::busy() const
3660 {
3661         return buffer().undo().activeUndoGroup();
3662 }
3663
3664
3665 void BufferView::draw(frontend::Painter & pain, bool paint_caret)
3666 {
3667         if (!ready())
3668                 return;
3669         LYXERR(Debug::PAINTING, (pain.isNull() ? "\t\t--- START NODRAW ---"
3670                                  : "\t\t*** START DRAWING ***"));
3671         Text & text = buffer_.text();
3672         TextMetrics const & tm = d->text_metrics_[&text];
3673         int const y = tm.first().second->position();
3674         PainterInfo pi(this, pain);
3675
3676         // Check whether the row where the cursor lives needs to be scrolled.
3677         // Update the drawing strategy if needed.
3678         checkCursorScrollOffset();
3679
3680         switch (d->update_strategy_) {
3681
3682         case NoScreenUpdate:
3683                 // no screen painting is actually needed. In nodraw stage
3684                 // however, the different coordinates of insets and paragraphs
3685                 // needs to be updated.
3686                 LYXERR(Debug::PAINTING, "Strategy: NoScreenUpdate");
3687                 if (pain.isNull()) {
3688                         pi.full_repaint = true;
3689                         tm.draw(pi, 0, y);
3690                 } else {
3691                         pi.full_repaint = false;
3692                         tm.draw(pi, 0, y);
3693                 }
3694                 break;
3695
3696         case SingleParUpdate:
3697                 pi.full_repaint = false;
3698                 LYXERR(Debug::PAINTING, "Strategy: SingleParUpdate");
3699                 // In general, only the current row of the outermost paragraph
3700                 // will be redrawn. Particular cases where selection spans
3701                 // multiple paragraph are correctly detected in TextMetrics.
3702                 tm.draw(pi, 0, y);
3703                 break;
3704
3705         case DecorationUpdate:
3706                 // FIXME: We should also distinguish DecorationUpdate to avoid text
3707                 // drawing if possible. This is not possible to do easily right now
3708                 // because of the single backing pixmap.
3709
3710         case FullScreenUpdate:
3711
3712                 LYXERR(Debug::PAINTING,
3713                        ((d->update_strategy_ == FullScreenUpdate)
3714                         ? "Strategy: FullScreenUpdate"
3715                         : "Strategy: DecorationUpdate"));
3716
3717                 // The whole screen, including insets, will be refreshed.
3718                 pi.full_repaint = true;
3719
3720                 // Clear background.
3721                 pain.fillRectangle(0, 0, width_, height_,
3722                         pi.backgroundColor(&buffer_.inset()));
3723
3724                 // Draw everything.
3725                 tm.draw(pi, 0, y);
3726
3727                 break;
3728         }
3729
3730         // Possibly grey out below
3731         if (d->update_strategy_ != NoScreenUpdate) {
3732                 pair<pit_type, ParagraphMetrics const *> lastpm = tm.last();
3733                 int const y2 = lastpm.second->bottom();
3734
3735                 if (y2 < height_) {
3736                         Color color = buffer().isInternal()
3737                                 ? Color_background : Color_bottomarea;
3738                         pain.fillRectangle(0, y2, width_, height_ - y2, color);
3739                 }
3740         }
3741
3742         LYXERR(Debug::PAINTING, (pain.isNull() ? "\t\t --- END NODRAW ---"
3743                                 : "\t\t *** END DRAWING ***"));
3744
3745         // The scrollbar needs an update.
3746         // FIXME: does it always? see ticket #11947.
3747         updateScrollbarParameters();
3748
3749         // Normalize anchor for next time (in case updateMetrics did not do it yet)
3750         // FIXME: is this useful?
3751         pair<pit_type, ParagraphMetrics const *> firstpm = tm.first();
3752         pair<pit_type, ParagraphMetrics const *> lastpm = tm.last();
3753         for (pit_type pit = firstpm.first; pit <= lastpm.first; ++pit) {
3754                 ParagraphMetrics const & pm = tm.parMetrics(pit);
3755                 if (pm.bottom() > 0) {
3756                         if (d->anchor_pit_ != pit
3757                             || d->anchor_ypos_ != pm.position())
3758                                 LYXERR0(__func__ << ": Found new anchor pit = " << pit
3759                                                 << "  anchor ypos = " << pm.position()
3760                                                 << " (was " << d->anchor_pit_ << ", " << d->anchor_ypos_ << ")"
3761                                                    "\nIf you see this message, please report.");
3762                         d->anchor_pit_ = pit;
3763                         d->anchor_ypos_ = pm.position();
3764                         break;
3765                 }
3766         }
3767
3768         if (!pain.isNull()) {
3769                 // reset the update flags, everything has been done
3770                 d->update_flags_ = Update::None;
3771         }
3772
3773         // If a caret has to be painted, mark its text row as dirty to
3774         //make sure that it will be repainted on next redraw.
3775         /* FIXME: investigate whether this can be avoided when the cursor did not
3776          * move at all
3777          */
3778         if (paint_caret) {
3779                 Cursor cur(d->cursor_);
3780                 while (cur.depth() > 1) {
3781                         if (!cur.inTexted())
3782                                 break;
3783                         TextMetrics const & tm = textMetrics(cur.text());
3784                         if (d->caret_geometry_.left >= tm.origin().x
3785                                 && d->caret_geometry_.right <= tm.origin().x + tm.dim().width())
3786                                 break;
3787                         cur.pop();
3788                 }
3789                 cur.textRow().changed(true);
3790         }
3791 }
3792
3793
3794 void BufferView::message(docstring const & msg)
3795 {
3796         if (d->gui_)
3797                 d->gui_->message(msg);
3798 }
3799
3800
3801 void BufferView::showDialog(string const & name)
3802 {
3803         if (d->gui_)
3804                 d->gui_->showDialog(name, string());
3805 }
3806
3807
3808 void BufferView::showDialog(string const & name,
3809         string const & data, Inset * inset)
3810 {
3811         if (d->gui_)
3812                 d->gui_->showDialog(name, data, inset);
3813 }
3814
3815
3816 void BufferView::updateDialog(string const & name, string const & data)
3817 {
3818         if (d->gui_)
3819                 d->gui_->updateDialog(name, data);
3820 }
3821
3822
3823 void BufferView::setGuiDelegate(frontend::GuiBufferViewDelegate * gui)
3824 {
3825         d->gui_ = gui;
3826 }
3827
3828
3829 // FIXME: Move this out of BufferView again
3830 docstring BufferView::contentsOfPlaintextFile(FileName const & fname)
3831 {
3832         if (!fname.isReadableFile()) {
3833                 docstring const error = from_ascii(strerror(errno));
3834                 docstring const file = makeDisplayPath(fname.absFileName(), 50);
3835                 docstring const text =
3836                   bformat(_("Could not read the specified document\n"
3837                             "%1$s\ndue to the error: %2$s"), file, error);
3838                 Alert::error(_("Could not read file"), text);
3839                 return docstring();
3840         }
3841
3842         if (!fname.isReadableFile()) {
3843                 docstring const file = makeDisplayPath(fname.absFileName(), 50);
3844                 docstring const text =
3845                   bformat(_("%1$s\n is not readable."), file);
3846                 Alert::error(_("Could not open file"), text);
3847                 return docstring();
3848         }
3849
3850         // FIXME UNICODE: We don't know the encoding of the file
3851         docstring file_content = fname.fileContents("UTF-8");
3852         if (file_content.empty()) {
3853                 Alert::error(_("Reading not UTF-8 encoded file"),
3854                              _("The file is not UTF-8 encoded.\n"
3855                                "It will be read as local 8Bit-encoded.\n"
3856                                "If this does not give the correct result\n"
3857                                "then please change the encoding of the file\n"
3858                                "to UTF-8 with a program other than LyX.\n"));
3859                 file_content = fname.fileContents("local8bit");
3860         }
3861
3862         return normalize_c(file_content);
3863 }
3864
3865
3866 void BufferView::insertPlaintextFile(FileName const & f, bool asParagraph)
3867 {
3868         docstring const tmpstr = contentsOfPlaintextFile(f);
3869
3870         if (tmpstr.empty())
3871                 return;
3872
3873         Cursor & cur = cursor();
3874         cap::replaceSelection(cur);
3875         buffer_.undo().recordUndo(cur);
3876         if (asParagraph)
3877                 cur.innerText()->insertStringAsParagraphs(cur, tmpstr, cur.current_font);
3878         else
3879                 cur.innerText()->insertStringAsLines(cur, tmpstr, cur.current_font);
3880
3881         buffer_.changed(true);
3882 }
3883
3884
3885 docstring const & BufferView::inlineCompletion() const
3886 {
3887         return d->inlineCompletion_;
3888 }
3889
3890
3891 size_t BufferView::inlineCompletionUniqueChars() const
3892 {
3893         return d->inlineCompletionUniqueChars_;
3894 }
3895
3896
3897 DocIterator const & BufferView::inlineCompletionPos() const
3898 {
3899         return d->inlineCompletionPos_;
3900 }
3901
3902
3903 void BufferView::resetInlineCompletionPos()
3904 {
3905         d->inlineCompletionPos_ = DocIterator();
3906 }
3907
3908
3909 bool samePar(DocIterator const & a, DocIterator const & b)
3910 {
3911         if (a.empty() && b.empty())
3912                 return true;
3913         if (a.empty() || b.empty())
3914                 return false;
3915         if (a.depth() != b.depth())
3916                 return false;
3917         return &a.innerParagraph() == &b.innerParagraph();
3918 }
3919
3920
3921 void BufferView::setInlineCompletion(Cursor const & cur, DocIterator const & pos,
3922         docstring const & completion, size_t uniqueChars)
3923 {
3924         uniqueChars = min(completion.size(), uniqueChars);
3925         bool changed = d->inlineCompletion_ != completion
3926                 || d->inlineCompletionUniqueChars_ != uniqueChars;
3927         bool singlePar = true;
3928         d->inlineCompletion_ = completion;
3929         d->inlineCompletionUniqueChars_ = min(completion.size(), uniqueChars);
3930
3931         //lyxerr << "setInlineCompletion pos=" << pos << " completion=" << completion << " uniqueChars=" << uniqueChars << std::endl;
3932
3933         // at new position?
3934         DocIterator const & old = d->inlineCompletionPos_;
3935         if (old != pos) {
3936                 //lyxerr << "inlineCompletionPos changed" << std::endl;
3937                 // old or pos are in another paragraph?
3938                 if ((!samePar(cur, pos) && !pos.empty())
3939                     || (!samePar(cur, old) && !old.empty())) {
3940                         singlePar = false;
3941                         //lyxerr << "different paragraph" << std::endl;
3942                 }
3943                 d->inlineCompletionPos_ = pos;
3944         }
3945
3946         // set update flags
3947         if (changed) {
3948                 if (singlePar && !(cur.result().screenUpdate() & Update::Force))
3949                         cur.screenUpdateFlags(cur.result().screenUpdate() | Update::SinglePar);
3950                 else
3951                         cur.screenUpdateFlags(cur.result().screenUpdate() | Update::Force);
3952         }
3953 }
3954
3955
3956 bool BufferView::clickableInset() const
3957 {
3958         return d->clickable_inset_;
3959 }
3960
3961
3962 bool BufferView::stats_update_trigger()
3963 {
3964         if (d->stats_update_trigger_) {
3965                 d->stats_update_trigger_ = false;
3966                 return true;
3967         }
3968         return false;
3969 }
3970
3971 } // namespace lyx