3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Alfredo Braunstein
7 * \author Lars Gullik Bjønnes
10 * \author Jürgen Vigna
12 * Full author contact details are available in file CREDITS.
17 #include "BufferView.h"
19 #include "BranchList.h"
21 #include "buffer_funcs.h"
22 #include "BufferList.h"
23 #include "BufferParams.h"
24 #include "CoordCache.h"
26 #include "CutAndPaste.h"
27 #include "DispatchResult.h"
28 #include "ErrorList.h"
30 #include "FloatList.h"
31 #include "FuncRequest.h"
32 #include "FuncStatus.h"
34 #include "InsetIterator.h"
36 #include "LaTeXFeatures.h"
42 #include "MetricsInfo.h"
43 #include "Paragraph.h"
44 #include "paragraph_funcs.h"
45 #include "ParagraphParameters.h"
46 #include "ParIterator.h"
49 #include "TextClass.h"
50 #include "TextMetrics.h"
52 #include "TocBackend.h"
54 #include "WordLangTuple.h"
56 #include "insets/InsetBibtex.h"
57 #include "insets/InsetCommand.h" // ChangeRefs
58 #include "insets/InsetExternal.h"
59 #include "insets/InsetGraphics.h"
60 #include "insets/InsetRef.h"
61 #include "insets/InsetText.h"
62 #include "insets/InsetNote.h"
64 #include "frontends/alert.h"
65 #include "frontends/Application.h"
66 #include "frontends/Delegates.h"
67 #include "frontends/FontMetrics.h"
68 #include "frontends/Painter.h"
69 #include "frontends/Selection.h"
71 #include "graphics/Previews.h"
73 #include "support/convert.h"
74 #include "support/debug.h"
75 #include "support/ExceptionMessage.h"
76 #include "support/filetools.h"
77 #include "support/gettext.h"
78 #include "support/lstrings.h"
79 #include "support/Package.h"
80 #include "support/types.h"
89 using namespace lyx::support
;
93 namespace Alert
= frontend::Alert
;
97 /// Return an inset of this class if it exists at the current cursor position
99 T
* getInsetByCode(Cursor
const & cur
, InsetCode code
)
101 DocIterator it
= cur
;
102 Inset
* inset
= it
.nextInset();
103 if (inset
&& inset
->lyxCode() == code
)
104 return static_cast<T
*>(inset
);
109 bool findInset(DocIterator
& dit
, vector
<InsetCode
> const & codes
,
112 bool findNextInset(DocIterator
& dit
, vector
<InsetCode
> const & codes
,
113 docstring
const & contents
)
115 DocIterator tmpdit
= dit
;
118 Inset
const * inset
= tmpdit
.nextInset();
120 && std::find(codes
.begin(), codes
.end(), inset
->lyxCode()) != codes
.end()
121 && (contents
.empty() ||
122 static_cast<InsetCommand
const *>(inset
)->getFirstNonOptParam() == contents
)) {
126 tmpdit
.forwardInset();
133 /// Looks for next inset with one of the given codes.
134 bool findInset(DocIterator
& dit
, vector
<InsetCode
> const & codes
,
138 DocIterator tmpdit
= dit
;
139 tmpdit
.forwardInset();
144 Inset
const * inset
= tmpdit
.nextInset();
146 && std::find(codes
.begin(), codes
.end(), inset
->lyxCode()) != codes
.end()) {
147 contents
= static_cast<InsetCommand
const *>(inset
)->getFirstNonOptParam();
151 if (!findNextInset(tmpdit
, codes
, contents
)) {
152 if (dit
.depth() != 1 || dit
.pit() != 0 || dit
.pos() != 0) {
153 Inset
* inset
= &tmpdit
.bottom().inset();
154 tmpdit
= doc_iterator_begin(&inset
->buffer(), inset
);
155 if (!findNextInset(tmpdit
, codes
, contents
))
167 /// Looks for next inset with the given code
168 void findInset(DocIterator
& dit
, InsetCode code
, bool same_content
)
170 findInset(dit
, vector
<InsetCode
>(1, code
), same_content
);
174 /// Moves cursor to the next inset with one of the given codes.
175 void gotoInset(BufferView
* bv
, vector
<InsetCode
> const & codes
,
178 Cursor tmpcur
= bv
->cursor();
179 if (!findInset(tmpcur
, codes
, same_content
)) {
180 bv
->cursor().message(_("No more insets"));
184 tmpcur
.clearSelection();
185 bv
->setCursor(tmpcur
);
190 /// Moves cursor to the next inset with given code.
191 void gotoInset(BufferView
* bv
, InsetCode code
, bool same_content
)
193 gotoInset(bv
, vector
<InsetCode
>(1, code
), same_content
);
197 /// A map from a Text to the associated text metrics
198 typedef map
<Text
const *, TextMetrics
> TextMetricsCache
;
200 enum ScreenUpdateStrategy
{
210 /////////////////////////////////////////////////////////////////////
214 /////////////////////////////////////////////////////////////////////
216 struct BufferView::Private
218 Private(BufferView
& bv
): wh_(0), cursor_(bv
),
219 anchor_pit_(0), anchor_ypos_(0),
220 inlineCompletionUniqueChars_(0),
221 last_inset_(0), bookmark_edit_position_(0), gui_(0)
225 ScrollbarParameters scrollbarParameters_
;
227 ScreenUpdateStrategy update_strategy_
;
229 CoordCache coord_cache_
;
231 /// Estimated average par height for scrollbar.
233 /// this is used to handle XSelection events in the right manner.
242 pit_type anchor_pit_
;
246 vector
<int> par_height_
;
249 DocIterator inlineCompletionPos_
;
251 docstring inlineCompletion_
;
253 size_t inlineCompletionUniqueChars_
;
255 /// keyboard mapping object.
258 /// last visited inset.
259 /** kept to send setMouseHover(false).
260 * Not owned, so don't delete.
264 // cache for id of the paragraph which was edited the last time
265 int bookmark_edit_position_
;
267 mutable TextMetricsCache text_metrics_
;
270 /** Not owned, so don't delete.
272 frontend::GuiBufferViewDelegate
* gui_
;
274 /// Cache for Find Next
275 FuncRequest search_request_cache_
;
279 BufferView::BufferView(Buffer
& buf
)
280 : width_(0), height_(0), full_screen_(false), buffer_(buf
),
281 d(new Private(*this))
283 d
->xsel_cache_
.set
= false;
284 d
->intl_
.initKeyMapper(lyxrc
.use_kbmap
);
286 d
->cursor_
.setBuffer(&buf
);
287 d
->cursor_
.push(buffer_
.inset());
288 d
->cursor_
.resetAnchor();
289 d
->cursor_
.setCurrentFont();
291 if (graphics::Previews::status() != LyXRC::PREVIEW_OFF
)
292 thePreviews().generateBufferPreviews(buffer_
);
296 BufferView::~BufferView()
298 // current buffer is going to be switched-off, save cursor pos
299 // Ideally, the whole cursor stack should be saved, but session
300 // currently can only handle bottom (whole document) level pit and pos.
301 // That is to say, if a cursor is in a nested inset, it will be
302 // restore to the left of the top level inset.
303 LastFilePosSection::FilePos fp
;
304 fp
.pit
= d
->cursor_
.bottom().pit();
305 fp
.pos
= d
->cursor_
.bottom().pos();
306 theSession().lastFilePos().save(buffer_
.fileName(), fp
);
312 int BufferView::rightMargin() const
314 // The additional test for the case the outliner is opened.
316 !lyxrc
.full_screen_limit
||
317 width_
< lyxrc
.full_screen_width
+ 20)
320 return (width_
- lyxrc
.full_screen_width
) / 2;
324 int BufferView::leftMargin() const
326 return rightMargin();
330 bool BufferView::isTopScreen() const
332 return d
->scrollbarParameters_
.position
== d
->scrollbarParameters_
.min
;
336 bool BufferView::isBottomScreen() const
338 return d
->scrollbarParameters_
.position
== d
->scrollbarParameters_
.max
;
342 Intl
& BufferView::getIntl()
348 Intl
const & BufferView::getIntl() const
354 CoordCache
& BufferView::coordCache()
356 return d
->coord_cache_
;
360 CoordCache
const & BufferView::coordCache() const
362 return d
->coord_cache_
;
366 Buffer
& BufferView::buffer()
372 Buffer
const & BufferView::buffer() const
378 bool BufferView::fitCursor()
380 if (cursorStatus(d
->cursor_
) == CUR_INSIDE
) {
381 frontend::FontMetrics
const & fm
=
382 theFontMetrics(d
->cursor_
.getFont().fontInfo());
383 int const asc
= fm
.maxAscent();
384 int const des
= fm
.maxDescent();
385 Point
const p
= getPos(d
->cursor_
, d
->cursor_
.boundary());
386 if (p
.y_
- asc
>= 0 && p
.y_
+ des
< height_
)
393 void BufferView::processUpdateFlags(Update::flags flags
)
395 // last_inset_ points to the last visited inset. This pointer may become
396 // invalid because of keyboard editing. Since all such operations
397 // causes screen update(), I reset last_inset_ to avoid such a problem.
399 // This is close to a hot-path.
400 LYXERR(Debug::DEBUG
, "BufferView::processUpdateFlags()"
401 << "[fitcursor = " << (flags
& Update::FitCursor
)
402 << ", forceupdate = " << (flags
& Update::Force
)
403 << ", singlepar = " << (flags
& Update::SinglePar
)
404 << "] buffer: " << &buffer_
);
406 buffer_
.updateMacros();
408 // Now do the first drawing step if needed. This consists on updating
409 // the CoordCache in updateMetrics().
410 // The second drawing step is done in WorkArea::redraw() if needed.
412 // Case when no explicit update is requested.
414 // no need to redraw anything.
415 d
->update_strategy_
= NoScreenUpdate
;
419 if (flags
== Update::Decoration
) {
420 d
->update_strategy_
= DecorationUpdate
;
425 if (flags
== Update::FitCursor
426 || flags
== (Update::Decoration
| Update::FitCursor
)) {
427 // tell the frontend to update the screen if needed.
432 if (flags
& Update::Decoration
) {
433 d
->update_strategy_
= DecorationUpdate
;
437 // no screen update is needed.
438 d
->update_strategy_
= NoScreenUpdate
;
442 bool const full_metrics
= flags
& Update::Force
|| !singleParUpdate();
445 // We have to update the full screen metrics.
448 if (!(flags
& Update::FitCursor
)) {
449 // Nothing to do anymore. Trigger a redraw and return
454 // updateMetrics() does not update paragraph position
455 // This is done at draw() time. So we need a redraw!
459 // The cursor is off screen so ensure it is visible.
466 void BufferView::updateScrollbar()
468 if (height_
== 0 && width_
== 0)
471 // We prefer fixed size line scrolling.
472 d
->scrollbarParameters_
.single_step
= defaultRowHeight();
473 // We prefer full screen page scrolling.
474 d
->scrollbarParameters_
.page_step
= height_
;
476 Text
& t
= buffer_
.text();
477 TextMetrics
& tm
= d
->text_metrics_
[&t
];
479 LYXERR(Debug::GUI
, " Updating scrollbar: height: "
480 << t
.paragraphs().size()
481 << " curr par: " << d
->cursor_
.bottom().pit()
482 << " default height " << defaultRowHeight());
484 size_t const parsize
= t
.paragraphs().size();
485 if (d
->par_height_
.size() != parsize
) {
486 d
->par_height_
.clear();
487 // FIXME: We assume a default paragraph height of 2 rows. This
488 // should probably be pondered with the screen width.
489 d
->par_height_
.resize(parsize
, defaultRowHeight() * 2);
492 // Look at paragraph heights on-screen
493 pair
<pit_type
, ParagraphMetrics
const *> first
= tm
.first();
494 pair
<pit_type
, ParagraphMetrics
const *> last
= tm
.last();
495 for (pit_type pit
= first
.first
; pit
<= last
.first
; ++pit
) {
496 d
->par_height_
[pit
] = tm
.parMetrics(pit
).height();
497 LYXERR(Debug::SCROLLING
, "storing height for pit " << pit
<< " : "
498 << d
->par_height_
[pit
]);
501 int top_pos
= first
.second
->position() - first
.second
->ascent();
502 int bottom_pos
= last
.second
->position() + last
.second
->descent();
503 bool first_visible
= first
.first
== 0 && top_pos
>= 0;
504 bool last_visible
= last
.first
+ 1 == int(parsize
) && bottom_pos
<= height_
;
505 if (first_visible
&& last_visible
) {
506 d
->scrollbarParameters_
.min
= 0;
507 d
->scrollbarParameters_
.max
= 0;
511 d
->scrollbarParameters_
.min
= top_pos
;
512 for (size_t i
= 0; i
!= size_t(first
.first
); ++i
)
513 d
->scrollbarParameters_
.min
-= d
->par_height_
[i
];
514 d
->scrollbarParameters_
.max
= bottom_pos
;
515 for (size_t i
= last
.first
+ 1; i
!= parsize
; ++i
)
516 d
->scrollbarParameters_
.max
+= d
->par_height_
[i
];
518 d
->scrollbarParameters_
.position
= 0;
519 // The reference is the top position so we remove one page.
520 if (lyxrc
.scroll_below_document
)
521 d
->scrollbarParameters_
.max
-= minVisiblePart();
523 d
->scrollbarParameters_
.max
-= d
->scrollbarParameters_
.page_step
;
527 ScrollbarParameters
const & BufferView::scrollbarParameters() const
529 return d
->scrollbarParameters_
;
533 docstring
BufferView::toolTip(int x
, int y
) const
535 // Get inset under mouse, if there is one.
536 Inset
const * covering_inset
= getCoveringInset(buffer_
.text(), x
, y
);
538 // No inset, no tooltip...
540 return covering_inset
->toolTip(*this, x
, y
);
544 docstring
BufferView::contextMenu(int x
, int y
) const
546 //If there is a selection, return the containing inset menu
547 if (d
->cursor_
.selection())
548 return d
->cursor_
.inset().contextMenu(*this, x
, y
);
550 // Get inset under mouse, if there is one.
551 Inset
const * covering_inset
= getCoveringInset(buffer_
.text(), x
, y
);
553 return covering_inset
->contextMenu(*this, x
, y
);
555 return buffer_
.inset().contextMenu(*this, x
, y
);
559 void BufferView::scrollDocView(int value
)
561 int const offset
= value
- d
->scrollbarParameters_
.position
;
563 // No scrolling at all? No need to redraw anything
567 // If the offset is less than 2 screen height, prefer to scroll instead.
568 if (abs(offset
) <= 2 * height_
) {
569 d
->anchor_ypos_
-= offset
;
575 // cut off at the top
576 if (value
<= d
->scrollbarParameters_
.min
) {
577 DocIterator dit
= doc_iterator_begin(&buffer_
);
579 LYXERR(Debug::SCROLLING
, "scroll to top");
583 // cut off at the bottom
584 if (value
>= d
->scrollbarParameters_
.max
) {
585 DocIterator dit
= doc_iterator_end(&buffer_
);
588 LYXERR(Debug::SCROLLING
, "scroll to bottom");
592 // find paragraph at target position
593 int par_pos
= d
->scrollbarParameters_
.min
;
595 for (; i
!= int(d
->par_height_
.size()); ++i
) {
596 par_pos
+= d
->par_height_
[i
];
597 if (par_pos
>= value
)
601 if (par_pos
< value
) {
602 // It seems we didn't find the correct pit so stay on the safe side and
604 LYXERR0("scrolling position not found!");
605 scrollDocView(d
->scrollbarParameters_
.max
);
609 DocIterator dit
= doc_iterator_begin(&buffer_
);
611 LYXERR(Debug::SCROLLING
, "value = " << value
<< " -> scroll to pit " << i
);
616 // FIXME: this method is not working well.
617 void BufferView::setCursorFromScrollbar()
619 TextMetrics
& tm
= d
->text_metrics_
[&buffer_
.text()];
621 int const height
= 2 * defaultRowHeight();
622 int const first
= height
;
623 int const last
= height_
- height
;
625 Cursor
const & oldcur
= d
->cursor_
;
627 switch (cursorStatus(oldcur
)) {
635 int const y
= getPos(oldcur
, oldcur
.boundary()).y_
;
636 newy
= min(last
, max(y
, first
));
640 // We reset the cursor because cursorStatus() does not
641 // work when the cursor is within mathed.
643 cur
.reset(buffer_
.inset());
644 tm
.setCursorFromCoordinates(cur
, 0, newy
);
646 // update the bufferview cursor and notify insets
647 // FIXME: Care about the d->cursor_ flags to redraw if needed
648 Cursor old
= d
->cursor_
;
650 bool badcursor
= notifyCursorLeavesOrEnters(old
, d
->cursor_
);
652 d
->cursor_
.fixIfBroken();
656 Change
const BufferView::getCurrentChange() const
658 if (!d
->cursor_
.selection())
659 return Change(Change::UNCHANGED
);
661 DocIterator dit
= d
->cursor_
.selectionBegin();
662 return dit
.paragraph().lookupChange(dit
.pos());
666 // this could be used elsewhere as well?
667 // FIXME: This does not work within mathed!
668 CursorStatus
BufferView::cursorStatus(DocIterator
const & dit
) const
670 Point
const p
= getPos(dit
, dit
.boundary());
673 if (p
.y_
> workHeight())
679 void BufferView::bookmarkEditPosition()
681 // Don't eat cpu time for each keystroke
682 if (d
->cursor_
.paragraph().id() == d
->bookmark_edit_position_
)
685 d
->bookmark_edit_position_
= d
->cursor_
.paragraph().id();
689 void BufferView::saveBookmark(unsigned int idx
)
691 // tentatively save bookmark, id and pos will be used to
692 // acturately locate a bookmark in a 'live' lyx session.
693 // pit and pos will be updated with bottom level pit/pos
695 theSession().bookmarks().save(
697 d
->cursor_
.bottom().pit(),
698 d
->cursor_
.bottom().pos(),
699 d
->cursor_
.paragraph().id(),
704 // emit message signal.
705 message(_("Save bookmark"));
709 bool BufferView::moveToPosition(pit_type bottom_pit
, pos_type bottom_pos
,
710 int top_id
, pos_type top_pos
)
712 bool success
= false;
715 d
->cursor_
.clearSelection();
717 // if a valid par_id is given, try it first
718 // This is the case for a 'live' bookmark when unique paragraph ID
719 // is used to track bookmarks.
721 dit
= buffer_
.getParFromID(top_id
);
723 dit
.pos() = min(dit
.paragraph().size(), top_pos
);
724 // Some slices of the iterator may not be
725 // reachable (e.g. closed collapsable inset)
726 // so the dociterator may need to be
727 // shortened. Otherwise, setCursor may crash
728 // lyx when the cursor can not be set to these
730 size_t const n
= dit
.depth();
731 for (size_t i
= 0; i
< n
; ++i
)
732 if (!dit
[i
].inset().editable()) {
740 // if top_id == 0, or searching through top_id failed
741 // This is the case for a 'restored' bookmark when only bottom
742 // (document level) pit was saved. Because of this, bookmark
743 // restoration is inaccurate. If a bookmark was within an inset,
744 // it will be restored to the left of the outmost inset that contains
746 if (bottom_pit
< int(buffer_
.paragraphs().size())) {
747 dit
= doc_iterator_begin(&buffer_
);
749 dit
.pit() = bottom_pit
;
750 dit
.pos() = min(bottom_pos
, dit
.paragraph().size());
755 // Note: only bottom (document) level pit is set.
757 // set the current font.
758 d
->cursor_
.setCurrentFont();
759 // To center the screen on this new position we need the
760 // paragraph position which is computed at draw() time.
761 // So we need a redraw!
771 void BufferView::translateAndInsert(char_type c
, Text
* t
, Cursor
& cur
)
773 if (lyxrc
.rtl_support
) {
774 if (d
->cursor_
.real_current_font
.isRightToLeft()) {
775 if (d
->intl_
.keymap
== Intl::PRIMARY
)
776 d
->intl_
.keyMapSec();
778 if (d
->intl_
.keymap
== Intl::SECONDARY
)
779 d
->intl_
.keyMapPrim();
783 d
->intl_
.getTransManager().translateAndInsert(c
, t
, cur
);
787 int BufferView::workWidth() const
793 void BufferView::recenter()
795 showCursor(d
->cursor_
, true);
799 void BufferView::showCursor()
801 showCursor(d
->cursor_
, false);
805 void BufferView::showCursor(DocIterator
const & dit
, bool recenter
)
807 if (scrollToCursor(dit
, recenter
))
812 void BufferView::scrollToCursor()
814 scrollToCursor(d
->cursor_
, false);
818 bool BufferView::scrollToCursor(DocIterator
const & dit
, bool recenter
)
820 // We are not properly started yet, delay until resizing is
825 LYXERR(Debug::SCROLLING
, "recentering!");
827 CursorSlice
const & bot
= dit
.bottom();
828 TextMetrics
& tm
= d
->text_metrics_
[bot
.text()];
830 pos_type
const max_pit
= pos_type(bot
.text()->paragraphs().size() - 1);
831 int bot_pit
= bot
.pit();
832 if (bot_pit
> max_pit
) {
833 // FIXME: Why does this happen?
834 LYXERR0("bottom pit is greater that max pit: "
835 << bot_pit
<< " > " << max_pit
);
839 if (bot_pit
== tm
.first().first
- 1)
840 tm
.newParMetricsUp();
841 else if (bot_pit
== tm
.last().first
+ 1)
842 tm
.newParMetricsDown();
844 if (tm
.contains(bot_pit
)) {
845 ParagraphMetrics
const & pm
= tm
.parMetrics(bot_pit
);
846 LASSERT(!pm
.rows().empty(), /**/);
847 // FIXME: smooth scrolling doesn't work in mathed.
848 CursorSlice
const & cs
= dit
.innerTextSlice();
849 int offset
= coordOffset(dit
, dit
.boundary()).y_
;
850 int ypos
= pm
.position() + offset
;
851 Dimension
const & row_dim
=
852 pm
.getRow(cs
.pos(), dit
.boundary()).dimension();
855 scrolled
= scroll(ypos
- height_
/2);
856 else if (ypos
- row_dim
.ascent() < 0)
857 scrolled
= scrollUp(- ypos
+ row_dim
.ascent());
858 else if (ypos
+ row_dim
.descent() > height_
)
859 scrolled
= scrollDown(ypos
- height_
+ defaultRowHeight() );
861 // else, nothing to do, the cursor is already visible so we just return.
869 // fix inline completion position
870 if (d
->inlineCompletionPos_
.fixIfBroken())
871 d
->inlineCompletionPos_
= DocIterator();
873 tm
.redoParagraph(bot_pit
);
874 ParagraphMetrics
const & pm
= tm
.parMetrics(bot_pit
);
875 int offset
= coordOffset(dit
, dit
.boundary()).y_
;
877 d
->anchor_pit_
= bot_pit
;
878 CursorSlice
const & cs
= dit
.innerTextSlice();
879 Dimension
const & row_dim
=
880 pm
.getRow(cs
.pos(), dit
.boundary()).dimension();
883 d
->anchor_ypos_
= height_
/2;
884 else if (d
->anchor_pit_
== 0)
885 d
->anchor_ypos_
= offset
+ pm
.ascent();
886 else if (d
->anchor_pit_
== max_pit
)
887 d
->anchor_ypos_
= height_
- offset
- row_dim
.descent();
888 else if (offset
> height_
)
889 d
->anchor_ypos_
= height_
- offset
- defaultRowHeight();
891 d
->anchor_ypos_
= defaultRowHeight() * 2;
898 bool BufferView::getStatus(FuncRequest
const & cmd
, FuncStatus
& flag
)
900 Cursor
& cur
= d
->cursor_
;
902 switch (cmd
.action
) {
905 flag
.setEnabled(buffer_
.undo().hasUndoStack());
908 flag
.setEnabled(buffer_
.undo().hasRedoStack());
910 case LFUN_FILE_INSERT
:
911 case LFUN_FILE_INSERT_PLAINTEXT_PARA
:
912 case LFUN_FILE_INSERT_PLAINTEXT
:
913 case LFUN_BOOKMARK_SAVE
:
914 // FIXME: Actually, these LFUNS should be moved to Text
915 flag
.setEnabled(cur
.inTexted());
918 case LFUN_FONT_STATE
:
919 case LFUN_LABEL_INSERT
:
920 case LFUN_INFO_INSERT
:
921 case LFUN_INSET_EDIT
:
922 case LFUN_PARAGRAPH_GOTO
:
924 case LFUN_REFERENCE_NEXT
:
926 case LFUN_WORD_FINDADV
:
927 case LFUN_WORD_REPLACE
:
930 case LFUN_MARK_TOGGLE
:
931 case LFUN_SCREEN_RECENTER
:
932 case LFUN_SCREEN_SHOW_CURSOR
:
933 case LFUN_BIBTEX_DATABASE_ADD
:
934 case LFUN_BIBTEX_DATABASE_DEL
:
935 case LFUN_NOTES_MUTATE
:
936 case LFUN_ALL_INSETS_TOGGLE
:
937 case LFUN_STATISTICS
:
938 case LFUN_BRANCH_ADD_INSERT
:
939 flag
.setEnabled(true);
942 // @todo Test if current WorkArea is the search WorkArea
943 case LFUN_REGEXP_MODE
:
944 flag
.setEnabled(! this->cursor().inRegexped());
947 case LFUN_LABEL_COPY_AS_REF
: {
948 // if there is an inset at cursor, see whether it
950 Inset
* inset
= cur
.nextInset();
951 if (!inset
|| !inset
->getStatus(cur
, cmd
, flag
))
952 flag
.setEnabled(false);
956 case LFUN_NEXT_INSET_MODIFY
: {
957 // this is the real function we want to invoke
958 FuncRequest tmpcmd
= cmd
;
959 tmpcmd
.action
= LFUN_INSET_MODIFY
;
960 // if there is an inset at cursor, see whether it
961 // handles the lfun, other start from scratch
962 Inset
* inset
= cur
.nextInset();
963 if (!inset
|| !inset
->getStatus(cur
, tmpcmd
, flag
))
964 flag
= lyx::getStatus(tmpcmd
);
968 case LFUN_LABEL_GOTO
: {
969 flag
.setEnabled(!cmd
.argument().empty()
970 || getInsetByCode
<InsetRef
>(cur
, REF_CODE
));
974 case LFUN_CHANGES_TRACK
:
975 flag
.setEnabled(true);
976 flag
.setOnOff(buffer_
.params().trackChanges
);
979 case LFUN_CHANGES_OUTPUT
:
980 flag
.setEnabled(true);
981 flag
.setOnOff(buffer_
.params().outputChanges
);
984 case LFUN_CHANGES_MERGE
:
985 case LFUN_CHANGE_NEXT
:
986 case LFUN_CHANGE_PREVIOUS
:
987 case LFUN_ALL_CHANGES_ACCEPT
:
988 case LFUN_ALL_CHANGES_REJECT
:
989 // TODO: context-sensitive enabling of LFUNs
990 // In principle, these command should only be enabled if there
991 // is a change in the document. However, without proper
992 // optimizations, this will inevitably result in poor performance.
993 flag
.setEnabled(true);
996 case LFUN_BUFFER_TOGGLE_COMPRESSION
: {
997 flag
.setOnOff(buffer_
.params().compressed
);
1001 case LFUN_SCREEN_UP
:
1002 case LFUN_SCREEN_DOWN
:
1004 case LFUN_SCREEN_UP_SELECT
:
1005 case LFUN_SCREEN_DOWN_SELECT
:
1006 flag
.setEnabled(true);
1009 case LFUN_LAYOUT_TABULAR
:
1010 flag
.setEnabled(cur
.innerInsetOfType(TABULAR_CODE
));
1014 flag
.setEnabled(!cur
.inset().forcePlainLayout(cur
.idx()));
1017 case LFUN_LAYOUT_PARAGRAPH
:
1018 flag
.setEnabled(cur
.inset().allowParagraphCustomization(cur
.idx()));
1021 case LFUN_DIALOG_SHOW_NEW_INSET
:
1022 // FIXME: this is wrong, but I do not understand the
1024 if (cur
.inset().lyxCode() == CAPTION_CODE
)
1025 return cur
.inset().getStatus(cur
, cmd
, flag
);
1026 // FIXME we should consider passthru paragraphs too.
1027 flag
.setEnabled(!cur
.inset().getLayout().isPassThru());
1031 flag
.setEnabled(false);
1039 bool BufferView::dispatch(FuncRequest
const & cmd
)
1041 //lyxerr << [ cmd = " << cmd << "]" << endl;
1043 // Make sure that the cached BufferView is correct.
1044 LYXERR(Debug::ACTION
, " action[" << cmd
.action
<< ']'
1045 << " arg[" << to_utf8(cmd
.argument()) << ']'
1046 << " x[" << cmd
.x
<< ']'
1047 << " y[" << cmd
.y
<< ']'
1048 << " button[" << cmd
.button() << ']');
1050 Cursor
& cur
= d
->cursor_
;
1052 switch (cmd
.action
) {
1055 cur
.message(_("Undo"));
1056 cur
.clearSelection();
1057 if (!cur
.textUndo())
1058 cur
.message(_("No further undo information"));
1060 processUpdateFlags(Update::Force
| Update::FitCursor
);
1064 cur
.message(_("Redo"));
1065 cur
.clearSelection();
1066 if (!cur
.textRedo())
1067 cur
.message(_("No further redo information"));
1069 processUpdateFlags(Update::Force
| Update::FitCursor
);
1072 case LFUN_FONT_STATE
:
1073 cur
.message(cur
.currentState());
1076 case LFUN_BOOKMARK_SAVE
:
1077 saveBookmark(convert
<unsigned int>(to_utf8(cmd
.argument())));
1080 case LFUN_LABEL_GOTO
: {
1081 docstring label
= cmd
.argument();
1082 if (label
.empty()) {
1084 getInsetByCode
<InsetRef
>(cur
, REF_CODE
);
1086 label
= inset
->getParam("reference");
1087 // persistent=false: use temp_bookmark
1096 case LFUN_INSET_EDIT
: {
1097 FuncRequest
fr(cmd
);
1098 // if there is an inset at cursor, see whether it
1100 Inset
* inset
= cur
.nextInset();
1102 inset
->dispatch(cur
, fr
);
1103 // if it did not work, try the underlying inset.
1104 if (!inset
|| !cur
.result().dispatched())
1107 // FIXME I'm adding the last break to solve a crash,
1108 // but that is obviously not right.
1109 if (!cur
.result().dispatched())
1110 // It did not work too; no action needed.
1115 case LFUN_PARAGRAPH_GOTO
: {
1116 int const id
= convert
<int>(cmd
.getArg(0));
1117 int const pos
= convert
<int>(cmd
.getArg(1));
1119 for (Buffer
* b
= &buffer_
; i
== 0 || b
!= &buffer_
;
1120 b
= theBufferList().next(b
)) {
1122 DocIterator dit
= b
->getParFromID(id
);
1124 LYXERR(Debug::INFO
, "No matching paragraph found! [" << id
<< "].");
1128 LYXERR(Debug::INFO
, "Paragraph " << dit
.paragraph().id()
1129 << " found in buffer `"
1130 << b
->absFileName() << "'.");
1132 if (b
== &buffer_
) {
1136 processUpdateFlags(Update::Force
| Update::FitCursor
);
1138 // Switch to other buffer view and resend cmd
1139 theLyXFunc().dispatch(FuncRequest(
1140 LFUN_BUFFER_SWITCH
, b
->absFileName()));
1141 theLyXFunc().dispatch(cmd
);
1148 case LFUN_NOTE_NEXT
:
1149 gotoInset(this, NOTE_CODE
, false);
1152 case LFUN_REFERENCE_NEXT
: {
1153 vector
<InsetCode
> tmp
;
1154 tmp
.push_back(LABEL_CODE
);
1155 tmp
.push_back(REF_CODE
);
1156 gotoInset(this, tmp
, true);
1160 case LFUN_CHANGES_TRACK
:
1161 buffer_
.params().trackChanges
= !buffer_
.params().trackChanges
;
1164 case LFUN_CHANGES_OUTPUT
:
1165 buffer_
.params().outputChanges
= !buffer_
.params().outputChanges
;
1166 if (buffer_
.params().outputChanges
) {
1167 bool dvipost
= LaTeXFeatures::isAvailable("dvipost");
1168 bool xcolorulem
= LaTeXFeatures::isAvailable("ulem") &&
1169 LaTeXFeatures::isAvailable("xcolor");
1171 if (!dvipost
&& !xcolorulem
) {
1172 Alert::warning(_("Changes not shown in LaTeX output"),
1173 _("Changes will not be highlighted in LaTeX output, "
1174 "because neither dvipost nor xcolor/ulem are installed.\n"
1175 "Please install these packages or redefine "
1176 "\\lyxadded and \\lyxdeleted in the LaTeX preamble."));
1177 } else if (!xcolorulem
) {
1178 Alert::warning(_("Changes not shown in LaTeX output"),
1179 _("Changes will not be highlighted in LaTeX output "
1180 "when using pdflatex, because xcolor and ulem are not installed.\n"
1181 "Please install both packages or redefine "
1182 "\\lyxadded and \\lyxdeleted in the LaTeX preamble."));
1187 case LFUN_CHANGE_NEXT
:
1188 findNextChange(this);
1189 // FIXME: Move this LFUN to Buffer so that we don't have to do this:
1190 processUpdateFlags(Update::Force
| Update::FitCursor
);
1193 case LFUN_CHANGE_PREVIOUS
:
1194 findPreviousChange(this);
1195 // FIXME: Move this LFUN to Buffer so that we don't have to do this:
1196 processUpdateFlags(Update::Force
| Update::FitCursor
);
1199 case LFUN_CHANGES_MERGE
:
1200 if (findNextChange(this) || findPreviousChange(this)) {
1201 processUpdateFlags(Update::Force
| Update::FitCursor
);
1202 showDialog("changes");
1206 case LFUN_ALL_CHANGES_ACCEPT
:
1207 // select complete document
1208 cur
.reset(buffer_
.inset());
1209 cur
.selHandle(true);
1210 buffer_
.text().cursorBottom(cur
);
1211 // accept everything in a single step to support atomic undo
1212 buffer_
.text().acceptOrRejectChanges(cur
, Text::ACCEPT
);
1213 // FIXME: Move this LFUN to Buffer so that we don't have to do this:
1214 processUpdateFlags(Update::Force
| Update::FitCursor
);
1217 case LFUN_ALL_CHANGES_REJECT
:
1218 // select complete document
1219 cur
.reset(buffer_
.inset());
1220 cur
.selHandle(true);
1221 buffer_
.text().cursorBottom(cur
);
1222 // reject everything in a single step to support atomic undo
1223 // Note: reject does not work recursively; the user may have to repeat the operation
1224 buffer_
.text().acceptOrRejectChanges(cur
, Text::REJECT
);
1225 // FIXME: Move this LFUN to Buffer so that we don't have to do this:
1226 processUpdateFlags(Update::Force
| Update::FitCursor
);
1229 case LFUN_WORD_FIND
: {
1230 FuncRequest req
= cmd
;
1231 if (cmd
.argument().empty() && !d
->search_request_cache_
.argument().empty())
1232 req
= d
->search_request_cache_
;
1233 if (req
.argument().empty()) {
1234 theLyXFunc().dispatch(FuncRequest(LFUN_DIALOG_SHOW
, "findreplace"));
1237 if (find(this, req
))
1240 message(_("String not found!"));
1241 d
->search_request_cache_
= req
;
1245 case LFUN_WORD_REPLACE
: {
1246 bool has_deleted
= false;
1247 if (cur
.selection()) {
1248 DocIterator beg
= cur
.selectionBegin();
1249 DocIterator end
= cur
.selectionEnd();
1250 if (beg
.pit() == end
.pit()) {
1251 for (pos_type p
= beg
.pos() ; p
< end
.pos() ; ++p
) {
1252 if (cur
.paragraph().isDeleted(p
))
1257 replace(this, cmd
, has_deleted
);
1261 case LFUN_WORD_FINDADV
:
1266 cur
.clearSelection();
1267 cur
.message(from_utf8(N_("Mark off")));
1271 cur
.clearSelection();
1273 cur
.message(from_utf8(N_("Mark on")));
1276 case LFUN_MARK_TOGGLE
:
1277 cur
.setSelection(false);
1280 cur
.message(from_utf8(N_("Mark removed")));
1283 cur
.message(from_utf8(N_("Mark set")));
1288 case LFUN_SCREEN_SHOW_CURSOR
:
1292 case LFUN_SCREEN_RECENTER
:
1296 case LFUN_BIBTEX_DATABASE_ADD
: {
1297 Cursor tmpcur
= cur
;
1298 findInset(tmpcur
, BIBTEX_CODE
, false);
1299 InsetBibtex
* inset
= getInsetByCode
<InsetBibtex
>(tmpcur
,
1302 if (inset
->addDatabase(cmd
.argument()))
1303 buffer_
.updateBibfilesCache();
1308 case LFUN_BIBTEX_DATABASE_DEL
: {
1309 Cursor tmpcur
= cur
;
1310 findInset(tmpcur
, BIBTEX_CODE
, false);
1311 InsetBibtex
* inset
= getInsetByCode
<InsetBibtex
>(tmpcur
,
1314 if (inset
->delDatabase(cmd
.argument()))
1315 buffer_
.updateBibfilesCache();
1320 case LFUN_STATISTICS
: {
1321 DocIterator from
, to
;
1322 if (cur
.selection()) {
1323 from
= cur
.selectionBegin();
1324 to
= cur
.selectionEnd();
1326 from
= doc_iterator_begin(&buffer_
);
1327 to
= doc_iterator_end(&buffer_
);
1329 int const words
= countWords(from
, to
);
1330 int const chars
= countChars(from
, to
, false);
1331 int const chars_blanks
= countChars(from
, to
, true);
1333 if (cur
.selection())
1334 message
= _("Statistics for the selection:");
1336 message
= _("Statistics for the document:");
1339 message
+= bformat(_("%1$d words"), words
);
1341 message
+= _("One word");
1343 if (chars_blanks
!= 1)
1344 message
+= bformat(_("%1$d characters (including blanks)"),
1347 message
+= _("One character (including blanks)");
1350 message
+= bformat(_("%1$d characters (excluding blanks)"),
1353 message
+= _("One character (excluding blanks)");
1355 Alert::information(_("Statistics"), message
);
1359 case LFUN_BUFFER_TOGGLE_COMPRESSION
:
1360 // turn compression on/off
1361 buffer_
.params().compressed
= !buffer_
.params().compressed
;
1364 case LFUN_LABEL_COPY_AS_REF
: {
1365 // if there is an inset at cursor, try to copy it
1366 Inset
* inset
= &cur
.inset();
1367 if (!inset
|| !inset
->asInsetMath())
1368 inset
= cur
.nextInset();
1370 FuncRequest tmpcmd
= cmd
;
1371 inset
->dispatch(cur
, tmpcmd
);
1373 if (!cur
.result().dispatched())
1374 // It did not work too; no action needed.
1376 cur
.clearSelection();
1377 processUpdateFlags(Update::SinglePar
| Update::FitCursor
);
1381 case LFUN_NEXT_INSET_MODIFY
: {
1382 // create the the real function we want to invoke
1383 FuncRequest tmpcmd
= cmd
;
1384 tmpcmd
.action
= LFUN_INSET_MODIFY
;
1385 // if there is an inset at cursor, see whether it
1387 Inset
* inset
= cur
.nextInset();
1390 inset
->dispatch(cur
, tmpcmd
);
1392 // if it did not work, try the underlying inset.
1393 if (!inset
|| !cur
.result().dispatched()) {
1395 cur
.dispatch(tmpcmd
);
1398 if (!cur
.result().dispatched())
1399 // It did not work too; no action needed.
1401 cur
.clearSelection();
1402 processUpdateFlags(Update::Force
| Update::FitCursor
);
1406 case LFUN_SCREEN_UP
:
1407 case LFUN_SCREEN_DOWN
: {
1408 Point p
= getPos(cur
, cur
.boundary());
1409 // This code has been commented out to enable to scroll down a
1410 // document, even if there are large insets in it (see bug #5465).
1411 /*if (p.y_ < 0 || p.y_ > height_) {
1412 // The cursor is off-screen so recenter before proceeding.
1414 p = getPos(cur, cur.boundary());
1416 int const scrolled
= scroll(cmd
.action
== LFUN_SCREEN_UP
1417 ? -height_
: height_
);
1418 if (cmd
.action
== LFUN_SCREEN_UP
&& scrolled
> -height_
)
1420 if (cmd
.action
== LFUN_SCREEN_DOWN
&& scrolled
< height_
)
1421 p
= Point(width_
, height_
);
1423 bool const in_texted
= cur
.inTexted();
1424 cur
.reset(buffer_
.inset());
1427 d
->text_metrics_
[&buffer_
.text()].editXY(cur
, p
.x_
, p
.y_
,
1428 true, cmd
.action
== LFUN_SCREEN_UP
);
1429 //FIXME: what to do with cur.x_target()?
1430 bool update
= in_texted
&& cur
.bv().checkDepm(cur
, old
);
1433 processUpdateFlags(Update::Force
| Update::FitCursor
);
1441 case LFUN_SCREEN_UP_SELECT
: {
1442 cur
.selHandle(true);
1443 if (isTopScreen()) {
1444 lyx::dispatch(FuncRequest(LFUN_BUFFER_BEGIN_SELECT
));
1448 int y
= getPos(cur
, cur
.boundary()).y_
;
1449 int const ymin
= y
- height_
+ defaultRowHeight();
1450 while (y
> ymin
&& cur
.up())
1451 y
= getPos(cur
, cur
.boundary()).y_
;
1454 processUpdateFlags(Update::SinglePar
| Update::FitCursor
);
1458 case LFUN_SCREEN_DOWN_SELECT
: {
1459 cur
.selHandle(true);
1460 if (isBottomScreen()) {
1461 lyx::dispatch(FuncRequest(LFUN_BUFFER_END_SELECT
));
1465 int y
= getPos(cur
, cur
.boundary()).y_
;
1466 int const ymax
= y
+ height_
- defaultRowHeight();
1467 while (y
< ymax
&& cur
.down())
1468 y
= getPos(cur
, cur
.boundary()).y_
;
1471 processUpdateFlags(Update::SinglePar
| Update::FitCursor
);
1475 // This could be rewriten using some command like forall <insetname> <command>
1476 // once the insets refactoring is done.
1477 case LFUN_NOTES_MUTATE
: {
1478 if (cmd
.argument().empty())
1481 if (mutateNotes(cur
, cmd
.getArg(0), cmd
.getArg(1))) {
1482 processUpdateFlags(Update::Force
);
1487 case LFUN_ALL_INSETS_TOGGLE
: {
1489 string
const name
= split(to_utf8(cmd
.argument()), action
, ' ');
1490 InsetCode
const inset_code
= insetCode(name
);
1492 FuncRequest
fr(LFUN_INSET_TOGGLE
, action
);
1494 Inset
& inset
= cur
.buffer()->inset();
1495 InsetIterator it
= inset_iterator_begin(inset
);
1496 InsetIterator
const end
= inset_iterator_end(inset
);
1497 for (; it
!= end
; ++it
) {
1498 if (it
->asInsetCollapsable()
1499 && (inset_code
== NO_CODE
1500 || inset_code
== it
->lyxCode())) {
1501 Cursor tmpcur
= cur
;
1502 tmpcur
.pushBackward(*it
);
1503 it
->dispatch(tmpcur
, fr
);
1506 processUpdateFlags(Update::Force
| Update::FitCursor
);
1510 case LFUN_BRANCH_ADD_INSERT
: {
1511 docstring branch_name
= from_utf8(cmd
.getArg(0));
1512 if (branch_name
.empty())
1513 if (!Alert::askForText(branch_name
, _("Branch name")) ||
1514 branch_name
.empty())
1517 DispatchResult drtmp
;
1518 buffer_
.dispatch(FuncRequest(LFUN_BRANCH_ADD
, branch_name
), drtmp
);
1519 if (drtmp
.error()) {
1520 Alert::warning(_("Branch already exists"), drtmp
.message());
1523 BranchList
& branch_list
= buffer_
.params().branchlist();
1524 Branch
const * branch
= branch_list
.find(branch_name
);
1525 string
const x11hexname
= X11hexname(branch
->color());
1526 docstring
const str
= branch_name
+ ' ' + from_ascii(x11hexname
);
1527 lyx::dispatch(FuncRequest(LFUN_SET_COLOR
, str
));
1528 lyx::dispatch(FuncRequest(LFUN_BRANCH_INSERT
, branch_name
));
1541 docstring
const BufferView::requestSelection()
1543 Cursor
& cur
= d
->cursor_
;
1545 LYXERR(Debug::SELECTION
, "requestSelection: cur.selection: " << cur
.selection());
1546 if (!cur
.selection()) {
1547 d
->xsel_cache_
.set
= false;
1551 LYXERR(Debug::SELECTION
, "requestSelection: xsel_cache.set: " << d
->xsel_cache_
.set
);
1552 if (!d
->xsel_cache_
.set
||
1553 cur
.top() != d
->xsel_cache_
.cursor
||
1554 cur
.anchor_
.top() != d
->xsel_cache_
.anchor
)
1556 d
->xsel_cache_
.cursor
= cur
.top();
1557 d
->xsel_cache_
.anchor
= cur
.anchor_
.top();
1558 d
->xsel_cache_
.set
= cur
.selection();
1559 return cur
.selectionAsString(false);
1565 void BufferView::clearSelection()
1567 d
->cursor_
.clearSelection();
1568 // Clear the selection buffer. Otherwise a subsequent
1569 // middle-mouse-button paste would use the selection buffer,
1570 // not the more current external selection.
1571 cap::clearSelection();
1572 d
->xsel_cache_
.set
= false;
1573 // The buffer did not really change, but this causes the
1574 // redraw we need because we cleared the selection above.
1579 void BufferView::resize(int width
, int height
)
1581 // Update from work area
1585 // Clear the paragraph height cache.
1586 d
->par_height_
.clear();
1587 // Redo the metrics.
1592 Inset
const * BufferView::getCoveringInset(Text
const & text
,
1595 TextMetrics
& tm
= d
->text_metrics_
[&text
];
1596 Inset
* inset
= tm
.checkInsetHit(x
, y
);
1600 if (!inset
->descendable())
1601 // No need to go further down if the inset is not
1605 size_t cell_number
= inset
->nargs();
1606 // Check all the inner cell.
1607 for (size_t i
= 0; i
!= cell_number
; ++i
) {
1608 Text
const * inner_text
= inset
->getText(i
);
1611 Inset
const * inset_deeper
=
1612 getCoveringInset(*inner_text
, x
, y
);
1614 return inset_deeper
;
1622 void BufferView::mouseEventDispatch(FuncRequest
const & cmd0
)
1624 //lyxerr << "[ cmd0 " << cmd0 << "]" << endl;
1626 // This is only called for mouse related events including
1627 // LFUN_FILE_OPEN generated by drag-and-drop.
1628 FuncRequest cmd
= cmd0
;
1630 Cursor old
= cursor();
1632 cur
.push(buffer_
.inset());
1633 cur
.setSelection(d
->cursor_
.selection());
1635 // Either the inset under the cursor or the
1636 // surrounding Text will handle this event.
1638 // make sure we stay within the screen...
1639 cmd
.y
= min(max(cmd
.y
, -1), height_
);
1641 if (cmd
.action
== LFUN_MOUSE_MOTION
&& cmd
.button() == mouse_button::none
) {
1643 // Get inset under mouse, if there is one.
1644 Inset
const * covering_inset
=
1645 getCoveringInset(buffer_
.text(), cmd
.x
, cmd
.y
);
1646 if (covering_inset
== d
->last_inset_
)
1647 // Same inset, no need to do anything...
1650 bool need_redraw
= false;
1651 // const_cast because of setMouseHover().
1652 Inset
* inset
= const_cast<Inset
*>(covering_inset
);
1654 // Remove the hint on the last hovered inset (if any).
1655 need_redraw
|= d
->last_inset_
->setMouseHover(false);
1657 // Highlighted the newly hovered inset (if any).
1658 need_redraw
|= inset
->setMouseHover(true);
1659 d
->last_inset_
= inset
;
1663 LYXERR(Debug::PAINTING
, "Mouse hover detected at: ("
1664 << cmd
.x
<< ", " << cmd
.y
<< ")");
1666 d
->update_strategy_
= DecorationUpdate
;
1668 // This event (moving without mouse click) is not passed further.
1669 // This should be changed if it is further utilized.
1674 // Build temporary cursor.
1675 Inset
* inset
= d
->text_metrics_
[&buffer_
.text()].editXY(cur
, cmd
.x
, cmd
.y
);
1677 // Put anchor at the same position.
1680 cur
.beginUndoGroup();
1682 // Try to dispatch to an non-editable inset near this position
1683 // via the temp cursor. If the inset wishes to change the real
1684 // cursor it has to do so explicitly by using
1685 // cur.bv().cursor() = cur; (or similar)
1687 inset
->dispatch(cur
, cmd
);
1689 // Now dispatch to the temporary cursor. If the real cursor should
1690 // be modified, the inset's dispatch has to do so explicitly.
1691 if (!inset
|| !cur
.result().dispatched())
1696 // Notify left insets
1699 bool badcursor
= notifyCursorLeavesOrEnters(old
, cur
);
1701 cursor().fixIfBroken();
1704 // Do we have a selection?
1705 theSelection().haveSelection(cursor().selection());
1707 // If the command has been dispatched,
1708 if (cur
.result().dispatched() || cur
.result().update())
1709 processUpdateFlags(cur
.result().update());
1713 void BufferView::lfunScroll(FuncRequest
const & cmd
)
1715 string
const scroll_type
= cmd
.getArg(0);
1716 int const scroll_step
=
1717 (scroll_type
== "line") ? d
->scrollbarParameters_
.single_step
1718 : (scroll_type
== "page") ? d
->scrollbarParameters_
.page_step
: 0;
1719 if (scroll_step
== 0)
1721 string
const scroll_quantity
= cmd
.getArg(1);
1722 if (scroll_quantity
== "up")
1723 scrollUp(scroll_step
);
1724 else if (scroll_quantity
== "down")
1725 scrollDown(scroll_step
);
1727 int const scroll_value
= convert
<int>(scroll_quantity
);
1729 scroll(scroll_step
* scroll_value
);
1736 int BufferView::minVisiblePart()
1738 return 2 * defaultRowHeight();
1742 int BufferView::scroll(int y
)
1745 return scrollDown(y
);
1747 return scrollUp(-y
);
1752 int BufferView::scrollDown(int offset
)
1754 Text
* text
= &buffer_
.text();
1755 TextMetrics
& tm
= d
->text_metrics_
[text
];
1756 int const ymax
= height_
+ offset
;
1758 pair
<pit_type
, ParagraphMetrics
const *> last
= tm
.last();
1759 int bottom_pos
= last
.second
->position() + last
.second
->descent();
1760 if (lyxrc
.scroll_below_document
)
1761 bottom_pos
+= height_
- minVisiblePart();
1762 if (last
.first
+ 1 == int(text
->paragraphs().size())) {
1763 if (bottom_pos
<= height_
)
1765 offset
= min(offset
, bottom_pos
- height_
);
1768 if (bottom_pos
> ymax
)
1770 tm
.newParMetricsDown();
1772 d
->anchor_ypos_
-= offset
;
1777 int BufferView::scrollUp(int offset
)
1779 Text
* text
= &buffer_
.text();
1780 TextMetrics
& tm
= d
->text_metrics_
[text
];
1781 int ymin
= - offset
;
1783 pair
<pit_type
, ParagraphMetrics
const *> first
= tm
.first();
1784 int top_pos
= first
.second
->position() - first
.second
->ascent();
1785 if (first
.first
== 0) {
1788 offset
= min(offset
, - top_pos
);
1793 tm
.newParMetricsUp();
1795 d
->anchor_ypos_
+= offset
;
1800 void BufferView::setCursorFromRow(int row
)
1805 buffer_
.texrow().getIdFromRow(row
, tmpid
, tmppos
);
1807 d
->cursor_
.reset(buffer_
.inset());
1809 buffer_
.text().setCursor(d
->cursor_
, 0, 0);
1811 buffer_
.text().setCursor(d
->cursor_
, buffer_
.getParFromID(tmpid
).pit(), tmppos
);
1815 bool BufferView::setCursorFromInset(Inset
const * inset
)
1817 // are we already there?
1818 if (cursor().nextInset() == inset
)
1821 // Inset is not at cursor position. Find it in the document.
1823 cur
.reset(buffer().inset());
1824 while (cur
&& cur
.nextInset() != inset
)
1835 void BufferView::gotoLabel(docstring
const & label
)
1837 std::vector
<Buffer
const *> bufs
= buffer().allRelatives();
1838 std::vector
<Buffer
const *>::iterator it
= bufs
.begin();
1839 for (; it
!= bufs
.end(); ++it
) {
1840 Buffer
const * buf
= *it
;
1843 Toc
& toc
= buf
->tocBackend().toc("label");
1844 TocIterator toc_it
= toc
.begin();
1845 TocIterator end
= toc
.end();
1846 for (; toc_it
!= end
; ++toc_it
) {
1847 if (label
== toc_it
->str()) {
1848 dispatch(toc_it
->action());
1856 TextMetrics
const & BufferView::textMetrics(Text
const * t
) const
1858 return const_cast<BufferView
*>(this)->textMetrics(t
);
1862 TextMetrics
& BufferView::textMetrics(Text
const * t
)
1864 TextMetricsCache::iterator tmc_it
= d
->text_metrics_
.find(t
);
1865 if (tmc_it
== d
->text_metrics_
.end()) {
1866 tmc_it
= d
->text_metrics_
.insert(
1867 make_pair(t
, TextMetrics(this, const_cast<Text
*>(t
)))).first
;
1869 return tmc_it
->second
;
1873 ParagraphMetrics
const & BufferView::parMetrics(Text
const * t
,
1876 return textMetrics(t
).parMetrics(pit
);
1880 int BufferView::workHeight() const
1886 void BufferView::setCursor(DocIterator
const & dit
)
1888 size_t const n
= dit
.depth();
1889 for (size_t i
= 0; i
< n
; ++i
)
1890 dit
[i
].inset().edit(d
->cursor_
, true);
1892 d
->cursor_
.setCursor(dit
);
1893 d
->cursor_
.setSelection(false);
1897 bool BufferView::checkDepm(Cursor
& cur
, Cursor
& old
)
1899 // Would be wrong to delete anything if we have a selection.
1900 if (cur
.selection())
1903 bool need_anchor_change
= false;
1904 bool changed
= d
->cursor_
.text()->deleteEmptyParagraphMechanism(cur
, old
,
1905 need_anchor_change
);
1907 if (need_anchor_change
)
1915 buffer_
.updateLabels();
1923 bool BufferView::mouseSetCursor(Cursor
& cur
, bool select
)
1925 LASSERT(&cur
.bv() == this, /**/);
1928 // this event will clear selection so we save selection for
1929 // persistent selection
1930 cap::saveSelection(cursor());
1932 // Has the cursor just left the inset?
1933 bool leftinset
= (&d
->cursor_
.inset() != &cur
.inset());
1935 d
->cursor_
.fixIfBroken();
1937 // FIXME: shift-mouse selection doesn't work well across insets.
1938 bool do_selection
= select
&& &d
->cursor_
.anchor().inset() == &cur
.inset();
1940 // do the dEPM magic if needed
1941 // FIXME: (1) move this to InsetText::notifyCursorLeaves?
1942 // FIXME: (2) if we had a working InsetText::notifyCursorLeaves,
1943 // the leftinset bool would not be necessary (badcursor instead).
1944 bool update
= leftinset
;
1945 if (!do_selection
&& d
->cursor_
.inTexted())
1946 update
|= checkDepm(cur
, d
->cursor_
);
1947 d
->cursor_
.macroModeClose();
1949 d
->cursor_
.resetAnchor();
1950 d
->cursor_
.setCursor(cur
);
1951 d
->cursor_
.boundary(cur
.boundary());
1953 d
->cursor_
.setSelection();
1955 d
->cursor_
.clearSelection();
1957 d
->cursor_
.finishUndo();
1958 d
->cursor_
.setCurrentFont();
1963 void BufferView::putSelectionAt(DocIterator
const & cur
,
1964 int length
, bool backwards
)
1966 d
->cursor_
.clearSelection();
1972 d
->cursor_
.pos() += length
;
1973 d
->cursor_
.setSelection(d
->cursor_
, -length
);
1975 d
->cursor_
.setSelection(d
->cursor_
, length
);
1977 // Ensure a redraw happens in any case because the new selection could
1978 // possibly be on the same screen as the previous selection.
1979 processUpdateFlags(Update::Force
| Update::FitCursor
);
1983 Cursor
& BufferView::cursor()
1989 Cursor
const & BufferView::cursor() const
1995 pit_type
BufferView::anchor_ref() const
1997 return d
->anchor_pit_
;
2001 bool BufferView::singleParUpdate()
2003 Text
& buftext
= buffer_
.text();
2004 pit_type
const bottom_pit
= d
->cursor_
.bottom().pit();
2005 TextMetrics
& tm
= textMetrics(&buftext
);
2006 int old_height
= tm
.parMetrics(bottom_pit
).height();
2008 // make sure inline completion pointer is ok
2009 if (d
->inlineCompletionPos_
.fixIfBroken())
2010 d
->inlineCompletionPos_
= DocIterator();
2012 // In Single Paragraph mode, rebreak only
2013 // the (main text, not inset!) paragraph containing the cursor.
2014 // (if this paragraph contains insets etc., rebreaking will
2015 // recursively descend)
2016 tm
.redoParagraph(bottom_pit
);
2017 ParagraphMetrics
const & pm
= tm
.parMetrics(bottom_pit
);
2018 if (pm
.height() != old_height
)
2019 // Paragraph height has changed so we cannot proceed to
2020 // the singlePar optimisation.
2023 d
->update_strategy_
= SingleParUpdate
;
2025 LYXERR(Debug::PAINTING
, "\ny1: " << pm
.position() - pm
.ascent()
2026 << " y2: " << pm
.position() + pm
.descent()
2027 << " pit: " << bottom_pit
2028 << " singlepar: 1");
2033 void BufferView::updateMetrics()
2035 if (height_
== 0 || width_
== 0)
2038 Text
& buftext
= buffer_
.text();
2039 pit_type
const npit
= int(buftext
.paragraphs().size());
2041 // Clear out the position cache in case of full screen redraw,
2042 d
->coord_cache_
.clear();
2044 // Clear out paragraph metrics to avoid having invalid metrics
2045 // in the cache from paragraphs not relayouted below
2046 // The complete text metrics will be redone.
2047 d
->text_metrics_
.clear();
2049 TextMetrics
& tm
= textMetrics(&buftext
);
2051 // make sure inline completion pointer is ok
2052 if (d
->inlineCompletionPos_
.fixIfBroken())
2053 d
->inlineCompletionPos_
= DocIterator();
2055 if (d
->anchor_pit_
>= npit
)
2056 // The anchor pit must have been deleted...
2057 d
->anchor_pit_
= npit
- 1;
2059 // Rebreak anchor paragraph.
2060 tm
.redoParagraph(d
->anchor_pit_
);
2061 ParagraphMetrics
& anchor_pm
= tm
.par_metrics_
[d
->anchor_pit_
];
2064 if (d
->anchor_pit_
== 0) {
2065 int scrollRange
= d
->scrollbarParameters_
.max
- d
->scrollbarParameters_
.min
;
2067 // Complete buffer visible? Then it's easy.
2068 if (scrollRange
== 0)
2069 d
->anchor_ypos_
= anchor_pm
.ascent();
2071 // FIXME: Some clever handling needed to show
2072 // the _first_ paragraph up to the top if the cursor is
2073 // in the first line.
2075 anchor_pm
.setPosition(d
->anchor_ypos_
);
2077 LYXERR(Debug::PAINTING
, "metrics: "
2078 << " anchor pit = " << d
->anchor_pit_
2079 << " anchor ypos = " << d
->anchor_ypos_
);
2081 // Redo paragraphs above anchor if necessary.
2082 int y1
= d
->anchor_ypos_
- anchor_pm
.ascent();
2083 // We are now just above the anchor paragraph.
2084 pit_type pit1
= d
->anchor_pit_
- 1;
2085 for (; pit1
>= 0 && y1
>= 0; --pit1
) {
2086 tm
.redoParagraph(pit1
);
2087 ParagraphMetrics
& pm
= tm
.par_metrics_
[pit1
];
2089 // Save the paragraph position in the cache.
2094 // Redo paragraphs below the anchor if necessary.
2095 int y2
= d
->anchor_ypos_
+ anchor_pm
.descent();
2096 // We are now just below the anchor paragraph.
2097 pit_type pit2
= d
->anchor_pit_
+ 1;
2098 for (; pit2
< npit
&& y2
<= height_
; ++pit2
) {
2099 tm
.redoParagraph(pit2
);
2100 ParagraphMetrics
& pm
= tm
.par_metrics_
[pit2
];
2102 // Save the paragraph position in the cache.
2107 LYXERR(Debug::PAINTING
, "Metrics: "
2108 << " anchor pit = " << d
->anchor_pit_
2109 << " anchor ypos = " << d
->anchor_ypos_
2112 << " pit1 = " << pit1
2113 << " pit2 = " << pit2
);
2115 d
->update_strategy_
= FullScreenUpdate
;
2117 if (lyxerr
.debugging(Debug::WORKAREA
)) {
2118 LYXERR(Debug::WORKAREA
, "BufferView::updateMetrics");
2119 d
->coord_cache_
.dump();
2124 void BufferView::insertLyXFile(FileName
const & fname
)
2126 LASSERT(d
->cursor_
.inTexted(), /**/);
2128 // Get absolute path of file and add ".lyx"
2129 // to the filename if necessary
2130 FileName filename
= fileSearch(string(), fname
.absFilename(), "lyx");
2132 docstring
const disp_fn
= makeDisplayPath(filename
.absFilename());
2133 // emit message signal.
2134 message(bformat(_("Inserting document %1$s..."), disp_fn
));
2137 Buffer
buf("", false);
2138 if (buf
.loadLyXFile(filename
)) {
2139 ErrorList
& el
= buffer_
.errorList("Parse");
2140 // Copy the inserted document error list into the current buffer one.
2141 el
= buf
.errorList("Parse");
2142 buffer_
.undo().recordUndo(d
->cursor_
);
2143 cap::pasteParagraphList(d
->cursor_
, buf
.paragraphs(),
2144 buf
.params().documentClassPtr(), el
);
2145 res
= _("Document %1$s inserted.");
2147 res
= _("Could not insert document %1$s");
2152 // emit message signal.
2153 message(bformat(res
, disp_fn
));
2154 buffer_
.errors("Parse");
2158 Point
BufferView::coordOffset(DocIterator
const & dit
, bool boundary
) const
2164 // Addup contribution of nested insets, from inside to outside,
2165 // keeping the outer paragraph for a special handling below
2166 for (size_t i
= dit
.depth() - 1; i
>= 1; --i
) {
2167 CursorSlice
const & sl
= dit
[i
];
2171 // get relative position inside sl.inset()
2172 sl
.inset().cursorPos(*this, sl
, boundary
&& (i
+ 1 == dit
.depth()), xx
, yy
);
2174 // Make relative position inside of the edited inset relative to sl.inset()
2178 // In case of an RTL inset, the edited inset will be positioned to the left
2181 bool boundary_i
= boundary
&& i
+ 1 == dit
.depth();
2182 bool rtl
= textMetrics(sl
.text()).isRTL(sl
, boundary_i
);
2187 // remember width for the case that sl.inset() is positioned in an RTL inset
2188 if (i
&& dit
[i
- 1].text()) {
2189 // If this Inset is inside a Text Inset, retrieve the Dimension
2190 // from the containing text instead of using Inset::dimension() which
2191 // might not be implemented.
2192 // FIXME (Abdel 23/09/2007): this is a bit messy because of the
2193 // elimination of Inset::dim_ cache. This coordOffset() method needs
2194 // to be rewritten in light of the new design.
2195 Dimension
const & dim
= parMetrics(dit
[i
- 1].text(),
2196 dit
[i
- 1].pit()).insetDimension(&sl
.inset());
2199 Dimension
const dim
= sl
.inset().dimension(*this);
2203 //lyxerr << "Cursor::getPos, i: "
2204 // << i << " x: " << xx << " y: " << y << endl;
2207 // Add contribution of initial rows of outermost paragraph
2208 CursorSlice
const & sl
= dit
[0];
2209 TextMetrics
const & tm
= textMetrics(sl
.text());
2210 ParagraphMetrics
const & pm
= tm
.parMetrics(sl
.pit());
2211 LASSERT(!pm
.rows().empty(), /**/);
2212 y
-= pm
.rows()[0].ascent();
2214 // FIXME: document this mess
2216 if (sl
.pos() > 0 && dit
.depth() == 1) {
2218 if (pos
&& boundary
)
2220 // lyxerr << "coordOffset: boundary:" << boundary << " depth:" << dit.depth() << " pos:" << pos << " sl.pos:" << sl.pos() << endl;
2221 rend
= pm
.pos2row(pos
);
2223 rend
= pm
.pos2row(sl
.pos());
2225 size_t rend
= pm
.pos2row(sl
.pos());
2227 for (size_t rit
= 0; rit
!= rend
; ++rit
)
2228 y
+= pm
.rows()[rit
].height();
2229 y
+= pm
.rows()[rend
].ascent();
2231 TextMetrics
const & bottom_tm
= textMetrics(dit
.bottom().text());
2233 // Make relative position from the nested inset now bufferview absolute.
2234 int xx
= bottom_tm
.cursorX(dit
.bottom(), boundary
&& dit
.depth() == 1);
2237 // In the RTL case place the nested inset at the left of the cursor in
2238 // the outer paragraph
2239 bool boundary_1
= boundary
&& 1 == dit
.depth();
2240 bool rtl
= bottom_tm
.isRTL(dit
.bottom(), boundary_1
);
2248 Point
BufferView::getPos(DocIterator
const & dit
, bool boundary
) const
2250 if (!paragraphVisible(dit
))
2251 return Point(-1, -1);
2253 CursorSlice
const & bot
= dit
.bottom();
2254 TextMetrics
const & tm
= textMetrics(bot
.text());
2256 Point p
= coordOffset(dit
, boundary
); // offset from outer paragraph
2257 p
.y_
+= tm
.parMetrics(bot
.pit()).position();
2262 bool BufferView::paragraphVisible(DocIterator
const & dit
) const
2264 CursorSlice
const & bot
= dit
.bottom();
2265 TextMetrics
const & tm
= textMetrics(bot
.text());
2267 return tm
.contains(bot
.pit());
2271 void BufferView::cursorPosAndHeight(Point
& p
, int & h
) const
2273 Cursor
const & cur
= cursor();
2274 Font
const font
= cur
.getFont();
2275 frontend::FontMetrics
const & fm
= theFontMetrics(font
);
2276 int const asc
= fm
.maxAscent();
2277 int const des
= fm
.maxDescent();
2279 p
= getPos(cur
, cur
.boundary());
2284 bool BufferView::cursorInView(Point
const & p
, int h
) const
2286 Cursor
const & cur
= cursor();
2287 // does the cursor touch the screen ?
2288 if (p
.y_
+ h
< 0 || p
.y_
>= workHeight() || !paragraphVisible(cur
))
2294 void BufferView::draw(frontend::Painter
& pain
)
2296 if (height_
== 0 || width_
== 0)
2298 LYXERR(Debug::PAINTING
, "\t\t*** START DRAWING ***");
2300 Text
& text
= buffer_
.text();
2301 TextMetrics
const & tm
= d
->text_metrics_
[&text
];
2302 int const y
= tm
.first().second
->position();
2303 PainterInfo
pi(this, pain
);
2305 switch (d
->update_strategy_
) {
2307 case NoScreenUpdate
:
2308 // If no screen painting is actually needed, only some the different
2309 // coordinates of insets and paragraphs needs to be updated.
2310 pi
.full_repaint
= true;
2311 pi
.pain
.setDrawingEnabled(false);
2315 case SingleParUpdate
:
2316 pi
.full_repaint
= false;
2317 // In general, only the current row of the outermost paragraph
2318 // will be redrawn. Particular cases where selection spans
2319 // multiple paragraph are correctly detected in TextMetrics.
2323 case DecorationUpdate
:
2324 // FIXME: We should also distinguish DecorationUpdate to avoid text
2325 // drawing if possible. This is not possible to do easily right now
2326 // because of the single backing pixmap.
2328 case FullScreenUpdate
:
2329 // The whole screen, including insets, will be refreshed.
2330 pi
.full_repaint
= true;
2332 // Clear background.
2333 pain
.fillRectangle(0, 0, width_
, height_
,
2334 pi
.backgroundColor(&buffer_
.inset()));
2339 // and possibly grey out below
2340 pair
<pit_type
, ParagraphMetrics
const *> lastpm
= tm
.last();
2341 int const y2
= lastpm
.second
->position() + lastpm
.second
->descent();
2343 pain
.fillRectangle(0, y2
, width_
, height_
- y2
, Color_bottomarea
);
2346 LYXERR(Debug::PAINTING
, "\n\t\t*** END DRAWING ***");
2348 // The scrollbar needs an update.
2351 // Normalize anchor for next time
2352 pair
<pit_type
, ParagraphMetrics
const *> firstpm
= tm
.first();
2353 pair
<pit_type
, ParagraphMetrics
const *> lastpm
= tm
.last();
2354 for (pit_type pit
= firstpm
.first
; pit
<= lastpm
.first
; ++pit
) {
2355 ParagraphMetrics
const & pm
= tm
.parMetrics(pit
);
2356 if (pm
.position() + pm
.descent() > 0) {
2357 d
->anchor_pit_
= pit
;
2358 d
->anchor_ypos_
= pm
.position();
2362 LYXERR(Debug::PAINTING
, "Found new anchor pit = " << d
->anchor_pit_
2363 << " anchor ypos = " << d
->anchor_ypos_
);
2367 void BufferView::message(docstring
const & msg
)
2370 d
->gui_
->message(msg
);
2374 void BufferView::showDialog(string
const & name
)
2377 d
->gui_
->showDialog(name
, string());
2381 void BufferView::showDialog(string
const & name
,
2382 string
const & data
, Inset
* inset
)
2385 d
->gui_
->showDialog(name
, data
, inset
);
2389 void BufferView::updateDialog(string
const & name
, string
const & data
)
2392 d
->gui_
->updateDialog(name
, data
);
2396 void BufferView::setGuiDelegate(frontend::GuiBufferViewDelegate
* gui
)
2402 // FIXME: Move this out of BufferView again
2403 docstring
BufferView::contentsOfPlaintextFile(FileName
const & fname
)
2405 if (!fname
.isReadableFile()) {
2406 docstring
const error
= from_ascii(strerror(errno
));
2407 docstring
const file
= makeDisplayPath(fname
.absFilename(), 50);
2408 docstring
const text
=
2409 bformat(_("Could not read the specified document\n"
2410 "%1$s\ndue to the error: %2$s"), file
, error
);
2411 Alert::error(_("Could not read file"), text
);
2415 if (!fname
.isReadableFile()) {
2416 docstring
const file
= makeDisplayPath(fname
.absFilename(), 50);
2417 docstring
const text
=
2418 bformat(_("%1$s\n is not readable."), file
);
2419 Alert::error(_("Could not open file"), text
);
2423 // FIXME UNICODE: We don't know the encoding of the file
2424 docstring file_content
= fname
.fileContents("UTF-8");
2425 if (file_content
.empty()) {
2426 Alert::error(_("Reading not UTF-8 encoded file"),
2427 _("The file is not UTF-8 encoded.\n"
2428 "It will be read as local 8Bit-encoded.\n"
2429 "If this does not give the correct result\n"
2430 "then please change the encoding of the file\n"
2431 "to UTF-8 with a program other than LyX.\n"));
2432 file_content
= fname
.fileContents("local8bit");
2435 return normalize_c(file_content
);
2439 void BufferView::insertPlaintextFile(FileName
const & f
, bool asParagraph
)
2441 docstring
const tmpstr
= contentsOfPlaintextFile(f
);
2446 Cursor
& cur
= cursor();
2447 cap::replaceSelection(cur
);
2448 buffer_
.undo().recordUndo(cur
);
2450 cur
.innerText()->insertStringAsParagraphs(cur
, tmpstr
);
2452 cur
.innerText()->insertStringAsLines(cur
, tmpstr
);
2459 docstring
const & BufferView::inlineCompletion() const
2461 return d
->inlineCompletion_
;
2465 size_t const & BufferView::inlineCompletionUniqueChars() const
2467 return d
->inlineCompletionUniqueChars_
;
2471 DocIterator
const & BufferView::inlineCompletionPos() const
2473 return d
->inlineCompletionPos_
;
2477 bool samePar(DocIterator
const & a
, DocIterator
const & b
)
2479 if (a
.empty() && b
.empty())
2481 if (a
.empty() || b
.empty())
2483 if (a
.depth() != b
.depth())
2485 return &a
.innerParagraph() == &b
.innerParagraph();
2489 void BufferView::setInlineCompletion(Cursor
& cur
, DocIterator
const & pos
,
2490 docstring
const & completion
, size_t uniqueChars
)
2492 uniqueChars
= min(completion
.size(), uniqueChars
);
2493 bool changed
= d
->inlineCompletion_
!= completion
2494 || d
->inlineCompletionUniqueChars_
!= uniqueChars
;
2495 bool singlePar
= true;
2496 d
->inlineCompletion_
= completion
;
2497 d
->inlineCompletionUniqueChars_
= min(completion
.size(), uniqueChars
);
2499 //lyxerr << "setInlineCompletion pos=" << pos << " completion=" << completion << " uniqueChars=" << uniqueChars << std::endl;
2502 DocIterator
const & old
= d
->inlineCompletionPos_
;
2504 //lyxerr << "inlineCompletionPos changed" << std::endl;
2505 // old or pos are in another paragraph?
2506 if ((!samePar(cur
, pos
) && !pos
.empty())
2507 || (!samePar(cur
, old
) && !old
.empty())) {
2509 //lyxerr << "different paragraph" << std::endl;
2511 d
->inlineCompletionPos_
= pos
;
2516 if (singlePar
&& !(cur
.disp_
.update() & Update::Force
))
2517 cur
.updateFlags(cur
.disp_
.update() | Update::SinglePar
);
2519 cur
.updateFlags(cur
.disp_
.update() | Update::Force
);