]> git.lyx.org Git - lyx.git/blob - src/BufferView.cpp
b55beb34a786fc33478aa3af741716bd8788eb65
[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                 // select complete document
1690                 cur.reset();
1691                 cur.selHandle(true);
1692                 buffer_.text().cursorBottom(cur);
1693                 // accept everything in a single step to support atomic undo
1694                 // temporarily disable track changes in order to end with really
1695                 // no new (e.g., DPSM-caused) changes (see #7487)
1696                 bool const track = buffer_.params().track_changes;
1697                 buffer_.params().track_changes = false;
1698                 buffer_.text().acceptOrRejectChanges(cur, Text::ACCEPT);
1699                 buffer_.params().track_changes = track;
1700                 cur.resetAnchor();
1701                 // FIXME: Move this LFUN to Buffer so that we don't have to do this:
1702                 dr.screenUpdate(Update::Force | Update::FitCursor);
1703                 dr.forceBufferUpdate();
1704                 break;
1705         }
1706
1707         case LFUN_ALL_CHANGES_REJECT: {
1708                 // select complete document
1709                 cur.reset();
1710                 cur.selHandle(true);
1711                 buffer_.text().cursorBottom(cur);
1712                 // reject everything in a single step to support atomic undo
1713                 // temporarily disable track changes in order to end with really
1714                 // no new (e.g., DPSM-caused) changes (see #7487)
1715                 bool const track = buffer_.params().track_changes;
1716                 buffer_.params().track_changes = false;
1717                 buffer_.text().acceptOrRejectChanges(cur, Text::REJECT);
1718                 buffer_.params().track_changes = track;
1719                 cur.resetAnchor();
1720                 // FIXME: Move this LFUN to Buffer so that we don't have to do this:
1721                 dr.screenUpdate(Update::Force | Update::FitCursor);
1722                 dr.forceBufferUpdate();
1723                 break;
1724         }
1725
1726         case LFUN_WORD_FIND_FORWARD:
1727         case LFUN_WORD_FIND_BACKWARD: {
1728                 docstring searched_string;
1729
1730                 if (!cmd.argument().empty()) {
1731                         setSearchRequestCache(cmd.argument());
1732                         searched_string = cmd.argument();
1733                 } else {
1734                         searched_string = searchRequestCache();
1735                 }
1736
1737                 if (searched_string.empty())
1738                         break;
1739
1740                 docstring const data =
1741                         find2string(searched_string, false, false,
1742                                     act == LFUN_WORD_FIND_FORWARD, false, false, false);
1743                 bool found = lyxfind(this, FuncRequest(LFUN_WORD_FIND, data));
1744                 if (found)
1745                         dr.screenUpdate(Update::Force | Update::FitCursor);
1746                 else
1747                         dr.setMessage(_("Search string not found!"));
1748                 break;
1749         }
1750
1751         case LFUN_WORD_FIND: {
1752                 docstring arg = cmd.argument();
1753                 if (arg.empty())
1754                         arg = searchRequestCache();
1755                 if (arg.empty()) {
1756                         lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "findreplace"));
1757                         break;
1758                 }
1759                 if (lyxfind(this, FuncRequest(act, arg)))
1760                         dr.screenUpdate(Update::Force | Update::FitCursor);
1761                 else
1762                         dr.setMessage(_("Search string not found!"));
1763
1764                 setSearchRequestCache(arg);
1765                 break;
1766         }
1767
1768         case LFUN_SEARCH_STRING_SET: {
1769                 docstring pattern = cmd.argument();
1770                 if (!pattern.empty()) {
1771                         setSearchRequestCache(pattern);
1772                         break;
1773                 }
1774                 if (cur.selection())
1775                         pattern = cur.selectionAsString(false);
1776                 else if (!cur.inTexted())
1777                         break; // not suitable for selectWord at cursor
1778                 else {
1779                         pos_type spos = cur.pos();
1780                         cur.innerText()->selectWord(cur, WHOLE_WORD);
1781                         pattern = cur.selectionAsString(false);
1782                         cur.selection(false);
1783                         cur.pos() = spos;
1784                 }
1785                 setSearchRequestCache(pattern);
1786                 break;
1787         }
1788
1789         case LFUN_WORD_REPLACE: {
1790                 if (lyxreplace(this, cmd)) {
1791                         dr.forceBufferUpdate();
1792                         dr.screenUpdate(Update::Force | Update::FitCursor);
1793                 }
1794                 else
1795                         dr.setMessage(_("Search string not found!"));
1796                 break;
1797         }
1798
1799         case LFUN_WORD_FINDADV: {
1800                 FindAndReplaceOptions opt;
1801                 istringstream iss(to_utf8(cmd.argument()));
1802                 iss >> opt;
1803                 if (findAdv(this, opt)) {
1804                         dr.screenUpdate(Update::Force | Update::FitCursor);
1805                         cur.dispatched();
1806                         dispatched = true;
1807                 } else {
1808                         cur.undispatched();
1809                         dispatched = false;
1810                 }
1811                 break;
1812         }
1813
1814         case LFUN_INDEX_TAG_ALL: {
1815                 if (cur.pos() == 0)
1816                         // nothing precedes
1817                         break;
1818
1819                 Inset * ins = cur.nextInset();
1820                 if (!ins || ins->lyxCode() != INDEX_CODE)
1821                         // not at index inset
1822                         break;
1823
1824                 // clone the index inset
1825                 InsetIndex * cins =
1826                         new InsetIndex(static_cast<InsetIndex &>(*cur.nextInset()));
1827                 // In order to avoid duplication, we compare the
1828                 // LaTeX output if we find another index inset after
1829                 // the word
1830                 odocstringstream oilatex;
1831                 otexstream oits(oilatex);
1832                 OutputParams rp(&cur.buffer()->params().encoding());
1833                 ins->latex(oits, rp);
1834                 cap::copyInsetToTemp(cur, cins);
1835
1836                 // move backwards into preceding word
1837                 // skip over other index insets
1838                 cur.backwardPosIgnoreCollapsed();
1839                 while (true) {
1840                         if (cur.inset().lyxCode() == INDEX_CODE)
1841                                 cur.pop_back();
1842                         else if (cur.prevInset() && cur.prevInset()->lyxCode() == INDEX_CODE)
1843                                 cur.backwardPosIgnoreCollapsed();
1844                         else
1845                                 break;
1846                 }
1847                 if (!cur.inTexted()) {
1848                         // Nothing to do here.
1849                         setCursorFromInset(ins);
1850                         break;
1851                 }
1852                 // Get word or selection
1853                 cur.text()->selectWord(cur, WHOLE_WORD);
1854                 docstring const searched_string = cur.selectionAsString(false);
1855                 if (searched_string.empty())
1856                         break;
1857                 // Start from the beginning
1858                 lyx::dispatch(FuncRequest(LFUN_BUFFER_BEGIN));
1859                 while (findOne(this, searched_string,
1860                                false,// case sensitive
1861                                true,// match whole word only
1862                                true,// forward
1863                                false,//find deleted
1864                                false,//check wrap
1865                                false,// auto-wrap
1866                                false,// instant
1867                                false// only selection
1868                                )) {
1869                         cur.clearSelection();
1870                         Inset * ains = cur.nextInset();
1871                         if (ains && ains->lyxCode() == INDEX_CODE) {
1872                                 // We have an index inset.
1873                                 // Check whether it has the same
1874                                 // LaTeX content and move on if so.
1875                                 odocstringstream filatex;
1876                                 otexstream fits(filatex);
1877                                 ains->latex(fits, rp);
1878                                 if (oilatex.str() == filatex.str())
1879                                         continue;
1880                         }
1881                         // Paste the inset and possibly continue
1882                         cap::pasteFromTemp(cursor(), cursor().buffer()->errorList("Paste"));
1883                 }
1884                 // Go back to start position.
1885                 setCursorFromInset(ins);
1886                 dr.screenUpdate(cur.result().screenUpdate());
1887                 if (cur.result().needBufferUpdate())
1888                         dr.forceBufferUpdate();
1889                 break;
1890         }
1891
1892         case LFUN_MARK_OFF:
1893                 cur.clearSelection();
1894                 dr.setMessage(from_utf8(N_("Mark off")));
1895                 break;
1896
1897         case LFUN_MARK_ON:
1898                 cur.clearSelection();
1899                 cur.setMark(true);
1900                 dr.setMessage(from_utf8(N_("Mark on")));
1901                 break;
1902
1903         case LFUN_MARK_TOGGLE:
1904                 cur.selection(false);
1905                 if (cur.mark()) {
1906                         cur.setMark(false);
1907                         dr.setMessage(from_utf8(N_("Mark removed")));
1908                 } else {
1909                         cur.setMark(true);
1910                         dr.setMessage(from_utf8(N_("Mark set")));
1911                 }
1912                 cur.resetAnchor();
1913                 break;
1914
1915         case LFUN_SCREEN_SHOW_CURSOR:
1916                 showCursor();
1917                 break;
1918
1919         case LFUN_SCREEN_RECENTER:
1920                 recenter();
1921                 break;
1922
1923         case LFUN_BIBTEX_DATABASE_ADD: {
1924                 Cursor tmpcur = cur;
1925                 findInset(tmpcur, { BIBTEX_CODE }, false);
1926                 InsetBibtex * inset = getInsetByCode<InsetBibtex>(tmpcur,
1927                                                 BIBTEX_CODE);
1928                 if (inset) {
1929                         if (inset->addDatabase(cmd.argument()))
1930                                 dr.forceBufferUpdate();
1931                 }
1932                 break;
1933         }
1934
1935         case LFUN_BIBTEX_DATABASE_DEL: {
1936                 Cursor tmpcur = cur;
1937                 findInset(tmpcur, { BIBTEX_CODE }, false);
1938                 InsetBibtex * inset = getInsetByCode<InsetBibtex>(tmpcur,
1939                                                 BIBTEX_CODE);
1940                 if (inset) {
1941                         if (inset->delDatabase(cmd.argument()))
1942                                 dr.forceBufferUpdate();
1943                 }
1944                 break;
1945         }
1946
1947         case LFUN_GRAPHICS_UNIFY: {
1948
1949                 cur.recordUndoFullBuffer();
1950
1951                 DocIterator from, to;
1952                 from = cur.selectionBegin();
1953                 to = cur.selectionEnd();
1954
1955                 string const newId = cmd.getArg(0);
1956                 bool fetchId = newId.empty(); //if we wait for groupId from first graphics inset
1957
1958                 InsetGraphicsParams grp_par;
1959                 if (!fetchId)
1960                         InsetGraphics::string2params(graphics::getGroupParams(buffer_, newId), buffer_, grp_par);
1961
1962                 if (!from.nextInset())  //move to closest inset
1963                         from.forwardInset();
1964
1965                 while (!from.empty() && from < to) {
1966                         Inset * inset = from.nextInset();
1967                         if (!inset)
1968                                 break;
1969                         InsetGraphics * ig = inset->asInsetGraphics();
1970                         if (ig) {
1971                                 InsetGraphicsParams inspar = ig->getParams();
1972                                 if (fetchId) {
1973                                         grp_par = inspar;
1974                                         fetchId = false;
1975                                 } else {
1976                                         grp_par.filename = inspar.filename;
1977                                         ig->setParams(grp_par);
1978                                 }
1979                         }
1980                         from.forwardInset();
1981                 }
1982                 dr.screenUpdate(Update::Force); //needed if triggered from context menu
1983                 break;
1984         }
1985
1986         case LFUN_BIBTEX_DATABASE_LIST: {
1987                 docstring_list const & files = buffer_.getBibfiles();
1988                 bool first = true;
1989                 docstring result;
1990                 char const separator(os::path_separator());
1991                 for (auto const & file : files) {
1992                         if (first)
1993                                 first = false;
1994                         else
1995                                 result += separator;
1996
1997                         FileName const fn = buffer_.getBibfilePath(file);
1998                         string const path = fn.realPath();
1999                         result += from_utf8(os::external_path(path));
2000                 }
2001                 dr.setMessage(result);
2002                 break;
2003         }
2004
2005         case LFUN_STATISTICS: {
2006                 DocIterator from, to;
2007                 if (cur.selection()) {
2008                         from = cur.selectionBegin();
2009                         to = cur.selectionEnd();
2010                 } else {
2011                         from = doc_iterator_begin(&buffer_);
2012                         to = doc_iterator_end(&buffer_);
2013                 }
2014                 buffer_.updateStatistics(from, to);
2015                 int const words = buffer_.wordCount();
2016                 int const chars = buffer_.charCount(false);
2017                 int const chars_blanks = buffer_.charCount(true);
2018                 docstring message;
2019                 if (cur.selection())
2020                         message = _("Statistics for the selection:");
2021                 else
2022                         message = _("Statistics for the document:");
2023                 message += "\n\n";
2024                 if (words != 1)
2025                         message += bformat(_("%1$d words"), words);
2026                 else
2027                         message += _("One word");
2028                 message += "\n";
2029                 if (chars_blanks != 1)
2030                         message += bformat(_("%1$d characters"), chars_blanks);
2031                 else
2032                         message += _("One character");
2033                 message += "\n";
2034                 if (chars != 1)
2035                         message += bformat(_("%1$d characters (no blanks)"), chars);
2036                 else
2037                         message += _("One character (no blanks)");
2038
2039                 Alert::information(_("Statistics"), message);
2040         }
2041                 break;
2042
2043         case LFUN_STATISTICS_REFERENCE_CLAMP: {
2044                 d->stats_update_trigger_ = true;
2045                 if  (cmd.argument() == "reset") {
2046                         d->stats_ref_value_w_ = d->stats_ref_value_c_ = d->stats_ref_value_nb_ = 0;
2047                         break;
2048                 }
2049
2050                 DocIterator from, to;
2051                 from = doc_iterator_begin(&buffer_);
2052                 to = doc_iterator_end(&buffer_);
2053                 buffer_.updateStatistics(from, to);
2054
2055                 d->stats_ref_value_w_ = buffer_.wordCount();
2056                 d->stats_ref_value_c_ = buffer_.charCount(true);
2057                 d->stats_ref_value_nb_ = buffer_.charCount(false);
2058                 break;
2059         }
2060
2061
2062         case LFUN_SCREEN_UP:
2063         case LFUN_SCREEN_DOWN: {
2064                 Point p = getPos(cur);
2065                 // This code has been commented out to enable to scroll down a
2066                 // document, even if there are large insets in it (see bug #5465).
2067                 /*if (p.y < 0 || p.y > height_) {
2068                         // The cursor is off-screen so recenter before proceeding.
2069                         showCursor();
2070                         p = getPos(cur);
2071                 }*/
2072                 int const scrolled = scroll(act == LFUN_SCREEN_UP
2073                         ? -height_ : height_);
2074                 if (act == LFUN_SCREEN_UP && scrolled > -height_)
2075                         p = Point(0, 0);
2076                 if (act == LFUN_SCREEN_DOWN && scrolled < height_)
2077                         p = Point(width_, height_);
2078                 bool const in_texted = cur.inTexted();
2079                 cur.setCursor(doc_iterator_begin(cur.buffer()));
2080                 cur.selHandle(false);
2081                 // Force an immediate computation of metrics because we need it below
2082                 if (scrolled)
2083                         processUpdateFlags(Update::Force);
2084
2085                 d->text_metrics_[&buffer_.text()].editXY(cur, p.x, p.y,
2086                         true, act == LFUN_SCREEN_UP);
2087                 //FIXME: what to do with cur.x_target()?
2088                 bool update = in_texted && cur.bv().checkDepm(cur, old);
2089                 cur.finishUndo();
2090
2091                 if (update || cur.mark())
2092                         dr.screenUpdate(Update::Force | Update::FitCursor);
2093                 if (update)
2094                         dr.forceBufferUpdate();
2095                 break;
2096         }
2097
2098         case LFUN_SCROLL: {
2099                 string const scroll_type = cmd.getArg(0);
2100                 int scroll_step = 0;
2101                 if (scroll_type == "line")
2102                         scroll_step = d->scrollbarParameters_.single_step;
2103                 else if (scroll_type == "page")
2104                         scroll_step = d->scrollbarParameters_.page_step;
2105                 else
2106                         return;
2107                 string const scroll_quantity = cmd.getArg(1);
2108                 if (scroll_quantity == "up")
2109                         scrollUp(scroll_step);
2110                 else if (scroll_quantity == "down")
2111                         scrollDown(scroll_step);
2112                 else {
2113                         int const scroll_value = convert<int>(scroll_quantity);
2114                         if (scroll_value)
2115                                 scroll(scroll_step * scroll_value);
2116                 }
2117                 dr.screenUpdate(Update::ForceDraw);
2118                 dr.forceBufferUpdate();
2119                 break;
2120         }
2121
2122         case LFUN_SCREEN_UP_SELECT: {
2123                 // FIXME: why is the algorithm different from LFUN_SCREEN_UP?
2124                 cur.selHandle(true);
2125                 if (isTopScreen()) {
2126                         lyx::dispatch(FuncRequest(LFUN_BUFFER_BEGIN_SELECT));
2127                         cur.finishUndo();
2128                         break;
2129                 }
2130                 int y = getPos(cur).y;
2131                 int const ymin = y - height_ + defaultRowHeight();
2132                 while (y > ymin && cur.up())
2133                         y = getPos(cur).y;
2134
2135                 cur.finishUndo();
2136                 dr.screenUpdate(Update::SinglePar | Update::FitCursor);
2137                 break;
2138         }
2139
2140         case LFUN_SCREEN_DOWN_SELECT: {
2141                 // FIXME: why is the algorithm different from LFUN_SCREEN_DOWN?
2142                 cur.selHandle(true);
2143                 if (isBottomScreen()) {
2144                         lyx::dispatch(FuncRequest(LFUN_BUFFER_END_SELECT));
2145                         cur.finishUndo();
2146                         break;
2147                 }
2148                 int y = getPos(cur).y;
2149                 int const ymax = y + height_ - defaultRowHeight();
2150                 while (y < ymax && cur.down())
2151                         y = getPos(cur).y;
2152
2153                 cur.finishUndo();
2154                 dr.screenUpdate(Update::SinglePar | Update::FitCursor);
2155                 break;
2156         }
2157
2158
2159         case LFUN_INSET_SELECT_ALL: {
2160                 // true if all cells are selected
2161                 bool const all_selected = cur.depth() > 1
2162                     && cur.selBegin().at_begin()
2163                     && cur.selEnd().at_end();
2164                 // true if some cells are selected
2165                 bool const cells_selected = cur.depth() > 1
2166                     && cur.selBegin().at_cell_begin()
2167                         && cur.selEnd().at_cell_end();
2168                 if (all_selected || (cells_selected && !cur.inset().isTable())) {
2169                         // All the contents of the inset if selected, or only at
2170                         // least one cell but inset is not a table.
2171                         // Select the inset from outside.
2172                         cur.pop();
2173                         cur.resetAnchor();
2174                         cur.selection(true);
2175                         cur.posForward();
2176                 } else if (cells_selected) {
2177                         // At least one complete cell is selected and inset is a table.
2178                         // Select all cells
2179                         cur.idx() = 0;
2180                         cur.pit() = 0;
2181                         cur.pos() = 0;
2182                         cur.resetAnchor();
2183                         cur.selection(true);
2184                         cur.idx() = cur.lastidx();
2185                         cur.pit() = cur.lastpit();
2186                         cur.pos() = cur.lastpos();
2187                 } else {
2188                         // select current cell
2189                         cur.pit() = 0;
2190                         cur.pos() = 0;
2191                         cur.resetAnchor();
2192                         cur.selection(true);
2193                         cur.pit() = cur.lastpit();
2194                         cur.pos() = cur.lastpos();
2195                 }
2196                 cur.setCurrentFont();
2197                 dr.screenUpdate(Update::Force);
2198                 break;
2199         }
2200
2201
2202         case LFUN_UNICODE_INSERT: {
2203                 if (cmd.argument().empty())
2204                         break;
2205
2206                 FuncCode code = cur.inset().currentMode() == Inset::MATH_MODE ?
2207                         LFUN_MATH_INSERT : LFUN_SELF_INSERT;
2208                 int i = 0;
2209                 while (true) {
2210                         docstring const arg = from_utf8(cmd.getArg(i));
2211                         if (arg.empty())
2212                                 break;
2213                         if (!isHex(arg)) {
2214                                 LYXERR0("Not a hexstring: " << arg);
2215                                 ++i;
2216                                 continue;
2217                         }
2218                         char_type c = hexToInt(arg);
2219                         if (c >= 32 && c < 0x10ffff) {
2220                                 LYXERR(Debug::KEY, "Inserting c: " << c);
2221                                 lyx::dispatch(FuncRequest(code, docstring(1, c)));
2222                         }
2223                         ++i;
2224                 }
2225                 break;
2226         }
2227
2228
2229         // This would be in Buffer class if only Cursor did not
2230         // require a bufferview
2231         case LFUN_INSET_FORALL: {
2232                 docstring const name = from_utf8(cmd.getArg(0));
2233                 string const commandstr = cmd.getLongArg(1);
2234                 FuncRequest const fr = lyxaction.lookupFunc(commandstr);
2235
2236                 // an arbitrary number to limit number of iterations
2237                 const int max_iter = 100000;
2238                 int iterations = 0;
2239                 Cursor & bvcur = d->cursor_;
2240                 Cursor const savecur = bvcur;
2241                 bvcur.reset();
2242                 if (!bvcur.nextInset())
2243                         bvcur.forwardInset();
2244                 bvcur.beginUndoGroup();
2245                 while(bvcur && iterations < max_iter) {
2246                         Inset * const ins = bvcur.nextInset();
2247                         if (!ins)
2248                                 break;
2249                         docstring insname = ins->layoutName();
2250                         while (!insname.empty()) {
2251                                 if (insname == name || name == from_utf8("*")) {
2252                                         lyx::dispatch(fr, dr);
2253                                         // we do not want to remember selection here
2254                                         bvcur.clearSelection();
2255                                         ++iterations;
2256                                         break;
2257                                 }
2258                                 size_t const i = insname.rfind(':');
2259                                 if (i == string::npos)
2260                                         break;
2261                                 insname = insname.substr(0, i);
2262                         }
2263                         // if we did not delete the inset, skip it
2264                         if (!bvcur.nextInset() || bvcur.nextInset() == ins)
2265                                 bvcur.forwardInset();
2266                 }
2267                 bvcur = savecur;
2268                 bvcur.fixIfBroken();
2269                 /** This is a dummy undo record only to remember the cursor
2270                  * that has just been set; this will be used on a redo action
2271                  * (see ticket #10097)
2272
2273                  * FIXME: a better fix would be to have a way to set the
2274                  * cursor value directly, but I am not sure it is worth it.
2275                  */
2276                 bvcur.recordUndo();
2277                 bvcur.endUndoGroup();
2278                 dr.screenUpdate(Update::Force);
2279                 dr.forceBufferUpdate();
2280
2281                 if (iterations >= max_iter) {
2282                         dr.setError(true);
2283                         dr.setMessage(bformat(_("`inset-forall' interrupted because number of actions is larger than %1$d"), max_iter));
2284                 } else
2285                         dr.setMessage(bformat(_("Applied \"%1$s\" to %2$d insets"), from_utf8(commandstr), iterations));
2286                 break;
2287         }
2288
2289
2290         case LFUN_BRANCH_ADD_INSERT: {
2291                 docstring branch_name = from_utf8(cmd.getArg(0));
2292                 if (branch_name.empty())
2293                         if (!Alert::askForText(branch_name, _("Branch name")) ||
2294                                                 branch_name.empty())
2295                                 break;
2296
2297                 DispatchResult drtmp;
2298                 buffer_.dispatch(FuncRequest(LFUN_BRANCH_ADD, branch_name), drtmp);
2299                 if (drtmp.error()) {
2300                         Alert::warning(_("Branch already exists"), drtmp.message());
2301                         break;
2302                 }
2303                 docstring const sep = buffer_.params().branchlist().separator();
2304                 for (docstring const & branch : getVectorFromString(branch_name, sep))
2305                         lyx::dispatch(FuncRequest(LFUN_BRANCH_INSERT, branch));
2306                 break;
2307         }
2308
2309         case LFUN_KEYMAP_OFF:
2310                 getIntl().keyMapOn(false);
2311                 break;
2312
2313         case LFUN_KEYMAP_PRIMARY:
2314                 getIntl().keyMapPrim();
2315                 break;
2316
2317         case LFUN_KEYMAP_SECONDARY:
2318                 getIntl().keyMapSec();
2319                 break;
2320
2321         case LFUN_KEYMAP_TOGGLE:
2322                 getIntl().toggleKeyMap();
2323                 break;
2324
2325         case LFUN_DIALOG_SHOW_NEW_INSET: {
2326                 string const name = cmd.getArg(0);
2327                 string data = trim(to_utf8(cmd.argument()).substr(name.size()));
2328                 if (decodeInsetParam(name, data, buffer_))
2329                         lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, name + " " + data));
2330                 else
2331                         lyxerr << "Inset type '" << name <<
2332                         "' not recognized in LFUN_DIALOG_SHOW_NEW_INSET" <<  endl;
2333                 break;
2334         }
2335
2336         case LFUN_CITATION_INSERT: {
2337                 if (argument.empty()) {
2338                         lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW_NEW_INSET, "citation"));
2339                         break;
2340                 }
2341                 // we can have one optional argument, delimited by '|'
2342                 // citation-insert <key>|<text_before>
2343                 // this should be enhanced to also support text_after
2344                 // and citation style
2345                 string arg = argument;
2346                 string opt1;
2347                 if (contains(argument, "|")) {
2348                         arg = token(argument, '|', 0);
2349                         opt1 = token(argument, '|', 1);
2350                 }
2351
2352                 // if our cursor is directly in front of or behind a citation inset,
2353                 // we will instead add the new key to it.
2354                 Inset * inset = cur.nextInset();
2355                 if (!inset || inset->lyxCode() != CITE_CODE)
2356                         inset = cur.prevInset();
2357                 if (inset && inset->lyxCode() == CITE_CODE) {
2358                         InsetCitation * icite = static_cast<InsetCitation *>(inset);
2359                         if (icite->addKey(arg)) {
2360                                 dr.forceBufferUpdate();
2361                                 dr.screenUpdate(Update::FitCursor | Update::SinglePar);
2362                                 if (!opt1.empty())
2363                                         LYXERR0("Discarding optional argument to citation-insert.");
2364                         }
2365                         dispatched = true;
2366                         break;
2367                 }
2368                 InsetCommandParams icp(CITE_CODE);
2369                 icp["key"] = from_utf8(arg);
2370                 if (!opt1.empty())
2371                         icp["before"] = from_utf8(opt1);
2372                 icp["literal"] = 
2373                         from_ascii(InsetCitation::last_literal ? "true" : "false");
2374                 string icstr = InsetCommand::params2string(icp);
2375                 FuncRequest fr(LFUN_INSET_INSERT, icstr);
2376                 lyx::dispatch(fr);
2377
2378                 // if the request comes from the LyX server, then we
2379                 // return a list of the undefined keys, in case some
2380                 // action could be taken.
2381                 if (cmd.origin() != FuncRequest::LYXSERVER)
2382                         break;
2383
2384                 vector<docstring> keys = getVectorFromString(from_utf8(arg));
2385                 vector<docstring>::iterator it = keys.begin();
2386                 vector<docstring>::const_iterator end = keys.end();
2387
2388                 BiblioInfo const & bibInfo = buffer_.masterBibInfo();
2389                 const BiblioInfo::const_iterator bibEnd = bibInfo.end();
2390                 while (it != end) {
2391                         if (bibInfo.find(*it) != bibEnd) {
2392                                 it = keys.erase(it);
2393                                 end = keys.end();
2394                         } else
2395                                 ++it;
2396                 }
2397                 dr.setMessage(getStringFromVector(keys));
2398
2399                 break;
2400         }
2401
2402         case LFUN_INSET_APPLY: {
2403                 string const name = cmd.getArg(0);
2404                 Inset * inset = editedInset(name);
2405                 if (!inset) {
2406                         FuncRequest fr(LFUN_INSET_INSERT, cmd.argument());
2407                         lyx::dispatch(fr);
2408                         break;
2409                 }
2410                 // put cursor in front of inset.
2411                 if (!setCursorFromInset(inset)) {
2412                         LASSERT(false, break);
2413                 }
2414                 cur.recordUndo();
2415                 FuncRequest fr(LFUN_INSET_MODIFY, cmd.argument());
2416                 inset->dispatch(cur, fr);
2417                 dr.screenUpdate(cur.result().screenUpdate());
2418                 if (cur.result().needBufferUpdate())
2419                         dr.forceBufferUpdate();
2420                 break;
2421         }
2422
2423         // FIXME:
2424         // The change of language of buffer belongs to the Buffer class.
2425         // We have to do it here because we need a cursor for Undo.
2426         // When Undo::recordUndoBufferParams() is implemented someday
2427         // LFUN_BUFFER_LANGUAGE should be handled by the Buffer class.
2428         case LFUN_BUFFER_LANGUAGE: {
2429                 Language const * oldL = buffer_.params().language;
2430                 Language const * newL = languages.getLanguage(argument);
2431                 if (!newL || oldL == newL)
2432                         break;
2433                 if (oldL->rightToLeft() == newL->rightToLeft()) {
2434                         cur.recordUndoFullBuffer();
2435                         buffer_.changeLanguage(oldL, newL);
2436                         cur.setCurrentFont();
2437                         dr.forceBufferUpdate();
2438                 }
2439                 break;
2440         }
2441
2442         case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2443         case LFUN_FILE_INSERT_PLAINTEXT: {
2444                 bool const as_paragraph = (act == LFUN_FILE_INSERT_PLAINTEXT_PARA);
2445                 string const fname = to_utf8(cmd.argument());
2446                 if (!FileName::isAbsolute(fname))
2447                         dr.setMessage(_("Absolute filename expected."));
2448                 else
2449                         insertPlaintextFile(FileName(fname), as_paragraph);
2450                 break;
2451         }
2452
2453         case LFUN_COPY:
2454                 // With multi-cell table content, we pass down to the inset
2455                 if (cur.inTexted() && cur.selection()
2456                     && cur.selectionBegin().idx() != cur.selectionEnd().idx()) {
2457                         buffer_.dispatch(cmd, dr);
2458                         dispatched = dr.dispatched();
2459                         break;
2460                 }
2461                 cap::copySelection(cur);
2462                 cur.message(_("Copy"));
2463                 break;
2464
2465         default:
2466                 // OK, so try the Buffer itself...
2467                 buffer_.dispatch(cmd, dr);
2468                 dispatched = dr.dispatched();
2469                 break;
2470         }
2471
2472         buffer_.undo().endUndoGroup();
2473         dr.dispatched(dispatched);
2474
2475         // NOTE: The code below is copied from Cursor::dispatch. If you
2476         // need to modify this, please update the other one too.
2477
2478         // notify insets we just entered/left
2479         if (cursor() != old) {
2480                 old.beginUndoGroup();
2481                 old.fixIfBroken();
2482                 bool badcursor = notifyCursorLeavesOrEnters(old, cursor());
2483                 if (badcursor) {
2484                         cursor().fixIfBroken();
2485                         resetInlineCompletionPos();
2486                 }
2487                 old.endUndoGroup();
2488         }
2489 }
2490
2491
2492 docstring BufferView::requestSelection()
2493 {
2494         Cursor & cur = d->cursor_;
2495
2496         LYXERR(Debug::SELECTION, "requestSelection: cur.selection: " << cur.selection());
2497         if (!cur.selection()) {
2498                 d->xsel_cache_.set = false;
2499                 return docstring();
2500         }
2501
2502         LYXERR(Debug::SELECTION, "requestSelection: xsel_cache.set: " << d->xsel_cache_.set);
2503         if (!d->xsel_cache_.set ||
2504             cur.top() != d->xsel_cache_.cursor ||
2505             cur.realAnchor().top() != d->xsel_cache_.anchor)
2506         {
2507                 d->xsel_cache_.cursor = cur.top();
2508                 d->xsel_cache_.anchor = cur.realAnchor().top();
2509                 d->xsel_cache_.set = cur.selection();
2510                 return cur.selectionAsString(false);
2511         }
2512         return docstring();
2513 }
2514
2515
2516 void BufferView::clearSelection()
2517 {
2518         d->cursor_.clearSelection();
2519         // Clear the selection buffer. Otherwise a subsequent
2520         // middle-mouse-button paste would use the selection buffer,
2521         // not the more current external selection.
2522         cap::clearSelection();
2523         d->xsel_cache_.set = false;
2524         // The buffer did not really change, but this causes the
2525         // redraw we need because we cleared the selection above.
2526         buffer_.changed(false);
2527 }
2528
2529
2530 void BufferView::resize(int width, int height)
2531 {
2532         height_ = height;
2533         // Update metrics only if width has changed
2534         if (width != width_) {
2535                 width_ = width;
2536
2537                 // Clear the paragraph height cache.
2538                 d->par_height_.clear();
2539                 // Redo the metrics.
2540                 updateMetrics(true);
2541         }
2542         // metrics is OK, full drawing is necessary now
2543         d->update_flags_ = (d->update_flags_ & ~Update::Force) | Update::ForceDraw;
2544         d->update_strategy_ = FullScreenUpdate;
2545 }
2546
2547
2548 Inset const * BufferView::getCoveringInset(Text const & text,
2549                 int x, int y) const
2550 {
2551         TextMetrics & tm = d->text_metrics_[&text];
2552         Inset * inset = tm.checkInsetHit(x, y);
2553         if (!inset)
2554                 return nullptr;
2555
2556         if (!inset->descendable(*this))
2557                 // No need to go further down if the inset is not
2558                 // descendable.
2559                 return inset;
2560
2561         size_t cell_number = inset->nargs();
2562         // Check all the inner cell.
2563         for (size_t i = 0; i != cell_number; ++i) {
2564                 Text const * inner_text = inset->getText(i);
2565                 if (inner_text) {
2566                         // Try deeper.
2567                         Inset const * inset_deeper =
2568                                 getCoveringInset(*inner_text, x, y);
2569                         if (inset_deeper)
2570                                 return inset_deeper;
2571                 }
2572         }
2573
2574         return inset;
2575 }
2576
2577
2578 Inset const * BufferView::clickableMathInset(InsetMathNest const * inset,
2579                 CoordCache::Insets const & inset_cache, int x, int y) const
2580 {
2581         for (size_t i = 0; i < inset->nargs(); ++i) {
2582                 MathData const & ar = inset->cell(i);
2583                 for (size_t j = 0; j < ar.size(); ++j) {
2584                         string const name = lyxerr.debugging(Debug::MATHED)
2585                                 ? insetName(ar[j].nucleus()->lyxCode())
2586                                 : string();
2587                         LYXERR(Debug::MATHED, "Checking inset: " << name);
2588                         if (ar[j].nucleus()->clickable(*this, x, y)) {
2589                                 if (inset_cache.covers(ar[j].nucleus(), x, y)) {
2590                                         LYXERR(Debug::MATHED, "Clickable inset: "
2591                                                << name);
2592                                         return ar[j].nucleus();
2593                                 }
2594                         }
2595                         InsetMathNest const * imn =
2596                                 ar[j].nucleus()->asNestInset();
2597                         if (imn) {
2598                                 Inset const * inner =
2599                                         clickableMathInset(imn, inset_cache, x, y);
2600                                 if (inner)
2601                                         return inner;
2602                         }
2603                 }
2604         }
2605         return nullptr;
2606 }
2607
2608
2609 void BufferView::updateHoveredInset() const
2610 {
2611         // Get inset under mouse, if there is one.
2612         int const x = d->mouse_position_cache_.x;
2613         int const y = d->mouse_position_cache_.y;
2614         Inset const * covering_inset = getCoveringInset(buffer_.text(), x, y);
2615         if (covering_inset && covering_inset->asInsetMath()) {
2616                 Inset const * inner_inset = clickableMathInset(
2617                                 covering_inset->asInsetMath()->asNestInset(),
2618                                 coordCache().getInsets(), x, y);
2619                 if (inner_inset)
2620                         covering_inset = inner_inset;
2621         }
2622
2623         d->clickable_inset_ = covering_inset && covering_inset->clickable(*this, x, y);
2624
2625         if (covering_inset == d->last_inset_)
2626                 // Same inset, no need to do anything...
2627                 return;
2628
2629         bool need_redraw = false;
2630         if (d->last_inset_) {
2631                 // Remove the hint on the last hovered inset (if any).
2632                 need_redraw |= d->last_inset_->setMouseHover(this, false);
2633                 d->last_inset_ = nullptr;
2634         }
2635
2636         if (covering_inset && covering_inset->setMouseHover(this, true)) {
2637                 need_redraw = true;
2638                 // Only the insets that accept the hover state, do
2639                 // clear the last_inset_, so only set the last_inset_
2640                 // member if the hovered setting is accepted.
2641                 d->last_inset_ = covering_inset;
2642         }
2643
2644         if (need_redraw) {
2645                 LYXERR(Debug::PAINTING, "Mouse hover detected at: ("
2646                                 << d->mouse_position_cache_.x << ", "
2647                                 << d->mouse_position_cache_.y << ")");
2648
2649                 d->update_strategy_ = DecorationUpdate;
2650
2651                 // This event (moving without mouse click) is not passed further.
2652                 // This should be changed if it is further utilized.
2653                 buffer_.changed(false);
2654         }
2655 }
2656
2657
2658 void BufferView::clearLastInset(Inset * inset) const
2659 {
2660         if (d->last_inset_ != inset) {
2661                 LYXERR0("Wrong last_inset!");
2662                 LATTEST(false);
2663         }
2664         d->last_inset_ = nullptr;
2665 }
2666
2667
2668 bool BufferView::mouseSelecting() const
2669 {
2670         return d->mouse_selecting_;
2671 }
2672
2673
2674 int BufferView::stats_ref_value_w() const
2675 {
2676         return d->stats_ref_value_w_;
2677 }
2678
2679
2680 int BufferView::stats_ref_value_c() const
2681 {
2682         return d->stats_ref_value_c_;
2683 }
2684
2685
2686 int BufferView::stats_ref_value_nb() const
2687 {
2688         return d->stats_ref_value_nb_;
2689 }
2690
2691
2692 void BufferView::mouseEventDispatch(FuncRequest const & cmd0)
2693 {
2694         //lyxerr << "[ cmd0 " << cmd0 << "]" << endl;
2695
2696         if (!ready())
2697                 return;
2698
2699         // This is only called for mouse related events including
2700         // LFUN_FILE_OPEN generated by drag-and-drop.
2701         FuncRequest cmd = cmd0;
2702
2703         // Either the inset under the cursor or the
2704         // surrounding Text will handle this event.
2705
2706         // make sure we stay within the screen...
2707         cmd.set_y(min(max(cmd.y(), -1), height_));
2708
2709         d->mouse_position_cache_.x = cmd.x();
2710         d->mouse_position_cache_.y = cmd.y();
2711
2712         d->mouse_selecting_ =
2713                 cmd.action() == LFUN_MOUSE_MOTION && cmd.button() == mouse_button::button1;
2714
2715         if (cmd.action() == LFUN_MOUSE_MOTION && cmd.button() == mouse_button::none) {
2716                 updateHoveredInset();
2717                 return;
2718         }
2719
2720         Cursor old = cursor();
2721         Cursor cur(*this);
2722         cur.push(buffer_.inset());
2723         cur.selection(d->cursor_.selection());
2724
2725         // Build temporary cursor.
2726         Inset * inset = d->text_metrics_[&buffer_.text()].editXY(cur, cmd.x(), cmd.y());
2727         if (inset) {
2728                 // If inset is not editable, cur.pos() might point behind the
2729                 // inset (depending on cmd.x(), cmd.y()). This is needed for
2730                 // editing to fix bug 9628, but e.g. the context menu needs a
2731                 // cursor in front of the inset.
2732                 if ((inset->hasSettings() || !inset->contextMenuName().empty()
2733                      || inset->lyxCode() == SEPARATOR_CODE) &&
2734                     cur.nextInset() != inset && cur.prevInset() == inset)
2735                         cur.posBackward();
2736         } else if (cur.inTexted() && cur.pos()
2737                         && cur.paragraph().isEnvSeparator(cur.pos() - 1)) {
2738                 // Always place cursor in front of a separator inset.
2739                 cur.posBackward();
2740         }
2741
2742         // Put anchor at the same position.
2743         cur.resetAnchor();
2744
2745         old.beginUndoGroup();
2746
2747         // Try to dispatch to an non-editable inset near this position
2748         // via the temp cursor. If the inset wishes to change the real
2749         // cursor it has to do so explicitly by using
2750         //  cur.bv().cursor() = cur;  (or similar)
2751         if (inset)
2752                 inset->dispatch(cur, cmd);
2753
2754         // Now dispatch to the temporary cursor. If the real cursor should
2755         // be modified, the inset's dispatch has to do so explicitly.
2756         if (!inset || !cur.result().dispatched())
2757                 cur.dispatch(cmd);
2758
2759         // Notify left insets
2760         if (cur != old) {
2761                 bool badcursor = old.fixIfBroken() || cur.fixIfBroken();
2762                 badcursor = badcursor || notifyCursorLeavesOrEnters(old, cur);
2763                 if (badcursor)
2764                         cursor().fixIfBroken();
2765         }
2766
2767         old.endUndoGroup();
2768
2769         // Do we have a selection?
2770         theSelection().haveSelection(cursor().selection());
2771
2772         if (cur.needBufferUpdate() || buffer().needUpdate()) {
2773                 cur.clearBufferUpdate();
2774                 buffer().updateBuffer();
2775         }
2776
2777         // If the command has been dispatched,
2778         if (cur.result().dispatched() || cur.result().screenUpdate())
2779                 processUpdateFlags(cur.result().screenUpdate());
2780 }
2781
2782
2783 int BufferView::minVisiblePart()
2784 {
2785         return 2 * defaultRowHeight();
2786 }
2787
2788
2789 int BufferView::scroll(int pixels)
2790 {
2791         if (pixels > 0)
2792                 return scrollDown(pixels);
2793         if (pixels < 0)
2794                 return scrollUp(-pixels);
2795         return 0;
2796 }
2797
2798
2799 int BufferView::scrollDown(int pixels)
2800 {
2801         Text * text = &buffer_.text();
2802         TextMetrics & tm = d->text_metrics_[text];
2803         int const ymax = height_ + pixels;
2804         while (true) {
2805                 pair<pit_type, ParagraphMetrics const *> last = tm.last();
2806                 int bottom_pos = last.second->bottom();
2807                 if (lyxrc.scroll_below_document)
2808                         bottom_pos += height_ - minVisiblePart();
2809                 if (last.first + 1 == int(text->paragraphs().size())) {
2810                         if (bottom_pos <= height_)
2811                                 return 0;
2812                         pixels = min(pixels, bottom_pos - height_);
2813                         break;
2814                 }
2815                 if (bottom_pos > ymax)
2816                         break;
2817                 tm.newParMetricsDown();
2818         }
2819         d->anchor_ypos_ -= pixels;
2820         return -pixels;
2821 }
2822
2823
2824 int BufferView::scrollUp(int pixels)
2825 {
2826         Text * text = &buffer_.text();
2827         TextMetrics & tm = d->text_metrics_[text];
2828         int ymin = - pixels;
2829         while (true) {
2830                 pair<pit_type, ParagraphMetrics const *> first = tm.first();
2831                 int top_pos = first.second->top();
2832                 if (first.first == 0) {
2833                         if (top_pos >= 0)
2834                                 return 0;
2835                         pixels = min(pixels, - top_pos);
2836                         break;
2837                 }
2838                 if (top_pos < ymin)
2839                         break;
2840                 tm.newParMetricsUp();
2841         }
2842         d->anchor_ypos_ += pixels;
2843         return pixels;
2844 }
2845
2846
2847 bool BufferView::setCursorFromRow(int row)
2848 {
2849         TexRow::TextEntry start, end;
2850         tie(start,end) = buffer_.texrow().getEntriesFromRow(row);
2851         LYXERR(Debug::OUTFILE,
2852                "setCursorFromRow: for row " << row << ", TexRow has found "
2853                "start (id=" << start.id << ",pos=" << start.pos << "), "
2854                "end (id=" << end.id << ",pos=" << end.pos << ")");
2855         return setCursorFromEntries(start, end);
2856 }
2857
2858
2859 bool BufferView::setCursorFromEntries(TexRow::TextEntry start,
2860                                       TexRow::TextEntry end)
2861 {
2862         DocIterator dit_start, dit_end;
2863         tie(dit_start,dit_end) =
2864                 TexRow::getDocIteratorsFromEntries(start, end, buffer_);
2865         if (!dit_start)
2866                 return false;
2867         // Setting selection start
2868         d->cursor_.clearSelection();
2869         setCursor(dit_start);
2870         // Setting selection end
2871         if (dit_end) {
2872                 d->cursor_.resetAnchor();
2873                 setCursorSelectionTo(dit_end);
2874         }
2875         return true;
2876 }
2877
2878
2879 bool BufferView::setCursorFromInset(Inset const * inset)
2880 {
2881         // are we already there?
2882         if (cursor().nextInset() == inset)
2883                 return true;
2884
2885         // Inset is not at cursor position. Find it in the document.
2886         Cursor cur(*this);
2887         cur.reset();
2888         while (cur && cur.nextInset() != inset)
2889                 cur.forwardInset();
2890
2891         if (cur) {
2892                 setCursor(cur);
2893                 return true;
2894         }
2895         return false;
2896 }
2897
2898
2899 void BufferView::gotoLabel(docstring const & label)
2900 {
2901         FuncRequest action;
2902         bool have_inactive = false;
2903         for (Buffer const * buf : buffer().allRelatives()) {
2904                 // find label
2905                 for (TocItem const & item : *buf->tocBackend().toc("label")) {
2906                         if (label == item.str() && item.isOutput()) {
2907                                 lyx::dispatch(item.action());
2908                                 return;
2909                         }
2910                         // If we find an inactive label, save it for the case
2911                         // that no active one is there
2912                         if (label == item.str() && !have_inactive) {
2913                                 have_inactive = true;
2914                                 action = item.action();
2915                         }
2916                 }
2917         }
2918         // We only found an inactive label. Go there.
2919         if (have_inactive)
2920                 lyx::dispatch(action);
2921 }
2922
2923
2924 TextMetrics const & BufferView::textMetrics(Text const * t) const
2925 {
2926         return const_cast<BufferView *>(this)->textMetrics(t);
2927 }
2928
2929
2930 TextMetrics & BufferView::textMetrics(Text const * t)
2931 {
2932         LBUFERR(t);
2933         TextMetricsCache::iterator tmc_it  = d->text_metrics_.find(t);
2934         if (tmc_it == d->text_metrics_.end()) {
2935                 tmc_it = d->text_metrics_.emplace(std::piecewise_construct,
2936                                 std::forward_as_tuple(t),
2937                                 std::forward_as_tuple(this, const_cast<Text *>(t))).first;
2938         }
2939         return tmc_it->second;
2940 }
2941
2942
2943 ParagraphMetrics const & BufferView::parMetrics(Text const * t,
2944                 pit_type pit) const
2945 {
2946         return textMetrics(t).parMetrics(pit);
2947 }
2948
2949
2950 int BufferView::workHeight() const
2951 {
2952         return height_;
2953 }
2954
2955
2956 void BufferView::setCursor(DocIterator const & dit)
2957 {
2958         d->cursor_.reset();
2959         size_t const n = dit.depth();
2960         for (size_t i = 0; i < n; ++i)
2961                 dit[i].inset().edit(d->cursor_, true);
2962
2963         d->cursor_.setCursor(dit);
2964         d->cursor_.selection(false);
2965         d->cursor_.setCurrentFont();
2966         // FIXME
2967         // It seems on general grounds as if this is probably needed, but
2968         // it is not yet clear.
2969         // See bug #7394 and r38388.
2970         // d->cursor.resetAnchor();
2971 }
2972
2973
2974 void BufferView::setCursorSelectionTo(DocIterator const & dit)
2975 {
2976         size_t const n = dit.depth();
2977         for (size_t i = 0; i < n; ++i)
2978                 dit[i].inset().edit(d->cursor_, true);
2979
2980         d->cursor_.selection(true);
2981         d->cursor_.setCursorSelectionTo(dit);
2982         d->cursor_.setCurrentFont();
2983 }
2984
2985
2986 bool BufferView::checkDepm(Cursor & cur, Cursor & old)
2987 {
2988         // Would be wrong to delete anything if we have a selection.
2989         if (cur.selection())
2990                 return false;
2991
2992         bool need_anchor_change = false;
2993         bool changed = Text::deleteEmptyParagraphMechanism(cur, old,
2994                 need_anchor_change);
2995
2996         if (need_anchor_change)
2997                 cur.resetAnchor();
2998
2999         if (!changed)
3000                 return false;
3001
3002         d->cursor_ = cur;
3003
3004         // we would rather not do this here, but it needs to be done before
3005         // the changed() signal is sent.
3006         buffer_.updateBuffer();
3007
3008         buffer_.changed(true);
3009         return true;
3010 }
3011
3012
3013 bool BufferView::mouseSetCursor(Cursor & cur, bool const select)
3014 {
3015         LASSERT(&cur.bv() == this, return false);
3016
3017         if (!select)
3018                 // this event will clear selection so we save selection for
3019                 // persistent selection
3020                 cap::saveSelection(cursor());
3021
3022         d->cursor_.macroModeClose();
3023         // If a macro has been finalized, the cursor might have been broken
3024         cur.fixIfBroken();
3025
3026         // Has the cursor just left the inset?
3027         bool const leftinset = (&d->cursor_.inset() != &cur.inset());
3028         if (leftinset)
3029                 d->cursor_.fixIfBroken();
3030
3031         // do the dEPM magic if needed
3032         // FIXME: (1) move this to InsetText::notifyCursorLeaves?
3033         // FIXME: (2) if we had a working InsetText::notifyCursorLeaves,
3034         // the leftinset bool would not be necessary (badcursor instead).
3035         bool update = leftinset;
3036
3037         if (select) {
3038                 d->cursor_.setSelection();
3039                 d->cursor_.setCursorSelectionTo(cur);
3040         } else {
3041                 if (d->cursor_.inTexted())
3042                         update |= checkDepm(cur, d->cursor_);
3043                 d->cursor_.resetAnchor();
3044                 d->cursor_.setCursor(cur);
3045                 d->cursor_.clearSelection();
3046         }
3047         d->cursor_.boundary(cur.boundary());
3048         d->cursor_.finishUndo();
3049         d->cursor_.setCurrentFont();
3050         if (update)
3051                 cur.forceBufferUpdate();
3052         return update;
3053 }
3054
3055
3056 void BufferView::putSelectionAt(DocIterator const & cur,
3057                                 int length, bool backwards)
3058 {
3059         d->cursor_.clearSelection();
3060
3061         setCursor(cur);
3062
3063         if (length) {
3064                 if (backwards) {
3065                         d->cursor_.pos() += length;
3066                         d->cursor_.setSelection(d->cursor_, -length);
3067                 } else
3068                         d->cursor_.setSelection(d->cursor_, length);
3069         }
3070 }
3071
3072
3073 void BufferView::setSelection(DocIterator const & from,
3074                               DocIterator const & to)
3075 {
3076         if (from.pit() != to.pit()) {
3077                 // there are multiple paragraphs in selection
3078                 cursor().setCursor(from);
3079                 cursor().clearSelection();
3080                 cursor().selection(true);
3081                 cursor().setCursor(to);
3082                 cursor().selection(true);
3083         } else {
3084                 // only single paragraph
3085                 int const size = to.pos() - from.pos();
3086                 putSelectionAt(from, size, false);
3087         }
3088         processUpdateFlags(Update::Force | Update::FitCursor);
3089 }
3090
3091
3092 bool BufferView::selectIfEmpty(DocIterator & cur)
3093 {
3094         if ((cur.inTexted() && !cur.paragraph().empty())
3095             || (cur.inMathed() && !cur.cell().empty()))
3096                 return false;
3097
3098         pit_type const beg_pit = cur.pit();
3099         if (beg_pit > 0) {
3100                 // The paragraph associated to this item isn't
3101                 // the first one, so it can be selected
3102                 cur.backwardPos();
3103         } else {
3104                 // We have to resort to select the space between the
3105                 // end of this item and the begin of the next one
3106                 cur.forwardPos();
3107         }
3108         if (cur.empty()) {
3109                 // If it is the only item in the document,
3110                 // nothing can be selected
3111                 return false;
3112         }
3113         pit_type const end_pit = cur.pit();
3114         pos_type const end_pos = cur.pos();
3115         d->cursor_.clearSelection();
3116         d->cursor_.reset();
3117         d->cursor_.setCursor(cur);
3118         d->cursor_.pit() = beg_pit;
3119         d->cursor_.pos() = 0;
3120         d->cursor_.selection(false);
3121         d->cursor_.resetAnchor();
3122         d->cursor_.pit() = end_pit;
3123         d->cursor_.pos() = end_pos;
3124         d->cursor_.setSelection();
3125         return true;
3126 }
3127
3128
3129 Cursor & BufferView::cursor()
3130 {
3131         return d->cursor_;
3132 }
3133
3134
3135 Cursor const & BufferView::cursor() const
3136 {
3137         return d->cursor_;
3138 }
3139
3140
3141 bool BufferView::singleParUpdate()
3142 {
3143         CursorSlice const & its = d->cursor_.innerTextSlice();
3144         pit_type const pit = its.pit();
3145         TextMetrics & tm = textMetrics(its.text());
3146         Dimension const old_dim = tm.parMetrics(pit).dim();
3147
3148         // make sure inline completion pointer is ok
3149         if (d->inlineCompletionPos_.fixIfBroken())
3150                 d->inlineCompletionPos_ = DocIterator();
3151
3152         /* Try to rebreak only the paragraph containing the cursor (if
3153          * this paragraph contains insets etc., rebreaking will
3154          * recursively descend). We need a full redraw if either
3155          * 1/ the height has changed
3156          * or
3157          * 2/ the width has changed and it was equal to the textmetrics
3158          *    width; the goal is to catch the case of a one-row inset that
3159          *    grows with its contents, but optimize the case of typing at
3160          *    the end of a mmultiple-row paragraph.
3161          *
3162          * NOTE: if only the height has changed, then it should be
3163          *   possible to update all metrics at minimal cost. However,
3164          *   since this is risky, we do not try that right now.
3165          */
3166         tm.redoParagraph(pit);
3167         ParagraphMetrics & pm = tm.parMetrics(pit);
3168         if (pm.height() != old_dim.height()
3169                 || (pm.width() != old_dim.width() && old_dim.width() == tm.width())) {
3170                 // Paragraph height or width has changed so we cannot proceed
3171                 // to the singlePar optimisation.
3172                 LYXERR(Debug::PAINTING, "SinglePar optimization failed.");
3173                 return false;
3174         }
3175         // Since position() points to the baseline of the first row, we
3176         // may have to update it. See ticket #11601 for an example where
3177         // the height does not change but the ascent does.
3178         pm.setPosition(pm.position() - old_dim.ascent() + pm.ascent());
3179
3180         tm.updatePosCache(pit);
3181
3182         LYXERR(Debug::PAINTING, "\ny1: " << pm.top() << " y2: " << pm.bottom()
3183                 << " pit: " << pit << " singlepar: 1");
3184         return true;
3185 }
3186
3187
3188 void BufferView::updateMetrics()
3189 {
3190         updateMetrics(true);
3191         // metrics is done, full drawing is necessary now
3192         d->update_flags_ = (d->update_flags_ & ~Update::Force) | Update::ForceDraw;
3193         d->update_strategy_ = FullScreenUpdate;
3194 }
3195
3196
3197 void BufferView::updateMetrics(bool force)
3198 {
3199         if (!ready())
3200                 return;
3201
3202         //LYXERR0("updateMetrics " << _v_(force));
3203
3204         Text & buftext = buffer_.text();
3205         pit_type const lastpit = int(buftext.paragraphs().size()) - 1;
3206
3207         if (force) {
3208                 // Clear out the position cache in case of full screen redraw,
3209                 d->coord_cache_.clear();
3210                 d->math_rows_.clear();
3211
3212                 // Clear out paragraph metrics to avoid having invalid metrics
3213                 // in the cache from paragraphs not relayouted below. The
3214                 // complete text metrics will be redone.
3215                 d->text_metrics_.clear();
3216         }
3217
3218         // This should not be moved earlier
3219         TextMetrics & tm = textMetrics(&buftext);
3220
3221         // make sure inline completion pointer is ok
3222         if (d->inlineCompletionPos_.fixIfBroken())
3223                 d->inlineCompletionPos_ = DocIterator();
3224
3225         if (d->anchor_pit_ > lastpit)
3226                 // The anchor pit must have been deleted...
3227                 d->anchor_pit_ = lastpit;
3228
3229         // Update metrics around the anchor
3230         tm.updateMetrics(d->anchor_pit_, d->anchor_ypos_, height_);
3231
3232         // Check that the end of the document is not too high
3233         int const min_visible = lyxrc.scroll_below_document ? minVisiblePart() : height_;
3234         if (tm.last().first == lastpit && tm.last().second->hasPosition()
3235              && tm.last().second->bottom() < min_visible) {
3236                 d->anchor_ypos_ += min_visible - tm.last().second->bottom();
3237                 LYXERR(Debug::SCROLLING, "Too high, adjusting anchor ypos to " << d->anchor_ypos_);
3238                 tm.updateMetrics(d->anchor_pit_, d->anchor_ypos_, height_);
3239         }
3240
3241         // Check that the start of the document is not too low
3242         if (tm.first().first == 0 && tm.first().second->hasPosition()
3243              && tm.first().second->top() > 0) {
3244                 d->anchor_ypos_ -= tm.first().second->top();
3245                 LYXERR(Debug::SCROLLING, "Too low, adjusting anchor ypos to " << d->anchor_ypos_);
3246                 tm.updateMetrics(d->anchor_pit_, d->anchor_ypos_, height_);
3247         }
3248
3249         /* FIXME: do we want that? It avoids potential issues with old
3250          * paragraphs that should have been recomputed but have not, at
3251          * the price of potential extra metrics computation. I do not
3252          * think that the performance gain is high, so that for now the
3253          * extra paragraphs are removed
3254          */
3255         // Remove paragraphs that are outside of screen
3256         while(!tm.first().second->hasPosition() || tm.first().second->bottom() <= 0) {
3257                 //LYXERR0("Forget pit: " << tm.first().first);
3258                 tm.forget(tm.first().first);
3259         }
3260         while(!tm.last().second->hasPosition() || tm.last().second->top() > height_) {
3261                 //LYXERR0("Forget pit: " << tm.first().first);
3262                 tm.forget(tm.last().first);
3263         }
3264
3265         /* FIXME: if paragraphs outside of the screen are not removed
3266          * above, one has to search for the first visible one here */
3267         // Normalize anchor for next time
3268         if (d->anchor_pit_ != tm.first().first
3269             || d->anchor_ypos_ != tm.first().second->position()) {
3270                 LYXERR(Debug::PAINTING, __func__ << ": Found new anchor pit = " << tm.first().first
3271                                 << "  anchor ypos = " << tm.first().second->position()
3272                                 << " (was " << d->anchor_pit_ << ", " << d->anchor_ypos_ << ")");
3273                 d->anchor_pit_ = tm.first().first;
3274                 d->anchor_ypos_ = tm.first().second->position();
3275         }
3276
3277         // Now update the positions of insets in the cache.
3278         updatePosCache();
3279
3280         if (lyxerr.debugging(Debug::WORKAREA)) {
3281                 LYXERR(Debug::WORKAREA, "BufferView::updateMetrics");
3282                 d->coord_cache_.dump();
3283         }
3284 }
3285
3286
3287 void BufferView::updatePosCache()
3288 {
3289         // this is the "nodraw" drawing stage: only set the positions of the
3290         // insets in metrics cache.
3291         frontend::NullPainter np;
3292         draw(np, false);
3293 }
3294
3295
3296 void BufferView::insertLyXFile(FileName const & fname, bool const ignorelang)
3297 {
3298         LASSERT(d->cursor_.inTexted(), return);
3299
3300         // Get absolute path of file and add ".lyx"
3301         // to the filename if necessary
3302         FileName filename = fileSearch(string(), fname.absFileName(), "lyx");
3303
3304         docstring const disp_fn = makeDisplayPath(filename.absFileName());
3305         // emit message signal.
3306         message(bformat(_("Inserting document %1$s..."), disp_fn));
3307
3308         docstring res;
3309         Buffer buf(filename.absFileName(), false);
3310         if (buf.loadLyXFile() == Buffer::ReadSuccess) {
3311                 ErrorList & el = buffer_.errorList("Parse");
3312                 // Copy the inserted document error list into the current buffer one.
3313                 el = buf.errorList("Parse");
3314                 ParagraphList & pars = buf.paragraphs();
3315                 if (ignorelang)
3316                         // set main language of imported file to context language
3317                         buf.changeLanguage(buf.language(), d->cursor_.getFont().language());
3318                 buffer_.undo().recordUndo(d->cursor_);
3319                 cap::replaceSelection(d->cursor_);
3320                 cap::pasteParagraphList(d->cursor_, pars,
3321                                         buf.params().documentClassPtr(),
3322                                         buf.params().authors(), el);
3323                 res = _("Document %1$s inserted.");
3324         } else {
3325                 res = _("Could not insert document %1$s");
3326         }
3327
3328         buffer_.changed(true);
3329         // emit message signal.
3330         message(bformat(res, disp_fn));
3331 }
3332
3333
3334 Point BufferView::coordOffset(DocIterator const & dit) const
3335 {
3336         int x = 0;
3337         int y = 0;
3338         int lastw = 0;
3339
3340         // Addup contribution of nested insets, from inside to outside,
3341         // keeping the outer paragraph for a special handling below
3342         for (size_t i = dit.depth() - 1; i >= 1; --i) {
3343                 CursorSlice const & sl = dit[i];
3344                 int xx = 0;
3345                 int yy = 0;
3346
3347                 // get relative position inside sl.inset()
3348                 sl.inset().cursorPos(*this, sl, dit.boundary() && (i + 1 == dit.depth()), xx, yy);
3349
3350                 // Make relative position inside of the edited inset relative to sl.inset()
3351                 x += xx;
3352                 y += yy;
3353
3354                 // In case of an RTL inset, the edited inset will be positioned to the left
3355                 // of xx:yy
3356                 if (sl.text()) {
3357                         bool boundary_i = dit.boundary() && i + 1 == dit.depth();
3358                         bool rtl = textMetrics(sl.text()).isRTL(sl, boundary_i);
3359                         if (rtl)
3360                                 x -= lastw;
3361                 }
3362
3363                 // remember width for the case that sl.inset() is positioned in an RTL inset
3364                 lastw = sl.inset().dimension(*this).wid;
3365
3366                 //lyxerr << "Cursor::getPos, i: "
3367                 // << i << " x: " << xx << " y: " << y << endl;
3368         }
3369
3370         // Add contribution of initial rows of outermost paragraph
3371         CursorSlice const & sl = dit[0];
3372         TextMetrics const & tm = textMetrics(sl.text());
3373         ParagraphMetrics const & pm = tm.parMetrics(sl.pit());
3374
3375         LBUFERR(!pm.rows().empty());
3376         y -= pm.rows()[0].ascent();
3377 #if 1
3378         // FIXME: document this mess
3379         size_t rend;
3380         if (sl.pos() > 0 && dit.depth() == 1) {
3381                 int pos = sl.pos();
3382                 if (pos && dit.boundary())
3383                         --pos;
3384 //              lyxerr << "coordOffset: boundary:" << dit.boundary() << " depth:" << dit.depth() << " pos:" << pos << " sl.pos:" << sl.pos() << endl;
3385                 rend = pm.pos2row(pos);
3386         } else
3387                 rend = pm.pos2row(sl.pos());
3388 #else
3389         size_t rend = pm.pos2row(sl.pos());
3390 #endif
3391         for (size_t rit = 0; rit != rend; ++rit)
3392                 y += pm.rows()[rit].height();
3393         y += pm.rows()[rend].ascent();
3394
3395         TextMetrics const & bottom_tm = textMetrics(dit.bottom().text());
3396
3397         // Make relative position from the nested inset now bufferview absolute.
3398         int xx = bottom_tm.cursorX(dit.bottom(), dit.boundary() && dit.depth() == 1);
3399         x += xx;
3400
3401         // In the RTL case place the nested inset at the left of the cursor in
3402         // the outer paragraph
3403         bool boundary_1 = dit.boundary() && 1 == dit.depth();
3404         bool rtl = bottom_tm.isRTL(dit.bottom(), boundary_1);
3405         if (rtl)
3406                 x -= lastw;
3407
3408         return Point(x, y);
3409 }
3410
3411
3412 Point BufferView::getPos(DocIterator const & dit) const
3413 {
3414         if (!paragraphVisible(dit))
3415                 return Point(-1, -1);
3416
3417         CursorSlice const & bot = dit.bottom();
3418         TextMetrics const & tm = textMetrics(bot.text());
3419
3420         // offset from outer paragraph
3421         Point p = coordOffset(dit);
3422         p.y += tm.parMetrics(bot.pit()).position();
3423         return p;
3424 }
3425
3426
3427 bool BufferView::paragraphVisible(DocIterator const & dit) const
3428 {
3429         CursorSlice const & bot = dit.bottom();
3430         TextMetrics const & tm = textMetrics(bot.text());
3431
3432         return tm.contains(bot.pit());
3433 }
3434
3435
3436 void BufferView::caretPosAndDim(Point & p, Dimension & dim) const
3437 {
3438         Cursor const & cur = cursor();
3439         if (cur.inMathed() && hasMathRow(&cur.cell())) {
3440                 MathRow const & mrow = mathRow(&cur.cell());
3441                 dim = mrow.caret_dim;
3442         } else {
3443                 Font const font = cur.real_current_font;
3444                 frontend::FontMetrics const & fm = theFontMetrics(font);
3445                 // lineWidth() can be 0 to mean 'thin line' on HiDpi, but the
3446                 // caret drawing code is not prepared for that.
3447                 dim.wid = max(fm.lineWidth(), 1);
3448                 dim.asc = fm.maxAscent();
3449                 dim.des = fm.maxDescent();
3450         }
3451         if (lyxrc.cursor_width > 0)
3452                 dim.wid = lyxrc.cursor_width;
3453
3454         p = getPos(cur);
3455         // center fat carets horizontally
3456         p.x -= dim.wid / 2;
3457         // p is top-left
3458         p.y -= dim.asc;
3459 }
3460
3461
3462 void BufferView::buildCaretGeometry(bool complet)
3463 {
3464         Point p;
3465         Dimension dim;
3466         caretPosAndDim(p, dim);
3467
3468         Cursor const & cur = d->cursor_;
3469         Font const & realfont = cur.real_current_font;
3470         frontend::FontMetrics const & fm = theFontMetrics(realfont.fontInfo());
3471         bool const isrtl = realfont.isVisibleRightToLeft();
3472         int const dir = isrtl ? -1 : 1;
3473
3474         frontend::CaretGeometry & cg = d->caret_geometry_;
3475         cg.shapes.clear();
3476
3477         // The caret itself, slanted for italics in text edit mode except
3478         // for selections because the selection rect does not slant
3479         bool const slant = fm.italic() && cur.inTexted() && !cur.selection();
3480         double const slope = slant ? fm.italicSlope() : 0;
3481         cg.shapes.push_back(
3482                 {{iround(p.x + dim.asc * slope),                 p.y},
3483                  {iround(p.x - dim.des * slope),                 p.y + dim.height()},
3484                  {iround(p.x + dir * dim.wid - dim.des * slope), p.y + dim.height()},
3485                  {iround(p.x + dir * dim.wid + dim.asc * slope), p.y}}
3486                 );
3487
3488         // The language indicator _| (if needed)
3489         Language const * doclang = buffer().params().language;
3490         if (!((realfont.language() == doclang && isrtl == doclang->rightToLeft())
3491                   || realfont.language() == latex_language)) {
3492                 int const lx = dim.height() / 3;
3493                 int const xx = iround(p.x - dim.des * slope);
3494                 int const yy = p.y + dim.height();
3495                 cg.shapes.push_back(
3496                         {{xx,                            yy - dim.wid},
3497                          {xx + dir * (dim.wid + lx - 1), yy - dim.wid},
3498                          {xx + dir * (dim.wid + lx - 1), yy},
3499                          {xx,                            yy}}
3500                         );
3501         }
3502
3503         // The completion triangle |> (if needed)
3504         if (complet) {
3505                 int const m = p.y + dim.height() / 2;
3506                 int const d = dim.height() / 8;
3507                 // offset for slanted carret
3508                 int const sx = iround((dim.asc - (dim.height() / 2 - d)) * slope);
3509                 // starting position x
3510                 int const xx = p.x + dir * dim.wid + sx;
3511                 cg.shapes.push_back(
3512                         {{xx,                     m - d},
3513                          {xx + dir * d,           m},
3514                          {xx,                     m + d},
3515                          {xx,                     m + d - dim.wid},
3516                          {xx + dir * d - dim.wid, m},
3517                          {xx,                     m - d + dim.wid}}
3518                         );
3519         }
3520
3521         // compute extremal x values
3522         cg.left = 1000000;
3523         cg.right = -1000000;
3524         cg.top = 1000000;
3525         cg.bottom = -1000000;
3526         for (auto const & shape : cg.shapes)
3527                 for (Point const & p : shape) {
3528                         cg.left = min(cg.left, p.x);
3529                         cg.right = max(cg.right, p.x);
3530                         cg.top = min(cg.top, p.y);
3531                         cg.bottom = max(cg.bottom, p.y);
3532                 }
3533 }
3534
3535
3536 frontend::CaretGeometry const &  BufferView::caretGeometry() const
3537 {
3538         return d->caret_geometry_;
3539 }
3540
3541
3542 bool BufferView::caretInView() const
3543 {
3544         if (!paragraphVisible(cursor()))
3545                 return false;
3546         Point p;
3547         Dimension dim;
3548         caretPosAndDim(p, dim);
3549
3550         // does the cursor touch the screen ?
3551         if (p.y + dim.height() < 0 || p.y >= workHeight())
3552                 return false;
3553         return true;
3554 }
3555
3556
3557 int BufferView::horizScrollOffset() const
3558 {
3559         return d->horiz_scroll_offset_;
3560 }
3561
3562
3563 int BufferView::horizScrollOffset(Text const * text,
3564                                   pit_type pit, pos_type pos) const
3565 {
3566         // Is this a row that is currently scrolled?
3567         if (!d->current_row_slice_.empty()
3568             && &text->inset() == d->current_row_slice_.inset().asInsetText()
3569             && pit ==  d->current_row_slice_.pit()
3570             && pos ==  d->current_row_slice_.pos())
3571                 return d->horiz_scroll_offset_;
3572         return 0;
3573 }
3574
3575
3576 void BufferView::setCurrentRowSlice(CursorSlice const & rowSlice)
3577 {
3578         // nothing to do if the cursor was already on this row
3579         if (d->current_row_slice_ == rowSlice)
3580                 return;
3581
3582         // if the (previous) current row was scrolled, we have to
3583         // remember it in order to repaint it next time.
3584         if (d->horiz_scroll_offset_ != 0) {
3585                 // search the old row in cache and mark it changed
3586                 for (auto & tm_pair : d->text_metrics_) {
3587                         if (&tm_pair.first->inset() == rowSlice.inset().asInsetText()) {
3588                                 tm_pair.second.setRowChanged(rowSlice.pit(), rowSlice.pos());
3589                                 // We found it, no need to continue.
3590                                 break;
3591                         }
3592                 }
3593         }
3594
3595         // Since we changed row, the scroll offset is not valid anymore
3596         d->horiz_scroll_offset_ = 0;
3597         d->current_row_slice_ = rowSlice;
3598 }
3599
3600
3601 void BufferView::checkCursorScrollOffset()
3602 {
3603         CursorSlice rowSlice = d->cursor_.bottom();
3604         TextMetrics const & tm = textMetrics(rowSlice.text());
3605
3606         // Stop if metrics have not been computed yet, since it means
3607         // that there is nothing to do.
3608         if (!tm.contains(rowSlice.pit()))
3609                 return;
3610         ParagraphMetrics const & pm = tm.parMetrics(rowSlice.pit());
3611         Row const & row = pm.getRow(rowSlice.pos(),
3612                                     d->cursor_.boundary() && rowSlice == d->cursor_.top());
3613         rowSlice.pos() = row.pos();
3614
3615         // Set the row on which the cursor lives.
3616         setCurrentRowSlice(rowSlice);
3617
3618         // Current x position of the cursor in pixels
3619         int cur_x = getPos(d->cursor_).x;
3620
3621         // Horizontal scroll offset of the cursor row in pixels
3622         int offset = d->horiz_scroll_offset_;
3623         int const MARGIN = 2 * theFontMetrics(d->cursor_.real_current_font).em()
3624                            + row.right_margin;
3625         if (row.right_x() <= workWidth() - row.right_margin) {
3626                 // Row is narrower than the work area, no offset needed.
3627                 offset = 0;
3628         } else {
3629                 if (cur_x - offset < MARGIN) {
3630                         // cursor would be too far right
3631                         offset = cur_x - MARGIN;
3632                 } else if (cur_x - offset > workWidth() - MARGIN) {
3633                         // cursor would be too far left
3634                         offset = cur_x - workWidth() + MARGIN;
3635                 }
3636                 // Correct the offset to make sure that we do not scroll too much
3637                 if (offset < 0)
3638                         offset = 0;
3639                 if (row.right_x() - offset < workWidth() - row.right_margin)
3640                         offset = row.right_x() - workWidth() + row.right_margin;
3641         }
3642
3643         //lyxerr << "cur_x=" << cur_x << ", offset=" << offset << ", row.wid=" << row.width() << ", margin=" << MARGIN << endl;
3644
3645         if (offset != d->horiz_scroll_offset_) {
3646                 LYXERR(Debug::PAINTING, "Horiz. scroll offset changed from "
3647                        << d->horiz_scroll_offset_ << " to " << offset);
3648                 row.changed(true);
3649                 if (d->update_strategy_ == NoScreenUpdate)
3650                         d->update_strategy_ = SingleParUpdate;
3651         }
3652
3653         d->horiz_scroll_offset_ = offset;
3654 }
3655
3656
3657 bool BufferView::busy() const
3658 {
3659         return buffer().undo().activeUndoGroup();
3660 }
3661
3662
3663 void BufferView::draw(frontend::Painter & pain, bool paint_caret)
3664 {
3665         if (!ready())
3666                 return;
3667         LYXERR(Debug::PAINTING, (pain.isNull() ? "\t\t--- START NODRAW ---"
3668                                  : "\t\t*** START DRAWING ***"));
3669         Text & text = buffer_.text();
3670         TextMetrics const & tm = d->text_metrics_[&text];
3671         int const y = tm.first().second->position();
3672         PainterInfo pi(this, pain);
3673
3674         // Check whether the row where the cursor lives needs to be scrolled.
3675         // Update the drawing strategy if needed.
3676         checkCursorScrollOffset();
3677
3678         switch (d->update_strategy_) {
3679
3680         case NoScreenUpdate:
3681                 // no screen painting is actually needed. In nodraw stage
3682                 // however, the different coordinates of insets and paragraphs
3683                 // needs to be updated.
3684                 LYXERR(Debug::PAINTING, "Strategy: NoScreenUpdate");
3685                 if (pain.isNull()) {
3686                         pi.full_repaint = true;
3687                         tm.draw(pi, 0, y);
3688                 } else {
3689                         pi.full_repaint = false;
3690                         tm.draw(pi, 0, y);
3691                 }
3692                 break;
3693
3694         case SingleParUpdate:
3695                 pi.full_repaint = false;
3696                 LYXERR(Debug::PAINTING, "Strategy: SingleParUpdate");
3697                 // In general, only the current row of the outermost paragraph
3698                 // will be redrawn. Particular cases where selection spans
3699                 // multiple paragraph are correctly detected in TextMetrics.
3700                 tm.draw(pi, 0, y);
3701                 break;
3702
3703         case DecorationUpdate:
3704                 // FIXME: We should also distinguish DecorationUpdate to avoid text
3705                 // drawing if possible. This is not possible to do easily right now
3706                 // because of the single backing pixmap.
3707
3708         case FullScreenUpdate:
3709
3710                 LYXERR(Debug::PAINTING,
3711                        ((d->update_strategy_ == FullScreenUpdate)
3712                         ? "Strategy: FullScreenUpdate"
3713                         : "Strategy: DecorationUpdate"));
3714
3715                 // The whole screen, including insets, will be refreshed.
3716                 pi.full_repaint = true;
3717
3718                 // Clear background.
3719                 pain.fillRectangle(0, 0, width_, height_,
3720                         pi.backgroundColor(&buffer_.inset()));
3721
3722                 // Draw everything.
3723                 tm.draw(pi, 0, y);
3724
3725                 break;
3726         }
3727
3728         // Possibly grey out below
3729         if (d->update_strategy_ != NoScreenUpdate) {
3730                 pair<pit_type, ParagraphMetrics const *> lastpm = tm.last();
3731                 int const y2 = lastpm.second->bottom();
3732
3733                 if (y2 < height_) {
3734                         Color color = buffer().isInternal()
3735                                 ? Color_background : Color_bottomarea;
3736                         pain.fillRectangle(0, y2, width_, height_ - y2, color);
3737                 }
3738         }
3739
3740         LYXERR(Debug::PAINTING, (pain.isNull() ? "\t\t --- END NODRAW ---"
3741                                 : "\t\t *** END DRAWING ***"));
3742
3743         // The scrollbar needs an update.
3744         // FIXME: does it always? see ticket #11947.
3745         updateScrollbarParameters();
3746
3747         // Normalize anchor for next time (in case updateMetrics did not do it yet)
3748         // FIXME: is this useful?
3749         pair<pit_type, ParagraphMetrics const *> firstpm = tm.first();
3750         pair<pit_type, ParagraphMetrics const *> lastpm = tm.last();
3751         for (pit_type pit = firstpm.first; pit <= lastpm.first; ++pit) {
3752                 ParagraphMetrics const & pm = tm.parMetrics(pit);
3753                 if (pm.bottom() > 0) {
3754                         if (d->anchor_pit_ != pit
3755                             || d->anchor_ypos_ != pm.position())
3756                                 LYXERR0(__func__ << ": Found new anchor pit = " << pit
3757                                                 << "  anchor ypos = " << pm.position()
3758                                                 << " (was " << d->anchor_pit_ << ", " << d->anchor_ypos_ << ")"
3759                                                    "\nIf you see this message, please report.");
3760                         d->anchor_pit_ = pit;
3761                         d->anchor_ypos_ = pm.position();
3762                         break;
3763                 }
3764         }
3765
3766         if (!pain.isNull()) {
3767                 // reset the update flags, everything has been done
3768                 d->update_flags_ = Update::None;
3769         }
3770
3771         // If a caret has to be painted, mark its text row as dirty to
3772         //make sure that it will be repainted on next redraw.
3773         /* FIXME: investigate whether this can be avoided when the cursor did not
3774          * move at all
3775          */
3776         if (paint_caret) {
3777                 Cursor cur(d->cursor_);
3778                 while (cur.depth() > 1) {
3779                         if (!cur.inTexted())
3780                                 break;
3781                         TextMetrics const & tm = textMetrics(cur.text());
3782                         if (d->caret_geometry_.left >= tm.origin().x
3783                                 && d->caret_geometry_.right <= tm.origin().x + tm.dim().width())
3784                                 break;
3785                         cur.pop();
3786                 }
3787                 cur.textRow().changed(true);
3788         }
3789 }
3790
3791
3792 void BufferView::message(docstring const & msg)
3793 {
3794         if (d->gui_)
3795                 d->gui_->message(msg);
3796 }
3797
3798
3799 void BufferView::showDialog(string const & name)
3800 {
3801         if (d->gui_)
3802                 d->gui_->showDialog(name, string());
3803 }
3804
3805
3806 void BufferView::showDialog(string const & name,
3807         string const & data, Inset * inset)
3808 {
3809         if (d->gui_)
3810                 d->gui_->showDialog(name, data, inset);
3811 }
3812
3813
3814 void BufferView::updateDialog(string const & name, string const & data)
3815 {
3816         if (d->gui_)
3817                 d->gui_->updateDialog(name, data);
3818 }
3819
3820
3821 void BufferView::setGuiDelegate(frontend::GuiBufferViewDelegate * gui)
3822 {
3823         d->gui_ = gui;
3824 }
3825
3826
3827 // FIXME: Move this out of BufferView again
3828 docstring BufferView::contentsOfPlaintextFile(FileName const & fname)
3829 {
3830         if (!fname.isReadableFile()) {
3831                 docstring const error = from_ascii(strerror(errno));
3832                 docstring const file = makeDisplayPath(fname.absFileName(), 50);
3833                 docstring const text =
3834                   bformat(_("Could not read the specified document\n"
3835                             "%1$s\ndue to the error: %2$s"), file, error);
3836                 Alert::error(_("Could not read file"), text);
3837                 return docstring();
3838         }
3839
3840         if (!fname.isReadableFile()) {
3841                 docstring const file = makeDisplayPath(fname.absFileName(), 50);
3842                 docstring const text =
3843                   bformat(_("%1$s\n is not readable."), file);
3844                 Alert::error(_("Could not open file"), text);
3845                 return docstring();
3846         }
3847
3848         // FIXME UNICODE: We don't know the encoding of the file
3849         docstring file_content = fname.fileContents("UTF-8");
3850         if (file_content.empty()) {
3851                 Alert::error(_("Reading not UTF-8 encoded file"),
3852                              _("The file is not UTF-8 encoded.\n"
3853                                "It will be read as local 8Bit-encoded.\n"
3854                                "If this does not give the correct result\n"
3855                                "then please change the encoding of the file\n"
3856                                "to UTF-8 with a program other than LyX.\n"));
3857                 file_content = fname.fileContents("local8bit");
3858         }
3859
3860         return normalize_c(file_content);
3861 }
3862
3863
3864 void BufferView::insertPlaintextFile(FileName const & f, bool asParagraph)
3865 {
3866         docstring const tmpstr = contentsOfPlaintextFile(f);
3867
3868         if (tmpstr.empty())
3869                 return;
3870
3871         Cursor & cur = cursor();
3872         cap::replaceSelection(cur);
3873         buffer_.undo().recordUndo(cur);
3874         if (asParagraph)
3875                 cur.innerText()->insertStringAsParagraphs(cur, tmpstr, cur.current_font);
3876         else
3877                 cur.innerText()->insertStringAsLines(cur, tmpstr, cur.current_font);
3878
3879         buffer_.changed(true);
3880 }
3881
3882
3883 docstring const & BufferView::inlineCompletion() const
3884 {
3885         return d->inlineCompletion_;
3886 }
3887
3888
3889 size_t BufferView::inlineCompletionUniqueChars() const
3890 {
3891         return d->inlineCompletionUniqueChars_;
3892 }
3893
3894
3895 DocIterator const & BufferView::inlineCompletionPos() const
3896 {
3897         return d->inlineCompletionPos_;
3898 }
3899
3900
3901 void BufferView::resetInlineCompletionPos()
3902 {
3903         d->inlineCompletionPos_ = DocIterator();
3904 }
3905
3906
3907 bool samePar(DocIterator const & a, DocIterator const & b)
3908 {
3909         if (a.empty() && b.empty())
3910                 return true;
3911         if (a.empty() || b.empty())
3912                 return false;
3913         if (a.depth() != b.depth())
3914                 return false;
3915         return &a.innerParagraph() == &b.innerParagraph();
3916 }
3917
3918
3919 void BufferView::setInlineCompletion(Cursor const & cur, DocIterator const & pos,
3920         docstring const & completion, size_t uniqueChars)
3921 {
3922         uniqueChars = min(completion.size(), uniqueChars);
3923         bool changed = d->inlineCompletion_ != completion
3924                 || d->inlineCompletionUniqueChars_ != uniqueChars;
3925         bool singlePar = true;
3926         d->inlineCompletion_ = completion;
3927         d->inlineCompletionUniqueChars_ = min(completion.size(), uniqueChars);
3928
3929         //lyxerr << "setInlineCompletion pos=" << pos << " completion=" << completion << " uniqueChars=" << uniqueChars << std::endl;
3930
3931         // at new position?
3932         DocIterator const & old = d->inlineCompletionPos_;
3933         if (old != pos) {
3934                 //lyxerr << "inlineCompletionPos changed" << std::endl;
3935                 // old or pos are in another paragraph?
3936                 if ((!samePar(cur, pos) && !pos.empty())
3937                     || (!samePar(cur, old) && !old.empty())) {
3938                         singlePar = false;
3939                         //lyxerr << "different paragraph" << std::endl;
3940                 }
3941                 d->inlineCompletionPos_ = pos;
3942         }
3943
3944         // set update flags
3945         if (changed) {
3946                 if (singlePar && !(cur.result().screenUpdate() & Update::Force))
3947                         cur.screenUpdateFlags(cur.result().screenUpdate() | Update::SinglePar);
3948                 else
3949                         cur.screenUpdateFlags(cur.result().screenUpdate() | Update::Force);
3950         }
3951 }
3952
3953
3954 bool BufferView::clickableInset() const
3955 {
3956         return d->clickable_inset_;
3957 }
3958
3959
3960 bool BufferView::stats_update_trigger()
3961 {
3962         if (d->stats_update_trigger_) {
3963                 d->stats_update_trigger_ = false;
3964                 return true;
3965         }
3966         return false;
3967 }
3968
3969 } // namespace lyx