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