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"
20 #include "buffer_funcs.h"
21 #include "BufferList.h"
22 #include "BufferParams.h"
23 #include "CoordCache.h"
25 #include "CutAndPaste.h"
26 #include "DispatchResult.h"
27 #include "EmbeddedFiles.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/InsetRef.h"
59 #include "insets/InsetText.h"
61 #include "frontends/alert.h"
62 #include "frontends/Application.h"
63 #include "frontends/Delegates.h"
64 #include "frontends/FontMetrics.h"
65 #include "frontends/Painter.h"
66 #include "frontends/Selection.h"
68 #include "graphics/Previews.h"
70 #include "support/convert.h"
71 #include "support/debug.h"
72 #include "support/ExceptionMessage.h"
73 #include "support/filetools.h"
74 #include "support/gettext.h"
75 #include "support/lstrings.h"
76 #include "support/Package.h"
77 #include "support/types.h"
86 using namespace lyx::support
;
90 namespace Alert
= frontend::Alert
;
94 /// Return an inset of this class if it exists at the current cursor position
96 T
* getInsetByCode(Cursor
const & cur
, InsetCode code
)
99 Inset
* inset
= it
.nextInset();
100 if (inset
&& inset
->lyxCode() == code
)
101 return static_cast<T
*>(inset
);
106 bool findInset(DocIterator
& dit
, vector
<InsetCode
> const & codes
,
109 bool findNextInset(DocIterator
& dit
, vector
<InsetCode
> const & codes
,
110 docstring
const & contents
)
112 DocIterator tmpdit
= dit
;
115 Inset
const * inset
= tmpdit
.nextInset();
117 && std::find(codes
.begin(), codes
.end(), inset
->lyxCode()) != codes
.end()
118 && (contents
.empty() ||
119 static_cast<InsetCommand
const *>(inset
)->getFirstNonOptParam() == contents
)) {
123 tmpdit
.forwardInset();
130 /// Looks for next inset with one of the given codes.
131 bool findInset(DocIterator
& dit
, vector
<InsetCode
> const & codes
,
135 DocIterator tmpdit
= dit
;
136 tmpdit
.forwardInset();
141 Inset
const * inset
= tmpdit
.nextInset();
143 && std::find(codes
.begin(), codes
.end(), inset
->lyxCode()) != codes
.end()) {
144 contents
= static_cast<InsetCommand
const *>(inset
)->getFirstNonOptParam();
148 if (!findNextInset(tmpdit
, codes
, contents
)) {
149 if (dit
.depth() != 1 || dit
.pit() != 0 || dit
.pos() != 0) {
150 tmpdit
= doc_iterator_begin(tmpdit
.bottom().inset());
151 if (!findNextInset(tmpdit
, codes
, contents
))
162 /// Looks for next inset with the given code
163 void findInset(DocIterator
& dit
, InsetCode code
, bool same_content
)
165 findInset(dit
, vector
<InsetCode
>(1, code
), same_content
);
169 /// Moves cursor to the next inset with one of the given codes.
170 void gotoInset(BufferView
* bv
, vector
<InsetCode
> const & codes
,
173 Cursor tmpcur
= bv
->cursor();
174 if (!findInset(tmpcur
, codes
, same_content
)) {
175 bv
->cursor().message(_("No more insets"));
179 tmpcur
.clearSelection();
180 bv
->setCursor(tmpcur
);
185 /// Moves cursor to the next inset with given code.
186 void gotoInset(BufferView
* bv
, InsetCode code
, bool same_content
)
188 gotoInset(bv
, vector
<InsetCode
>(1, code
), same_content
);
192 /// A map from a Text to the associated text metrics
193 typedef map
<Text
const *, TextMetrics
> TextMetricsCache
;
195 enum ScreenUpdateStrategy
{
205 /////////////////////////////////////////////////////////////////////
209 /////////////////////////////////////////////////////////////////////
211 struct BufferView::Private
213 Private(BufferView
& bv
): wh_(0), cursor_(bv
),
214 anchor_pit_(0), anchor_ypos_(0),
215 last_inset_(0), gui_(0)
219 ScrollbarParameters scrollbarParameters_
;
221 ScreenUpdateStrategy update_strategy_
;
223 CoordCache coord_cache_
;
225 /// Estimated average par height for scrollbar.
227 /// this is used to handle XSelection events in the right manner.
236 pit_type anchor_pit_
;
240 vector
<int> par_height_
;
243 DocIterator inlineCompletionPos
;
245 docstring inlineCompletion
;
247 size_t inlineCompletionUniqueChars
;
249 /// keyboard mapping object.
252 /// last visited inset.
253 /** kept to send setMouseHover(false).
254 * Not owned, so don't delete.
258 mutable TextMetricsCache text_metrics_
;
261 /** Not owned, so don't delete.
263 frontend::GuiBufferViewDelegate
* gui_
;
267 BufferView::BufferView(Buffer
& buf
)
268 : width_(0), height_(0), full_screen_(false), buffer_(buf
), d(new Private(*this))
270 d
->xsel_cache_
.set
= false;
271 d
->intl_
.initKeyMapper(lyxrc
.use_kbmap
);
273 d
->cursor_
.push(buffer_
.inset());
274 d
->cursor_
.resetAnchor();
275 d
->cursor_
.setCurrentFont();
277 if (graphics::Previews::status() != LyXRC::PREVIEW_OFF
)
278 graphics::Previews::get().generateBufferPreviews(buffer_
);
282 BufferView::~BufferView()
284 // current buffer is going to be switched-off, save cursor pos
285 // Ideally, the whole cursor stack should be saved, but session
286 // currently can only handle bottom (whole document) level pit and pos.
287 // That is to say, if a cursor is in a nested inset, it will be
288 // restore to the left of the top level inset.
289 LastFilePosSection::FilePos fp
;
290 fp
.pit
= d
->cursor_
.bottom().pit();
291 fp
.pos
= d
->cursor_
.bottom().pos();
292 LyX::ref().session().lastFilePos().save(buffer_
.fileName(), fp
);
298 int BufferView::rightMargin() const
300 // The additional test for the case the outliner is opened.
302 !lyxrc
.full_screen_limit
||
303 width_
< lyxrc
.full_screen_width
+ 20)
306 return (width_
- lyxrc
.full_screen_width
) / 2;
310 int BufferView::leftMargin() const
312 return rightMargin();
316 bool BufferView::isTopScreen() const
318 return d
->scrollbarParameters_
.position
== d
->scrollbarParameters_
.min
;
322 bool BufferView::isBottomScreen() const
324 return d
->scrollbarParameters_
.position
== d
->scrollbarParameters_
.max
;
328 Intl
& BufferView::getIntl()
334 Intl
const & BufferView::getIntl() const
340 CoordCache
& BufferView::coordCache()
342 return d
->coord_cache_
;
346 CoordCache
const & BufferView::coordCache() const
348 return d
->coord_cache_
;
352 Buffer
& BufferView::buffer()
358 Buffer
const & BufferView::buffer() const
364 bool BufferView::fitCursor()
366 if (cursorStatus(d
->cursor_
) == CUR_INSIDE
) {
367 frontend::FontMetrics
const & fm
=
368 theFontMetrics(d
->cursor_
.getFont().fontInfo());
369 int const asc
= fm
.maxAscent();
370 int const des
= fm
.maxDescent();
371 Point
const p
= getPos(d
->cursor_
, d
->cursor_
.boundary());
372 if (p
.y_
- asc
>= 0 && p
.y_
+ des
< height_
)
379 void BufferView::processUpdateFlags(Update::flags flags
)
381 // last_inset_ points to the last visited inset. This pointer may become
382 // invalid because of keyboard editing. Since all such operations
383 // causes screen update(), I reset last_inset_ to avoid such a problem.
385 // This is close to a hot-path.
386 LYXERR(Debug::DEBUG
, "BufferView::processUpdateFlags()"
387 << "[fitcursor = " << (flags
& Update::FitCursor
)
388 << ", forceupdate = " << (flags
& Update::Force
)
389 << ", singlepar = " << (flags
& Update::SinglePar
)
390 << "] buffer: " << &buffer_
);
392 buffer_
.updateMacros();
394 // Now do the first drawing step if needed. This consists on updating
395 // the CoordCache in updateMetrics().
396 // The second drawing step is done in WorkArea::redraw() if needed.
398 // Case when no explicit update is requested.
400 // no need to redraw anything.
401 d
->update_strategy_
= NoScreenUpdate
;
405 if (flags
== Update::Decoration
) {
406 d
->update_strategy_
= DecorationUpdate
;
411 if (flags
== Update::FitCursor
412 || flags
== (Update::Decoration
| Update::FitCursor
)) {
413 // tell the frontend to update the screen if needed.
418 if (flags
& Update::Decoration
) {
419 d
->update_strategy_
= DecorationUpdate
;
423 // no screen update is needed.
424 d
->update_strategy_
= NoScreenUpdate
;
428 bool const full_metrics
= flags
& Update::Force
|| !singleParUpdate();
431 // We have to update the full screen metrics.
434 if (!(flags
& Update::FitCursor
)) {
435 // Nothing to do anymore. Trigger a redraw and return
440 // updateMetrics() does not update paragraph position
441 // This is done at draw() time. So we need a redraw!
445 // The cursor is off screen so ensure it is visible.
452 void BufferView::updateScrollbar()
457 // We prefer fixed size line scrolling.
458 d
->scrollbarParameters_
.single_step
= defaultRowHeight();
459 // We prefer full screen page scrolling.
460 d
->scrollbarParameters_
.page_step
= height_
;
462 Text
& t
= buffer_
.text();
463 TextMetrics
& tm
= d
->text_metrics_
[&t
];
465 LYXERR(Debug::GUI
, " Updating scrollbar: height: "
466 << t
.paragraphs().size()
467 << " curr par: " << d
->cursor_
.bottom().pit()
468 << " default height " << defaultRowHeight());
470 size_t const parsize
= t
.paragraphs().size();
471 if (d
->par_height_
.size() != parsize
) {
472 d
->par_height_
.clear();
473 // FIXME: We assume a default paragraph height of 2 rows. This
474 // should probably be pondered with the screen width.
475 d
->par_height_
.resize(parsize
, defaultRowHeight() * 2);
478 // Look at paragraph heights on-screen
479 pair
<pit_type
, ParagraphMetrics
const *> first
= tm
.first();
480 pair
<pit_type
, ParagraphMetrics
const *> last
= tm
.last();
481 for (pit_type pit
= first
.first
; pit
<= last
.first
; ++pit
) {
482 d
->par_height_
[pit
] = tm
.parMetrics(pit
).height();
483 LYXERR(Debug::SCROLLING
, "storing height for pit " << pit
<< " : "
484 << d
->par_height_
[pit
]);
487 int top_pos
= first
.second
->position() - first
.second
->ascent();
488 int bottom_pos
= last
.second
->position() + last
.second
->descent();
489 bool first_visible
= first
.first
== 0 && top_pos
>= 0;
490 bool last_visible
= last
.first
+ 1 == int(parsize
) && bottom_pos
<= height_
;
491 if (first_visible
&& last_visible
) {
492 d
->scrollbarParameters_
.min
= 0;
493 d
->scrollbarParameters_
.max
= 0;
497 d
->scrollbarParameters_
.min
= top_pos
;
498 for (size_t i
= 0; i
!= size_t(first
.first
); ++i
)
499 d
->scrollbarParameters_
.min
-= d
->par_height_
[i
];
500 d
->scrollbarParameters_
.max
= bottom_pos
;
501 for (size_t i
= last
.first
+ 1; i
!= parsize
; ++i
)
502 d
->scrollbarParameters_
.max
+= d
->par_height_
[i
];
504 d
->scrollbarParameters_
.position
= 0;
505 // The reference is the top position so we remove one page.
506 d
->scrollbarParameters_
.max
-= d
->scrollbarParameters_
.page_step
;
510 ScrollbarParameters
const & BufferView::scrollbarParameters() const
512 return d
->scrollbarParameters_
;
516 docstring
BufferView::toolTip(int x
, int y
) const
518 // Get inset under mouse, if there is one.
519 Inset
const * covering_inset
= getCoveringInset(buffer_
.text(), x
, y
);
521 // No inset, no tooltip...
523 return covering_inset
->toolTip(*this, x
, y
);
527 docstring
BufferView::contextMenu(int x
, int y
) const
529 // Get inset under mouse, if there is one.
530 Inset
const * covering_inset
= getCoveringInset(buffer_
.text(), x
, y
);
532 return covering_inset
->contextMenu(*this, x
, y
);
534 return buffer_
.inset().contextMenu(*this, x
, y
);
538 void BufferView::scrollDocView(int value
)
540 int const offset
= value
- d
->scrollbarParameters_
.position
;
541 // If the offset is less than 2 screen height, prefer to scroll instead.
542 if (abs(offset
) <= 2 * height_
) {
547 // cut off at the top
548 if (value
<= d
->scrollbarParameters_
.min
) {
549 DocIterator dit
= doc_iterator_begin(buffer_
.inset());
551 LYXERR(Debug::SCROLLING
, "scroll to top");
555 // cut off at the bottom
556 if (value
>= d
->scrollbarParameters_
.max
) {
557 DocIterator dit
= doc_iterator_end(buffer_
.inset());
560 LYXERR(Debug::SCROLLING
, "scroll to bottom");
564 // find paragraph at target position
565 int par_pos
= d
->scrollbarParameters_
.min
;
567 for (; i
!= int(d
->par_height_
.size()); ++i
) {
568 par_pos
+= d
->par_height_
[i
];
569 if (par_pos
>= value
)
573 if (par_pos
< value
) {
574 // It seems we didn't find the correct pit so stay on the safe side and
576 LYXERR0("scrolling position not found!");
577 scrollDocView(d
->scrollbarParameters_
.max
);
581 DocIterator dit
= doc_iterator_begin(buffer_
.inset());
583 LYXERR(Debug::SCROLLING
, "value = " << value
<< " -> scroll to pit " << i
);
588 // FIXME: this method is not working well.
589 void BufferView::setCursorFromScrollbar()
591 TextMetrics
& tm
= d
->text_metrics_
[&buffer_
.text()];
593 int const height
= 2 * defaultRowHeight();
594 int const first
= height
;
595 int const last
= height_
- height
;
596 Cursor
& cur
= d
->cursor_
;
598 switch (cursorStatus(cur
)) {
600 // We reset the cursor because cursorStatus() does not
601 // work when the cursor is within mathed.
602 cur
.reset(buffer_
.inset());
603 tm
.setCursorFromCoordinates(cur
, 0, first
);
604 cur
.clearSelection();
607 // We reset the cursor because cursorStatus() does not
608 // work when the cursor is within mathed.
609 cur
.reset(buffer_
.inset());
610 tm
.setCursorFromCoordinates(cur
, 0, last
);
611 cur
.clearSelection();
614 int const y
= getPos(cur
, cur
.boundary()).y_
;
615 int const newy
= min(last
, max(y
, first
));
617 cur
.reset(buffer_
.inset());
618 tm
.setCursorFromCoordinates(cur
, 0, newy
);
624 Change
const BufferView::getCurrentChange() const
626 if (!d
->cursor_
.selection())
627 return Change(Change::UNCHANGED
);
629 DocIterator dit
= d
->cursor_
.selectionBegin();
630 return dit
.paragraph().lookupChange(dit
.pos());
634 // this could be used elsewhere as well?
635 // FIXME: This does not work within mathed!
636 CursorStatus
BufferView::cursorStatus(DocIterator
const & dit
) const
638 Point
const p
= getPos(dit
, dit
.boundary());
641 if (p
.y_
> workHeight())
647 void BufferView::saveBookmark(unsigned int idx
)
649 // tenatively save bookmark, id and pos will be used to
650 // acturately locate a bookmark in a 'live' lyx session.
651 // pit and pos will be updated with bottom level pit/pos
653 LyX::ref().session().bookmarks().save(
655 d
->cursor_
.bottom().pit(),
656 d
->cursor_
.bottom().pos(),
657 d
->cursor_
.paragraph().id(),
662 // emit message signal.
663 message(_("Save bookmark"));
667 bool BufferView::moveToPosition(pit_type bottom_pit
, pos_type bottom_pos
,
668 int top_id
, pos_type top_pos
)
670 bool success
= false;
673 d
->cursor_
.clearSelection();
675 // if a valid par_id is given, try it first
676 // This is the case for a 'live' bookmark when unique paragraph ID
677 // is used to track bookmarks.
679 dit
= buffer_
.getParFromID(top_id
);
681 dit
.pos() = min(dit
.paragraph().size(), top_pos
);
682 // Some slices of the iterator may not be
683 // reachable (e.g. closed collapsable inset)
684 // so the dociterator may need to be
685 // shortened. Otherwise, setCursor may crash
686 // lyx when the cursor can not be set to these
688 size_t const n
= dit
.depth();
689 for (size_t i
= 0; i
< n
; ++i
)
690 if (dit
[i
].inset().editable() != Inset::HIGHLY_EDITABLE
) {
698 // if top_id == 0, or searching through top_id failed
699 // This is the case for a 'restored' bookmark when only bottom
700 // (document level) pit was saved. Because of this, bookmark
701 // restoration is inaccurate. If a bookmark was within an inset,
702 // it will be restored to the left of the outmost inset that contains
704 if (bottom_pit
< int(buffer_
.paragraphs().size())) {
705 dit
= doc_iterator_begin(buffer_
.inset());
707 dit
.pit() = bottom_pit
;
708 dit
.pos() = min(bottom_pos
, dit
.paragraph().size());
713 // Note: only bottom (document) level pit is set.
715 // set the current font.
716 d
->cursor_
.setCurrentFont();
717 // To center the screen on this new position we need the
718 // paragraph position which is computed at draw() time.
719 // So we need a redraw!
729 void BufferView::translateAndInsert(char_type c
, Text
* t
, Cursor
& cur
)
731 if (lyxrc
.rtl_support
) {
732 if (d
->cursor_
.real_current_font
.isRightToLeft()) {
733 if (d
->intl_
.keymap
== Intl::PRIMARY
)
734 d
->intl_
.keyMapSec();
736 if (d
->intl_
.keymap
== Intl::SECONDARY
)
737 d
->intl_
.keyMapPrim();
741 d
->intl_
.getTransManager().translateAndInsert(c
, t
, cur
);
745 int BufferView::workWidth() const
751 void BufferView::showCursor()
753 showCursor(d
->cursor_
);
757 void BufferView::showCursor(DocIterator
const & dit
)
759 // We are not properly started yet, delay until resizing is
764 LYXERR(Debug::SCROLLING
, "recentering!");
766 CursorSlice
const & bot
= dit
.bottom();
767 TextMetrics
& tm
= d
->text_metrics_
[bot
.text()];
769 pos_type
const max_pit
= pos_type(bot
.text()->paragraphs().size() - 1);
770 int bot_pit
= bot
.pit();
771 if (bot_pit
> max_pit
) {
772 // FIXME: Why does this happen?
773 LYXERR0("bottom pit is greater that max pit: "
774 << bot_pit
<< " > " << max_pit
);
778 if (bot_pit
== tm
.first().first
- 1)
779 tm
.newParMetricsUp();
780 else if (bot_pit
== tm
.last().first
+ 1)
781 tm
.newParMetricsDown();
783 if (tm
.contains(bot_pit
)) {
784 ParagraphMetrics
const & pm
= tm
.parMetrics(bot_pit
);
785 BOOST_ASSERT(!pm
.rows().empty());
786 // FIXME: smooth scrolling doesn't work in mathed.
787 CursorSlice
const & cs
= dit
.innerTextSlice();
788 int offset
= coordOffset(dit
, dit
.boundary()).y_
;
789 int ypos
= pm
.position() + offset
;
790 Dimension
const & row_dim
=
791 pm
.getRow(cs
.pos(), dit
.boundary()).dimension();
792 if (ypos
- row_dim
.ascent() < 0)
793 scrollUp(- ypos
+ row_dim
.ascent());
794 else if (ypos
+ row_dim
.descent() > height_
)
795 scrollDown(ypos
- height_
+ row_dim
.descent());
796 // else, nothing to do, the cursor is already visible so we just return.
800 // fix inline completion position
801 if (d
->inlineCompletionPos
.fixIfBroken())
802 d
->inlineCompletionPos
= DocIterator();
804 tm
.redoParagraph(bot_pit
);
805 ParagraphMetrics
const & pm
= tm
.parMetrics(bot_pit
);
806 int offset
= coordOffset(dit
, dit
.boundary()).y_
;
808 d
->anchor_pit_
= bot_pit
;
809 CursorSlice
const & cs
= dit
.innerTextSlice();
810 Dimension
const & row_dim
=
811 pm
.getRow(cs
.pos(), dit
.boundary()).dimension();
813 if (d
->anchor_pit_
== 0)
814 d
->anchor_ypos_
= offset
+ pm
.ascent();
815 else if (d
->anchor_pit_
== max_pit
)
816 d
->anchor_ypos_
= height_
- offset
- row_dim
.descent();
818 d
->anchor_ypos_
= defaultRowHeight() * 2 - offset
- row_dim
.descent();
825 FuncStatus
BufferView::getStatus(FuncRequest
const & cmd
)
829 Cursor
& cur
= d
->cursor_
;
831 switch (cmd
.action
) {
834 flag
.enabled(buffer_
.undo().hasUndoStack());
837 flag
.enabled(buffer_
.undo().hasRedoStack());
839 case LFUN_FILE_INSERT
:
840 case LFUN_FILE_INSERT_PLAINTEXT_PARA
:
841 case LFUN_FILE_INSERT_PLAINTEXT
:
842 case LFUN_BOOKMARK_SAVE
:
843 // FIXME: Actually, these LFUNS should be moved to Text
844 flag
.enabled(cur
.inTexted());
846 case LFUN_FONT_STATE
:
847 case LFUN_LABEL_INSERT
:
848 case LFUN_INFO_INSERT
:
849 case LFUN_PARAGRAPH_GOTO
:
851 case LFUN_REFERENCE_NEXT
:
853 case LFUN_WORD_REPLACE
:
856 case LFUN_MARK_TOGGLE
:
857 case LFUN_SCREEN_RECENTER
:
858 case LFUN_BIBTEX_DATABASE_ADD
:
859 case LFUN_BIBTEX_DATABASE_DEL
:
860 case LFUN_STATISTICS
:
861 case LFUN_NEXT_INSET_TOGGLE
:
865 case LFUN_LABEL_GOTO
: {
866 flag
.enabled(!cmd
.argument().empty()
867 || getInsetByCode
<InsetRef
>(cur
, REF_CODE
));
871 case LFUN_CHANGES_TRACK
:
873 flag
.setOnOff(buffer_
.params().trackChanges
);
876 case LFUN_CHANGES_OUTPUT
:
878 flag
.setOnOff(buffer_
.params().outputChanges
);
881 case LFUN_CHANGES_MERGE
:
882 case LFUN_CHANGE_NEXT
:
883 case LFUN_ALL_CHANGES_ACCEPT
:
884 case LFUN_ALL_CHANGES_REJECT
:
885 // TODO: context-sensitive enabling of LFUNs
886 // In principle, these command should only be enabled if there
887 // is a change in the document. However, without proper
888 // optimizations, this will inevitably result in poor performance.
892 case LFUN_BUFFER_TOGGLE_COMPRESSION
: {
893 flag
.setOnOff(buffer_
.params().compressed
);
897 case LFUN_BUFFER_TOGGLE_EMBEDDING
: {
898 flag
.setOnOff(buffer_
.params().embedded
);
903 case LFUN_SCREEN_DOWN
:
905 case LFUN_SCREEN_UP_SELECT
:
906 case LFUN_SCREEN_DOWN_SELECT
:
910 case LFUN_LAYOUT_TABULAR
:
911 flag
.enabled(cur
.innerInsetOfType(TABULAR_CODE
));
915 flag
.enabled(!cur
.inset().forceEmptyLayout(cur
.idx()));
918 case LFUN_LAYOUT_PARAGRAPH
:
919 flag
.enabled(cur
.inset().allowParagraphCustomization(cur
.idx()));
922 case LFUN_INSET_SETTINGS
: {
923 InsetCode code
= cur
.inset().lyxCode();
927 enable
= cmd
.argument() == "tabular";
930 enable
= cmd
.argument() == "ert";
933 enable
= cmd
.argument() == "float";
936 enable
= cmd
.argument() == "wrap";
939 enable
= cmd
.argument() == "note";
942 enable
= cmd
.argument() == "branch";
945 enable
= cmd
.argument() == "box";
948 enable
= cmd
.argument() == "listings";
953 flag
.enabled(enable
);
957 case LFUN_DIALOG_SHOW_NEW_INSET
:
958 flag
.enabled(cur
.inset().lyxCode() != ERT_CODE
&&
959 cur
.inset().lyxCode() != LISTINGS_CODE
);
960 if (cur
.inset().lyxCode() == CAPTION_CODE
) {
962 if (cur
.inset().getStatus(cur
, cmd
, flag
))
975 bool BufferView::dispatch(FuncRequest
const & cmd
)
977 //lyxerr << [ cmd = " << cmd << "]" << endl;
979 // Make sure that the cached BufferView is correct.
980 LYXERR(Debug::ACTION
, " action[" << cmd
.action
<< ']'
981 << " arg[" << to_utf8(cmd
.argument()) << ']'
982 << " x[" << cmd
.x
<< ']'
983 << " y[" << cmd
.y
<< ']'
984 << " button[" << cmd
.button() << ']');
986 Cursor
& cur
= d
->cursor_
;
988 switch (cmd
.action
) {
991 cur
.message(_("Undo"));
992 cur
.clearSelection();
994 cur
.message(_("No further undo information"));
996 processUpdateFlags(Update::Force
| Update::FitCursor
);
1000 cur
.message(_("Redo"));
1001 cur
.clearSelection();
1002 if (!cur
.textRedo())
1003 cur
.message(_("No further redo information"));
1005 processUpdateFlags(Update::Force
| Update::FitCursor
);
1008 case LFUN_FONT_STATE
:
1009 cur
.message(cur
.currentState());
1012 case LFUN_BOOKMARK_SAVE
:
1013 saveBookmark(convert
<unsigned int>(to_utf8(cmd
.argument())));
1016 case LFUN_LABEL_GOTO
: {
1017 docstring label
= cmd
.argument();
1018 if (label
.empty()) {
1020 getInsetByCode
<InsetRef
>(d
->cursor_
,
1023 label
= inset
->getParam("reference");
1024 // persistent=false: use temp_bookmark
1034 case LFUN_PARAGRAPH_GOTO
: {
1035 int const id
= convert
<int>(to_utf8(cmd
.argument()));
1037 for (Buffer
* b
= &buffer_
; i
== 0 || b
!= &buffer_
;
1038 b
= theBufferList().next(b
)) {
1040 DocIterator dit
= b
->getParFromID(id
);
1042 LYXERR(Debug::INFO
, "No matching paragraph found! [" << id
<< "].");
1046 LYXERR(Debug::INFO
, "Paragraph " << dit
.paragraph().id()
1047 << " found in buffer `"
1048 << b
->absFileName() << "'.");
1050 if (b
== &buffer_
) {
1053 processUpdateFlags(Update::Force
| Update::FitCursor
);
1055 // Switch to other buffer view and resend cmd
1056 theLyXFunc().dispatch(FuncRequest(
1057 LFUN_BUFFER_SWITCH
, b
->absFileName()));
1058 theLyXFunc().dispatch(cmd
);
1065 case LFUN_NOTE_NEXT
:
1066 gotoInset(this, NOTE_CODE
, false);
1069 case LFUN_REFERENCE_NEXT
: {
1070 vector
<InsetCode
> tmp
;
1071 tmp
.push_back(LABEL_CODE
);
1072 tmp
.push_back(REF_CODE
);
1073 gotoInset(this, tmp
, true);
1077 case LFUN_CHANGES_TRACK
:
1078 buffer_
.params().trackChanges
= !buffer_
.params().trackChanges
;
1081 case LFUN_CHANGES_OUTPUT
:
1082 buffer_
.params().outputChanges
= !buffer_
.params().outputChanges
;
1083 if (buffer_
.params().outputChanges
) {
1084 bool dvipost
= LaTeXFeatures::isAvailable("dvipost");
1085 bool xcolorsoul
= LaTeXFeatures::isAvailable("soul") &&
1086 LaTeXFeatures::isAvailable("xcolor");
1088 if (!dvipost
&& !xcolorsoul
) {
1089 Alert::warning(_("Changes not shown in LaTeX output"),
1090 _("Changes will not be highlighted in LaTeX output, "
1091 "because neither dvipost nor xcolor/soul are installed.\n"
1092 "Please install these packages or redefine "
1093 "\\lyxadded and \\lyxdeleted in the LaTeX preamble."));
1094 } else if (!xcolorsoul
) {
1095 Alert::warning(_("Changes not shown in LaTeX output"),
1096 _("Changes will not be highlighted in LaTeX output "
1097 "when using pdflatex, because xcolor and soul are not installed.\n"
1098 "Please install both packages or redefine "
1099 "\\lyxadded and \\lyxdeleted in the LaTeX preamble."));
1104 case LFUN_CHANGE_NEXT
:
1105 findNextChange(this);
1108 case LFUN_CHANGES_MERGE
:
1109 if (findNextChange(this))
1110 showDialog("changes");
1113 case LFUN_ALL_CHANGES_ACCEPT
:
1114 // select complete document
1115 d
->cursor_
.reset(buffer_
.inset());
1116 d
->cursor_
.selHandle(true);
1117 buffer_
.text().cursorBottom(d
->cursor_
);
1118 // accept everything in a single step to support atomic undo
1119 buffer_
.text().acceptOrRejectChanges(d
->cursor_
, Text::ACCEPT
);
1122 case LFUN_ALL_CHANGES_REJECT
:
1123 // select complete document
1124 d
->cursor_
.reset(buffer_
.inset());
1125 d
->cursor_
.selHandle(true);
1126 buffer_
.text().cursorBottom(d
->cursor_
);
1127 // reject everything in a single step to support atomic undo
1128 // Note: reject does not work recursively; the user may have to repeat the operation
1129 buffer_
.text().acceptOrRejectChanges(d
->cursor_
, Text::REJECT
);
1132 case LFUN_WORD_FIND
:
1133 if (find(this, cmd
))
1136 message(_("String not found!"));
1139 case LFUN_WORD_REPLACE
: {
1140 bool has_deleted
= false;
1141 if (cur
.selection()) {
1142 DocIterator beg
= cur
.selectionBegin();
1143 DocIterator end
= cur
.selectionEnd();
1144 if (beg
.pit() == end
.pit()) {
1145 for (pos_type p
= beg
.pos() ; p
< end
.pos() ; ++p
) {
1146 if (cur
.paragraph().isDeleted(p
))
1151 replace(this, cmd
, has_deleted
);
1156 cur
.clearSelection();
1158 cur
.message(from_utf8(N_("Mark off")));
1162 cur
.clearSelection();
1165 cur
.message(from_utf8(N_("Mark on")));
1168 case LFUN_MARK_TOGGLE
:
1169 cur
.clearSelection();
1172 cur
.message(from_utf8(N_("Mark removed")));
1175 cur
.message(from_utf8(N_("Mark set")));
1180 case LFUN_SCREEN_RECENTER
:
1184 case LFUN_BIBTEX_DATABASE_ADD
: {
1185 Cursor tmpcur
= d
->cursor_
;
1186 findInset(tmpcur
, BIBTEX_CODE
, false);
1187 InsetBibtex
* inset
= getInsetByCode
<InsetBibtex
>(tmpcur
,
1190 if (inset
->addDatabase(to_utf8(cmd
.argument())))
1191 buffer_
.updateBibfilesCache();
1196 case LFUN_BIBTEX_DATABASE_DEL
: {
1197 Cursor tmpcur
= d
->cursor_
;
1198 findInset(tmpcur
, BIBTEX_CODE
, false);
1199 InsetBibtex
* inset
= getInsetByCode
<InsetBibtex
>(tmpcur
,
1202 if (inset
->delDatabase(to_utf8(cmd
.argument())))
1203 buffer_
.updateBibfilesCache();
1208 case LFUN_STATISTICS
: {
1209 DocIterator from
, to
;
1210 if (cur
.selection()) {
1211 from
= cur
.selectionBegin();
1212 to
= cur
.selectionEnd();
1214 from
= doc_iterator_begin(buffer_
.inset());
1215 to
= doc_iterator_end(buffer_
.inset());
1217 int const words
= countWords(from
, to
);
1218 int const chars
= countChars(from
, to
, false);
1219 int const chars_blanks
= countChars(from
, to
, true);
1221 if (cur
.selection())
1222 message
= _("Statistics for the selection:");
1224 message
= _("Statistics for the document:");
1227 message
+= bformat(_("%1$d words"), words
);
1229 message
+= _("One word");
1231 if (chars_blanks
!= 1)
1232 message
+= bformat(_("%1$d characters (including blanks)"),
1235 message
+= _("One character (including blanks)");
1238 message
+= bformat(_("%1$d characters (excluding blanks)"),
1241 message
+= _("One character (excluding blanks)");
1243 Alert::information(_("Statistics"), message
);
1247 case LFUN_BUFFER_TOGGLE_COMPRESSION
:
1248 // turn compression on/off
1249 buffer_
.params().compressed
= !buffer_
.params().compressed
;
1252 case LFUN_BUFFER_TOGGLE_EMBEDDING
: {
1253 // turn embedding on/off
1255 buffer_
.embeddedFiles().enable(!buffer_
.params().embedded
, buffer_
, true);
1256 } catch (ExceptionMessage
const & message
) {
1257 Alert::error(message
.title_
, message
.details_
);
1262 case LFUN_NEXT_INSET_TOGGLE
: {
1263 // this is the real function we want to invoke
1264 FuncRequest tmpcmd
= FuncRequest(LFUN_INSET_TOGGLE
, cmd
.origin
);
1265 // if there is an inset at cursor, see whether it
1267 Inset
* inset
= cur
.nextInset();
1269 if (inset
->isActive()) {
1270 Cursor tmpcur
= cur
;
1271 tmpcur
.pushBackward(*inset
);
1272 inset
->dispatch(tmpcur
, tmpcmd
);
1273 if (tmpcur
.result().dispatched()) {
1276 } else if (inset
->editable() == Inset::IS_EDITABLE
) {
1277 inset
->edit(cur
, true);
1280 // if it did not work, try the underlying inset.
1281 if (!cur
.result().dispatched())
1282 cur
.dispatch(tmpcmd
);
1284 if (!cur
.result().dispatched())
1285 // It did not work too; no action needed.
1287 cur
.clearSelection();
1288 processUpdateFlags(Update::SinglePar
| Update::FitCursor
);
1292 case LFUN_SCREEN_UP
:
1293 case LFUN_SCREEN_DOWN
: {
1294 Point p
= getPos(cur
, cur
.boundary());
1295 if (p
.y_
< 0 || p
.y_
> height_
) {
1296 // The cursor is off-screen so recenter before proceeding.
1298 p
= getPos(cur
, cur
.boundary());
1300 scroll(cmd
.action
== LFUN_SCREEN_UP
? - height_
: height_
);
1301 cur
.reset(buffer_
.inset());
1302 d
->text_metrics_
[&buffer_
.text()].editXY(cur
, p
.x_
, p
.y_
);
1303 //FIXME: what to do with cur.x_target()?
1312 case LFUN_SCREEN_UP_SELECT
: {
1313 cur
.selHandle(true);
1314 if (isTopScreen()) {
1315 lyx::dispatch(FuncRequest(LFUN_BUFFER_BEGIN_SELECT
));
1319 int y
= getPos(cur
, cur
.boundary()).y_
;
1320 int const ymin
= y
- height_
+ defaultRowHeight();
1321 while (y
> ymin
&& cur
.up())
1322 y
= getPos(cur
, cur
.boundary()).y_
;
1325 processUpdateFlags(Update::SinglePar
| Update::FitCursor
);
1329 case LFUN_SCREEN_DOWN_SELECT
: {
1330 cur
.selHandle(true);
1331 if (isBottomScreen()) {
1332 lyx::dispatch(FuncRequest(LFUN_BUFFER_END_SELECT
));
1336 int y
= getPos(cur
, cur
.boundary()).y_
;
1337 int const ymax
= y
+ height_
- defaultRowHeight();
1338 while (y
< ymax
&& cur
.down())
1339 y
= getPos(cur
, cur
.boundary()).y_
;
1342 processUpdateFlags(Update::SinglePar
| Update::FitCursor
);
1354 docstring
const BufferView::requestSelection()
1356 Cursor
& cur
= d
->cursor_
;
1358 if (!cur
.selection()) {
1359 d
->xsel_cache_
.set
= false;
1363 if (!d
->xsel_cache_
.set
||
1364 cur
.top() != d
->xsel_cache_
.cursor
||
1365 cur
.anchor_
.top() != d
->xsel_cache_
.anchor
)
1367 d
->xsel_cache_
.cursor
= cur
.top();
1368 d
->xsel_cache_
.anchor
= cur
.anchor_
.top();
1369 d
->xsel_cache_
.set
= cur
.selection();
1370 return cur
.selectionAsString(false);
1376 void BufferView::clearSelection()
1378 d
->cursor_
.clearSelection();
1379 // Clear the selection buffer. Otherwise a subsequent
1380 // middle-mouse-button paste would use the selection buffer,
1381 // not the more current external selection.
1382 cap::clearSelection();
1383 d
->xsel_cache_
.set
= false;
1384 // The buffer did not really change, but this causes the
1385 // redraw we need because we cleared the selection above.
1390 void BufferView::resize(int width
, int height
)
1392 // Update from work area
1396 // Clear the paragraph height cache.
1397 d
->par_height_
.clear();
1398 // Redo the metrics.
1403 Inset
const * BufferView::getCoveringInset(Text
const & text
,
1406 TextMetrics
& tm
= d
->text_metrics_
[&text
];
1407 Inset
* inset
= tm
.checkInsetHit(x
, y
);
1411 if (!inset
->descendable())
1412 // No need to go further down if the inset is not
1416 size_t cell_number
= inset
->nargs();
1417 // Check all the inner cell.
1418 for (size_t i
= 0; i
!= cell_number
; ++i
) {
1419 Text
const * inner_text
= inset
->getText(i
);
1422 Inset
const * inset_deeper
=
1423 getCoveringInset(*inner_text
, x
, y
);
1425 return inset_deeper
;
1433 void BufferView::mouseEventDispatch(FuncRequest
const & cmd0
)
1435 //lyxerr << "[ cmd0 " << cmd0 << "]" << endl;
1437 // This is only called for mouse related events including
1438 // LFUN_FILE_OPEN generated by drag-and-drop.
1439 FuncRequest cmd
= cmd0
;
1441 Cursor old
= cursor();
1443 cur
.push(buffer_
.inset());
1444 cur
.selection() = d
->cursor_
.selection();
1446 // Either the inset under the cursor or the
1447 // surrounding Text will handle this event.
1449 // make sure we stay within the screen...
1450 cmd
.y
= min(max(cmd
.y
, -1), height_
);
1452 if (cmd
.action
== LFUN_MOUSE_MOTION
&& cmd
.button() == mouse_button::none
) {
1454 // Get inset under mouse, if there is one.
1455 Inset
const * covering_inset
=
1456 getCoveringInset(buffer_
.text(), cmd
.x
, cmd
.y
);
1457 if (covering_inset
== d
->last_inset_
)
1458 // Same inset, no need to do anything...
1461 bool need_redraw
= false;
1462 // const_cast because of setMouseHover().
1463 Inset
* inset
= const_cast<Inset
*>(covering_inset
);
1465 // Remove the hint on the last hovered inset (if any).
1466 need_redraw
|= d
->last_inset_
->setMouseHover(false);
1468 // Highlighted the newly hovered inset (if any).
1469 need_redraw
|= inset
->setMouseHover(true);
1470 d
->last_inset_
= inset
;
1474 LYXERR(Debug::PAINTING
, "Mouse hover detected at: ("
1475 << cmd
.x
<< ", " << cmd
.y
<< ")");
1477 d
->update_strategy_
= DecorationUpdate
;
1479 // This event (moving without mouse click) is not passed further.
1480 // This should be changed if it is further utilized.
1485 // Build temporary cursor.
1486 Inset
* inset
= d
->text_metrics_
[&buffer_
.text()].editXY(cur
, cmd
.x
, cmd
.y
);
1488 // Put anchor at the same position.
1491 // Try to dispatch to an non-editable inset near this position
1492 // via the temp cursor. If the inset wishes to change the real
1493 // cursor it has to do so explicitly by using
1494 // cur.bv().cursor() = cur; (or similar)
1496 inset
->dispatch(cur
, cmd
);
1498 // Now dispatch to the temporary cursor. If the real cursor should
1499 // be modified, the inset's dispatch has to do so explicitly.
1500 if (!cur
.result().dispatched())
1503 // Notify left insets
1506 bool badcursor
= notifyCursorLeaves(old
, cur
);
1508 cursor().fixIfBroken();
1511 // Do we have a selection?
1512 theSelection().haveSelection(cursor().selection());
1514 // If the command has been dispatched,
1515 if (cur
.result().dispatched() || cur
.result().update())
1516 processUpdateFlags(cur
.result().update());
1520 void BufferView::lfunScroll(FuncRequest
const & cmd
)
1522 string
const scroll_type
= cmd
.getArg(0);
1523 int const scroll_step
=
1524 (scroll_type
== "line")? d
->scrollbarParameters_
.single_step
1525 : (scroll_type
== "page")? d
->scrollbarParameters_
.page_step
: 0;
1526 if (scroll_step
== 0)
1528 string
const scroll_quantity
= cmd
.getArg(1);
1529 if (scroll_quantity
== "up")
1530 scrollUp(scroll_step
);
1531 else if (scroll_quantity
== "down")
1532 scrollDown(scroll_step
);
1534 int const scroll_value
= convert
<int>(scroll_quantity
);
1536 scroll(scroll_step
* scroll_value
);
1541 void BufferView::scroll(int y
)
1550 void BufferView::scrollDown(int offset
)
1552 Text
* text
= &buffer_
.text();
1553 TextMetrics
& tm
= d
->text_metrics_
[text
];
1554 int ymax
= height_
+ offset
;
1556 pair
<pit_type
, ParagraphMetrics
const *> last
= tm
.last();
1557 int bottom_pos
= last
.second
->position() + last
.second
->descent();
1558 if (last
.first
+ 1 == int(text
->paragraphs().size())) {
1559 if (bottom_pos
<= height_
)
1561 offset
= min(offset
, bottom_pos
- height_
);
1564 if (bottom_pos
> ymax
)
1566 tm
.newParMetricsDown();
1568 d
->anchor_ypos_
-= offset
;
1574 void BufferView::scrollUp(int offset
)
1576 Text
* text
= &buffer_
.text();
1577 TextMetrics
& tm
= d
->text_metrics_
[text
];
1578 int ymin
= - offset
;
1580 pair
<pit_type
, ParagraphMetrics
const *> first
= tm
.first();
1581 int top_pos
= first
.second
->position() - first
.second
->ascent();
1582 if (first
.first
== 0) {
1585 offset
= min(offset
, - top_pos
);
1590 tm
.newParMetricsUp();
1592 d
->anchor_ypos_
+= offset
;
1598 void BufferView::setCursorFromRow(int row
)
1603 buffer_
.texrow().getIdFromRow(row
, tmpid
, tmppos
);
1605 d
->cursor_
.reset(buffer_
.inset());
1607 buffer_
.text().setCursor(d
->cursor_
, 0, 0);
1609 buffer_
.text().setCursor(d
->cursor_
, buffer_
.getParFromID(tmpid
).pit(), tmppos
);
1613 void BufferView::gotoLabel(docstring
const & label
)
1615 Toc
& toc
= buffer().tocBackend().toc("label");
1616 TocIterator toc_it
= toc
.begin();
1617 TocIterator end
= toc
.end();
1618 for (; toc_it
!= end
; ++toc_it
) {
1619 if (label
== toc_it
->str())
1620 dispatch(toc_it
->action());
1622 //FIXME: We could do a bit more searching thanks to this:
1623 //InsetLabel const * inset = buffer_.insetLabel(label);
1627 TextMetrics
const & BufferView::textMetrics(Text
const * t
) const
1629 return const_cast<BufferView
*>(this)->textMetrics(t
);
1633 TextMetrics
& BufferView::textMetrics(Text
const * t
)
1635 TextMetricsCache::iterator tmc_it
= d
->text_metrics_
.find(t
);
1636 if (tmc_it
== d
->text_metrics_
.end()) {
1637 tmc_it
= d
->text_metrics_
.insert(
1638 make_pair(t
, TextMetrics(this, const_cast<Text
*>(t
)))).first
;
1640 return tmc_it
->second
;
1644 ParagraphMetrics
const & BufferView::parMetrics(Text
const * t
,
1647 return textMetrics(t
).parMetrics(pit
);
1651 int BufferView::workHeight() const
1657 void BufferView::setCursor(DocIterator
const & dit
)
1659 size_t const n
= dit
.depth();
1660 for (size_t i
= 0; i
< n
; ++i
)
1661 dit
[i
].inset().edit(d
->cursor_
, true);
1663 d
->cursor_
.setCursor(dit
);
1664 d
->cursor_
.selection() = false;
1668 bool BufferView::checkDepm(Cursor
& cur
, Cursor
& old
)
1670 // Would be wrong to delete anything if we have a selection.
1671 if (cur
.selection())
1674 bool need_anchor_change
= false;
1675 bool changed
= d
->cursor_
.text()->deleteEmptyParagraphMechanism(cur
, old
,
1676 need_anchor_change
);
1678 if (need_anchor_change
)
1686 updateLabels(buffer_
);
1694 bool BufferView::mouseSetCursor(Cursor
& cur
, bool select
)
1696 BOOST_ASSERT(&cur
.bv() == this);
1699 // this event will clear selection so we save selection for
1700 // persistent selection
1701 cap::saveSelection(cursor());
1703 // Has the cursor just left the inset?
1704 bool badcursor
= false;
1705 bool leftinset
= (&d
->cursor_
.inset() != &cur
.inset());
1707 d
->cursor_
.fixIfBroken();
1708 badcursor
= notifyCursorLeaves(d
->cursor_
, cur
);
1713 // FIXME: shift-mouse selection doesn't work well across insets.
1714 bool do_selection
= select
&& &d
->cursor_
.anchor().inset() == &cur
.inset();
1716 // do the dEPM magic if needed
1717 // FIXME: (1) move this to InsetText::notifyCursorLeaves?
1718 // FIXME: (2) if we had a working InsetText::notifyCursorLeaves,
1719 // the leftinset bool would not be necessary (badcursor instead).
1720 bool update
= leftinset
;
1721 if (!do_selection
&& !badcursor
&& d
->cursor_
.inTexted())
1722 update
|= checkDepm(cur
, d
->cursor_
);
1724 d
->cursor_
.setCursor(cur
);
1725 d
->cursor_
.boundary(cur
.boundary());
1727 d
->cursor_
.setSelection();
1729 d
->cursor_
.clearSelection();
1731 d
->cursor_
.finishUndo();
1732 d
->cursor_
.setCurrentFont();
1737 void BufferView::putSelectionAt(DocIterator
const & cur
,
1738 int length
, bool backwards
)
1740 d
->cursor_
.clearSelection();
1746 d
->cursor_
.pos() += length
;
1747 d
->cursor_
.setSelection(d
->cursor_
, -length
);
1749 d
->cursor_
.setSelection(d
->cursor_
, length
);
1751 // Ensure a redraw happens in any case because the new selection could
1752 // possibly be on the same screen as the previous selection.
1753 processUpdateFlags(Update::Force
| Update::FitCursor
);
1757 Cursor
& BufferView::cursor()
1763 Cursor
const & BufferView::cursor() const
1769 pit_type
BufferView::anchor_ref() const
1771 return d
->anchor_pit_
;
1775 bool BufferView::singleParUpdate()
1777 Text
& buftext
= buffer_
.text();
1778 pit_type
const bottom_pit
= d
->cursor_
.bottom().pit();
1779 TextMetrics
& tm
= textMetrics(&buftext
);
1780 int old_height
= tm
.parMetrics(bottom_pit
).height();
1782 // make sure inline completion pointer is ok
1783 if (d
->inlineCompletionPos
.fixIfBroken())
1784 d
->inlineCompletionPos
= DocIterator();
1786 // In Single Paragraph mode, rebreak only
1787 // the (main text, not inset!) paragraph containing the cursor.
1788 // (if this paragraph contains insets etc., rebreaking will
1789 // recursively descend)
1790 tm
.redoParagraph(bottom_pit
);
1791 ParagraphMetrics
const & pm
= tm
.parMetrics(bottom_pit
);
1792 if (pm
.height() != old_height
)
1793 // Paragraph height has changed so we cannot proceed to
1794 // the singlePar optimisation.
1797 d
->update_strategy_
= SingleParUpdate
;
1799 LYXERR(Debug::PAINTING
, "\ny1: " << pm
.position() - pm
.ascent()
1800 << " y2: " << pm
.position() + pm
.descent()
1801 << " pit: " << bottom_pit
1802 << " singlepar: 1");
1807 void BufferView::updateMetrics()
1809 Text
& buftext
= buffer_
.text();
1810 pit_type
const npit
= int(buftext
.paragraphs().size());
1812 // Clear out the position cache in case of full screen redraw,
1813 d
->coord_cache_
.clear();
1815 // Clear out paragraph metrics to avoid having invalid metrics
1816 // in the cache from paragraphs not relayouted below
1817 // The complete text metrics will be redone.
1818 d
->text_metrics_
.clear();
1820 TextMetrics
& tm
= textMetrics(&buftext
);
1822 // make sure inline completion pointer is ok
1823 if (d
->inlineCompletionPos
.fixIfBroken())
1824 d
->inlineCompletionPos
= DocIterator();
1826 if (d
->anchor_pit_
>= npit
)
1827 // The anchor pit must have been deleted...
1828 d
->anchor_pit_
= npit
- 1;
1830 // Rebreak anchor paragraph.
1831 tm
.redoParagraph(d
->anchor_pit_
);
1832 ParagraphMetrics
& anchor_pm
= tm
.par_metrics_
[d
->anchor_pit_
];
1835 if (d
->anchor_pit_
== 0) {
1836 int scrollRange
= d
->scrollbarParameters_
.max
- d
->scrollbarParameters_
.min
;
1838 // Complete buffer visible? Then it's easy.
1839 if (scrollRange
== 0)
1840 d
->anchor_ypos_
= anchor_pm
.ascent();
1842 // FIXME: Some clever handling needed to show
1843 // the _first_ paragraph up to the top if the cursor is
1844 // in the first line.
1846 anchor_pm
.setPosition(d
->anchor_ypos_
);
1848 LYXERR(Debug::PAINTING
, "metrics: "
1849 << " anchor pit = " << d
->anchor_pit_
1850 << " anchor ypos = " << d
->anchor_ypos_
);
1852 // Redo paragraphs above anchor if necessary.
1853 int y1
= d
->anchor_ypos_
- anchor_pm
.ascent();
1854 // We are now just above the anchor paragraph.
1855 pit_type pit1
= d
->anchor_pit_
- 1;
1856 for (; pit1
>= 0 && y1
>= 0; --pit1
) {
1857 tm
.redoParagraph(pit1
);
1858 ParagraphMetrics
& pm
= tm
.par_metrics_
[pit1
];
1860 // Save the paragraph position in the cache.
1865 // Redo paragraphs below the anchor if necessary.
1866 int y2
= d
->anchor_ypos_
+ anchor_pm
.descent();
1867 // We are now just below the anchor paragraph.
1868 pit_type pit2
= d
->anchor_pit_
+ 1;
1869 for (; pit2
< npit
&& y2
<= height_
; ++pit2
) {
1870 tm
.redoParagraph(pit2
);
1871 ParagraphMetrics
& pm
= tm
.par_metrics_
[pit2
];
1873 // Save the paragraph position in the cache.
1878 LYXERR(Debug::PAINTING
, "Metrics: "
1879 << " anchor pit = " << d
->anchor_pit_
1880 << " anchor ypos = " << d
->anchor_ypos_
1883 << " pit1 = " << pit1
1884 << " pit2 = " << pit2
);
1886 d
->update_strategy_
= FullScreenUpdate
;
1888 if (lyxerr
.debugging(Debug::WORKAREA
)) {
1889 LYXERR(Debug::WORKAREA
, "BufferView::updateMetrics");
1890 d
->coord_cache_
.dump();
1895 void BufferView::insertLyXFile(FileName
const & fname
)
1897 BOOST_ASSERT(d
->cursor_
.inTexted());
1899 // Get absolute path of file and add ".lyx"
1900 // to the filename if necessary
1901 FileName filename
= fileSearch(string(), fname
.absFilename(), "lyx");
1903 docstring
const disp_fn
= makeDisplayPath(filename
.absFilename());
1904 // emit message signal.
1905 message(bformat(_("Inserting document %1$s..."), disp_fn
));
1908 Buffer
buf("", false);
1909 if (buf
.loadLyXFile(filename
)) {
1910 ErrorList
& el
= buffer_
.errorList("Parse");
1911 // Copy the inserted document error list into the current buffer one.
1912 el
= buf
.errorList("Parse");
1913 buffer_
.undo().recordUndo(d
->cursor_
);
1914 cap::pasteParagraphList(d
->cursor_
, buf
.paragraphs(),
1915 buf
.params().documentClassPtr(), el
);
1916 res
= _("Document %1$s inserted.");
1918 res
= _("Could not insert document %1$s");
1923 // emit message signal.
1924 message(bformat(res
, disp_fn
));
1925 buffer_
.errors("Parse");
1929 Point
BufferView::coordOffset(DocIterator
const & dit
, bool boundary
) const
1935 // Addup contribution of nested insets, from inside to outside,
1936 // keeping the outer paragraph for a special handling below
1937 for (size_t i
= dit
.depth() - 1; i
>= 1; --i
) {
1938 CursorSlice
const & sl
= dit
[i
];
1942 // get relative position inside sl.inset()
1943 sl
.inset().cursorPos(*this, sl
, boundary
&& (i
+ 1 == dit
.depth()), xx
, yy
);
1945 // Make relative position inside of the edited inset relative to sl.inset()
1949 // In case of an RTL inset, the edited inset will be positioned to the left
1952 bool boundary_i
= boundary
&& i
+ 1 == dit
.depth();
1953 bool rtl
= textMetrics(sl
.text()).isRTL(sl
, boundary_i
);
1958 // remember width for the case that sl.inset() is positioned in an RTL inset
1959 if (i
&& dit
[i
- 1].text()) {
1960 // If this Inset is inside a Text Inset, retrieve the Dimension
1961 // from the containing text instead of using Inset::dimension() which
1962 // might not be implemented.
1963 // FIXME (Abdel 23/09/2007): this is a bit messy because of the
1964 // elimination of Inset::dim_ cache. This coordOffset() method needs
1965 // to be rewritten in light of the new design.
1966 Dimension
const & dim
= parMetrics(dit
[i
- 1].text(),
1967 dit
[i
- 1].pit()).insetDimension(&sl
.inset());
1970 Dimension
const dim
= sl
.inset().dimension(*this);
1974 //lyxerr << "Cursor::getPos, i: "
1975 // << i << " x: " << xx << " y: " << y << endl;
1978 // Add contribution of initial rows of outermost paragraph
1979 CursorSlice
const & sl
= dit
[0];
1980 TextMetrics
const & tm
= textMetrics(sl
.text());
1981 ParagraphMetrics
const & pm
= tm
.parMetrics(sl
.pit());
1982 BOOST_ASSERT(!pm
.rows().empty());
1983 y
-= pm
.rows()[0].ascent();
1985 // FIXME: document this mess
1987 if (sl
.pos() > 0 && dit
.depth() == 1) {
1989 if (pos
&& boundary
)
1991 // lyxerr << "coordOffset: boundary:" << boundary << " depth:" << dit.depth() << " pos:" << pos << " sl.pos:" << sl.pos() << endl;
1992 rend
= pm
.pos2row(pos
);
1994 rend
= pm
.pos2row(sl
.pos());
1996 size_t rend
= pm
.pos2row(sl
.pos());
1998 for (size_t rit
= 0; rit
!= rend
; ++rit
)
1999 y
+= pm
.rows()[rit
].height();
2000 y
+= pm
.rows()[rend
].ascent();
2002 TextMetrics
const & bottom_tm
= textMetrics(dit
.bottom().text());
2004 // Make relative position from the nested inset now bufferview absolute.
2005 int xx
= bottom_tm
.cursorX(dit
.bottom(), boundary
&& dit
.depth() == 1);
2008 // In the RTL case place the nested inset at the left of the cursor in
2009 // the outer paragraph
2010 bool boundary_1
= boundary
&& 1 == dit
.depth();
2011 bool rtl
= bottom_tm
.isRTL(dit
.bottom(), boundary_1
);
2019 Point
BufferView::getPos(DocIterator
const & dit
, bool boundary
) const
2021 CursorSlice
const & bot
= dit
.bottom();
2022 TextMetrics
const & tm
= textMetrics(bot
.text());
2023 if (!tm
.contains(bot
.pit()))
2024 return Point(-1, -1);
2026 Point p
= coordOffset(dit
, boundary
); // offset from outer paragraph
2027 p
.y_
+= tm
.parMetrics(bot
.pit()).position();
2032 void BufferView::draw(frontend::Painter
& pain
)
2034 LYXERR(Debug::PAINTING
, "\t\t*** START DRAWING ***");
2035 Text
& text
= buffer_
.text();
2036 TextMetrics
const & tm
= d
->text_metrics_
[&text
];
2037 int const y
= tm
.first().second
->position();
2038 PainterInfo
pi(this, pain
);
2040 switch (d
->update_strategy_
) {
2042 case NoScreenUpdate
:
2043 // If no screen painting is actually needed, only some the different
2044 // coordinates of insets and paragraphs needs to be updated.
2045 pi
.full_repaint
= true;
2046 pi
.pain
.setDrawingEnabled(false);
2050 case SingleParUpdate
:
2051 pi
.full_repaint
= false;
2052 // In general, only the current row of the outermost paragraph
2053 // will be redrawn. Particular cases where selection spans
2054 // multiple paragraph are correctly detected in TextMetrics.
2058 case DecorationUpdate
:
2059 // FIXME: We should also distinguish DecorationUpdate to avoid text
2060 // drawing if possible. This is not possible to do easily right now
2061 // because of the single backing pixmap.
2063 case FullScreenUpdate
:
2064 // The whole screen, including insets, will be refreshed.
2065 pi
.full_repaint
= true;
2067 // Clear background.
2068 pain
.fillRectangle(0, 0, width_
, height_
,
2069 buffer_
.inset().backgroundColor());
2074 // and possibly grey out below
2075 pair
<pit_type
, ParagraphMetrics
const *> lastpm
= tm
.last();
2076 int const y2
= lastpm
.second
->position() + lastpm
.second
->descent();
2078 pain
.fillRectangle(0, y2
, width_
, height_
- y2
, Color_bottomarea
);
2081 LYXERR(Debug::PAINTING
, "\n\t\t*** END DRAWING ***");
2083 // The scrollbar needs an update.
2086 // Normalize anchor for next time
2087 pair
<pit_type
, ParagraphMetrics
const *> firstpm
= tm
.first();
2088 pair
<pit_type
, ParagraphMetrics
const *> lastpm
= tm
.last();
2089 for (pit_type pit
= firstpm
.first
; pit
<= lastpm
.first
; ++pit
) {
2090 ParagraphMetrics
const & pm
= tm
.parMetrics(pit
);
2091 if (pm
.position() + pm
.descent() > 0) {
2092 d
->anchor_pit_
= pit
;
2093 d
->anchor_ypos_
= pm
.position();
2097 LYXERR(Debug::PAINTING
, "Found new anchor pit = " << d
->anchor_pit_
2098 << " anchor ypos = " << d
->anchor_ypos_
);
2102 void BufferView::message(docstring
const & msg
)
2105 d
->gui_
->message(msg
);
2109 void BufferView::showDialog(string
const & name
)
2112 d
->gui_
->showDialog(name
, string());
2116 void BufferView::showDialog(string
const & name
,
2117 string
const & data
, Inset
* inset
)
2120 d
->gui_
->showDialog(name
, data
, inset
);
2124 void BufferView::updateDialog(string
const & name
, string
const & data
)
2127 d
->gui_
->updateDialog(name
, data
);
2131 void BufferView::setGuiDelegate(frontend::GuiBufferViewDelegate
* gui
)
2137 // FIXME: Move this out of BufferView again
2138 docstring
BufferView::contentsOfPlaintextFile(FileName
const & fname
)
2140 if (!fname
.isReadableFile()) {
2141 docstring
const error
= from_ascii(strerror(errno
));
2142 docstring
const file
= makeDisplayPath(fname
.absFilename(), 50);
2143 docstring
const text
=
2144 bformat(_("Could not read the specified document\n"
2145 "%1$s\ndue to the error: %2$s"), file
, error
);
2146 Alert::error(_("Could not read file"), text
);
2150 if (!fname
.isReadableFile()) {
2151 docstring
const file
= makeDisplayPath(fname
.absFilename(), 50);
2152 docstring
const text
=
2153 bformat(_("%1$s\n is not readable."), file
);
2154 Alert::error(_("Could not open file"), text
);
2158 // FIXME UNICODE: We don't know the encoding of the file
2159 docstring file_content
= fname
.fileContents("UTF-8");
2160 if (file_content
.empty()) {
2161 Alert::error(_("Reading not UTF-8 encoded file"),
2162 _("The file is not UTF-8 encoded.\n"
2163 "It will be read as local 8Bit-encoded.\n"
2164 "If this does not give the correct result\n"
2165 "then please change the encoding of the file\n"
2166 "to UTF-8 with a program other than LyX.\n"));
2167 file_content
= fname
.fileContents("local8bit");
2170 return normalize_c(file_content
);
2174 void BufferView::insertPlaintextFile(FileName
const & f
, bool asParagraph
)
2176 docstring
const tmpstr
= contentsOfPlaintextFile(f
);
2181 Cursor
& cur
= cursor();
2182 cap::replaceSelection(cur
);
2183 buffer_
.undo().recordUndo(cur
);
2185 cur
.innerText()->insertStringAsParagraphs(cur
, tmpstr
);
2187 cur
.innerText()->insertStringAsLines(cur
, tmpstr
);
2194 docstring
const & BufferView::inlineCompletion() const
2196 return d
->inlineCompletion
;
2200 size_t const & BufferView::inlineCompletionUniqueChars() const
2202 return d
->inlineCompletionUniqueChars
;
2206 DocIterator
const & BufferView::inlineCompletionPos() const
2208 return d
->inlineCompletionPos
;
2212 bool samePar(DocIterator
const & a
, DocIterator
const & b
)
2214 if (a
.empty() && b
.empty())
2216 if (a
.empty() || b
.empty())
2218 return &a
.innerParagraph() == &b
.innerParagraph();
2222 void BufferView::setInlineCompletion(Cursor
& cur
, DocIterator
const & pos
,
2223 docstring
const & completion
, size_t uniqueChars
)
2225 uniqueChars
= min(completion
.size(), uniqueChars
);
2226 bool changed
= d
->inlineCompletion
!= completion
2227 || d
->inlineCompletionUniqueChars
!= uniqueChars
;
2228 bool singlePar
= true;
2229 d
->inlineCompletion
= completion
;
2230 d
->inlineCompletionUniqueChars
= min(completion
.size(), uniqueChars
);
2232 //lyxerr << "setInlineCompletion pos=" << pos << " completion=" << completion << " uniqueChars=" << uniqueChars << std::endl;
2235 DocIterator
const & old
= d
->inlineCompletionPos
;
2237 //lyxerr << "inlineCompletionPos changed" << std::endl;
2238 // old or pos are in another paragraph?
2239 if ((!samePar(cur
, pos
) && !pos
.empty())
2240 || (!samePar(cur
, old
) && !old
.empty())) {
2242 //lyxerr << "different paragraph" << std::endl;
2244 d
->inlineCompletionPos
= pos
;
2249 if (singlePar
&& !(cur
.disp_
.update() & Update::Force
))
2250 cur
.updateFlags(cur
.disp_
.update() | Update::SinglePar
);
2252 cur
.updateFlags(cur
.disp_
.update() | Update::Force
);