1 /* This file is part of
2 * ======================================================
4 * LyX, The Document Processor
6 * Copyright 1995 Matthias Ettrich
7 * Copyright 1995-2001 The LyX Team.
9 * ====================================================== */
16 #include "paragraph.h"
17 #include "funcrequest.h"
18 #include "frontends/LyXView.h"
19 #include "undo_funcs.h"
21 #include "buffer_funcs.h"
22 #include "bufferparams.h"
23 #include "errorlist.h"
25 #include "BufferView.h"
26 #include "CutAndPaste.h"
27 #include "frontends/Painter.h"
28 #include "frontends/font_metrics.h"
32 #include "FloatList.h"
34 #include "ParagraphParameters.h"
36 #include "lyxrow_funcs.h"
37 #include "paragraph_funcs.h"
39 #include "insets/insetbibitem.h"
40 #include "insets/insetenv.h"
41 #include "insets/insetfloat.h"
42 #include "insets/insetwrap.h"
44 #include "support/LAssert.h"
45 #include "support/textutils.h"
46 #include "support/lstrings.h"
48 #include <boost/tuple/tuple.hpp>
50 using namespace lyx::support;
60 LyXText::LyXText(BufferView * bv)
61 : height(0), width(0), anchor_row_offset_(0),
62 inset_owner(0), the_locking_inset(0), bv_owner(bv)
64 anchor_row_ = rows().end();
65 need_break_row = rows().end();
66 refresh_row = rows().end();
72 LyXText::LyXText(BufferView * bv, InsetText * inset)
73 : height(0), width(0), anchor_row_offset_(0),
74 inset_owner(inset), the_locking_inset(0), bv_owner(bv)
76 anchor_row_ = rows().end();
77 need_break_row = rows().end();
78 refresh_row = rows().end();
84 void LyXText::init(BufferView * bview, bool reinit)
89 need_break_row = rows().end();
93 } else if (!rowlist_.empty())
96 ParagraphList::iterator pit = ownerParagraphs().begin();
97 ParagraphList::iterator end = ownerParagraphs().end();
99 current_font = getFont(bview->buffer(), pit, 0);
101 for (; pit != end; ++pit) {
102 insertParagraph(pit, rowlist_.end());
104 setCursorIntern(rowlist_.begin()->par(), 0);
105 selection.cursor = cursor;
111 // Gets the fully instantiated font at a given position in a paragraph
112 // Basically the same routine as Paragraph::getFont() in paragraph.C.
113 // The difference is that this one is used for displaying, and thus we
114 // are allowed to make cosmetic improvements. For instance make footnotes
116 // If position is -1, we get the layout font of the paragraph.
117 // If position is -2, we get the font of the manual label of the paragraph.
118 LyXFont const LyXText::getFont(Buffer const * buf, ParagraphList::iterator pit,
123 LyXLayout_ptr const & layout = pit->layout();
125 // We specialize the 95% common case:
126 if (!pit->getDepth()) {
127 if (layout->labeltype == LABEL_MANUAL
128 && pos < pit->beginningOfBody()) {
130 LyXFont f = pit->getFontSettings(buf->params, pos);
132 pit->inInset()->getDrawFont(f);
133 return f.realize(layout->reslabelfont);
135 LyXFont f = pit->getFontSettings(buf->params, pos);
137 pit->inInset()->getDrawFont(f);
138 return f.realize(layout->resfont);
142 // The uncommon case need not be optimized as much
146 if (pos < pit->beginningOfBody()) {
148 layoutfont = layout->labelfont;
151 layoutfont = layout->font;
154 LyXFont tmpfont = pit->getFontSettings(buf->params, pos);
155 tmpfont.realize(layoutfont);
158 pit->inInset()->getDrawFont(tmpfont);
160 // Realize with the fonts of lesser depth.
161 tmpfont.realize(outerFont(pit, ownerParagraphs()));
163 return realizeFont(tmpfont, buf->params);
167 LyXFont const LyXText::getLayoutFont(Buffer const * buf,
168 ParagraphList::iterator pit) const
170 LyXLayout_ptr const & layout = pit->layout();
172 if (!pit->getDepth()) {
173 return layout->resfont;
176 LyXFont font(layout->font);
177 // Realize with the fonts of lesser depth.
178 font.realize(outerFont(pit, ownerParagraphs()));
180 return realizeFont(font, buf->params);
184 LyXFont const LyXText::getLabelFont(Buffer const * buf,
185 ParagraphList::iterator pit) const
187 LyXLayout_ptr const & layout = pit->layout();
189 if (!pit->getDepth()) {
190 return layout->reslabelfont;
193 LyXFont font(layout->labelfont);
194 // Realize with the fonts of lesser depth.
195 font.realize(outerFont(pit, ownerParagraphs()));
197 return realizeFont(layout->labelfont, buf->params);
201 void LyXText::setCharFont(ParagraphList::iterator pit,
202 pos_type pos, LyXFont const & fnt,
205 Buffer const * buf = bv()->buffer();
206 LyXFont font = getFont(buf, pit, pos);
207 font.update(fnt, buf->params.language, toggleall);
208 // Let the insets convert their font
209 if (pit->isInset(pos)) {
210 Inset * inset = pit->getInset(pos);
211 if (isEditableInset(inset)) {
212 UpdatableInset * uinset =
213 static_cast<UpdatableInset *>(inset);
214 uinset->setFont(bv(), fnt, toggleall, true);
218 // Plug thru to version below:
219 setCharFont(buf, pit, pos, font);
223 void LyXText::setCharFont(Buffer const * buf, ParagraphList::iterator pit,
224 pos_type pos, LyXFont const & fnt)
228 LyXTextClass const & tclass = buf->params.getLyXTextClass();
229 LyXLayout_ptr const & layout = pit->layout();
231 // Get concrete layout font to reduce against
234 if (pos < pit->beginningOfBody())
235 layoutfont = layout->labelfont;
237 layoutfont = layout->font;
239 // Realize against environment font information
240 if (pit->getDepth()) {
241 ParagraphList::iterator tp = pit;
242 while (!layoutfont.resolved() &&
243 tp != ownerParagraphs().end() &&
245 tp = outerHook(tp, ownerParagraphs());
246 if (tp != ownerParagraphs().end())
247 layoutfont.realize(tp->layout()->font);
251 layoutfont.realize(tclass.defaultfont());
253 // Now, reduce font against full layout font
254 font.reduce(layoutfont);
256 pit->setFont(pos, font);
260 // removes the row and reset the touched counters
261 void LyXText::removeRow(RowList::iterator rit)
263 /* FIXME: when we cache the bview, this should just
264 * become a postPaint(), I think */
265 if (refresh_row == rit) {
266 if (rit == rows().begin())
267 refresh_row = boost::next(rit);
269 refresh_row = boost::prior(rit);
271 // what about refresh_y
274 if (anchor_row_ == rit) {
275 if (rit != rows().begin()) {
276 anchor_row_ = boost::prior(rit);
277 anchor_row_offset_ += anchor_row_->height();
279 anchor_row_ = boost::next(rit);
280 anchor_row_offset_ -= rit->height();
284 // the text becomes smaller
285 height -= rit->height();
291 // remove all following rows of the paragraph of the specified row.
292 void LyXText::removeParagraph(RowList::iterator rit)
294 ParagraphList::iterator tmppit = rit->par();
297 while (rit != rows().end() && rit->par() == tmppit) {
298 RowList::iterator tmprit = boost::next(rit);
305 void LyXText::insertParagraph(ParagraphList::iterator pit,
306 RowList::iterator rowit)
308 // insert a new row, starting at position 0
310 RowList::iterator rit = rowlist_.insert(rowit, newrow);
312 // and now append the whole paragraph before the new row
313 appendParagraph(rit);
317 Inset * LyXText::getInset() const
319 ParagraphList::iterator pit = cursor.par();
320 pos_type const pos = cursor.pos();
322 if (pos < pit->size() && pit->isInset(pos)) {
323 return pit->getInset(pos);
329 void LyXText::toggleInset()
331 Inset * inset = getInset();
332 // is there an editable inset at cursor position?
333 if (!isEditableInset(inset)) {
334 // No, try to see if we are inside a collapsable inset
335 if (inset_owner && inset_owner->owner()
336 && inset_owner->owner()->isOpen()) {
337 bv()->unlockInset(inset_owner->owner());
338 inset_owner->owner()->close(bv());
339 bv()->getLyXText()->cursorRight(bv());
343 //bv()->owner()->message(inset->editMessage());
345 // do we want to keep this?? (JMarc)
346 if (!isHighlyEditableInset(inset))
347 setCursorParUndo(bv());
349 if (inset->isOpen()) {
355 bv()->updateInset(inset);
359 /* used in setlayout */
360 // Asger is not sure we want to do this...
361 void LyXText::makeFontEntriesLayoutSpecific(Buffer const & buf,
364 LyXLayout_ptr const & layout = par.layout();
365 pos_type const psize = par.size();
368 for (pos_type pos = 0; pos < psize; ++pos) {
369 if (pos < par.beginningOfBody())
370 layoutfont = layout->labelfont;
372 layoutfont = layout->font;
374 LyXFont tmpfont = par.getFontSettings(buf.params, pos);
375 tmpfont.reduce(layoutfont);
376 par.setFont(pos, tmpfont);
381 ParagraphList::iterator
382 LyXText::setLayout(LyXCursor & cur, LyXCursor & sstart_cur,
383 LyXCursor & send_cur,
384 string const & layout)
386 ParagraphList::iterator endpit = boost::next(send_cur.par());
387 ParagraphList::iterator undoendpit = endpit;
388 ParagraphList::iterator pars_end = ownerParagraphs().end();
390 if (endpit != pars_end && endpit->getDepth()) {
391 while (endpit != pars_end && endpit->getDepth()) {
395 } else if (endpit != pars_end) {
396 // because of parindents etc.
400 setUndo(bv(), Undo::EDIT, sstart_cur.par(), boost::prior(undoendpit));
402 // ok we have a selection. This is always between sstart_cur
403 // and sel_end cursor
405 ParagraphList::iterator pit = sstart_cur.par();
406 ParagraphList::iterator epit = boost::next(send_cur.par());
408 LyXLayout_ptr const & lyxlayout =
409 bv()->buffer()->params.getLyXTextClass()[layout];
412 pit->applyLayout(lyxlayout);
413 makeFontEntriesLayoutSpecific(*bv()->buffer(), *pit);
414 ParagraphList::iterator fppit = pit;
415 fppit->params().spaceTop(lyxlayout->fill_top ?
416 VSpace(VSpace::VFILL)
417 : VSpace(VSpace::NONE));
418 fppit->params().spaceBottom(lyxlayout->fill_bottom ?
419 VSpace(VSpace::VFILL)
420 : VSpace(VSpace::NONE));
421 if (lyxlayout->margintype == MARGIN_MANUAL)
422 pit->setLabelWidthString(lyxlayout->labelstring());
425 } while (pit != epit);
431 // set layout over selection and make a total rebreak of those paragraphs
432 void LyXText::setLayout(string const & layout)
434 LyXCursor tmpcursor = cursor; // store the current cursor
436 // if there is no selection just set the layout
437 // of the current paragraph
438 if (!selection.set()) {
439 selection.start = cursor; // dummy selection
440 selection.end = cursor;
443 // special handling of new environment insets
444 BufferParams const & params = bv()->buffer()->params;
445 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
446 if (lyxlayout->is_environment) {
447 // move everything in a new environment inset
448 lyxerr << "setting layout " << layout << endl;
449 bv()->owner()->dispatch(FuncRequest(LFUN_HOME));
450 bv()->owner()->dispatch(FuncRequest(LFUN_ENDSEL));
451 bv()->owner()->dispatch(FuncRequest(LFUN_CUT));
452 Inset * inset = new InsetEnvironment(params, layout);
453 if (bv()->insertInset(inset)) {
455 //bv()->owner()->dispatch(FuncRequest(LFUN_PASTE));
462 ParagraphList::iterator endpit = setLayout(cursor, selection.start,
463 selection.end, layout);
464 redoParagraphs(selection.start, endpit);
466 // we have to reset the selection, because the
467 // geometry could have changed
468 setCursor(selection.start.par(), selection.start.pos(), false);
469 selection.cursor = cursor;
470 setCursor(selection.end.par(), selection.end.pos(), false);
474 setCursor(tmpcursor.par(), tmpcursor.pos(), true);
478 bool LyXText::changeDepth(bv_funcs::DEPTH_CHANGE type, bool test_only)
480 ParagraphList::iterator pit(cursor.par());
481 ParagraphList::iterator end(cursor.par());
482 ParagraphList::iterator start = pit;
484 if (selection.set()) {
485 pit = selection.start.par();
486 end = selection.end.par();
490 ParagraphList::iterator pastend = boost::next(end);
493 setUndo(bv(), Undo::EDIT, start, end);
495 bool changed = false;
497 int prev_after_depth = 0;
498 #warning parlist ... could be nicer ?
499 if (start != ownerParagraphs().begin()) {
500 prev_after_depth = boost::prior(start)->getMaxDepthAfter();
504 int const depth = pit->params().depth();
505 if (type == bv_funcs::INC_DEPTH) {
506 if (depth < prev_after_depth
507 && pit->layout()->labeltype != LABEL_BIBLIO) {
510 pit->params().depth(depth + 1);
517 pit->params().depth(depth - 1);
520 prev_after_depth = pit->getMaxDepthAfter();
532 // Wow, redoParagraphs is stupid.
534 setCursor(tmpcursor, start, 0);
536 redoParagraphs(tmpcursor, pastend);
538 // We need to actually move the text->cursor. I don't
539 // understand why ...
542 // we have to reset the visual selection because the
543 // geometry could have changed
544 if (selection.set()) {
545 setCursor(selection.start.par(), selection.start.pos());
546 selection.cursor = cursor;
547 setCursor(selection.end.par(), selection.end.pos());
550 // this handles the counter labels, and also fixes up
551 // depth values for follow-on (child) paragraphs
555 setCursor(tmpcursor.par(), tmpcursor.pos());
561 // set font over selection and make a total rebreak of those paragraphs
562 void LyXText::setFont(LyXFont const & font, bool toggleall)
564 // if there is no selection just set the current_font
565 if (!selection.set()) {
566 // Determine basis font
568 if (cursor.pos() < cursor.par()->beginningOfBody()) {
569 layoutfont = getLabelFont(bv()->buffer(),
572 layoutfont = getLayoutFont(bv()->buffer(),
575 // Update current font
576 real_current_font.update(font,
577 bv()->buffer()->params.language,
580 // Reduce to implicit settings
581 current_font = real_current_font;
582 current_font.reduce(layoutfont);
583 // And resolve it completely
584 real_current_font.realize(layoutfont);
589 LyXCursor tmpcursor = cursor; // store the current cursor
591 // ok we have a selection. This is always between sel_start_cursor
592 // and sel_end cursor
594 setUndo(bv(), Undo::EDIT, selection.start.par(), selection.end.par());
596 cursor = selection.start;
597 while (cursor.par() != selection.end.par() ||
598 cursor.pos() < selection.end.pos())
600 if (cursor.pos() < cursor.par()->size()) {
601 // an open footnote should behave like a closed one
602 setCharFont(cursor.par(), cursor.pos(),
604 cursor.pos(cursor.pos() + 1);
607 cursor.par(boost::next(cursor.par()));
612 redoParagraphs(selection.start, boost::next(selection.end.par()));
614 // we have to reset the selection, because the
615 // geometry could have changed, but we keep
616 // it for user convenience
617 setCursor(selection.start.par(), selection.start.pos());
618 selection.cursor = cursor;
619 setCursor(selection.end.par(), selection.end.pos());
621 setCursor(tmpcursor.par(), tmpcursor.pos(), true,
622 tmpcursor.boundary());
626 void LyXText::redoHeightOfParagraph()
628 RowList::iterator tmprow = cursorRow();
629 int y = cursor.y() - tmprow->baseline();
631 setHeightOfRow(tmprow);
633 while (tmprow != rows().begin()
634 && boost::prior(tmprow)->par() == tmprow->par()) {
636 y -= tmprow->height();
637 setHeightOfRow(tmprow);
642 setCursor(cursor.par(), cursor.pos(), false, cursor.boundary());
646 void LyXText::redoDrawingOfParagraph(LyXCursor const & cur)
648 RowList::iterator tmprow = getRow(cur);
650 int y = cur.y() - tmprow->baseline();
651 setHeightOfRow(tmprow);
653 while (tmprow != rows().begin()
654 && boost::prior(tmprow)->par() == tmprow->par()) {
656 y -= tmprow->height();
660 setCursor(cur.par(), cur.pos());
664 // deletes and inserts again all paragraphs between the cursor
665 // and the specified par
666 // This function is needed after SetLayout and SetFont etc.
667 void LyXText::redoParagraphs(LyXCursor const & cur,
668 ParagraphList::iterator endpit)
670 RowList::iterator tmprit = getRow(cur);
671 int y = cur.y() - tmprit->baseline();
673 ParagraphList::iterator first_phys_pit;
674 RowList::iterator prevrit;
675 if (tmprit == rows().begin()) {
676 // A trick/hack for UNDO.
677 // This is needed because in an UNDO/REDO we could have
678 // changed the ownerParagraph() so the paragraph inside
679 // the row is NOT my really first par anymore.
680 // Got it Lars ;) (Jug 20011206)
681 first_phys_pit = ownerParagraphs().begin();
682 prevrit = rows().end();
684 first_phys_pit = tmprit->par();
685 while (tmprit != rows().begin()
686 && boost::prior(tmprit)->par() == first_phys_pit)
689 y -= tmprit->height();
691 prevrit = boost::prior(tmprit);
695 while (tmprit != rows().end() && tmprit->par() != endpit) {
696 RowList::iterator tmprit2 = tmprit++;
700 // Reinsert the paragraphs.
701 ParagraphList::iterator tmppit = first_phys_pit;
703 while (tmppit != ownerParagraphs().end()) {
704 insertParagraph(tmppit, tmprit);
705 while (tmprit != rows().end()
706 && tmprit->par() == tmppit) {
710 if (tmppit == endpit)
713 if (prevrit != rows().end()) {
714 setHeightOfRow(prevrit);
715 postPaint(y - prevrit->height());
717 setHeightOfRow(rows().begin());
720 if (tmprit != rows().end())
721 setHeightOfRow(tmprit);
727 void LyXText::fullRebreak()
729 need_break_row = rows().begin();
731 setCursorIntern(cursor.par(), cursor.pos());
735 void LyXText::partialRebreak()
737 if (rows().empty()) {
742 RowList::iterator rows_end = rows().end();
744 if (need_break_row != rows_end) {
745 breakAgain(need_break_row);
746 need_break_row = rows_end;
752 // important for the screen
755 // the cursor set functions have a special mechanism. When they
756 // realize, that you left an empty paragraph, they will delete it.
757 // They also delete the corresponding row
759 // need the selection cursor:
760 void LyXText::setSelection()
762 bool const lsel = TextCursor::setSelection();
764 if (inset_owner && (selection.set() || lsel))
765 inset_owner->setUpdateStatus(bv(), InsetText::SELECTION);
770 void LyXText::clearSelection()
772 TextCursor::clearSelection();
774 // reset this in the bv_owner!
775 if (bv_owner && bv_owner->text)
776 bv_owner->text->xsel_cache.set(false);
780 void LyXText::cursorHome()
782 setCursor(cursor.par(), cursorRow()->pos());
786 void LyXText::cursorEnd()
788 if (cursor.par()->empty())
791 RowList::iterator rit = cursorRow();
792 RowList::iterator next_rit = boost::next(rit);
793 ParagraphList::iterator pit = rit->par();
794 pos_type last_pos = lastPos(*this, rit);
796 if (next_rit == rows().end() || next_rit->par() != pit) {
800 (pit->getChar(last_pos) != ' ' && !pit->isNewline(last_pos))) {
805 setCursor(pit, last_pos);
809 void LyXText::cursorTop()
811 setCursor(ownerParagraphs().begin(), 0);
815 void LyXText::cursorBottom()
817 ParagraphList::iterator lastpit =
818 boost::prior(ownerParagraphs().end());
819 setCursor(lastpit, lastpit->size());
823 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
825 // If the mask is completely neutral, tell user
826 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
827 // Could only happen with user style
828 bv()->owner()->message(_("No font change defined. Use Character under the Layout menu to define font change."));
832 // Try implicit word selection
833 // If there is a change in the language the implicit word selection
835 LyXCursor resetCursor = cursor;
836 bool implicitSelection = (font.language() == ignore_language
837 && font.number() == LyXFont::IGNORE)
838 ? selectWordWhenUnderCursor(lyx::WHOLE_WORD_STRICT) : false;
841 setFont(font, toggleall);
843 // Implicit selections are cleared afterwards
844 //and cursor is set to the original position.
845 if (implicitSelection) {
847 cursor = resetCursor;
848 setCursor(cursor.par(), cursor.pos());
849 selection.cursor = cursor;
852 inset_owner->setUpdateStatus(bv(), InsetText::CURSOR_PAR);
856 string LyXText::getStringToIndex()
858 // Try implicit word selection
859 // If there is a change in the language the implicit word selection
861 LyXCursor const reset_cursor = cursor;
862 bool const implicitSelection =
863 selectWordWhenUnderCursor(lyx::PREVIOUS_WORD);
866 if (!selection.set())
867 bv()->owner()->message(_("Nothing to index!"));
868 else if (selection.start.par() != selection.end.par())
869 bv()->owner()->message(_("Cannot index more than one paragraph!"));
871 idxstring = selectionAsString(bv()->buffer(), false);
873 // Reset cursors to their original position.
874 cursor = reset_cursor;
875 setCursor(cursor.par(), cursor.pos());
876 selection.cursor = cursor;
878 // Clear the implicit selection.
879 if (implicitSelection)
886 // the DTP switches for paragraphs. LyX will store them in the first
887 // physicla paragraph. When a paragraph is broken, the top settings rest,
888 // the bottom settings are given to the new one. So I can make shure,
889 // they do not duplicate themself and you cannnot make dirty things with
892 void LyXText::setParagraph(bool line_top, bool line_bottom,
893 bool pagebreak_top, bool pagebreak_bottom,
894 VSpace const & space_top,
895 VSpace const & space_bottom,
896 Spacing const & spacing,
898 string const & labelwidthstring,
901 LyXCursor tmpcursor = cursor;
902 if (!selection.set()) {
903 selection.start = cursor;
904 selection.end = cursor;
907 // make sure that the depth behind the selection are restored, too
908 ParagraphList::iterator endpit = boost::next(selection.end.par());
909 ParagraphList::iterator undoendpit = endpit;
910 ParagraphList::iterator pars_end = ownerParagraphs().end();
912 if (endpit != pars_end && endpit->getDepth()) {
913 while (endpit != pars_end && endpit->getDepth()) {
917 } else if (endpit != pars_end) {
918 // because of parindents etc.
922 setUndo(bv(), Undo::EDIT, selection.start.par(),
923 boost::prior(undoendpit));
926 ParagraphList::iterator tmppit = selection.end.par();
928 while (tmppit != boost::prior(selection.start.par())) {
929 setCursor(tmppit, 0);
930 postPaint(cursor.y() - cursorRow()->baseline());
932 ParagraphList::iterator pit = cursor.par();
933 ParagraphParameters & params = pit->params();
935 params.lineTop(line_top);
936 params.lineBottom(line_bottom);
937 params.pagebreakTop(pagebreak_top);
938 params.pagebreakBottom(pagebreak_bottom);
939 params.spaceTop(space_top);
940 params.spaceBottom(space_bottom);
941 params.spacing(spacing);
942 // does the layout allow the new alignment?
943 LyXLayout_ptr const & layout = pit->layout();
945 if (align == LYX_ALIGN_LAYOUT)
946 align = layout->align;
947 if (align & layout->alignpossible) {
948 if (align == layout->align)
949 params.align(LYX_ALIGN_LAYOUT);
953 pit->setLabelWidthString(labelwidthstring);
954 params.noindent(noindent);
955 tmppit = boost::prior(pit);
958 redoParagraphs(selection.start, endpit);
961 setCursor(selection.start.par(), selection.start.pos());
962 selection.cursor = cursor;
963 setCursor(selection.end.par(), selection.end.pos());
965 setCursor(tmpcursor.par(), tmpcursor.pos());
967 bv()->updateInset(inset_owner);
971 // set the counter of a paragraph. This includes the labels
972 void LyXText::setCounter(Buffer const * buf, ParagraphList::iterator pit)
974 LyXTextClass const & textclass = buf->params.getLyXTextClass();
975 LyXLayout_ptr const & layout = pit->layout();
977 if (pit != ownerParagraphs().begin()) {
979 pit->params().appendix(boost::prior(pit)->params().appendix());
980 if (!pit->params().appendix() &&
981 pit->params().startOfAppendix()) {
982 pit->params().appendix(true);
983 textclass.counters().reset();
985 pit->enumdepth = boost::prior(pit)->enumdepth;
986 pit->itemdepth = boost::prior(pit)->itemdepth;
988 pit->params().appendix(pit->params().startOfAppendix());
993 /* Maybe we have to increment the enumeration depth.
994 * BUT, enumeration in a footnote is considered in isolation from its
995 * surrounding paragraph so don't increment if this is the
996 * first line of the footnote
997 * AND, bibliographies can't have their depth changed ie. they
998 * are always of depth 0
1000 if (pit != ownerParagraphs().begin()
1001 && boost::prior(pit)->getDepth() < pit->getDepth()
1002 && boost::prior(pit)->layout()->labeltype == LABEL_COUNTER_ENUMI
1003 && pit->enumdepth < 3
1004 && layout->labeltype != LABEL_BIBLIO) {
1008 // Maybe we have to decrement the enumeration depth, see note above
1009 if (pit != ownerParagraphs().begin()
1010 && boost::prior(pit)->getDepth() > pit->getDepth()
1011 && layout->labeltype != LABEL_BIBLIO) {
1012 pit->enumdepth = depthHook(pit, ownerParagraphs(),
1013 pit->getDepth())->enumdepth;
1016 if (!pit->params().labelString().empty()) {
1017 pit->params().labelString(string());
1020 if (layout->margintype == MARGIN_MANUAL) {
1021 if (pit->params().labelWidthString().empty()) {
1022 pit->setLabelWidthString(layout->labelstring());
1025 pit->setLabelWidthString(string());
1028 // is it a layout that has an automatic label?
1029 if (layout->labeltype >= LABEL_COUNTER_CHAPTER) {
1030 int const i = layout->labeltype - LABEL_COUNTER_CHAPTER;
1034 if (i >= 0 && i <= buf->params.secnumdepth) {
1038 textclass.counters().step(layout->latexname());
1040 // Is there a label? Useful for Chapter layout
1041 if (!pit->params().appendix()) {
1042 s << buf->B_(layout->labelstring());
1044 s << buf->B_(layout->labelstring_appendix());
1047 // Use of an integer is here less than elegant. For now.
1048 int head = textclass.maxcounter() - LABEL_COUNTER_CHAPTER;
1049 if (!pit->params().appendix()) {
1050 numbertype = "sectioning";
1052 numbertype = "appendix";
1053 if (pit->isRightToLeftPar(buf->params))
1054 langtype = "hebrew";
1060 << textclass.counters()
1061 .numberLabel(layout->latexname(),
1062 numbertype, langtype, head);
1064 pit->params().labelString(STRCONV(s.str()));
1066 // reset enum counters
1067 textclass.counters().reset("enum");
1068 } else if (layout->labeltype < LABEL_COUNTER_ENUMI) {
1069 textclass.counters().reset("enum");
1070 } else if (layout->labeltype == LABEL_COUNTER_ENUMI) {
1072 // Yes I know this is a really, really! bad solution
1074 string enumcounter("enum");
1076 switch (pit->enumdepth) {
1085 enumcounter += "iv";
1088 // not a valid enumdepth...
1092 textclass.counters().step(enumcounter);
1094 s << textclass.counters()
1095 .numberLabel(enumcounter, "enumeration");
1096 pit->params().labelString(STRCONV(s.str()));
1098 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
1099 textclass.counters().step("bibitem");
1100 int number = textclass.counters().value("bibitem");
1101 if (pit->bibitem()) {
1102 pit->bibitem()->setCounter(number);
1103 pit->params().labelString(layout->labelstring());
1105 // In biblio should't be following counters but...
1107 string s = buf->B_(layout->labelstring());
1109 // the caption hack:
1110 if (layout->labeltype == LABEL_SENSITIVE) {
1111 ParagraphList::iterator tmppit = pit;
1114 while (tmppit != ownerParagraphs().end() &&
1116 // the single '=' is intended below
1117 && (in = tmppit->inInset()->owner())) {
1118 if (in->lyxCode() == Inset::FLOAT_CODE ||
1119 in->lyxCode() == Inset::WRAP_CODE) {
1123 tmppit = std::find(ownerParagraphs().begin(), ownerParagraphs().end(), *in->parOwner());
1130 if (in->lyxCode() == Inset::FLOAT_CODE)
1131 type = static_cast<InsetFloat*>(in)->params().type;
1132 else if (in->lyxCode() == Inset::WRAP_CODE)
1133 type = static_cast<InsetWrap*>(in)->params().type;
1137 Floating const & fl = textclass.floats().getType(type);
1139 textclass.counters().step(fl.type());
1141 // Doesn't work... yet.
1142 s = bformat(_("%1$s #:"), buf->B_(fl.name()));
1144 // par->SetLayout(0);
1145 // s = layout->labelstring;
1146 s = _("Senseless: ");
1149 pit->params().labelString(s);
1151 // reset the enumeration counter. They are always reset
1152 // when there is any other layout between
1153 // Just fall-through between the cases so that all
1154 // enum counters deeper than enumdepth is also reset.
1155 switch (pit->enumdepth) {
1157 textclass.counters().reset("enumi");
1159 textclass.counters().reset("enumii");
1161 textclass.counters().reset("enumiii");
1163 textclass.counters().reset("enumiv");
1169 // Updates all counters. Paragraphs with changed label string will be rebroken
1170 void LyXText::updateCounters()
1172 RowList::iterator rowit = rows().begin();
1173 ParagraphList::iterator pit = rowit->par();
1175 // CHECK if this is really needed. (Lgb)
1176 bv()->buffer()->params.getLyXTextClass().counters().reset();
1178 ParagraphList::iterator beg = ownerParagraphs().begin();
1179 ParagraphList::iterator end = ownerParagraphs().end();
1180 for (; pit != end; ++pit) {
1181 while (rowit->par() != pit)
1184 string const oldLabel = pit->params().labelString();
1186 size_t maxdepth = 0;
1188 maxdepth = boost::prior(pit)->getMaxDepthAfter();
1190 if (pit->params().depth() > maxdepth)
1191 pit->params().depth(maxdepth);
1193 // setCounter can potentially change the labelString.
1194 setCounter(bv()->buffer(), pit);
1196 string const & newLabel = pit->params().labelString();
1198 if (oldLabel.empty() && !newLabel.empty()) {
1199 removeParagraph(rowit);
1200 appendParagraph(rowit);
1206 void LyXText::insertInset(Inset * inset)
1208 if (!cursor.par()->insetAllowed(inset->lyxCode()))
1210 setUndo(bv(), Undo::FINISH, cursor.par());
1212 cursor.par()->insertInset(cursor.pos(), inset);
1213 // Just to rebreak and refresh correctly.
1214 // The character will not be inserted a second time
1215 insertChar(Paragraph::META_INSET);
1216 // If we enter a highly editable inset the cursor should be to before
1217 // the inset. This couldn't happen before as Undo was not handled inside
1218 // inset now after the Undo LyX tries to call inset->Edit(...) again
1219 // and cannot do this as the cursor is behind the inset and GetInset
1220 // does not return the inset!
1221 if (isHighlyEditableInset(inset)) {
1228 void LyXText::cutSelection(bool doclear, bool realcut)
1230 // Stuff what we got on the clipboard. Even if there is no selection.
1232 // There is a problem with having the stuffing here in that the
1233 // larger the selection the slower LyX will get. This can be
1234 // solved by running the line below only when the selection has
1235 // finished. The solution used currently just works, to make it
1236 // faster we need to be more clever and probably also have more
1237 // calls to stuffClipboard. (Lgb)
1238 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1240 // This doesn't make sense, if there is no selection
1241 if (!selection.set())
1244 // OK, we have a selection. This is always between selection.start
1245 // and selection.end
1247 // make sure that the depth behind the selection are restored, too
1248 ParagraphList::iterator endpit = boost::next(selection.end.par());
1249 ParagraphList::iterator undoendpit = endpit;
1250 ParagraphList::iterator pars_end = ownerParagraphs().end();
1252 if (endpit != pars_end && endpit->getDepth()) {
1253 while (endpit != pars_end && endpit->getDepth()) {
1255 undoendpit = endpit;
1257 } else if (endpit != pars_end) {
1258 // because of parindents etc.
1262 setUndo(bv(), Undo::DELETE, selection.start.par(),
1263 boost::prior(undoendpit));
1266 endpit = selection.end.par();
1267 int endpos = selection.end.pos();
1269 boost::tie(endpit, endpos) = realcut ?
1270 CutAndPaste::cutSelection(bv()->buffer()->params,
1272 selection.start.par(), endpit,
1273 selection.start.pos(), endpos,
1274 bv()->buffer()->params.textclass,
1276 : CutAndPaste::eraseSelection(bv()->buffer()->params,
1278 selection.start.par(), endpit,
1279 selection.start.pos(), endpos,
1281 // sometimes necessary
1283 selection.start.par()->stripLeadingSpaces();
1285 redoParagraphs(selection.start, boost::next(endpit));
1286 #warning FIXME latent bug
1287 // endpit will be invalidated on redoParagraphs once ParagraphList
1288 // becomes a std::list? There are maybe other places on which this
1289 // can happend? (Ab)
1290 // cutSelection can invalidate the cursor so we need to set
1292 // we prefer the end for when tracking changes
1296 // need a valid cursor. (Lgb)
1299 setCursor(cursor.par(), cursor.pos());
1300 selection.cursor = cursor;
1305 void LyXText::copySelection()
1307 // stuff the selection onto the X clipboard, from an explicit copy request
1308 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1310 // this doesnt make sense, if there is no selection
1311 if (!selection.set())
1314 // ok we have a selection. This is always between selection.start
1315 // and sel_end cursor
1317 // copy behind a space if there is one
1318 while (selection.start.par()->size() > selection.start.pos()
1319 && selection.start.par()->isLineSeparator(selection.start.pos())
1320 && (selection.start.par() != selection.end.par()
1321 || selection.start.pos() < selection.end.pos()))
1322 selection.start.pos(selection.start.pos() + 1);
1324 CutAndPaste::copySelection(selection.start.par(),
1325 selection.end.par(),
1326 selection.start.pos(), selection.end.pos(),
1327 bv()->buffer()->params.textclass);
1331 void LyXText::pasteSelection(size_t sel_index)
1333 // this does not make sense, if there is nothing to paste
1334 if (!CutAndPaste::checkPastePossible())
1337 setUndo(bv(), Undo::INSERT, cursor.par());
1339 ParagraphList::iterator endpit;
1344 boost::tie(ppp, endpit) =
1345 CutAndPaste::pasteSelection(*bv()->buffer(),
1347 cursor.par(), cursor.pos(),
1348 bv()->buffer()->params.textclass,
1350 bufferErrors(*bv()->buffer(), el);
1351 bv()->showErrorList(_("Paste"));
1353 redoParagraphs(cursor, endpit);
1355 setCursor(cursor.par(), cursor.pos());
1358 selection.cursor = cursor;
1359 setCursor(ppp.first, ppp.second);
1365 void LyXText::setSelectionRange(lyx::pos_type length)
1370 selection.cursor = cursor;
1377 // simple replacing. The font of the first selected character is used
1378 void LyXText::replaceSelectionWithString(string const & str)
1380 setCursorParUndo(bv());
1383 if (!selection.set()) { // create a dummy selection
1384 selection.end = cursor;
1385 selection.start = cursor;
1388 // Get font setting before we cut
1389 pos_type pos = selection.end.pos();
1390 LyXFont const font = selection.start.par()
1391 ->getFontSettings(bv()->buffer()->params,
1392 selection.start.pos());
1394 // Insert the new string
1395 string::const_iterator cit = str.begin();
1396 string::const_iterator end = str.end();
1397 for (; cit != end; ++cit) {
1398 selection.end.par()->insertChar(pos, (*cit), font);
1402 // Cut the selection
1403 cutSelection(true, false);
1409 // needed to insert the selection
1410 void LyXText::insertStringAsLines(string const & str)
1412 ParagraphList::iterator pit = cursor.par();
1413 pos_type pos = cursor.pos();
1414 ParagraphList::iterator endpit = boost::next(cursor.par());
1416 setCursorParUndo(bv());
1418 // only to be sure, should not be neccessary
1421 bv()->buffer()->insertStringAsLines(pit, pos, current_font, str);
1423 redoParagraphs(cursor, endpit);
1424 setCursor(cursor.par(), cursor.pos());
1425 selection.cursor = cursor;
1426 setCursor(pit, pos);
1431 // turns double-CR to single CR, others where converted into one
1432 // blank. Then InsertStringAsLines is called
1433 void LyXText::insertStringAsParagraphs(string const & str)
1435 string linestr(str);
1436 bool newline_inserted = false;
1437 string::size_type const siz = linestr.length();
1439 for (string::size_type i = 0; i < siz; ++i) {
1440 if (linestr[i] == '\n') {
1441 if (newline_inserted) {
1442 // we know that \r will be ignored by
1443 // InsertStringA. Of course, it is a dirty
1444 // trick, but it works...
1445 linestr[i - 1] = '\r';
1449 newline_inserted = true;
1451 } else if (IsPrintable(linestr[i])) {
1452 newline_inserted = false;
1455 insertStringAsLines(linestr);
1459 void LyXText::checkParagraph(ParagraphList::iterator pit, pos_type pos)
1461 LyXCursor tmpcursor;
1465 RowList::iterator row = getRow(pit, pos, y);
1466 RowList::iterator beg = rows().begin();
1468 // is there a break one row above
1470 && boost::prior(row)->par() == row->par()) {
1471 z = rowBreakPoint(*boost::prior(row));
1472 if (z >= row->pos()) {
1473 // set the dimensions of the row above
1474 y -= boost::prior(row)->height();
1477 breakAgain(boost::prior(row));
1479 // set the cursor again. Otherwise
1480 // dangling pointers are possible
1481 setCursor(cursor.par(), cursor.pos(),
1482 false, cursor.boundary());
1483 selection.cursor = cursor;
1488 int const tmpheight = row->height();
1489 pos_type const tmplast = lastPos(*this, row);
1492 if (row->height() == tmpheight && lastPos(*this, row) == tmplast) {
1493 postRowPaint(row, y);
1498 // check the special right address boxes
1499 if (pit->layout()->margintype == MARGIN_RIGHT_ADDRESS_BOX) {
1505 redoDrawingOfParagraph(tmpcursor);
1508 // set the cursor again. Otherwise dangling pointers are possible
1509 // also set the selection
1511 if (selection.set()) {
1513 setCursorIntern(selection.cursor.par(), selection.cursor.pos(),
1514 false, selection.cursor.boundary());
1515 selection.cursor = cursor;
1516 setCursorIntern(selection.start.par(),
1517 selection.start.pos(),
1518 false, selection.start.boundary());
1519 selection.start = cursor;
1520 setCursorIntern(selection.end.par(),
1521 selection.end.pos(),
1522 false, selection.end.boundary());
1523 selection.end = cursor;
1524 setCursorIntern(last_sel_cursor.par(),
1525 last_sel_cursor.pos(),
1526 false, last_sel_cursor.boundary());
1527 last_sel_cursor = cursor;
1530 setCursorIntern(cursor.par(), cursor.pos(),
1531 false, cursor.boundary());
1535 // returns false if inset wasn't found
1536 bool LyXText::updateInset(Inset * inset)
1538 // first check the current paragraph
1539 int pos = cursor.par()->getPositionOfInset(inset);
1541 checkParagraph(cursor.par(), pos);
1545 // check every paragraph
1547 ParagraphList::iterator par = ownerParagraphs().begin();
1548 ParagraphList::iterator end = ownerParagraphs().end();
1549 for (; par != end; ++par) {
1550 pos = par->getPositionOfInset(inset);
1552 checkParagraph(par, pos);
1561 bool LyXText::setCursor(ParagraphList::iterator pit,
1563 bool setfont, bool boundary)
1565 LyXCursor old_cursor = cursor;
1566 setCursorIntern(pit, pos, setfont, boundary);
1567 return deleteEmptyParagraphMechanism(old_cursor);
1571 void LyXText::setCursor(LyXCursor & cur, ParagraphList::iterator pit,
1572 pos_type pos, bool boundary)
1574 Assert(pit != ownerParagraphs().end());
1578 cur.boundary(boundary);
1580 // get the cursor y position in text
1582 RowList::iterator row = getRow(pit, pos, y);
1583 RowList::iterator beg = rows().begin();
1585 RowList::iterator old_row = row;
1587 // if we are before the first char of this row and are still in the
1588 // same paragraph and there is a previous row then put the cursor on
1589 // the end of the previous row
1590 cur.iy(y + row->baseline());
1593 boost::prior(row)->par() == row->par() &&
1594 pos < pit->size() &&
1595 pit->getChar(pos) == Paragraph::META_INSET) {
1596 Inset * ins = pit->getInset(pos);
1597 if (ins && (ins->needFullRow() || ins->display())) {
1603 // y is now the beginning of the cursor row
1604 y += row->baseline();
1605 // y is now the cursor baseline
1608 pos_type last = lastPrintablePos(*this, old_row);
1610 // None of these should happen, but we're scaredy-cats
1611 if (pos > pit->size()) {
1612 lyxerr << "dont like 1 please report" << endl;
1615 } else if (pos > last + 1) {
1616 lyxerr << "dont like 2 please report" << endl;
1617 // This shouldn't happen.
1620 } else if (pos < row->pos()) {
1621 lyxerr << "dont like 3 please report" << endl;
1626 // now get the cursors x position
1627 float x = getCursorX(row, pos, last, boundary);
1630 if (old_row != row) {
1631 x = getCursorX(old_row, pos, last, boundary);
1635 /* We take out this for the time being because 1) the redraw code is not
1636 prepared to this yet and 2) because some good policy has yet to be decided
1637 while editting: for instance how to act on rows being created/deleted
1641 //if the cursor is in a visible row, anchor to it
1643 if (topy < y && y < topy + bv()->workHeight())
1649 float LyXText::getCursorX(RowList::iterator rit,
1650 pos_type pos, pos_type last, bool boundary) const
1652 pos_type cursor_vpos = 0;
1654 float fill_separator;
1656 float fill_label_hfill;
1657 // This call HAS to be here because of the BidiTables!!!
1658 prepareToPrint(rit, x, fill_separator, fill_hfill,
1661 ParagraphList::iterator rit_par = rit->par();
1662 pos_type const rit_pos = rit->pos();
1665 cursor_vpos = rit_pos;
1666 else if (pos > last && !boundary)
1667 cursor_vpos = (rit_par->isRightToLeftPar(bv()->buffer()->params))
1668 ? rit_pos : last + 1;
1669 else if (pos > rit_pos && (pos > last || boundary))
1670 /// Place cursor after char at (logical) position pos - 1
1671 cursor_vpos = (bidi_level(pos - 1) % 2 == 0)
1672 ? log2vis(pos - 1) + 1 : log2vis(pos - 1);
1674 /// Place cursor before char at (logical) position pos
1675 cursor_vpos = (bidi_level(pos) % 2 == 0)
1676 ? log2vis(pos) : log2vis(pos) + 1;
1678 pos_type body_pos = rit_par->beginningOfBody();
1679 if ((body_pos > 0) &&
1680 ((body_pos - 1 > last) || !rit_par->isLineSeparator(body_pos - 1)))
1683 for (pos_type vpos = rit_pos; vpos < cursor_vpos; ++vpos) {
1684 pos_type pos = vis2log(vpos);
1685 if (body_pos > 0 && pos == body_pos - 1) {
1686 x += fill_label_hfill +
1687 font_metrics::width(
1688 rit_par->layout()->labelsep,
1689 getLabelFont(bv()->buffer(), rit_par));
1690 if (rit_par->isLineSeparator(body_pos - 1))
1691 x -= singleWidth(rit_par, body_pos - 1);
1694 if (hfillExpansion(*this, rit, pos)) {
1695 x += singleWidth(rit_par, pos);
1696 if (pos >= body_pos)
1699 x += fill_label_hfill;
1700 } else if (rit_par->isSeparator(pos)) {
1701 x += singleWidth(rit_par, pos);
1702 if (pos >= body_pos)
1703 x += fill_separator;
1705 x += singleWidth(rit_par, pos);
1711 void LyXText::setCursorIntern(ParagraphList::iterator pit,
1712 pos_type pos, bool setfont, bool boundary)
1714 UpdatableInset * it = pit->inInset();
1716 if (it != inset_owner) {
1717 lyxerr[Debug::INSETS] << "InsetText is " << it
1719 << "inset_owner is "
1720 << inset_owner << endl;
1721 #ifdef WITH_WARNINGS
1722 #warning I believe this code is wrong. (Lgb)
1723 #warning Jürgen, have a look at this. (Lgb)
1724 #warning Hmmm, I guess you are right but we
1725 #warning should verify when this is needed
1727 // Jürgen, would you like to have a look?
1728 // I guess we need to move the outer cursor
1729 // and open and lock the inset (bla bla bla)
1730 // stuff I don't know... so can you have a look?
1732 // I moved the lyxerr stuff in here so we can see if
1733 // this is actually really needed and where!
1735 // it->getLyXText(bv())->setCursorIntern(bv(), par, pos, setfont, boundary);
1740 setCursor(cursor, pit, pos, boundary);
1746 void LyXText::setCurrentFont()
1748 pos_type pos = cursor.pos();
1749 ParagraphList::iterator pit = cursor.par();
1751 if (cursor.boundary() && pos > 0)
1755 if (pos == pit->size())
1757 else // potentional bug... BUG (Lgb)
1758 if (pit->isSeparator(pos)) {
1759 if (pos > cursorRow()->pos() &&
1760 bidi_level(pos) % 2 ==
1761 bidi_level(pos - 1) % 2)
1763 else if (pos + 1 < pit->size())
1769 pit->getFontSettings(bv()->buffer()->params, pos);
1770 real_current_font = getFont(bv()->buffer(), pit, pos);
1772 if (cursor.pos() == pit->size() &&
1773 isBoundary(bv()->buffer(), *pit, cursor.pos()) &&
1774 !cursor.boundary()) {
1775 Language const * lang =
1776 pit->getParLanguage(bv()->buffer()->params);
1777 current_font.setLanguage(lang);
1778 current_font.setNumber(LyXFont::OFF);
1779 real_current_font.setLanguage(lang);
1780 real_current_font.setNumber(LyXFont::OFF);
1785 // returns the column near the specified x-coordinate of the row
1786 // x is set to the real beginning of this column
1788 LyXText::getColumnNearX(RowList::iterator rit, int & x, bool & boundary) const
1791 float fill_separator;
1793 float fill_label_hfill;
1795 prepareToPrint(rit, tmpx, fill_separator,
1796 fill_hfill, fill_label_hfill);
1798 pos_type vc = rit->pos();
1799 pos_type last = lastPrintablePos(*this, rit);
1802 ParagraphList::iterator rit_par = rit->par();
1803 LyXLayout_ptr const & layout = rit->par()->layout();
1805 bool left_side = false;
1807 pos_type body_pos = rit_par->beginningOfBody();
1808 float last_tmpx = tmpx;
1811 (body_pos - 1 > last ||
1812 !rit_par->isLineSeparator(body_pos - 1)))
1815 // check for empty row
1816 if (!rit_par->size()) {
1821 while (vc <= last && tmpx <= x) {
1824 if (body_pos > 0 && c == body_pos - 1) {
1825 tmpx += fill_label_hfill +
1826 font_metrics::width(layout->labelsep,
1827 getLabelFont(bv()->buffer(), rit_par));
1828 if (rit_par->isLineSeparator(body_pos - 1))
1829 tmpx -= singleWidth(rit_par, body_pos - 1);
1832 if (hfillExpansion(*this, rit, c)) {
1833 tmpx += singleWidth(rit_par, c);
1837 tmpx += fill_label_hfill;
1838 } else if (rit_par->isSeparator(c)) {
1839 tmpx += singleWidth(rit_par, c);
1841 tmpx+= fill_separator;
1843 tmpx += singleWidth(rit_par, c);
1848 if ((tmpx + last_tmpx) / 2 > x) {
1853 if (vc > last + 1) // This shouldn't happen.
1857 // This (rtl_support test) is not needed, but gives
1858 // some speedup if rtl_support=false
1859 RowList::iterator next_rit = boost::next(rit);
1861 bool const lastrow = lyxrc.rtl_support &&
1862 (next_rit == rowlist_.end() ||
1863 next_rit->par() != rit_par);
1865 // If lastrow is false, we don't need to compute
1866 // the value of rtl.
1867 bool const rtl = (lastrow)
1868 ? rit_par->isRightToLeftPar(bv()->buffer()->params)
1871 ((rtl && left_side && vc == rit->pos() && x < tmpx - 5) ||
1872 (!rtl && !left_side && vc == last + 1 && x > tmpx + 5)))
1874 else if (vc == rit->pos()) {
1876 if (bidi_level(c) % 2 == 1)
1879 c = vis2log(vc - 1);
1880 bool const rtl = (bidi_level(c) % 2 == 1);
1881 if (left_side == rtl) {
1883 boundary = isBoundary(bv()->buffer(), *rit_par, c);
1887 if (rit->pos() <= last && c > last
1888 && rit_par->isNewline(last)) {
1889 if (bidi_level(last) % 2 == 0)
1890 tmpx -= singleWidth(rit_par, last);
1892 tmpx += singleWidth(rit_par, last);
1902 void LyXText::setCursorFromCoordinates(int x, int y)
1904 LyXCursor old_cursor = cursor;
1906 setCursorFromCoordinates(cursor, x, y);
1908 deleteEmptyParagraphMechanism(old_cursor);
1915 * return true if the cursor given is at the end of a row,
1916 * and the next row is filled by an inset that spans an entire
1919 bool beforeFullRowInset(LyXText & lt, LyXCursor const & cur)
1921 RowList::iterator row = lt.getRow(cur);
1922 if (boost::next(row) == lt.rows().end())
1925 Row const & next = *boost::next(row);
1927 if (next.pos() != cur.pos() || next.par() != cur.par())
1930 if (cur.pos() == cur.par()->size()
1931 || !cur.par()->isInset(cur.pos()))
1934 Inset const * inset = cur.par()->getInset(cur.pos());
1935 if (inset->needFullRow() || inset->display())
1943 void LyXText::setCursorFromCoordinates(LyXCursor & cur, int x, int y)
1945 // Get the row first.
1947 RowList::iterator row = getRowNearY(y);
1949 pos_type const column = getColumnNearX(row, x, bound);
1950 cur.par(row->par());
1951 cur.pos(row->pos() + column);
1953 cur.y(y + row->baseline());
1955 if (beforeFullRowInset(*this, cur)) {
1956 pos_type const last = lastPrintablePos(*this, row);
1957 RowList::iterator next_row = boost::next(row);
1959 float x = getCursorX(next_row, cur.pos(), last, bound);
1961 cur.iy(y + row->height() + next_row->baseline());
1968 cur.boundary(bound);
1972 void LyXText::cursorLeft(bool internal)
1974 if (cursor.pos() > 0) {
1975 bool boundary = cursor.boundary();
1976 setCursor(cursor.par(), cursor.pos() - 1, true, false);
1977 if (!internal && !boundary &&
1978 isBoundary(bv()->buffer(), *cursor.par(), cursor.pos() + 1))
1979 setCursor(cursor.par(), cursor.pos() + 1, true, true);
1980 } else if (cursor.par() != ownerParagraphs().begin()) { // steps into the above paragraph.
1981 ParagraphList::iterator pit = boost::prior(cursor.par());
1982 setCursor(pit, pit->size());
1987 void LyXText::cursorRight(bool internal)
1989 bool const at_end = (cursor.pos() == cursor.par()->size());
1990 bool const at_newline = !at_end &&
1991 cursor.par()->isNewline(cursor.pos());
1993 if (!internal && cursor.boundary() && !at_newline)
1994 setCursor(cursor.par(), cursor.pos(), true, false);
1996 setCursor(cursor.par(), cursor.pos() + 1, true, false);
1998 isBoundary(bv()->buffer(), *cursor.par(), cursor.pos()))
1999 setCursor(cursor.par(), cursor.pos(), true, true);
2000 } else if (boost::next(cursor.par()) != ownerParagraphs().end())
2001 setCursor(boost::next(cursor.par()), 0);
2005 void LyXText::cursorUp(bool selecting)
2008 int x = cursor.x_fix();
2009 int y = cursor.y() - cursorRow()->baseline() - 1;
2010 setCursorFromCoordinates(x, y);
2013 int y1 = cursor.iy() - topy;
2016 Inset * inset_hit = checkInsetHit(x, y1);
2017 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2018 inset_hit->localDispatch(
2019 FuncRequest(bv(), LFUN_INSET_EDIT, x, y - (y2 - y1), mouse_button::none));
2023 setCursorFromCoordinates(bv(), cursor.x_fix(),
2024 cursor.y() - cursorRow()->baseline() - 1);
2029 void LyXText::cursorDown(bool selecting)
2032 int x = cursor.x_fix();
2033 int y = cursor.y() - cursorRow()->baseline() +
2034 cursorRow()->height() + 1;
2035 setCursorFromCoordinates(x, y);
2036 if (!selecting && cursorRow() == cursor.irow()) {
2038 int y1 = cursor.iy() - topy;
2041 Inset * inset_hit = checkInsetHit(x, y1);
2042 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2043 FuncRequest cmd(bv(), LFUN_INSET_EDIT, x, y - (y2 - y1), mouse_button::none);
2044 inset_hit->localDispatch(cmd);
2048 setCursorFromCoordinates(bv(), cursor.x_fix(),
2049 cursor.y() - cursorRow()->baseline()
2050 + cursorRow()->height() + 1);
2055 void LyXText::cursorUpParagraph()
2057 if (cursor.pos() > 0) {
2058 setCursor(cursor.par(), 0);
2060 else if (cursor.par() != ownerParagraphs().begin()) {
2061 setCursor(boost::prior(cursor.par()), 0);
2066 void LyXText::cursorDownParagraph()
2068 ParagraphList::iterator par = cursor.par();
2069 ParagraphList::iterator next_par = boost::next(par);
2071 if (next_par != ownerParagraphs().end()) {
2072 setCursor(next_par, 0);
2074 setCursor(par, par->size());
2078 // fix the cursor `cur' after a characters has been deleted at `where'
2079 // position. Called by deleteEmptyParagraphMechanism
2080 void LyXText::fixCursorAfterDelete(LyXCursor & cur,
2081 LyXCursor const & where)
2083 // if cursor is not in the paragraph where the delete occured,
2085 if (cur.par() != where.par())
2088 // if cursor position is after the place where the delete occured,
2090 if (cur.pos() > where.pos())
2091 cur.pos(cur.pos()-1);
2093 // check also if we don't want to set the cursor on a spot behind the
2094 // pagragraph because we erased the last character.
2095 if (cur.pos() > cur.par()->size())
2096 cur.pos(cur.par()->size());
2098 // recompute row et al. for this cursor
2099 setCursor(cur, cur.par(), cur.pos(), cur.boundary());
2103 bool LyXText::deleteEmptyParagraphMechanism(LyXCursor const & old_cursor)
2105 // Would be wrong to delete anything if we have a selection.
2106 if (selection.set())
2109 // We allow all kinds of "mumbo-jumbo" when freespacing.
2110 if (old_cursor.par()->layout()->free_spacing
2111 || old_cursor.par()->isFreeSpacing()) {
2115 /* Ok I'll put some comments here about what is missing.
2116 I have fixed BackSpace (and thus Delete) to not delete
2117 double-spaces automagically. I have also changed Cut,
2118 Copy and Paste to hopefully do some sensible things.
2119 There are still some small problems that can lead to
2120 double spaces stored in the document file or space at
2121 the beginning of paragraphs. This happens if you have
2122 the cursor betwenn to spaces and then save. Or if you
2123 cut and paste and the selection have a space at the
2124 beginning and then save right after the paste. I am
2125 sure none of these are very hard to fix, but I will
2126 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
2127 that I can get some feedback. (Lgb)
2130 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
2131 // delete the LineSeparator.
2134 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
2135 // delete the LineSeparator.
2138 // If the pos around the old_cursor were spaces, delete one of them.
2139 if (old_cursor.par() != cursor.par()
2140 || old_cursor.pos() != cursor.pos()) {
2141 // Only if the cursor has really moved
2143 if (old_cursor.pos() > 0
2144 && old_cursor.pos() < old_cursor.par()->size()
2145 && old_cursor.par()->isLineSeparator(old_cursor.pos())
2146 && old_cursor.par()->isLineSeparator(old_cursor.pos() - 1)) {
2147 old_cursor.par()->erase(old_cursor.pos() - 1);
2148 redoParagraphs(old_cursor, boost::next(old_cursor.par()));
2150 #ifdef WITH_WARNINGS
2151 #warning This will not work anymore when we have multiple views of the same buffer
2152 // In this case, we will have to correct also the cursors held by
2153 // other bufferviews. It will probably be easier to do that in a more
2154 // automated way in LyXCursor code. (JMarc 26/09/2001)
2156 // correct all cursors held by the LyXText
2157 fixCursorAfterDelete(cursor, old_cursor);
2158 fixCursorAfterDelete(selection.cursor,
2160 fixCursorAfterDelete(selection.start,
2162 fixCursorAfterDelete(selection.end, old_cursor);
2163 fixCursorAfterDelete(last_sel_cursor,
2165 fixCursorAfterDelete(toggle_cursor, old_cursor);
2166 fixCursorAfterDelete(toggle_end_cursor,
2172 // don't delete anything if this is the ONLY paragraph!
2173 if (ownerParagraphs().size() == 1)
2176 // Do not delete empty paragraphs with keepempty set.
2177 if (old_cursor.par()->allowEmpty())
2180 // only do our magic if we changed paragraph
2181 if (old_cursor.par() == cursor.par())
2184 // record if we have deleted a paragraph
2185 // we can't possibly have deleted a paragraph before this point
2186 bool deleted = false;
2188 if (old_cursor.par()->empty() ||
2189 (old_cursor.par()->size() == 1 &&
2190 old_cursor.par()->isLineSeparator(0))) {
2191 // ok, we will delete anything
2192 LyXCursor tmpcursor;
2196 bool selection_position_was_oldcursor_position = (
2197 selection.cursor.par() == old_cursor.par()
2198 && selection.cursor.pos() == old_cursor.pos());
2200 if (getRow(old_cursor) != rows().begin()) {
2202 prevrow = boost::prior(getRow(old_cursor));
2203 postPaint(old_cursor.y() - getRow(old_cursor)->baseline() - prevrow->height());
2205 cursor = old_cursor; // that undo can restore the right cursor position
2206 #warning FIXME. --end() iterator is usable here
2207 ParagraphList::iterator endpit = boost::next(old_cursor.par());
2208 while (endpit != ownerParagraphs().end() &&
2209 endpit->getDepth()) {
2213 setUndo(bv(), Undo::DELETE, old_cursor.par(),
2214 boost::prior(endpit));
2218 removeRow(getRow(old_cursor));
2220 ownerParagraphs().erase(old_cursor.par());
2222 /* Breakagain the next par. Needed because of
2223 * the parindent that can occur or dissappear.
2224 * The next row can change its height, if
2225 * there is another layout before */
2226 RowList::iterator tmprit = boost::next(prevrow);
2227 if (tmprit != rows().end()) {
2231 setHeightOfRow(prevrow);
2233 RowList::iterator nextrow = boost::next(getRow(old_cursor));
2234 postPaint(old_cursor.y() - getRow(old_cursor)->baseline());
2237 cursor = old_cursor; // that undo can restore the right cursor position
2238 #warning FIXME. --end() iterator is usable here
2239 ParagraphList::iterator endpit = boost::next(old_cursor.par());
2240 while (endpit != ownerParagraphs().end() &&
2241 endpit->getDepth()) {
2245 setUndo(bv(), Undo::DELETE, old_cursor.par(), boost::prior(endpit));
2249 removeRow(getRow(old_cursor));
2251 ownerParagraphs().erase(old_cursor.par());
2253 /* Breakagain the next par. Needed because of
2254 the parindent that can occur or dissappear.
2255 The next row can change its height, if
2256 there is another layout before */
2257 if (nextrow != rows().end()) {
2258 breakAgain(nextrow);
2264 setCursorIntern(cursor.par(), cursor.pos());
2266 if (selection_position_was_oldcursor_position) {
2267 // correct selection
2268 selection.cursor = cursor;
2272 if (old_cursor.par()->stripLeadingSpaces()) {
2273 redoParagraphs(old_cursor, boost::next(old_cursor.par()));
2275 setCursorIntern(cursor.par(), cursor.pos());
2276 selection.cursor = cursor;
2283 ParagraphList & LyXText::ownerParagraphs() const
2286 return inset_owner->paragraphs;
2288 return bv_owner->buffer()->paragraphs;
2292 LyXText::refresh_status LyXText::refreshStatus() const
2294 return refresh_status_;
2298 void LyXText::clearPaint()
2300 refresh_status_ = REFRESH_NONE;
2301 refresh_row = rows().end();
2306 void LyXText::postPaint(int start_y)
2308 refresh_status old = refresh_status_;
2310 refresh_status_ = REFRESH_AREA;
2311 refresh_row = rows().end();
2313 if (old != REFRESH_NONE && refresh_y < start_y)
2316 refresh_y = start_y;
2321 // We are an inset's lyxtext. Tell the top-level lyxtext
2322 // it needs to update the row we're in.
2323 LyXText * t = bv()->text;
2324 t->postRowPaint(t->cursorRow(), t->cursor.y() - t->cursorRow()->baseline());
2328 // FIXME: we should probably remove this y parameter,
2329 // make refresh_y be 0, and use row->y etc.
2330 void LyXText::postRowPaint(RowList::iterator rit, int start_y)
2332 if (refresh_status_ != REFRESH_NONE && refresh_y < start_y) {
2333 refresh_status_ = REFRESH_AREA;
2336 refresh_y = start_y;
2339 if (refresh_status_ == REFRESH_AREA)
2342 refresh_status_ = REFRESH_ROW;
2348 // We are an inset's lyxtext. Tell the top-level lyxtext
2349 // it needs to update the row we're in.
2350 LyXText * t = bv()->text;
2351 t->postRowPaint(t->cursorRow(), t->cursor.y() - t->cursorRow()->baseline());
2355 bool LyXText::isInInset() const
2357 // Sub-level has non-null bv owner and
2358 // non-null inset owner.
2359 return inset_owner != 0 && bv_owner != 0;
2363 int defaultRowHeight()
2365 LyXFont const font(LyXFont::ALL_SANE);
2366 return int(font_metrics::maxAscent(font)
2367 + font_metrics::maxDescent(font) * 1.5);