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 tmpdit
= doc_iterator_begin(tmpdit
.bottom().inset());
154 if (!findNextInset(tmpdit
, codes
, contents
))
165 /// Looks for next inset with the given code
166 void findInset(DocIterator
& dit
, InsetCode code
, bool same_content
)
168 findInset(dit
, vector
<InsetCode
>(1, code
), same_content
);
172 /// Moves cursor to the next inset with one of the given codes.
173 void gotoInset(BufferView
* bv
, vector
<InsetCode
> const & codes
,
176 Cursor tmpcur
= bv
->cursor();
177 if (!findInset(tmpcur
, codes
, same_content
)) {
178 bv
->cursor().message(_("No more insets"));
182 tmpcur
.clearSelection();
183 bv
->setCursor(tmpcur
);
188 /// Moves cursor to the next inset with given code.
189 void gotoInset(BufferView
* bv
, InsetCode code
, bool same_content
)
191 gotoInset(bv
, vector
<InsetCode
>(1, code
), same_content
);
195 /// A map from a Text to the associated text metrics
196 typedef map
<Text
const *, TextMetrics
> TextMetricsCache
;
198 enum ScreenUpdateStrategy
{
208 /////////////////////////////////////////////////////////////////////
212 /////////////////////////////////////////////////////////////////////
214 struct BufferView::Private
216 Private(BufferView
& bv
): wh_(0), cursor_(bv
),
217 anchor_pit_(0), anchor_ypos_(0),
218 inlineCompletionUniqueChars_(0),
219 last_inset_(0), gui_(0)
223 ScrollbarParameters scrollbarParameters_
;
225 ScreenUpdateStrategy update_strategy_
;
227 CoordCache coord_cache_
;
229 /// Estimated average par height for scrollbar.
231 /// this is used to handle XSelection events in the right manner.
240 pit_type anchor_pit_
;
244 vector
<int> par_height_
;
247 DocIterator inlineCompletionPos_
;
249 docstring inlineCompletion_
;
251 size_t inlineCompletionUniqueChars_
;
253 /// keyboard mapping object.
256 /// last visited inset.
257 /** kept to send setMouseHover(false).
258 * Not owned, so don't delete.
262 mutable TextMetricsCache text_metrics_
;
265 /** Not owned, so don't delete.
267 frontend::GuiBufferViewDelegate
* gui_
;
269 /// Cache for Find Next
270 FuncRequest search_request_cache_
;
274 BufferView::BufferView(Buffer
& buf
)
275 : width_(0), height_(0), full_screen_(false), buffer_(buf
), d(new Private(*this))
277 d
->xsel_cache_
.set
= false;
278 d
->intl_
.initKeyMapper(lyxrc
.use_kbmap
);
280 d
->cursor_
.push(buffer_
.inset());
281 d
->cursor_
.resetAnchor();
282 d
->cursor_
.setCurrentFont();
284 if (graphics::Previews::status() != LyXRC::PREVIEW_OFF
)
285 thePreviews().generateBufferPreviews(buffer_
);
289 BufferView::~BufferView()
291 // current buffer is going to be switched-off, save cursor pos
292 // Ideally, the whole cursor stack should be saved, but session
293 // currently can only handle bottom (whole document) level pit and pos.
294 // That is to say, if a cursor is in a nested inset, it will be
295 // restore to the left of the top level inset.
296 LastFilePosSection::FilePos fp
;
297 fp
.pit
= d
->cursor_
.bottom().pit();
298 fp
.pos
= d
->cursor_
.bottom().pos();
299 theSession().lastFilePos().save(buffer_
.fileName(), fp
);
305 int BufferView::rightMargin() const
307 // The additional test for the case the outliner is opened.
309 !lyxrc
.full_screen_limit
||
310 width_
< lyxrc
.full_screen_width
+ 20)
313 return (width_
- lyxrc
.full_screen_width
) / 2;
317 int BufferView::leftMargin() const
319 return rightMargin();
323 bool BufferView::isTopScreen() const
325 return d
->scrollbarParameters_
.position
== d
->scrollbarParameters_
.min
;
329 bool BufferView::isBottomScreen() const
331 return d
->scrollbarParameters_
.position
== d
->scrollbarParameters_
.max
;
335 Intl
& BufferView::getIntl()
341 Intl
const & BufferView::getIntl() const
347 CoordCache
& BufferView::coordCache()
349 return d
->coord_cache_
;
353 CoordCache
const & BufferView::coordCache() const
355 return d
->coord_cache_
;
359 Buffer
& BufferView::buffer()
365 Buffer
const & BufferView::buffer() const
371 bool BufferView::fitCursor()
373 if (cursorStatus(d
->cursor_
) == CUR_INSIDE
) {
374 frontend::FontMetrics
const & fm
=
375 theFontMetrics(d
->cursor_
.getFont().fontInfo());
376 int const asc
= fm
.maxAscent();
377 int const des
= fm
.maxDescent();
378 Point
const p
= getPos(d
->cursor_
, d
->cursor_
.boundary());
379 if (p
.y_
- asc
>= 0 && p
.y_
+ des
< height_
)
386 void BufferView::processUpdateFlags(Update::flags flags
)
388 // last_inset_ points to the last visited inset. This pointer may become
389 // invalid because of keyboard editing. Since all such operations
390 // causes screen update(), I reset last_inset_ to avoid such a problem.
392 // This is close to a hot-path.
393 LYXERR(Debug::DEBUG
, "BufferView::processUpdateFlags()"
394 << "[fitcursor = " << (flags
& Update::FitCursor
)
395 << ", forceupdate = " << (flags
& Update::Force
)
396 << ", singlepar = " << (flags
& Update::SinglePar
)
397 << "] buffer: " << &buffer_
);
399 buffer_
.updateMacros();
401 // Now do the first drawing step if needed. This consists on updating
402 // the CoordCache in updateMetrics().
403 // The second drawing step is done in WorkArea::redraw() if needed.
405 // Case when no explicit update is requested.
407 // no need to redraw anything.
408 d
->update_strategy_
= NoScreenUpdate
;
412 if (flags
== Update::Decoration
) {
413 d
->update_strategy_
= DecorationUpdate
;
418 if (flags
== Update::FitCursor
419 || flags
== (Update::Decoration
| Update::FitCursor
)) {
420 // tell the frontend to update the screen if needed.
425 if (flags
& Update::Decoration
) {
426 d
->update_strategy_
= DecorationUpdate
;
430 // no screen update is needed.
431 d
->update_strategy_
= NoScreenUpdate
;
435 bool const full_metrics
= flags
& Update::Force
|| !singleParUpdate();
438 // We have to update the full screen metrics.
441 if (!(flags
& Update::FitCursor
)) {
442 // Nothing to do anymore. Trigger a redraw and return
447 // updateMetrics() does not update paragraph position
448 // This is done at draw() time. So we need a redraw!
452 // The cursor is off screen so ensure it is visible.
459 void BufferView::updateScrollbar()
461 if (height_
== 0 && width_
== 0)
464 // We prefer fixed size line scrolling.
465 d
->scrollbarParameters_
.single_step
= defaultRowHeight();
466 // We prefer full screen page scrolling.
467 d
->scrollbarParameters_
.page_step
= height_
;
469 Text
& t
= buffer_
.text();
470 TextMetrics
& tm
= d
->text_metrics_
[&t
];
472 LYXERR(Debug::GUI
, " Updating scrollbar: height: "
473 << t
.paragraphs().size()
474 << " curr par: " << d
->cursor_
.bottom().pit()
475 << " default height " << defaultRowHeight());
477 size_t const parsize
= t
.paragraphs().size();
478 if (d
->par_height_
.size() != parsize
) {
479 d
->par_height_
.clear();
480 // FIXME: We assume a default paragraph height of 2 rows. This
481 // should probably be pondered with the screen width.
482 d
->par_height_
.resize(parsize
, defaultRowHeight() * 2);
485 // Look at paragraph heights on-screen
486 pair
<pit_type
, ParagraphMetrics
const *> first
= tm
.first();
487 pair
<pit_type
, ParagraphMetrics
const *> last
= tm
.last();
488 for (pit_type pit
= first
.first
; pit
<= last
.first
; ++pit
) {
489 d
->par_height_
[pit
] = tm
.parMetrics(pit
).height();
490 LYXERR(Debug::SCROLLING
, "storing height for pit " << pit
<< " : "
491 << d
->par_height_
[pit
]);
494 int top_pos
= first
.second
->position() - first
.second
->ascent();
495 int bottom_pos
= last
.second
->position() + last
.second
->descent();
496 bool first_visible
= first
.first
== 0 && top_pos
>= 0;
497 bool last_visible
= last
.first
+ 1 == int(parsize
) && bottom_pos
<= height_
;
498 if (first_visible
&& last_visible
) {
499 d
->scrollbarParameters_
.min
= 0;
500 d
->scrollbarParameters_
.max
= 0;
504 d
->scrollbarParameters_
.min
= top_pos
;
505 for (size_t i
= 0; i
!= size_t(first
.first
); ++i
)
506 d
->scrollbarParameters_
.min
-= d
->par_height_
[i
];
507 d
->scrollbarParameters_
.max
= bottom_pos
;
508 for (size_t i
= last
.first
+ 1; i
!= parsize
; ++i
)
509 d
->scrollbarParameters_
.max
+= d
->par_height_
[i
];
511 d
->scrollbarParameters_
.position
= 0;
512 // The reference is the top position so we remove one page.
513 d
->scrollbarParameters_
.max
-= d
->scrollbarParameters_
.page_step
;
517 ScrollbarParameters
const & BufferView::scrollbarParameters() const
519 return d
->scrollbarParameters_
;
523 docstring
BufferView::toolTip(int x
, int y
) const
525 // Get inset under mouse, if there is one.
526 Inset
const * covering_inset
= getCoveringInset(buffer_
.text(), x
, y
);
528 // No inset, no tooltip...
530 return covering_inset
->toolTip(*this, x
, y
);
534 docstring
BufferView::contextMenu(int x
, int y
) const
536 //If there is a selection, return the containing inset menu
537 if (d
->cursor_
.selection())
538 return d
->cursor_
.inset().contextMenu(*this, x
, y
);
540 // Get inset under mouse, if there is one.
541 Inset
const * covering_inset
= getCoveringInset(buffer_
.text(), x
, y
);
543 return covering_inset
->contextMenu(*this, x
, y
);
545 return buffer_
.inset().contextMenu(*this, x
, y
);
549 void BufferView::scrollDocView(int value
)
551 int const offset
= value
- d
->scrollbarParameters_
.position
;
552 // If the offset is less than 2 screen height, prefer to scroll instead.
553 if (abs(offset
) <= 2 * height_
) {
560 // cut off at the top
561 if (value
<= d
->scrollbarParameters_
.min
) {
562 DocIterator dit
= doc_iterator_begin(buffer_
.inset());
564 LYXERR(Debug::SCROLLING
, "scroll to top");
568 // cut off at the bottom
569 if (value
>= d
->scrollbarParameters_
.max
) {
570 DocIterator dit
= doc_iterator_end(buffer_
.inset());
573 LYXERR(Debug::SCROLLING
, "scroll to bottom");
577 // find paragraph at target position
578 int par_pos
= d
->scrollbarParameters_
.min
;
580 for (; i
!= int(d
->par_height_
.size()); ++i
) {
581 par_pos
+= d
->par_height_
[i
];
582 if (par_pos
>= value
)
586 if (par_pos
< value
) {
587 // It seems we didn't find the correct pit so stay on the safe side and
589 LYXERR0("scrolling position not found!");
590 scrollDocView(d
->scrollbarParameters_
.max
);
594 DocIterator dit
= doc_iterator_begin(buffer_
.inset());
596 LYXERR(Debug::SCROLLING
, "value = " << value
<< " -> scroll to pit " << i
);
601 // FIXME: this method is not working well.
602 void BufferView::setCursorFromScrollbar()
604 TextMetrics
& tm
= d
->text_metrics_
[&buffer_
.text()];
606 int const height
= 2 * defaultRowHeight();
607 int const first
= height
;
608 int const last
= height_
- height
;
610 Cursor
const & oldcur
= d
->cursor_
;
612 switch (cursorStatus(oldcur
)) {
620 int const y
= getPos(oldcur
, oldcur
.boundary()).y_
;
621 newy
= min(last
, max(y
, first
));
625 // We reset the cursor because cursorStatus() does not
626 // work when the cursor is within mathed.
628 cur
.reset(buffer_
.inset());
629 tm
.setCursorFromCoordinates(cur
, 0, newy
);
634 Change
const BufferView::getCurrentChange() const
636 if (!d
->cursor_
.selection())
637 return Change(Change::UNCHANGED
);
639 DocIterator dit
= d
->cursor_
.selectionBegin();
640 return dit
.paragraph().lookupChange(dit
.pos());
644 // this could be used elsewhere as well?
645 // FIXME: This does not work within mathed!
646 CursorStatus
BufferView::cursorStatus(DocIterator
const & dit
) const
648 Point
const p
= getPos(dit
, dit
.boundary());
651 if (p
.y_
> workHeight())
657 void BufferView::saveBookmark(unsigned int idx
)
659 // tenatively save bookmark, id and pos will be used to
660 // acturately locate a bookmark in a 'live' lyx session.
661 // pit and pos will be updated with bottom level pit/pos
663 theSession().bookmarks().save(
665 d
->cursor_
.bottom().pit(),
666 d
->cursor_
.bottom().pos(),
667 d
->cursor_
.paragraph().id(),
672 // emit message signal.
673 message(_("Save bookmark"));
677 bool BufferView::moveToPosition(pit_type bottom_pit
, pos_type bottom_pos
,
678 int top_id
, pos_type top_pos
)
680 bool success
= false;
683 d
->cursor_
.clearSelection();
685 // if a valid par_id is given, try it first
686 // This is the case for a 'live' bookmark when unique paragraph ID
687 // is used to track bookmarks.
689 dit
= buffer_
.getParFromID(top_id
);
691 dit
.pos() = min(dit
.paragraph().size(), top_pos
);
692 // Some slices of the iterator may not be
693 // reachable (e.g. closed collapsable inset)
694 // so the dociterator may need to be
695 // shortened. Otherwise, setCursor may crash
696 // lyx when the cursor can not be set to these
698 size_t const n
= dit
.depth();
699 for (size_t i
= 0; i
< n
; ++i
)
700 if (dit
[i
].inset().editable() != Inset::HIGHLY_EDITABLE
) {
708 // if top_id == 0, or searching through top_id failed
709 // This is the case for a 'restored' bookmark when only bottom
710 // (document level) pit was saved. Because of this, bookmark
711 // restoration is inaccurate. If a bookmark was within an inset,
712 // it will be restored to the left of the outmost inset that contains
714 if (bottom_pit
< int(buffer_
.paragraphs().size())) {
715 dit
= doc_iterator_begin(buffer_
.inset());
717 dit
.pit() = bottom_pit
;
718 dit
.pos() = min(bottom_pos
, dit
.paragraph().size());
723 // Note: only bottom (document) level pit is set.
725 // set the current font.
726 d
->cursor_
.setCurrentFont();
727 // To center the screen on this new position we need the
728 // paragraph position which is computed at draw() time.
729 // So we need a redraw!
739 void BufferView::translateAndInsert(char_type c
, Text
* t
, Cursor
& cur
)
741 if (lyxrc
.rtl_support
) {
742 if (d
->cursor_
.real_current_font
.isRightToLeft()) {
743 if (d
->intl_
.keymap
== Intl::PRIMARY
)
744 d
->intl_
.keyMapSec();
746 if (d
->intl_
.keymap
== Intl::SECONDARY
)
747 d
->intl_
.keyMapPrim();
751 d
->intl_
.getTransManager().translateAndInsert(c
, t
, cur
);
755 int BufferView::workWidth() const
761 void BufferView::showCursor()
763 showCursor(d
->cursor_
);
767 void BufferView::showCursor(DocIterator
const & dit
)
769 // We are not properly started yet, delay until resizing is
774 LYXERR(Debug::SCROLLING
, "recentering!");
776 CursorSlice
const & bot
= dit
.bottom();
777 TextMetrics
& tm
= d
->text_metrics_
[bot
.text()];
779 pos_type
const max_pit
= pos_type(bot
.text()->paragraphs().size() - 1);
780 int bot_pit
= bot
.pit();
781 if (bot_pit
> max_pit
) {
782 // FIXME: Why does this happen?
783 LYXERR0("bottom pit is greater that max pit: "
784 << bot_pit
<< " > " << max_pit
);
788 if (bot_pit
== tm
.first().first
- 1)
789 tm
.newParMetricsUp();
790 else if (bot_pit
== tm
.last().first
+ 1)
791 tm
.newParMetricsDown();
793 if (tm
.contains(bot_pit
)) {
794 ParagraphMetrics
const & pm
= tm
.parMetrics(bot_pit
);
795 LASSERT(!pm
.rows().empty(), /**/);
796 // FIXME: smooth scrolling doesn't work in mathed.
797 CursorSlice
const & cs
= dit
.innerTextSlice();
798 int offset
= coordOffset(dit
, dit
.boundary()).y_
;
799 int ypos
= pm
.position() + offset
;
800 Dimension
const & row_dim
=
801 pm
.getRow(cs
.pos(), dit
.boundary()).dimension();
803 if (ypos
- row_dim
.ascent() < 0)
804 scrolled
= scrollUp(- ypos
+ row_dim
.ascent());
805 else if (ypos
+ row_dim
.descent() > height_
)
806 scrolled
= scrollDown(ypos
- height_
+ row_dim
.descent());
807 // else, nothing to do, the cursor is already visible so we just return.
815 // fix inline completion position
816 if (d
->inlineCompletionPos_
.fixIfBroken())
817 d
->inlineCompletionPos_
= DocIterator();
819 tm
.redoParagraph(bot_pit
);
820 ParagraphMetrics
const & pm
= tm
.parMetrics(bot_pit
);
821 int offset
= coordOffset(dit
, dit
.boundary()).y_
;
823 d
->anchor_pit_
= bot_pit
;
824 CursorSlice
const & cs
= dit
.innerTextSlice();
825 Dimension
const & row_dim
=
826 pm
.getRow(cs
.pos(), dit
.boundary()).dimension();
828 if (d
->anchor_pit_
== 0)
829 d
->anchor_ypos_
= offset
+ pm
.ascent();
830 else if (d
->anchor_pit_
== max_pit
)
831 d
->anchor_ypos_
= height_
- offset
- row_dim
.descent();
833 d
->anchor_ypos_
= defaultRowHeight() * 2 - offset
- row_dim
.descent();
840 FuncStatus
BufferView::getStatus(FuncRequest
const & cmd
)
844 Cursor
& cur
= d
->cursor_
;
846 switch (cmd
.action
) {
849 flag
.setEnabled(buffer_
.undo().hasUndoStack());
852 flag
.setEnabled(buffer_
.undo().hasRedoStack());
854 case LFUN_FILE_INSERT
:
855 case LFUN_FILE_INSERT_PLAINTEXT_PARA
:
856 case LFUN_FILE_INSERT_PLAINTEXT
:
857 case LFUN_BOOKMARK_SAVE
:
858 // FIXME: Actually, these LFUNS should be moved to Text
859 flag
.setEnabled(cur
.inTexted());
861 case LFUN_FONT_STATE
:
862 case LFUN_LABEL_INSERT
:
863 case LFUN_INFO_INSERT
:
864 case LFUN_INSET_EDIT
:
865 case LFUN_PARAGRAPH_GOTO
:
867 case LFUN_REFERENCE_NEXT
:
869 case LFUN_WORD_REPLACE
:
872 case LFUN_MARK_TOGGLE
:
873 case LFUN_SCREEN_RECENTER
:
874 case LFUN_BIBTEX_DATABASE_ADD
:
875 case LFUN_BIBTEX_DATABASE_DEL
:
876 case LFUN_NOTES_MUTATE
:
877 case LFUN_ALL_INSETS_TOGGLE
:
878 case LFUN_STATISTICS
:
879 flag
.setEnabled(true);
882 case LFUN_NEXT_INSET_TOGGLE
:
883 case LFUN_NEXT_INSET_MODIFY
: {
884 // this is the real function we want to invoke
885 FuncRequest tmpcmd
= cmd
;
886 tmpcmd
.action
= (cmd
.action
== LFUN_NEXT_INSET_TOGGLE
)
887 ? LFUN_INSET_TOGGLE
: LFUN_INSET_MODIFY
;
888 // if there is an inset at cursor, see whether it
889 // handles the lfun, other start from scratch
890 Inset
* inset
= cur
.nextInset();
891 if (!inset
|| !inset
->getStatus(cur
, tmpcmd
, flag
))
892 flag
= lyx::getStatus(tmpcmd
);
896 case LFUN_LABEL_GOTO
: {
897 flag
.setEnabled(!cmd
.argument().empty()
898 || getInsetByCode
<InsetRef
>(cur
, REF_CODE
));
902 case LFUN_CHANGES_TRACK
:
903 flag
.setEnabled(true);
904 flag
.setOnOff(buffer_
.params().trackChanges
);
907 case LFUN_CHANGES_OUTPUT
:
908 flag
.setEnabled(true);
909 flag
.setOnOff(buffer_
.params().outputChanges
);
912 case LFUN_CHANGES_MERGE
:
913 case LFUN_CHANGE_NEXT
:
914 case LFUN_ALL_CHANGES_ACCEPT
:
915 case LFUN_ALL_CHANGES_REJECT
:
916 // TODO: context-sensitive enabling of LFUNs
917 // In principle, these command should only be enabled if there
918 // is a change in the document. However, without proper
919 // optimizations, this will inevitably result in poor performance.
920 flag
.setEnabled(true);
923 case LFUN_BUFFER_TOGGLE_COMPRESSION
: {
924 flag
.setOnOff(buffer_
.params().compressed
);
929 case LFUN_SCREEN_DOWN
:
931 case LFUN_SCREEN_UP_SELECT
:
932 case LFUN_SCREEN_DOWN_SELECT
:
933 flag
.setEnabled(true);
936 case LFUN_LAYOUT_TABULAR
:
937 flag
.setEnabled(cur
.innerInsetOfType(TABULAR_CODE
));
941 flag
.setEnabled(!cur
.inset().forcePlainLayout(cur
.idx()));
944 case LFUN_LAYOUT_PARAGRAPH
:
945 flag
.setEnabled(cur
.inset().allowParagraphCustomization(cur
.idx()));
948 case LFUN_INSET_SETTINGS
: {
949 InsetCode code
= cur
.inset().lyxCode();
950 if (cmd
.getArg(0) == insetName(code
)) {
951 flag
.setEnabled(true);
955 InsetCode next_code
= cur
.nextInset()
956 ? cur
.nextInset()->lyxCode() : NO_CODE
;
957 //FIXME: remove these special cases:
967 enable
= (cmd
.argument().empty() ||
968 cmd
.getArg(0) == insetName(next_code
));
973 flag
.setEnabled(enable
);
977 case LFUN_DIALOG_SHOW_NEW_INSET
:
978 flag
.setEnabled(cur
.inset().lyxCode() != ERT_CODE
&&
979 cur
.inset().lyxCode() != LISTINGS_CODE
);
980 if (cur
.inset().lyxCode() == CAPTION_CODE
) {
982 if (cur
.inset().getStatus(cur
, cmd
, flag
))
987 case LFUN_BRANCH_ACTIVATE
:
988 case LFUN_BRANCH_DEACTIVATE
: {
990 docstring
const branchName
= cmd
.argument();
991 if (!branchName
.empty())
992 enable
= buffer_
.params().branchlist().find(branchName
);
993 flag
.setEnabled(enable
);
998 flag
.setEnabled(false);
1005 bool BufferView::dispatch(FuncRequest
const & cmd
)
1007 //lyxerr << [ cmd = " << cmd << "]" << endl;
1009 // Make sure that the cached BufferView is correct.
1010 LYXERR(Debug::ACTION
, " action[" << cmd
.action
<< ']'
1011 << " arg[" << to_utf8(cmd
.argument()) << ']'
1012 << " x[" << cmd
.x
<< ']'
1013 << " y[" << cmd
.y
<< ']'
1014 << " button[" << cmd
.button() << ']');
1016 Cursor
& cur
= d
->cursor_
;
1018 switch (cmd
.action
) {
1021 cur
.message(_("Undo"));
1022 cur
.clearSelection();
1023 if (!cur
.textUndo())
1024 cur
.message(_("No further undo information"));
1026 processUpdateFlags(Update::Force
| Update::FitCursor
);
1030 cur
.message(_("Redo"));
1031 cur
.clearSelection();
1032 if (!cur
.textRedo())
1033 cur
.message(_("No further redo information"));
1035 processUpdateFlags(Update::Force
| Update::FitCursor
);
1038 case LFUN_FONT_STATE
:
1039 cur
.message(cur
.currentState());
1042 case LFUN_BOOKMARK_SAVE
:
1043 saveBookmark(convert
<unsigned int>(to_utf8(cmd
.argument())));
1046 case LFUN_LABEL_GOTO
: {
1047 docstring label
= cmd
.argument();
1048 if (label
.empty()) {
1050 getInsetByCode
<InsetRef
>(d
->cursor_
,
1053 label
= inset
->getParam("reference");
1054 // persistent=false: use temp_bookmark
1064 case LFUN_INSET_EDIT
: {
1065 FuncRequest
fr(cmd
);
1066 // if there is an inset at cursor, see whether it
1068 Inset
* inset
= cur
.nextInset();
1070 inset
->dispatch(cur
, fr
);
1071 // if it did not work, try the underlying inset.
1072 if (!inset
|| !cur
.result().dispatched())
1075 // FIXME I'm adding the last break to solve a crash,
1076 // but that is obviously not right.
1077 if (!cur
.result().dispatched())
1078 // It did not work too; no action needed.
1083 case LFUN_PARAGRAPH_GOTO
: {
1084 int const id
= convert
<int>(cmd
.getArg(0));
1085 int const pos
= convert
<int>(cmd
.getArg(1));
1087 for (Buffer
* b
= &buffer_
; i
== 0 || b
!= &buffer_
;
1088 b
= theBufferList().next(b
)) {
1090 DocIterator dit
= b
->getParFromID(id
);
1092 LYXERR(Debug::INFO
, "No matching paragraph found! [" << id
<< "].");
1096 LYXERR(Debug::INFO
, "Paragraph " << dit
.paragraph().id()
1097 << " found in buffer `"
1098 << b
->absFileName() << "'.");
1100 if (b
== &buffer_
) {
1104 processUpdateFlags(Update::Force
| Update::FitCursor
);
1106 // Switch to other buffer view and resend cmd
1107 theLyXFunc().dispatch(FuncRequest(
1108 LFUN_BUFFER_SWITCH
, b
->absFileName()));
1109 theLyXFunc().dispatch(cmd
);
1116 case LFUN_NOTE_NEXT
:
1117 gotoInset(this, NOTE_CODE
, false);
1120 case LFUN_REFERENCE_NEXT
: {
1121 vector
<InsetCode
> tmp
;
1122 tmp
.push_back(LABEL_CODE
);
1123 tmp
.push_back(REF_CODE
);
1124 gotoInset(this, tmp
, true);
1128 case LFUN_CHANGES_TRACK
:
1129 buffer_
.params().trackChanges
= !buffer_
.params().trackChanges
;
1132 case LFUN_CHANGES_OUTPUT
:
1133 buffer_
.params().outputChanges
= !buffer_
.params().outputChanges
;
1134 if (buffer_
.params().outputChanges
) {
1135 bool dvipost
= LaTeXFeatures::isAvailable("dvipost");
1136 bool xcolorsoul
= LaTeXFeatures::isAvailable("soul") &&
1137 LaTeXFeatures::isAvailable("xcolor");
1139 if (!dvipost
&& !xcolorsoul
) {
1140 Alert::warning(_("Changes not shown in LaTeX output"),
1141 _("Changes will not be highlighted in LaTeX output, "
1142 "because neither dvipost nor xcolor/soul are installed.\n"
1143 "Please install these packages or redefine "
1144 "\\lyxadded and \\lyxdeleted in the LaTeX preamble."));
1145 } else if (!xcolorsoul
) {
1146 Alert::warning(_("Changes not shown in LaTeX output"),
1147 _("Changes will not be highlighted in LaTeX output "
1148 "when using pdflatex, because xcolor and soul are not installed.\n"
1149 "Please install both packages or redefine "
1150 "\\lyxadded and \\lyxdeleted in the LaTeX preamble."));
1155 case LFUN_CHANGE_NEXT
:
1156 findNextChange(this);
1159 case LFUN_CHANGES_MERGE
:
1160 if (findNextChange(this))
1161 showDialog("changes");
1164 case LFUN_ALL_CHANGES_ACCEPT
:
1165 // select complete document
1166 d
->cursor_
.reset(buffer_
.inset());
1167 d
->cursor_
.selHandle(true);
1168 buffer_
.text().cursorBottom(d
->cursor_
);
1169 // accept everything in a single step to support atomic undo
1170 buffer_
.text().acceptOrRejectChanges(d
->cursor_
, Text::ACCEPT
);
1171 // FIXME: Move this LFUN to Buffer so that we don't have to do this:
1172 processUpdateFlags(Update::Force
| Update::FitCursor
);
1175 case LFUN_ALL_CHANGES_REJECT
:
1176 // select complete document
1177 d
->cursor_
.reset(buffer_
.inset());
1178 d
->cursor_
.selHandle(true);
1179 buffer_
.text().cursorBottom(d
->cursor_
);
1180 // reject everything in a single step to support atomic undo
1181 // Note: reject does not work recursively; the user may have to repeat the operation
1182 buffer_
.text().acceptOrRejectChanges(d
->cursor_
, Text::REJECT
);
1183 // FIXME: Move this LFUN to Buffer so that we don't have to do this:
1184 processUpdateFlags(Update::Force
| Update::FitCursor
);
1187 case LFUN_WORD_FIND
: {
1188 FuncRequest req
= cmd
;
1189 if (cmd
.argument().empty() && !d
->search_request_cache_
.argument().empty())
1190 req
= d
->search_request_cache_
;
1191 if (req
.argument().empty()) {
1192 theLyXFunc().dispatch(FuncRequest(LFUN_DIALOG_SHOW
, "findreplace"));
1195 if (find(this, req
))
1198 message(_("String not found!"));
1199 d
->search_request_cache_
= req
;
1203 case LFUN_WORD_REPLACE
: {
1204 bool has_deleted
= false;
1205 if (cur
.selection()) {
1206 DocIterator beg
= cur
.selectionBegin();
1207 DocIterator end
= cur
.selectionEnd();
1208 if (beg
.pit() == end
.pit()) {
1209 for (pos_type p
= beg
.pos() ; p
< end
.pos() ; ++p
) {
1210 if (cur
.paragraph().isDeleted(p
))
1215 replace(this, cmd
, has_deleted
);
1220 cur
.clearSelection();
1222 cur
.message(from_utf8(N_("Mark off")));
1226 cur
.clearSelection();
1229 cur
.message(from_utf8(N_("Mark on")));
1232 case LFUN_MARK_TOGGLE
:
1233 cur
.clearSelection();
1236 cur
.message(from_utf8(N_("Mark removed")));
1239 cur
.message(from_utf8(N_("Mark set")));
1244 case LFUN_SCREEN_RECENTER
:
1248 case LFUN_BIBTEX_DATABASE_ADD
: {
1249 Cursor tmpcur
= d
->cursor_
;
1250 findInset(tmpcur
, BIBTEX_CODE
, false);
1251 InsetBibtex
* inset
= getInsetByCode
<InsetBibtex
>(tmpcur
,
1254 if (inset
->addDatabase(cmd
.argument()))
1255 buffer_
.updateBibfilesCache();
1260 case LFUN_BIBTEX_DATABASE_DEL
: {
1261 Cursor tmpcur
= d
->cursor_
;
1262 findInset(tmpcur
, BIBTEX_CODE
, false);
1263 InsetBibtex
* inset
= getInsetByCode
<InsetBibtex
>(tmpcur
,
1266 if (inset
->delDatabase(cmd
.argument()))
1267 buffer_
.updateBibfilesCache();
1272 case LFUN_STATISTICS
: {
1273 DocIterator from
, to
;
1274 if (cur
.selection()) {
1275 from
= cur
.selectionBegin();
1276 to
= cur
.selectionEnd();
1278 from
= doc_iterator_begin(buffer_
.inset());
1279 to
= doc_iterator_end(buffer_
.inset());
1281 int const words
= countWords(from
, to
);
1282 int const chars
= countChars(from
, to
, false);
1283 int const chars_blanks
= countChars(from
, to
, true);
1285 if (cur
.selection())
1286 message
= _("Statistics for the selection:");
1288 message
= _("Statistics for the document:");
1291 message
+= bformat(_("%1$d words"), words
);
1293 message
+= _("One word");
1295 if (chars_blanks
!= 1)
1296 message
+= bformat(_("%1$d characters (including blanks)"),
1299 message
+= _("One character (including blanks)");
1302 message
+= bformat(_("%1$d characters (excluding blanks)"),
1305 message
+= _("One character (excluding blanks)");
1307 Alert::information(_("Statistics"), message
);
1311 case LFUN_BUFFER_TOGGLE_COMPRESSION
:
1312 // turn compression on/off
1313 buffer_
.params().compressed
= !buffer_
.params().compressed
;
1316 case LFUN_NEXT_INSET_TOGGLE
: {
1317 // create the the real function we want to invoke
1318 FuncRequest tmpcmd
= cmd
;
1319 tmpcmd
.action
= LFUN_INSET_TOGGLE
;
1320 // if there is an inset at cursor, see whether it
1322 Inset
* inset
= cur
.nextInset();
1324 if (inset
->isActive()) {
1325 Cursor tmpcur
= cur
;
1326 tmpcur
.pushBackward(*inset
);
1327 inset
->dispatch(tmpcur
, tmpcmd
);
1328 if (tmpcur
.result().dispatched())
1331 inset
->dispatch(cur
, tmpcmd
);
1333 // if it did not work, try the underlying inset.
1334 if (!inset
|| !cur
.result().dispatched())
1335 cur
.dispatch(tmpcmd
);
1337 if (!cur
.result().dispatched())
1338 // It did not work too; no action needed.
1340 cur
.clearSelection();
1341 processUpdateFlags(Update::SinglePar
| Update::FitCursor
);
1345 case LFUN_NEXT_INSET_MODIFY
: {
1346 // create the the real function we want to invoke
1347 FuncRequest tmpcmd
= cmd
;
1348 tmpcmd
.action
= LFUN_INSET_MODIFY
;
1349 // if there is an inset at cursor, see whether it
1351 Inset
* inset
= cur
.nextInset();
1353 inset
->dispatch(cur
, tmpcmd
);
1354 // if it did not work, try the underlying inset.
1355 if (!inset
|| !cur
.result().dispatched())
1356 cur
.dispatch(tmpcmd
);
1358 if (!cur
.result().dispatched())
1359 // It did not work too; no action needed.
1361 cur
.clearSelection();
1362 processUpdateFlags(Update::Force
| Update::FitCursor
);
1366 case LFUN_SCREEN_UP
:
1367 case LFUN_SCREEN_DOWN
: {
1368 Point p
= getPos(cur
, cur
.boundary());
1369 if (p
.y_
< 0 || p
.y_
> height_
) {
1370 // The cursor is off-screen so recenter before proceeding.
1372 p
= getPos(cur
, cur
.boundary());
1374 int const scrolled
= scroll(cmd
.action
== LFUN_SCREEN_UP
1375 ? - height_
: height_
);
1376 if (cmd
.action
== LFUN_SCREEN_UP
&& scrolled
> - height_
)
1378 if (cmd
.action
== LFUN_SCREEN_DOWN
&& scrolled
< height_
)
1379 p
= Point(width_
, height_
);
1380 cur
.reset(buffer_
.inset());
1383 d
->text_metrics_
[&buffer_
.text()].editXY(cur
, p
.x_
, p
.y_
);
1384 //FIXME: what to do with cur.x_target()?
1393 case LFUN_SCREEN_UP_SELECT
: {
1394 cur
.selHandle(true);
1395 if (isTopScreen()) {
1396 lyx::dispatch(FuncRequest(LFUN_BUFFER_BEGIN_SELECT
));
1400 int y
= getPos(cur
, cur
.boundary()).y_
;
1401 int const ymin
= y
- height_
+ defaultRowHeight();
1402 while (y
> ymin
&& cur
.up())
1403 y
= getPos(cur
, cur
.boundary()).y_
;
1406 processUpdateFlags(Update::SinglePar
| Update::FitCursor
);
1410 case LFUN_SCREEN_DOWN_SELECT
: {
1411 cur
.selHandle(true);
1412 if (isBottomScreen()) {
1413 lyx::dispatch(FuncRequest(LFUN_BUFFER_END_SELECT
));
1417 int y
= getPos(cur
, cur
.boundary()).y_
;
1418 int const ymax
= y
+ height_
- defaultRowHeight();
1419 while (y
< ymax
&& cur
.down())
1420 y
= getPos(cur
, cur
.boundary()).y_
;
1423 processUpdateFlags(Update::SinglePar
| Update::FitCursor
);
1427 case LFUN_BRANCH_ACTIVATE
:
1428 case LFUN_BRANCH_DEACTIVATE
:
1429 buffer_
.dispatch(cmd
);
1430 processUpdateFlags(Update::Force
);
1433 // This could be rewriten using some command like forall <insetname> <command>
1434 // once the insets refactoring is done.
1435 case LFUN_NOTES_MUTATE
: {
1436 if (cmd
.argument().empty())
1439 if (mutateNotes(cur
, cmd
.getArg(0), cmd
.getArg(1))) {
1440 processUpdateFlags(Update::Force
);
1445 case LFUN_ALL_INSETS_TOGGLE
: {
1447 string
const name
= split(to_utf8(cmd
.argument()), action
, ' ');
1448 InsetCode
const inset_code
= insetCode(name
);
1450 FuncRequest
fr(LFUN_INSET_TOGGLE
, action
);
1452 Inset
& inset
= cur
.buffer().inset();
1453 InsetIterator it
= inset_iterator_begin(inset
);
1454 InsetIterator
const end
= inset_iterator_end(inset
);
1455 for (; it
!= end
; ++it
) {
1456 if (it
->asInsetCollapsable()
1457 && (inset_code
== NO_CODE
1458 || inset_code
== it
->lyxCode())) {
1459 Cursor tmpcur
= cur
;
1460 tmpcur
.pushBackward(*it
);
1461 it
->dispatch(tmpcur
, fr
);
1464 processUpdateFlags(Update::Force
| Update::FitCursor
);
1476 docstring
const BufferView::requestSelection()
1478 Cursor
& cur
= d
->cursor_
;
1480 LYXERR(Debug::SELECTION
, "requestSelection: cur.selection: " << cur
.selection());
1481 if (!cur
.selection()) {
1482 d
->xsel_cache_
.set
= false;
1486 LYXERR(Debug::SELECTION
, "requestSelection: xsel_cache.set: " << d
->xsel_cache_
.set
);
1487 if (!d
->xsel_cache_
.set
||
1488 cur
.top() != d
->xsel_cache_
.cursor
||
1489 cur
.anchor_
.top() != d
->xsel_cache_
.anchor
)
1491 d
->xsel_cache_
.cursor
= cur
.top();
1492 d
->xsel_cache_
.anchor
= cur
.anchor_
.top();
1493 d
->xsel_cache_
.set
= cur
.selection();
1494 return cur
.selectionAsString(false);
1500 void BufferView::clearSelection()
1502 d
->cursor_
.clearSelection();
1503 // Clear the selection buffer. Otherwise a subsequent
1504 // middle-mouse-button paste would use the selection buffer,
1505 // not the more current external selection.
1506 cap::clearSelection();
1507 d
->xsel_cache_
.set
= false;
1508 // The buffer did not really change, but this causes the
1509 // redraw we need because we cleared the selection above.
1514 void BufferView::resize(int width
, int height
)
1516 // Update from work area
1520 // Clear the paragraph height cache.
1521 d
->par_height_
.clear();
1522 // Redo the metrics.
1527 Inset
const * BufferView::getCoveringInset(Text
const & text
,
1530 TextMetrics
& tm
= d
->text_metrics_
[&text
];
1531 Inset
* inset
= tm
.checkInsetHit(x
, y
);
1535 if (!inset
->descendable())
1536 // No need to go further down if the inset is not
1540 size_t cell_number
= inset
->nargs();
1541 // Check all the inner cell.
1542 for (size_t i
= 0; i
!= cell_number
; ++i
) {
1543 Text
const * inner_text
= inset
->getText(i
);
1546 Inset
const * inset_deeper
=
1547 getCoveringInset(*inner_text
, x
, y
);
1549 return inset_deeper
;
1557 void BufferView::mouseEventDispatch(FuncRequest
const & cmd0
)
1559 //lyxerr << "[ cmd0 " << cmd0 << "]" << endl;
1561 // This is only called for mouse related events including
1562 // LFUN_FILE_OPEN generated by drag-and-drop.
1563 FuncRequest cmd
= cmd0
;
1565 Cursor old
= cursor();
1567 cur
.push(buffer_
.inset());
1568 cur
.setSelection(d
->cursor_
.selection());
1570 // Either the inset under the cursor or the
1571 // surrounding Text will handle this event.
1573 // make sure we stay within the screen...
1574 cmd
.y
= min(max(cmd
.y
, -1), height_
);
1576 if (cmd
.action
== LFUN_MOUSE_MOTION
&& cmd
.button() == mouse_button::none
) {
1578 // Get inset under mouse, if there is one.
1579 Inset
const * covering_inset
=
1580 getCoveringInset(buffer_
.text(), cmd
.x
, cmd
.y
);
1581 if (covering_inset
== d
->last_inset_
)
1582 // Same inset, no need to do anything...
1585 bool need_redraw
= false;
1586 // const_cast because of setMouseHover().
1587 Inset
* inset
= const_cast<Inset
*>(covering_inset
);
1589 // Remove the hint on the last hovered inset (if any).
1590 need_redraw
|= d
->last_inset_
->setMouseHover(false);
1592 // Highlighted the newly hovered inset (if any).
1593 need_redraw
|= inset
->setMouseHover(true);
1594 d
->last_inset_
= inset
;
1598 LYXERR(Debug::PAINTING
, "Mouse hover detected at: ("
1599 << cmd
.x
<< ", " << cmd
.y
<< ")");
1601 d
->update_strategy_
= DecorationUpdate
;
1603 // This event (moving without mouse click) is not passed further.
1604 // This should be changed if it is further utilized.
1609 // Build temporary cursor.
1610 Inset
* inset
= d
->text_metrics_
[&buffer_
.text()].editXY(cur
, cmd
.x
, cmd
.y
);
1612 // Put anchor at the same position.
1615 cur
.beginUndoGroup();
1617 // Try to dispatch to an non-editable inset near this position
1618 // via the temp cursor. If the inset wishes to change the real
1619 // cursor it has to do so explicitly by using
1620 // cur.bv().cursor() = cur; (or similar)
1622 inset
->dispatch(cur
, cmd
);
1624 // Now dispatch to the temporary cursor. If the real cursor should
1625 // be modified, the inset's dispatch has to do so explicitly.
1626 if (!inset
|| !cur
.result().dispatched())
1631 // Notify left insets
1634 bool badcursor
= notifyCursorLeaves(old
, cur
);
1636 cursor().fixIfBroken();
1639 // Do we have a selection?
1640 theSelection().haveSelection(cursor().selection());
1642 // If the command has been dispatched,
1643 if (cur
.result().dispatched() || cur
.result().update())
1644 processUpdateFlags(cur
.result().update());
1648 void BufferView::lfunScroll(FuncRequest
const & cmd
)
1650 string
const scroll_type
= cmd
.getArg(0);
1651 int const scroll_step
=
1652 (scroll_type
== "line") ? d
->scrollbarParameters_
.single_step
1653 : (scroll_type
== "page") ? d
->scrollbarParameters_
.page_step
: 0;
1654 if (scroll_step
== 0)
1656 string
const scroll_quantity
= cmd
.getArg(1);
1657 if (scroll_quantity
== "up")
1658 scrollUp(scroll_step
);
1659 else if (scroll_quantity
== "down")
1660 scrollDown(scroll_step
);
1662 int const scroll_value
= convert
<int>(scroll_quantity
);
1664 scroll(scroll_step
* scroll_value
);
1671 int BufferView::scroll(int y
)
1674 return scrollDown(y
);
1676 return scrollUp(-y
);
1681 int BufferView::scrollDown(int offset
)
1683 Text
* text
= &buffer_
.text();
1684 TextMetrics
& tm
= d
->text_metrics_
[text
];
1685 int ymax
= height_
+ offset
;
1687 pair
<pit_type
, ParagraphMetrics
const *> last
= tm
.last();
1688 int bottom_pos
= last
.second
->position() + last
.second
->descent();
1689 if (last
.first
+ 1 == int(text
->paragraphs().size())) {
1690 if (bottom_pos
<= height_
)
1692 offset
= min(offset
, bottom_pos
- height_
);
1695 if (bottom_pos
> ymax
)
1697 tm
.newParMetricsDown();
1699 d
->anchor_ypos_
-= offset
;
1704 int BufferView::scrollUp(int offset
)
1706 Text
* text
= &buffer_
.text();
1707 TextMetrics
& tm
= d
->text_metrics_
[text
];
1708 int ymin
= - offset
;
1710 pair
<pit_type
, ParagraphMetrics
const *> first
= tm
.first();
1711 int top_pos
= first
.second
->position() - first
.second
->ascent();
1712 if (first
.first
== 0) {
1715 offset
= min(offset
, - top_pos
);
1720 tm
.newParMetricsUp();
1722 d
->anchor_ypos_
+= offset
;
1727 void BufferView::setCursorFromRow(int row
)
1732 buffer_
.texrow().getIdFromRow(row
, tmpid
, tmppos
);
1734 d
->cursor_
.reset(buffer_
.inset());
1736 buffer_
.text().setCursor(d
->cursor_
, 0, 0);
1738 buffer_
.text().setCursor(d
->cursor_
, buffer_
.getParFromID(tmpid
).pit(), tmppos
);
1742 bool BufferView::setCursorFromInset(Inset
const * inset
)
1744 // are we already there?
1745 if (cursor().nextInset() == inset
)
1748 // Inset is not at cursor position. Find it in the document.
1750 cur
.reset(buffer().inset());
1751 while (cur
&& cur
.nextInset() != inset
)
1762 void BufferView::gotoLabel(docstring
const & label
)
1764 Toc
& toc
= buffer().tocBackend().toc("label");
1765 TocIterator toc_it
= toc
.begin();
1766 TocIterator end
= toc
.end();
1767 for (; toc_it
!= end
; ++toc_it
) {
1768 if (label
== toc_it
->str())
1769 dispatch(toc_it
->action());
1771 //FIXME: We could do a bit more searching thanks to this:
1772 //InsetLabel const * inset = buffer_.insetLabel(label);
1776 TextMetrics
const & BufferView::textMetrics(Text
const * t
) const
1778 return const_cast<BufferView
*>(this)->textMetrics(t
);
1782 TextMetrics
& BufferView::textMetrics(Text
const * t
)
1784 TextMetricsCache::iterator tmc_it
= d
->text_metrics_
.find(t
);
1785 if (tmc_it
== d
->text_metrics_
.end()) {
1786 tmc_it
= d
->text_metrics_
.insert(
1787 make_pair(t
, TextMetrics(this, const_cast<Text
*>(t
)))).first
;
1789 return tmc_it
->second
;
1793 ParagraphMetrics
const & BufferView::parMetrics(Text
const * t
,
1796 return textMetrics(t
).parMetrics(pit
);
1800 int BufferView::workHeight() const
1806 void BufferView::setCursor(DocIterator
const & dit
)
1808 size_t const n
= dit
.depth();
1809 for (size_t i
= 0; i
< n
; ++i
)
1810 dit
[i
].inset().edit(d
->cursor_
, true);
1812 d
->cursor_
.setCursor(dit
);
1813 d
->cursor_
.setSelection(false);
1817 bool BufferView::checkDepm(Cursor
& cur
, Cursor
& old
)
1819 // Would be wrong to delete anything if we have a selection.
1820 if (cur
.selection())
1823 bool need_anchor_change
= false;
1824 bool changed
= d
->cursor_
.text()->deleteEmptyParagraphMechanism(cur
, old
,
1825 need_anchor_change
);
1827 if (need_anchor_change
)
1835 updateLabels(buffer_
);
1843 bool BufferView::mouseSetCursor(Cursor
& cur
, bool select
)
1845 LASSERT(&cur
.bv() == this, /**/);
1848 // this event will clear selection so we save selection for
1849 // persistent selection
1850 cap::saveSelection(cursor());
1852 // Has the cursor just left the inset?
1853 bool badcursor
= false;
1854 bool leftinset
= (&d
->cursor_
.inset() != &cur
.inset());
1856 d
->cursor_
.fixIfBroken();
1857 badcursor
= notifyCursorLeaves(d
->cursor_
, cur
);
1862 // FIXME: shift-mouse selection doesn't work well across insets.
1863 bool do_selection
= select
&& &d
->cursor_
.anchor().inset() == &cur
.inset();
1865 // do the dEPM magic if needed
1866 // FIXME: (1) move this to InsetText::notifyCursorLeaves?
1867 // FIXME: (2) if we had a working InsetText::notifyCursorLeaves,
1868 // the leftinset bool would not be necessary (badcursor instead).
1869 bool update
= leftinset
;
1870 if (!do_selection
&& !badcursor
&& d
->cursor_
.inTexted())
1871 update
|= checkDepm(cur
, d
->cursor_
);
1873 d
->cursor_
.setCursor(cur
);
1874 d
->cursor_
.boundary(cur
.boundary());
1876 d
->cursor_
.setSelection();
1878 d
->cursor_
.clearSelection();
1880 d
->cursor_
.finishUndo();
1881 d
->cursor_
.setCurrentFont();
1886 void BufferView::putSelectionAt(DocIterator
const & cur
,
1887 int length
, bool backwards
)
1889 d
->cursor_
.clearSelection();
1895 d
->cursor_
.pos() += length
;
1896 d
->cursor_
.setSelection(d
->cursor_
, -length
);
1898 d
->cursor_
.setSelection(d
->cursor_
, length
);
1900 // Ensure a redraw happens in any case because the new selection could
1901 // possibly be on the same screen as the previous selection.
1902 processUpdateFlags(Update::Force
| Update::FitCursor
);
1906 Cursor
& BufferView::cursor()
1912 Cursor
const & BufferView::cursor() const
1918 pit_type
BufferView::anchor_ref() const
1920 return d
->anchor_pit_
;
1924 bool BufferView::singleParUpdate()
1926 Text
& buftext
= buffer_
.text();
1927 pit_type
const bottom_pit
= d
->cursor_
.bottom().pit();
1928 TextMetrics
& tm
= textMetrics(&buftext
);
1929 int old_height
= tm
.parMetrics(bottom_pit
).height();
1931 // make sure inline completion pointer is ok
1932 if (d
->inlineCompletionPos_
.fixIfBroken())
1933 d
->inlineCompletionPos_
= DocIterator();
1935 // In Single Paragraph mode, rebreak only
1936 // the (main text, not inset!) paragraph containing the cursor.
1937 // (if this paragraph contains insets etc., rebreaking will
1938 // recursively descend)
1939 tm
.redoParagraph(bottom_pit
);
1940 ParagraphMetrics
const & pm
= tm
.parMetrics(bottom_pit
);
1941 if (pm
.height() != old_height
)
1942 // Paragraph height has changed so we cannot proceed to
1943 // the singlePar optimisation.
1946 d
->update_strategy_
= SingleParUpdate
;
1948 LYXERR(Debug::PAINTING
, "\ny1: " << pm
.position() - pm
.ascent()
1949 << " y2: " << pm
.position() + pm
.descent()
1950 << " pit: " << bottom_pit
1951 << " singlepar: 1");
1956 void BufferView::updateMetrics()
1958 if (height_
== 0 || width_
== 0)
1961 Text
& buftext
= buffer_
.text();
1962 pit_type
const npit
= int(buftext
.paragraphs().size());
1964 // Clear out the position cache in case of full screen redraw,
1965 d
->coord_cache_
.clear();
1967 // Clear out paragraph metrics to avoid having invalid metrics
1968 // in the cache from paragraphs not relayouted below
1969 // The complete text metrics will be redone.
1970 d
->text_metrics_
.clear();
1972 TextMetrics
& tm
= textMetrics(&buftext
);
1974 // make sure inline completion pointer is ok
1975 if (d
->inlineCompletionPos_
.fixIfBroken())
1976 d
->inlineCompletionPos_
= DocIterator();
1978 if (d
->anchor_pit_
>= npit
)
1979 // The anchor pit must have been deleted...
1980 d
->anchor_pit_
= npit
- 1;
1982 // Rebreak anchor paragraph.
1983 tm
.redoParagraph(d
->anchor_pit_
);
1984 ParagraphMetrics
& anchor_pm
= tm
.par_metrics_
[d
->anchor_pit_
];
1987 if (d
->anchor_pit_
== 0) {
1988 int scrollRange
= d
->scrollbarParameters_
.max
- d
->scrollbarParameters_
.min
;
1990 // Complete buffer visible? Then it's easy.
1991 if (scrollRange
== 0)
1992 d
->anchor_ypos_
= anchor_pm
.ascent();
1994 // FIXME: Some clever handling needed to show
1995 // the _first_ paragraph up to the top if the cursor is
1996 // in the first line.
1998 anchor_pm
.setPosition(d
->anchor_ypos_
);
2000 LYXERR(Debug::PAINTING
, "metrics: "
2001 << " anchor pit = " << d
->anchor_pit_
2002 << " anchor ypos = " << d
->anchor_ypos_
);
2004 // Redo paragraphs above anchor if necessary.
2005 int y1
= d
->anchor_ypos_
- anchor_pm
.ascent();
2006 // We are now just above the anchor paragraph.
2007 pit_type pit1
= d
->anchor_pit_
- 1;
2008 for (; pit1
>= 0 && y1
>= 0; --pit1
) {
2009 tm
.redoParagraph(pit1
);
2010 ParagraphMetrics
& pm
= tm
.par_metrics_
[pit1
];
2012 // Save the paragraph position in the cache.
2017 // Redo paragraphs below the anchor if necessary.
2018 int y2
= d
->anchor_ypos_
+ anchor_pm
.descent();
2019 // We are now just below the anchor paragraph.
2020 pit_type pit2
= d
->anchor_pit_
+ 1;
2021 for (; pit2
< npit
&& y2
<= height_
; ++pit2
) {
2022 tm
.redoParagraph(pit2
);
2023 ParagraphMetrics
& pm
= tm
.par_metrics_
[pit2
];
2025 // Save the paragraph position in the cache.
2030 LYXERR(Debug::PAINTING
, "Metrics: "
2031 << " anchor pit = " << d
->anchor_pit_
2032 << " anchor ypos = " << d
->anchor_ypos_
2035 << " pit1 = " << pit1
2036 << " pit2 = " << pit2
);
2038 d
->update_strategy_
= FullScreenUpdate
;
2040 if (lyxerr
.debugging(Debug::WORKAREA
)) {
2041 LYXERR(Debug::WORKAREA
, "BufferView::updateMetrics");
2042 d
->coord_cache_
.dump();
2047 void BufferView::insertLyXFile(FileName
const & fname
)
2049 LASSERT(d
->cursor_
.inTexted(), /**/);
2051 // Get absolute path of file and add ".lyx"
2052 // to the filename if necessary
2053 FileName filename
= fileSearch(string(), fname
.absFilename(), "lyx");
2055 docstring
const disp_fn
= makeDisplayPath(filename
.absFilename());
2056 // emit message signal.
2057 message(bformat(_("Inserting document %1$s..."), disp_fn
));
2060 Buffer
buf("", false);
2061 if (buf
.loadLyXFile(filename
)) {
2062 ErrorList
& el
= buffer_
.errorList("Parse");
2063 // Copy the inserted document error list into the current buffer one.
2064 el
= buf
.errorList("Parse");
2065 buffer_
.undo().recordUndo(d
->cursor_
);
2066 cap::pasteParagraphList(d
->cursor_
, buf
.paragraphs(),
2067 buf
.params().documentClassPtr(), el
);
2068 res
= _("Document %1$s inserted.");
2070 res
= _("Could not insert document %1$s");
2075 // emit message signal.
2076 message(bformat(res
, disp_fn
));
2077 buffer_
.errors("Parse");
2081 Point
BufferView::coordOffset(DocIterator
const & dit
, bool boundary
) const
2087 // Addup contribution of nested insets, from inside to outside,
2088 // keeping the outer paragraph for a special handling below
2089 for (size_t i
= dit
.depth() - 1; i
>= 1; --i
) {
2090 CursorSlice
const & sl
= dit
[i
];
2094 // get relative position inside sl.inset()
2095 sl
.inset().cursorPos(*this, sl
, boundary
&& (i
+ 1 == dit
.depth()), xx
, yy
);
2097 // Make relative position inside of the edited inset relative to sl.inset()
2101 // In case of an RTL inset, the edited inset will be positioned to the left
2104 bool boundary_i
= boundary
&& i
+ 1 == dit
.depth();
2105 bool rtl
= textMetrics(sl
.text()).isRTL(sl
, boundary_i
);
2110 // remember width for the case that sl.inset() is positioned in an RTL inset
2111 if (i
&& dit
[i
- 1].text()) {
2112 // If this Inset is inside a Text Inset, retrieve the Dimension
2113 // from the containing text instead of using Inset::dimension() which
2114 // might not be implemented.
2115 // FIXME (Abdel 23/09/2007): this is a bit messy because of the
2116 // elimination of Inset::dim_ cache. This coordOffset() method needs
2117 // to be rewritten in light of the new design.
2118 Dimension
const & dim
= parMetrics(dit
[i
- 1].text(),
2119 dit
[i
- 1].pit()).insetDimension(&sl
.inset());
2122 Dimension
const dim
= sl
.inset().dimension(*this);
2126 //lyxerr << "Cursor::getPos, i: "
2127 // << i << " x: " << xx << " y: " << y << endl;
2130 // Add contribution of initial rows of outermost paragraph
2131 CursorSlice
const & sl
= dit
[0];
2132 TextMetrics
const & tm
= textMetrics(sl
.text());
2133 ParagraphMetrics
const & pm
= tm
.parMetrics(sl
.pit());
2134 LASSERT(!pm
.rows().empty(), /**/);
2135 y
-= pm
.rows()[0].ascent();
2137 // FIXME: document this mess
2139 if (sl
.pos() > 0 && dit
.depth() == 1) {
2141 if (pos
&& boundary
)
2143 // lyxerr << "coordOffset: boundary:" << boundary << " depth:" << dit.depth() << " pos:" << pos << " sl.pos:" << sl.pos() << endl;
2144 rend
= pm
.pos2row(pos
);
2146 rend
= pm
.pos2row(sl
.pos());
2148 size_t rend
= pm
.pos2row(sl
.pos());
2150 for (size_t rit
= 0; rit
!= rend
; ++rit
)
2151 y
+= pm
.rows()[rit
].height();
2152 y
+= pm
.rows()[rend
].ascent();
2154 TextMetrics
const & bottom_tm
= textMetrics(dit
.bottom().text());
2156 // Make relative position from the nested inset now bufferview absolute.
2157 int xx
= bottom_tm
.cursorX(dit
.bottom(), boundary
&& dit
.depth() == 1);
2160 // In the RTL case place the nested inset at the left of the cursor in
2161 // the outer paragraph
2162 bool boundary_1
= boundary
&& 1 == dit
.depth();
2163 bool rtl
= bottom_tm
.isRTL(dit
.bottom(), boundary_1
);
2171 Point
BufferView::getPos(DocIterator
const & dit
, bool boundary
) const
2173 CursorSlice
const & bot
= dit
.bottom();
2174 TextMetrics
const & tm
= textMetrics(bot
.text());
2175 if (!tm
.contains(bot
.pit()))
2176 return Point(-1, -1);
2178 Point p
= coordOffset(dit
, boundary
); // offset from outer paragraph
2179 p
.y_
+= tm
.parMetrics(bot
.pit()).position();
2184 void BufferView::draw(frontend::Painter
& pain
)
2186 if (height_
== 0 || width_
== 0)
2188 LYXERR(Debug::PAINTING
, "\t\t*** START DRAWING ***");
2189 Text
& text
= buffer_
.text();
2190 TextMetrics
const & tm
= d
->text_metrics_
[&text
];
2191 int const y
= tm
.first().second
->position();
2192 PainterInfo
pi(this, pain
);
2194 switch (d
->update_strategy_
) {
2196 case NoScreenUpdate
:
2197 // If no screen painting is actually needed, only some the different
2198 // coordinates of insets and paragraphs needs to be updated.
2199 pi
.full_repaint
= true;
2200 pi
.pain
.setDrawingEnabled(false);
2204 case SingleParUpdate
:
2205 pi
.full_repaint
= false;
2206 // In general, only the current row of the outermost paragraph
2207 // will be redrawn. Particular cases where selection spans
2208 // multiple paragraph are correctly detected in TextMetrics.
2212 case DecorationUpdate
:
2213 // FIXME: We should also distinguish DecorationUpdate to avoid text
2214 // drawing if possible. This is not possible to do easily right now
2215 // because of the single backing pixmap.
2217 case FullScreenUpdate
:
2218 // The whole screen, including insets, will be refreshed.
2219 pi
.full_repaint
= true;
2221 // Clear background.
2222 pain
.fillRectangle(0, 0, width_
, height_
,
2223 buffer_
.inset().backgroundColor());
2228 // and possibly grey out below
2229 pair
<pit_type
, ParagraphMetrics
const *> lastpm
= tm
.last();
2230 int const y2
= lastpm
.second
->position() + lastpm
.second
->descent();
2232 pain
.fillRectangle(0, y2
, width_
, height_
- y2
, Color_bottomarea
);
2235 LYXERR(Debug::PAINTING
, "\n\t\t*** END DRAWING ***");
2237 // The scrollbar needs an update.
2240 // Normalize anchor for next time
2241 pair
<pit_type
, ParagraphMetrics
const *> firstpm
= tm
.first();
2242 pair
<pit_type
, ParagraphMetrics
const *> lastpm
= tm
.last();
2243 for (pit_type pit
= firstpm
.first
; pit
<= lastpm
.first
; ++pit
) {
2244 ParagraphMetrics
const & pm
= tm
.parMetrics(pit
);
2245 if (pm
.position() + pm
.descent() > 0) {
2246 d
->anchor_pit_
= pit
;
2247 d
->anchor_ypos_
= pm
.position();
2251 LYXERR(Debug::PAINTING
, "Found new anchor pit = " << d
->anchor_pit_
2252 << " anchor ypos = " << d
->anchor_ypos_
);
2256 void BufferView::message(docstring
const & msg
)
2259 d
->gui_
->message(msg
);
2263 void BufferView::showDialog(string
const & name
)
2266 d
->gui_
->showDialog(name
, string());
2270 void BufferView::showDialog(string
const & name
,
2271 string
const & data
, Inset
* inset
)
2274 d
->gui_
->showDialog(name
, data
, inset
);
2278 void BufferView::updateDialog(string
const & name
, string
const & data
)
2281 d
->gui_
->updateDialog(name
, data
);
2285 void BufferView::setGuiDelegate(frontend::GuiBufferViewDelegate
* gui
)
2291 // FIXME: Move this out of BufferView again
2292 docstring
BufferView::contentsOfPlaintextFile(FileName
const & fname
)
2294 if (!fname
.isReadableFile()) {
2295 docstring
const error
= from_ascii(strerror(errno
));
2296 docstring
const file
= makeDisplayPath(fname
.absFilename(), 50);
2297 docstring
const text
=
2298 bformat(_("Could not read the specified document\n"
2299 "%1$s\ndue to the error: %2$s"), file
, error
);
2300 Alert::error(_("Could not read file"), text
);
2304 if (!fname
.isReadableFile()) {
2305 docstring
const file
= makeDisplayPath(fname
.absFilename(), 50);
2306 docstring
const text
=
2307 bformat(_("%1$s\n is not readable."), file
);
2308 Alert::error(_("Could not open file"), text
);
2312 // FIXME UNICODE: We don't know the encoding of the file
2313 docstring file_content
= fname
.fileContents("UTF-8");
2314 if (file_content
.empty()) {
2315 Alert::error(_("Reading not UTF-8 encoded file"),
2316 _("The file is not UTF-8 encoded.\n"
2317 "It will be read as local 8Bit-encoded.\n"
2318 "If this does not give the correct result\n"
2319 "then please change the encoding of the file\n"
2320 "to UTF-8 with a program other than LyX.\n"));
2321 file_content
= fname
.fileContents("local8bit");
2324 return normalize_c(file_content
);
2328 void BufferView::insertPlaintextFile(FileName
const & f
, bool asParagraph
)
2330 docstring
const tmpstr
= contentsOfPlaintextFile(f
);
2335 Cursor
& cur
= cursor();
2336 cap::replaceSelection(cur
);
2337 buffer_
.undo().recordUndo(cur
);
2339 cur
.innerText()->insertStringAsParagraphs(cur
, tmpstr
);
2341 cur
.innerText()->insertStringAsLines(cur
, tmpstr
);
2348 docstring
const & BufferView::inlineCompletion() const
2350 return d
->inlineCompletion_
;
2354 size_t const & BufferView::inlineCompletionUniqueChars() const
2356 return d
->inlineCompletionUniqueChars_
;
2360 DocIterator
const & BufferView::inlineCompletionPos() const
2362 return d
->inlineCompletionPos_
;
2366 bool samePar(DocIterator
const & a
, DocIterator
const & b
)
2368 if (a
.empty() && b
.empty())
2370 if (a
.empty() || b
.empty())
2372 return &a
.innerParagraph() == &b
.innerParagraph();
2376 void BufferView::setInlineCompletion(Cursor
& cur
, DocIterator
const & pos
,
2377 docstring
const & completion
, size_t uniqueChars
)
2379 uniqueChars
= min(completion
.size(), uniqueChars
);
2380 bool changed
= d
->inlineCompletion_
!= completion
2381 || d
->inlineCompletionUniqueChars_
!= uniqueChars
;
2382 bool singlePar
= true;
2383 d
->inlineCompletion_
= completion
;
2384 d
->inlineCompletionUniqueChars_
= min(completion
.size(), uniqueChars
);
2386 //lyxerr << "setInlineCompletion pos=" << pos << " completion=" << completion << " uniqueChars=" << uniqueChars << std::endl;
2389 DocIterator
const & old
= d
->inlineCompletionPos_
;
2391 //lyxerr << "inlineCompletionPos changed" << std::endl;
2392 // old or pos are in another paragraph?
2393 if ((!samePar(cur
, pos
) && !pos
.empty())
2394 || (!samePar(cur
, old
) && !old
.empty())) {
2396 //lyxerr << "different paragraph" << std::endl;
2398 d
->inlineCompletionPos_
= pos
;
2403 if (singlePar
&& !(cur
.disp_
.update() & Update::Force
))
2404 cur
.updateFlags(cur
.disp_
.update() | Update::SinglePar
);
2406 cur
.updateFlags(cur
.disp_
.update() | Update::Force
);