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)
89 need_break_row = rows().end();
93 anchor_row_ = rows().end();
94 anchor_row_offset_ = 0;
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()
730 setCursorIntern(cursor.par(), cursor.pos());
734 void LyXText::partialRebreak()
736 if (rows().empty()) {
741 RowList::iterator rows_end = rows().end();
743 if (need_break_row != rows_end) {
744 breakAgain(need_break_row);
745 need_break_row = rows_end;
751 // important for the screen
754 // the cursor set functions have a special mechanism. When they
755 // realize, that you left an empty paragraph, they will delete it.
756 // They also delete the corresponding row
758 // need the selection cursor:
759 void LyXText::setSelection()
761 bool const lsel = TextCursor::setSelection();
763 if (inset_owner && (selection.set() || lsel))
764 inset_owner->setUpdateStatus(InsetText::SELECTION);
769 void LyXText::clearSelection()
771 TextCursor::clearSelection();
773 // reset this in the bv_owner!
774 if (bv_owner && bv_owner->text)
775 bv_owner->text->xsel_cache.set(false);
779 void LyXText::cursorHome()
781 setCursor(cursor.par(), cursorRow()->pos());
785 void LyXText::cursorEnd()
787 if (cursor.par()->empty())
790 RowList::iterator rit = cursorRow();
791 RowList::iterator next_rit = boost::next(rit);
792 ParagraphList::iterator pit = rit->par();
793 pos_type last_pos = lastPos(*this, rit);
795 if (next_rit == rows().end() || next_rit->par() != pit) {
799 (pit->getChar(last_pos) != ' ' && !pit->isNewline(last_pos))) {
804 setCursor(pit, last_pos);
808 void LyXText::cursorTop()
810 setCursor(ownerParagraphs().begin(), 0);
814 void LyXText::cursorBottom()
816 ParagraphList::iterator lastpit =
817 boost::prior(ownerParagraphs().end());
818 setCursor(lastpit, lastpit->size());
822 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
824 // If the mask is completely neutral, tell user
825 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
826 // Could only happen with user style
827 bv()->owner()->message(_("No font change defined. Use Character under the Layout menu to define font change."));
831 // Try implicit word selection
832 // If there is a change in the language the implicit word selection
834 LyXCursor resetCursor = cursor;
835 bool implicitSelection = (font.language() == ignore_language
836 && font.number() == LyXFont::IGNORE)
837 ? selectWordWhenUnderCursor(lyx::WHOLE_WORD_STRICT) : false;
840 setFont(font, toggleall);
842 // Implicit selections are cleared afterwards
843 //and cursor is set to the original position.
844 if (implicitSelection) {
846 cursor = resetCursor;
847 setCursor(cursor.par(), cursor.pos());
848 selection.cursor = cursor;
851 inset_owner->setUpdateStatus(InsetText::CURSOR_PAR);
855 string LyXText::getStringToIndex()
857 // Try implicit word selection
858 // If there is a change in the language the implicit word selection
860 LyXCursor const reset_cursor = cursor;
861 bool const implicitSelection =
862 selectWordWhenUnderCursor(lyx::PREVIOUS_WORD);
865 if (!selection.set())
866 bv()->owner()->message(_("Nothing to index!"));
867 else if (selection.start.par() != selection.end.par())
868 bv()->owner()->message(_("Cannot index more than one paragraph!"));
870 idxstring = selectionAsString(bv()->buffer(), false);
872 // Reset cursors to their original position.
873 cursor = reset_cursor;
874 setCursor(cursor.par(), cursor.pos());
875 selection.cursor = cursor;
877 // Clear the implicit selection.
878 if (implicitSelection)
885 // the DTP switches for paragraphs. LyX will store them in the first
886 // physicla paragraph. When a paragraph is broken, the top settings rest,
887 // the bottom settings are given to the new one. So I can make shure,
888 // they do not duplicate themself and you cannnot make dirty things with
891 void LyXText::setParagraph(bool line_top, bool line_bottom,
892 bool pagebreak_top, bool pagebreak_bottom,
893 VSpace const & space_top,
894 VSpace const & space_bottom,
895 Spacing const & spacing,
897 string const & labelwidthstring,
900 LyXCursor tmpcursor = cursor;
901 if (!selection.set()) {
902 selection.start = cursor;
903 selection.end = cursor;
906 // make sure that the depth behind the selection are restored, too
907 ParagraphList::iterator endpit = boost::next(selection.end.par());
908 ParagraphList::iterator undoendpit = endpit;
909 ParagraphList::iterator pars_end = ownerParagraphs().end();
911 if (endpit != pars_end && endpit->getDepth()) {
912 while (endpit != pars_end && endpit->getDepth()) {
916 } else if (endpit != pars_end) {
917 // because of parindents etc.
921 setUndo(bv(), Undo::EDIT, selection.start.par(),
922 boost::prior(undoendpit));
925 ParagraphList::iterator tmppit = selection.end.par();
927 while (tmppit != boost::prior(selection.start.par())) {
928 setCursor(tmppit, 0);
929 postPaint(cursor.y() - cursorRow()->baseline());
931 ParagraphList::iterator pit = cursor.par();
932 ParagraphParameters & params = pit->params();
934 params.lineTop(line_top);
935 params.lineBottom(line_bottom);
936 params.pagebreakTop(pagebreak_top);
937 params.pagebreakBottom(pagebreak_bottom);
938 params.spaceTop(space_top);
939 params.spaceBottom(space_bottom);
940 params.spacing(spacing);
941 // does the layout allow the new alignment?
942 LyXLayout_ptr const & layout = pit->layout();
944 if (align == LYX_ALIGN_LAYOUT)
945 align = layout->align;
946 if (align & layout->alignpossible) {
947 if (align == layout->align)
948 params.align(LYX_ALIGN_LAYOUT);
952 pit->setLabelWidthString(labelwidthstring);
953 params.noindent(noindent);
954 tmppit = boost::prior(pit);
957 redoParagraphs(selection.start, endpit);
960 setCursor(selection.start.par(), selection.start.pos());
961 selection.cursor = cursor;
962 setCursor(selection.end.par(), selection.end.pos());
964 setCursor(tmpcursor.par(), tmpcursor.pos());
966 bv()->updateInset(inset_owner);
970 // set the counter of a paragraph. This includes the labels
971 void LyXText::setCounter(Buffer const * buf, ParagraphList::iterator pit)
973 LyXTextClass const & textclass = buf->params.getLyXTextClass();
974 LyXLayout_ptr const & layout = pit->layout();
976 if (pit != ownerParagraphs().begin()) {
978 pit->params().appendix(boost::prior(pit)->params().appendix());
979 if (!pit->params().appendix() &&
980 pit->params().startOfAppendix()) {
981 pit->params().appendix(true);
982 textclass.counters().reset();
984 pit->enumdepth = boost::prior(pit)->enumdepth;
985 pit->itemdepth = boost::prior(pit)->itemdepth;
987 pit->params().appendix(pit->params().startOfAppendix());
992 /* Maybe we have to increment the enumeration depth.
993 * BUT, enumeration in a footnote is considered in isolation from its
994 * surrounding paragraph so don't increment if this is the
995 * first line of the footnote
996 * AND, bibliographies can't have their depth changed ie. they
997 * are always of depth 0
999 if (pit != ownerParagraphs().begin()
1000 && boost::prior(pit)->getDepth() < pit->getDepth()
1001 && boost::prior(pit)->layout()->labeltype == LABEL_COUNTER_ENUMI
1002 && pit->enumdepth < 3
1003 && layout->labeltype != LABEL_BIBLIO) {
1007 // Maybe we have to decrement the enumeration depth, see note above
1008 if (pit != ownerParagraphs().begin()
1009 && boost::prior(pit)->getDepth() > pit->getDepth()
1010 && layout->labeltype != LABEL_BIBLIO) {
1011 pit->enumdepth = depthHook(pit, ownerParagraphs(),
1012 pit->getDepth())->enumdepth;
1015 if (!pit->params().labelString().empty()) {
1016 pit->params().labelString(string());
1019 if (layout->margintype == MARGIN_MANUAL) {
1020 if (pit->params().labelWidthString().empty()) {
1021 pit->setLabelWidthString(layout->labelstring());
1024 pit->setLabelWidthString(string());
1027 // is it a layout that has an automatic label?
1028 if (layout->labeltype >= LABEL_COUNTER_CHAPTER) {
1029 int const i = layout->labeltype - LABEL_COUNTER_CHAPTER;
1033 if (i >= 0 && i <= buf->params.secnumdepth) {
1037 textclass.counters().step(layout->latexname());
1039 // Is there a label? Useful for Chapter layout
1040 if (!pit->params().appendix()) {
1041 s << buf->B_(layout->labelstring());
1043 s << buf->B_(layout->labelstring_appendix());
1046 // Use of an integer is here less than elegant. For now.
1047 int head = textclass.maxcounter() - LABEL_COUNTER_CHAPTER;
1048 if (!pit->params().appendix()) {
1049 numbertype = "sectioning";
1051 numbertype = "appendix";
1052 if (pit->isRightToLeftPar(buf->params))
1053 langtype = "hebrew";
1059 << textclass.counters()
1060 .numberLabel(layout->latexname(),
1061 numbertype, langtype, head);
1063 pit->params().labelString(STRCONV(s.str()));
1065 // reset enum counters
1066 textclass.counters().reset("enum");
1067 } else if (layout->labeltype < LABEL_COUNTER_ENUMI) {
1068 textclass.counters().reset("enum");
1069 } else if (layout->labeltype == LABEL_COUNTER_ENUMI) {
1071 // Yes I know this is a really, really! bad solution
1073 string enumcounter("enum");
1075 switch (pit->enumdepth) {
1084 enumcounter += "iv";
1087 // not a valid enumdepth...
1091 textclass.counters().step(enumcounter);
1093 s << textclass.counters()
1094 .numberLabel(enumcounter, "enumeration");
1095 pit->params().labelString(STRCONV(s.str()));
1097 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
1098 textclass.counters().step("bibitem");
1099 int number = textclass.counters().value("bibitem");
1100 if (pit->bibitem()) {
1101 pit->bibitem()->setCounter(number);
1102 pit->params().labelString(layout->labelstring());
1104 // In biblio should't be following counters but...
1106 string s = buf->B_(layout->labelstring());
1108 // the caption hack:
1109 if (layout->labeltype == LABEL_SENSITIVE) {
1110 ParagraphList::iterator tmppit = pit;
1113 while (tmppit != ownerParagraphs().end() &&
1115 // the single '=' is intended below
1116 && (in = tmppit->inInset()->owner())) {
1117 if (in->lyxCode() == Inset::FLOAT_CODE ||
1118 in->lyxCode() == Inset::WRAP_CODE) {
1122 tmppit = std::find(ownerParagraphs().begin(), ownerParagraphs().end(), *in->parOwner());
1129 if (in->lyxCode() == Inset::FLOAT_CODE)
1130 type = static_cast<InsetFloat*>(in)->params().type;
1131 else if (in->lyxCode() == Inset::WRAP_CODE)
1132 type = static_cast<InsetWrap*>(in)->params().type;
1136 Floating const & fl = textclass.floats().getType(type);
1138 textclass.counters().step(fl.type());
1140 // Doesn't work... yet.
1141 s = bformat(_("%1$s #:"), buf->B_(fl.name()));
1143 // par->SetLayout(0);
1144 // s = layout->labelstring;
1145 s = _("Senseless: ");
1148 pit->params().labelString(s);
1150 // reset the enumeration counter. They are always reset
1151 // when there is any other layout between
1152 // Just fall-through between the cases so that all
1153 // enum counters deeper than enumdepth is also reset.
1154 switch (pit->enumdepth) {
1156 textclass.counters().reset("enumi");
1158 textclass.counters().reset("enumii");
1160 textclass.counters().reset("enumiii");
1162 textclass.counters().reset("enumiv");
1168 // Updates all counters. Paragraphs with changed label string will be rebroken
1169 void LyXText::updateCounters()
1171 RowList::iterator rowit = rows().begin();
1172 ParagraphList::iterator pit = rowit->par();
1174 // CHECK if this is really needed. (Lgb)
1175 bv()->buffer()->params.getLyXTextClass().counters().reset();
1177 ParagraphList::iterator beg = ownerParagraphs().begin();
1178 ParagraphList::iterator end = ownerParagraphs().end();
1179 for (; pit != end; ++pit) {
1180 while (rowit->par() != pit)
1183 string const oldLabel = pit->params().labelString();
1185 size_t maxdepth = 0;
1187 maxdepth = boost::prior(pit)->getMaxDepthAfter();
1189 if (pit->params().depth() > maxdepth)
1190 pit->params().depth(maxdepth);
1192 // setCounter can potentially change the labelString.
1193 setCounter(bv()->buffer(), pit);
1195 string const & newLabel = pit->params().labelString();
1197 if (oldLabel.empty() && !newLabel.empty()) {
1198 removeParagraph(rowit);
1199 appendParagraph(rowit);
1205 void LyXText::insertInset(Inset * inset)
1207 if (!cursor.par()->insetAllowed(inset->lyxCode()))
1209 setUndo(bv(), Undo::FINISH, cursor.par());
1211 cursor.par()->insertInset(cursor.pos(), inset);
1212 // Just to rebreak and refresh correctly.
1213 // The character will not be inserted a second time
1214 insertChar(Paragraph::META_INSET);
1215 // If we enter a highly editable inset the cursor should be to before
1216 // the inset. This couldn't happen before as Undo was not handled inside
1217 // inset now after the Undo LyX tries to call inset->Edit(...) again
1218 // and cannot do this as the cursor is behind the inset and GetInset
1219 // does not return the inset!
1220 if (isHighlyEditableInset(inset)) {
1227 void LyXText::cutSelection(bool doclear, bool realcut)
1229 // Stuff what we got on the clipboard. Even if there is no selection.
1231 // There is a problem with having the stuffing here in that the
1232 // larger the selection the slower LyX will get. This can be
1233 // solved by running the line below only when the selection has
1234 // finished. The solution used currently just works, to make it
1235 // faster we need to be more clever and probably also have more
1236 // calls to stuffClipboard. (Lgb)
1237 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1239 // This doesn't make sense, if there is no selection
1240 if (!selection.set())
1243 // OK, we have a selection. This is always between selection.start
1244 // and selection.end
1246 // make sure that the depth behind the selection are restored, too
1247 ParagraphList::iterator endpit = boost::next(selection.end.par());
1248 ParagraphList::iterator undoendpit = endpit;
1249 ParagraphList::iterator pars_end = ownerParagraphs().end();
1251 if (endpit != pars_end && endpit->getDepth()) {
1252 while (endpit != pars_end && endpit->getDepth()) {
1254 undoendpit = endpit;
1256 } else if (endpit != pars_end) {
1257 // because of parindents etc.
1261 setUndo(bv(), Undo::DELETE, selection.start.par(),
1262 boost::prior(undoendpit));
1265 endpit = selection.end.par();
1266 int endpos = selection.end.pos();
1268 boost::tie(endpit, endpos) = realcut ?
1269 CutAndPaste::cutSelection(bv()->buffer()->params,
1271 selection.start.par(), endpit,
1272 selection.start.pos(), endpos,
1273 bv()->buffer()->params.textclass,
1275 : CutAndPaste::eraseSelection(bv()->buffer()->params,
1277 selection.start.par(), endpit,
1278 selection.start.pos(), endpos,
1280 // sometimes necessary
1282 selection.start.par()->stripLeadingSpaces();
1284 redoParagraphs(selection.start, boost::next(endpit));
1285 #warning FIXME latent bug
1286 // endpit will be invalidated on redoParagraphs once ParagraphList
1287 // becomes a std::list? There are maybe other places on which this
1288 // can happend? (Ab)
1289 // cutSelection can invalidate the cursor so we need to set
1291 // we prefer the end for when tracking changes
1295 // need a valid cursor. (Lgb)
1298 setCursor(cursor.par(), cursor.pos());
1299 selection.cursor = cursor;
1304 void LyXText::copySelection()
1306 // stuff the selection onto the X clipboard, from an explicit copy request
1307 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1309 // this doesnt make sense, if there is no selection
1310 if (!selection.set())
1313 // ok we have a selection. This is always between selection.start
1314 // and sel_end cursor
1316 // copy behind a space if there is one
1317 while (selection.start.par()->size() > selection.start.pos()
1318 && selection.start.par()->isLineSeparator(selection.start.pos())
1319 && (selection.start.par() != selection.end.par()
1320 || selection.start.pos() < selection.end.pos()))
1321 selection.start.pos(selection.start.pos() + 1);
1323 CutAndPaste::copySelection(selection.start.par(),
1324 selection.end.par(),
1325 selection.start.pos(), selection.end.pos(),
1326 bv()->buffer()->params.textclass);
1330 void LyXText::pasteSelection(size_t sel_index)
1332 // this does not make sense, if there is nothing to paste
1333 if (!CutAndPaste::checkPastePossible())
1336 setUndo(bv(), Undo::INSERT, cursor.par());
1338 ParagraphList::iterator endpit;
1343 boost::tie(ppp, endpit) =
1344 CutAndPaste::pasteSelection(*bv()->buffer(),
1346 cursor.par(), cursor.pos(),
1347 bv()->buffer()->params.textclass,
1349 bufferErrors(*bv()->buffer(), el);
1350 bv()->showErrorList(_("Paste"));
1352 redoParagraphs(cursor, endpit);
1354 setCursor(cursor.par(), cursor.pos());
1357 selection.cursor = cursor;
1358 setCursor(ppp.first, ppp.second);
1364 void LyXText::setSelectionRange(lyx::pos_type length)
1369 selection.cursor = cursor;
1376 // simple replacing. The font of the first selected character is used
1377 void LyXText::replaceSelectionWithString(string const & str)
1379 setCursorParUndo(bv());
1382 if (!selection.set()) { // create a dummy selection
1383 selection.end = cursor;
1384 selection.start = cursor;
1387 // Get font setting before we cut
1388 pos_type pos = selection.end.pos();
1389 LyXFont const font = selection.start.par()
1390 ->getFontSettings(bv()->buffer()->params,
1391 selection.start.pos());
1393 // Insert the new string
1394 string::const_iterator cit = str.begin();
1395 string::const_iterator end = str.end();
1396 for (; cit != end; ++cit) {
1397 selection.end.par()->insertChar(pos, (*cit), font);
1401 // Cut the selection
1402 cutSelection(true, false);
1408 // needed to insert the selection
1409 void LyXText::insertStringAsLines(string const & str)
1411 ParagraphList::iterator pit = cursor.par();
1412 pos_type pos = cursor.pos();
1413 ParagraphList::iterator endpit = boost::next(cursor.par());
1415 setCursorParUndo(bv());
1417 // only to be sure, should not be neccessary
1420 bv()->buffer()->insertStringAsLines(pit, pos, current_font, str);
1422 redoParagraphs(cursor, endpit);
1423 setCursor(cursor.par(), cursor.pos());
1424 selection.cursor = cursor;
1425 setCursor(pit, pos);
1430 // turns double-CR to single CR, others where converted into one
1431 // blank. Then InsertStringAsLines is called
1432 void LyXText::insertStringAsParagraphs(string const & str)
1434 string linestr(str);
1435 bool newline_inserted = false;
1436 string::size_type const siz = linestr.length();
1438 for (string::size_type i = 0; i < siz; ++i) {
1439 if (linestr[i] == '\n') {
1440 if (newline_inserted) {
1441 // we know that \r will be ignored by
1442 // InsertStringA. Of course, it is a dirty
1443 // trick, but it works...
1444 linestr[i - 1] = '\r';
1448 newline_inserted = true;
1450 } else if (IsPrintable(linestr[i])) {
1451 newline_inserted = false;
1454 insertStringAsLines(linestr);
1458 void LyXText::checkParagraph(ParagraphList::iterator pit, pos_type pos)
1460 LyXCursor tmpcursor;
1464 RowList::iterator row = getRow(pit, pos, y);
1465 RowList::iterator beg = rows().begin();
1467 // is there a break one row above
1469 && boost::prior(row)->par() == row->par()) {
1470 z = rowBreakPoint(*boost::prior(row));
1471 if (z >= row->pos()) {
1472 // set the dimensions of the row above
1473 y -= boost::prior(row)->height();
1476 breakAgain(boost::prior(row));
1478 // set the cursor again. Otherwise
1479 // dangling pointers are possible
1480 setCursor(cursor.par(), cursor.pos(),
1481 false, cursor.boundary());
1482 selection.cursor = cursor;
1487 int const tmpheight = row->height();
1488 pos_type const tmplast = lastPos(*this, row);
1491 if (row->height() == tmpheight && lastPos(*this, row) == tmplast) {
1492 postRowPaint(row, y);
1497 // check the special right address boxes
1498 if (pit->layout()->margintype == MARGIN_RIGHT_ADDRESS_BOX) {
1504 redoDrawingOfParagraph(tmpcursor);
1507 // set the cursor again. Otherwise dangling pointers are possible
1508 // also set the selection
1510 if (selection.set()) {
1512 setCursorIntern(selection.cursor.par(), selection.cursor.pos(),
1513 false, selection.cursor.boundary());
1514 selection.cursor = cursor;
1515 setCursorIntern(selection.start.par(),
1516 selection.start.pos(),
1517 false, selection.start.boundary());
1518 selection.start = cursor;
1519 setCursorIntern(selection.end.par(),
1520 selection.end.pos(),
1521 false, selection.end.boundary());
1522 selection.end = cursor;
1523 setCursorIntern(last_sel_cursor.par(),
1524 last_sel_cursor.pos(),
1525 false, last_sel_cursor.boundary());
1526 last_sel_cursor = cursor;
1529 setCursorIntern(cursor.par(), cursor.pos(),
1530 false, cursor.boundary());
1534 // returns false if inset wasn't found
1535 bool LyXText::updateInset(Inset * inset)
1537 // first check the current paragraph
1538 int pos = cursor.par()->getPositionOfInset(inset);
1540 checkParagraph(cursor.par(), pos);
1544 // check every paragraph
1546 ParagraphList::iterator par = ownerParagraphs().begin();
1547 ParagraphList::iterator end = ownerParagraphs().end();
1548 for (; par != end; ++par) {
1549 pos = par->getPositionOfInset(inset);
1551 checkParagraph(par, pos);
1560 bool LyXText::setCursor(ParagraphList::iterator pit,
1562 bool setfont, bool boundary)
1564 LyXCursor old_cursor = cursor;
1565 setCursorIntern(pit, pos, setfont, boundary);
1566 return deleteEmptyParagraphMechanism(old_cursor);
1570 void LyXText::setCursor(LyXCursor & cur, ParagraphList::iterator pit,
1571 pos_type pos, bool boundary)
1573 Assert(pit != ownerParagraphs().end());
1577 cur.boundary(boundary);
1579 // get the cursor y position in text
1581 RowList::iterator row = getRow(pit, pos, y);
1582 RowList::iterator beg = rows().begin();
1584 RowList::iterator old_row = row;
1586 // if we are before the first char of this row and are still in the
1587 // same paragraph and there is a previous row then put the cursor on
1588 // the end of the previous row
1589 cur.iy(y + row->baseline());
1592 boost::prior(row)->par() == row->par() &&
1593 pos < pit->size() &&
1594 pit->getChar(pos) == Paragraph::META_INSET) {
1595 Inset * ins = pit->getInset(pos);
1596 if (ins && (ins->needFullRow() || ins->display())) {
1602 // y is now the beginning of the cursor row
1603 y += row->baseline();
1604 // y is now the cursor baseline
1607 pos_type last = lastPrintablePos(*this, old_row);
1609 // None of these should happen, but we're scaredy-cats
1610 if (pos > pit->size()) {
1611 lyxerr << "dont like 1 please report" << endl;
1614 } else if (pos > last + 1) {
1615 lyxerr << "dont like 2 please report" << endl;
1616 // This shouldn't happen.
1619 } else if (pos < row->pos()) {
1620 lyxerr << "dont like 3 please report" << endl;
1625 // now get the cursors x position
1626 float x = getCursorX(row, pos, last, boundary);
1629 if (old_row != row) {
1630 x = getCursorX(old_row, pos, last, boundary);
1634 /* We take out this for the time being because 1) the redraw code is not
1635 prepared to this yet and 2) because some good policy has yet to be decided
1636 while editting: for instance how to act on rows being created/deleted
1640 //if the cursor is in a visible row, anchor to it
1642 if (topy < y && y < topy + bv()->workHeight())
1648 float LyXText::getCursorX(RowList::iterator rit,
1649 pos_type pos, pos_type last, bool boundary) const
1651 pos_type cursor_vpos = 0;
1653 float fill_separator;
1655 float fill_label_hfill;
1656 // This call HAS to be here because of the BidiTables!!!
1657 prepareToPrint(rit, x, fill_separator, fill_hfill,
1660 ParagraphList::iterator rit_par = rit->par();
1661 pos_type const rit_pos = rit->pos();
1664 cursor_vpos = rit_pos;
1665 else if (pos > last && !boundary)
1666 cursor_vpos = (rit_par->isRightToLeftPar(bv()->buffer()->params))
1667 ? rit_pos : last + 1;
1668 else if (pos > rit_pos && (pos > last || boundary))
1669 /// Place cursor after char at (logical) position pos - 1
1670 cursor_vpos = (bidi_level(pos - 1) % 2 == 0)
1671 ? log2vis(pos - 1) + 1 : log2vis(pos - 1);
1673 /// Place cursor before char at (logical) position pos
1674 cursor_vpos = (bidi_level(pos) % 2 == 0)
1675 ? log2vis(pos) : log2vis(pos) + 1;
1677 pos_type body_pos = rit_par->beginningOfBody();
1678 if ((body_pos > 0) &&
1679 ((body_pos - 1 > last) || !rit_par->isLineSeparator(body_pos - 1)))
1682 for (pos_type vpos = rit_pos; vpos < cursor_vpos; ++vpos) {
1683 pos_type pos = vis2log(vpos);
1684 if (body_pos > 0 && pos == body_pos - 1) {
1685 x += fill_label_hfill +
1686 font_metrics::width(
1687 rit_par->layout()->labelsep,
1688 getLabelFont(bv()->buffer(), rit_par));
1689 if (rit_par->isLineSeparator(body_pos - 1))
1690 x -= singleWidth(rit_par, body_pos - 1);
1693 if (hfillExpansion(*this, rit, pos)) {
1694 x += singleWidth(rit_par, pos);
1695 if (pos >= body_pos)
1698 x += fill_label_hfill;
1699 } else if (rit_par->isSeparator(pos)) {
1700 x += singleWidth(rit_par, pos);
1701 if (pos >= body_pos)
1702 x += fill_separator;
1704 x += singleWidth(rit_par, pos);
1710 void LyXText::setCursorIntern(ParagraphList::iterator pit,
1711 pos_type pos, bool setfont, bool boundary)
1713 UpdatableInset * it = pit->inInset();
1715 if (it != inset_owner) {
1716 lyxerr[Debug::INSETS] << "InsetText is " << it
1718 << "inset_owner is "
1719 << inset_owner << endl;
1720 #ifdef WITH_WARNINGS
1721 #warning I believe this code is wrong. (Lgb)
1722 #warning Jürgen, have a look at this. (Lgb)
1723 #warning Hmmm, I guess you are right but we
1724 #warning should verify when this is needed
1726 // Jürgen, would you like to have a look?
1727 // I guess we need to move the outer cursor
1728 // and open and lock the inset (bla bla bla)
1729 // stuff I don't know... so can you have a look?
1731 // I moved the lyxerr stuff in here so we can see if
1732 // this is actually really needed and where!
1734 // it->getLyXText(bv())->setCursorIntern(bv(), par, pos, setfont, boundary);
1739 setCursor(cursor, pit, pos, boundary);
1745 void LyXText::setCurrentFont()
1747 pos_type pos = cursor.pos();
1748 ParagraphList::iterator pit = cursor.par();
1750 if (cursor.boundary() && pos > 0)
1754 if (pos == pit->size())
1756 else // potentional bug... BUG (Lgb)
1757 if (pit->isSeparator(pos)) {
1758 if (pos > cursorRow()->pos() &&
1759 bidi_level(pos) % 2 ==
1760 bidi_level(pos - 1) % 2)
1762 else if (pos + 1 < pit->size())
1768 pit->getFontSettings(bv()->buffer()->params, pos);
1769 real_current_font = getFont(bv()->buffer(), pit, pos);
1771 if (cursor.pos() == pit->size() &&
1772 isBoundary(bv()->buffer(), *pit, cursor.pos()) &&
1773 !cursor.boundary()) {
1774 Language const * lang =
1775 pit->getParLanguage(bv()->buffer()->params);
1776 current_font.setLanguage(lang);
1777 current_font.setNumber(LyXFont::OFF);
1778 real_current_font.setLanguage(lang);
1779 real_current_font.setNumber(LyXFont::OFF);
1784 // returns the column near the specified x-coordinate of the row
1785 // x is set to the real beginning of this column
1787 LyXText::getColumnNearX(RowList::iterator rit, int & x, bool & boundary) const
1790 float fill_separator;
1792 float fill_label_hfill;
1794 prepareToPrint(rit, tmpx, fill_separator,
1795 fill_hfill, fill_label_hfill);
1797 pos_type vc = rit->pos();
1798 pos_type last = lastPrintablePos(*this, rit);
1801 ParagraphList::iterator rit_par = rit->par();
1802 LyXLayout_ptr const & layout = rit->par()->layout();
1804 bool left_side = false;
1806 pos_type body_pos = rit_par->beginningOfBody();
1807 float last_tmpx = tmpx;
1810 (body_pos - 1 > last ||
1811 !rit_par->isLineSeparator(body_pos - 1)))
1814 // check for empty row
1815 if (!rit_par->size()) {
1820 while (vc <= last && tmpx <= x) {
1823 if (body_pos > 0 && c == body_pos - 1) {
1824 tmpx += fill_label_hfill +
1825 font_metrics::width(layout->labelsep,
1826 getLabelFont(bv()->buffer(), rit_par));
1827 if (rit_par->isLineSeparator(body_pos - 1))
1828 tmpx -= singleWidth(rit_par, body_pos - 1);
1831 if (hfillExpansion(*this, rit, c)) {
1832 tmpx += singleWidth(rit_par, c);
1836 tmpx += fill_label_hfill;
1837 } else if (rit_par->isSeparator(c)) {
1838 tmpx += singleWidth(rit_par, c);
1840 tmpx+= fill_separator;
1842 tmpx += singleWidth(rit_par, c);
1847 if ((tmpx + last_tmpx) / 2 > x) {
1852 if (vc > last + 1) // This shouldn't happen.
1856 // This (rtl_support test) is not needed, but gives
1857 // some speedup if rtl_support=false
1858 RowList::iterator next_rit = boost::next(rit);
1860 bool const lastrow = lyxrc.rtl_support &&
1861 (next_rit == rowlist_.end() ||
1862 next_rit->par() != rit_par);
1864 // If lastrow is false, we don't need to compute
1865 // the value of rtl.
1866 bool const rtl = (lastrow)
1867 ? rit_par->isRightToLeftPar(bv()->buffer()->params)
1870 ((rtl && left_side && vc == rit->pos() && x < tmpx - 5) ||
1871 (!rtl && !left_side && vc == last + 1 && x > tmpx + 5)))
1873 else if (vc == rit->pos()) {
1875 if (bidi_level(c) % 2 == 1)
1878 c = vis2log(vc - 1);
1879 bool const rtl = (bidi_level(c) % 2 == 1);
1880 if (left_side == rtl) {
1882 boundary = isBoundary(bv()->buffer(), *rit_par, c);
1886 if (rit->pos() <= last && c > last
1887 && rit_par->isNewline(last)) {
1888 if (bidi_level(last) % 2 == 0)
1889 tmpx -= singleWidth(rit_par, last);
1891 tmpx += singleWidth(rit_par, last);
1901 void LyXText::setCursorFromCoordinates(int x, int y)
1903 LyXCursor old_cursor = cursor;
1905 setCursorFromCoordinates(cursor, x, y);
1907 deleteEmptyParagraphMechanism(old_cursor);
1914 * return true if the cursor given is at the end of a row,
1915 * and the next row is filled by an inset that spans an entire
1918 bool beforeFullRowInset(LyXText & lt, LyXCursor const & cur)
1920 RowList::iterator row = lt.getRow(cur);
1921 if (boost::next(row) == lt.rows().end())
1924 Row const & next = *boost::next(row);
1926 if (next.pos() != cur.pos() || next.par() != cur.par())
1929 if (cur.pos() == cur.par()->size()
1930 || !cur.par()->isInset(cur.pos()))
1933 Inset const * inset = cur.par()->getInset(cur.pos());
1934 if (inset->needFullRow() || inset->display())
1942 void LyXText::setCursorFromCoordinates(LyXCursor & cur, int x, int y)
1944 // Get the row first.
1946 RowList::iterator row = getRowNearY(y);
1948 pos_type const column = getColumnNearX(row, x, bound);
1949 cur.par(row->par());
1950 cur.pos(row->pos() + column);
1952 cur.y(y + row->baseline());
1954 if (beforeFullRowInset(*this, cur)) {
1955 pos_type const last = lastPrintablePos(*this, row);
1956 RowList::iterator next_row = boost::next(row);
1958 float x = getCursorX(next_row, cur.pos(), last, bound);
1960 cur.iy(y + row->height() + next_row->baseline());
1967 cur.boundary(bound);
1971 void LyXText::cursorLeft(bool internal)
1973 if (cursor.pos() > 0) {
1974 bool boundary = cursor.boundary();
1975 setCursor(cursor.par(), cursor.pos() - 1, true, false);
1976 if (!internal && !boundary &&
1977 isBoundary(bv()->buffer(), *cursor.par(), cursor.pos() + 1))
1978 setCursor(cursor.par(), cursor.pos() + 1, true, true);
1979 } else if (cursor.par() != ownerParagraphs().begin()) { // steps into the above paragraph.
1980 ParagraphList::iterator pit = boost::prior(cursor.par());
1981 setCursor(pit, pit->size());
1986 void LyXText::cursorRight(bool internal)
1988 bool const at_end = (cursor.pos() == cursor.par()->size());
1989 bool const at_newline = !at_end &&
1990 cursor.par()->isNewline(cursor.pos());
1992 if (!internal && cursor.boundary() && !at_newline)
1993 setCursor(cursor.par(), cursor.pos(), true, false);
1995 setCursor(cursor.par(), cursor.pos() + 1, true, false);
1997 isBoundary(bv()->buffer(), *cursor.par(), cursor.pos()))
1998 setCursor(cursor.par(), cursor.pos(), true, true);
1999 } else if (boost::next(cursor.par()) != ownerParagraphs().end())
2000 setCursor(boost::next(cursor.par()), 0);
2004 void LyXText::cursorUp(bool selecting)
2007 int x = cursor.x_fix();
2008 int y = cursor.y() - cursorRow()->baseline() - 1;
2009 setCursorFromCoordinates(x, y);
2012 int y1 = cursor.iy() - topy;
2015 Inset * inset_hit = checkInsetHit(x, y1);
2016 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2017 inset_hit->localDispatch(
2018 FuncRequest(bv(), LFUN_INSET_EDIT, x, y - (y2 - y1), mouse_button::none));
2022 setCursorFromCoordinates(bv(), cursor.x_fix(),
2023 cursor.y() - cursorRow()->baseline() - 1);
2028 void LyXText::cursorDown(bool selecting)
2031 int x = cursor.x_fix();
2032 int y = cursor.y() - cursorRow()->baseline() +
2033 cursorRow()->height() + 1;
2034 setCursorFromCoordinates(x, y);
2035 if (!selecting && cursorRow() == cursor.irow()) {
2037 int y1 = cursor.iy() - topy;
2040 Inset * inset_hit = checkInsetHit(x, y1);
2041 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2042 FuncRequest cmd(bv(), LFUN_INSET_EDIT, x, y - (y2 - y1), mouse_button::none);
2043 inset_hit->localDispatch(cmd);
2047 setCursorFromCoordinates(bv(), cursor.x_fix(),
2048 cursor.y() - cursorRow()->baseline()
2049 + cursorRow()->height() + 1);
2054 void LyXText::cursorUpParagraph()
2056 if (cursor.pos() > 0) {
2057 setCursor(cursor.par(), 0);
2059 else if (cursor.par() != ownerParagraphs().begin()) {
2060 setCursor(boost::prior(cursor.par()), 0);
2065 void LyXText::cursorDownParagraph()
2067 ParagraphList::iterator par = cursor.par();
2068 ParagraphList::iterator next_par = boost::next(par);
2070 if (next_par != ownerParagraphs().end()) {
2071 setCursor(next_par, 0);
2073 setCursor(par, par->size());
2077 // fix the cursor `cur' after a characters has been deleted at `where'
2078 // position. Called by deleteEmptyParagraphMechanism
2079 void LyXText::fixCursorAfterDelete(LyXCursor & cur,
2080 LyXCursor const & where)
2082 // if cursor is not in the paragraph where the delete occured,
2084 if (cur.par() != where.par())
2087 // if cursor position is after the place where the delete occured,
2089 if (cur.pos() > where.pos())
2090 cur.pos(cur.pos()-1);
2092 // check also if we don't want to set the cursor on a spot behind the
2093 // pagragraph because we erased the last character.
2094 if (cur.pos() > cur.par()->size())
2095 cur.pos(cur.par()->size());
2097 // recompute row et al. for this cursor
2098 setCursor(cur, cur.par(), cur.pos(), cur.boundary());
2102 bool LyXText::deleteEmptyParagraphMechanism(LyXCursor const & old_cursor)
2104 // Would be wrong to delete anything if we have a selection.
2105 if (selection.set())
2108 // We allow all kinds of "mumbo-jumbo" when freespacing.
2109 if (old_cursor.par()->layout()->free_spacing
2110 || old_cursor.par()->isFreeSpacing()) {
2114 /* Ok I'll put some comments here about what is missing.
2115 I have fixed BackSpace (and thus Delete) to not delete
2116 double-spaces automagically. I have also changed Cut,
2117 Copy and Paste to hopefully do some sensible things.
2118 There are still some small problems that can lead to
2119 double spaces stored in the document file or space at
2120 the beginning of paragraphs. This happens if you have
2121 the cursor betwenn to spaces and then save. Or if you
2122 cut and paste and the selection have a space at the
2123 beginning and then save right after the paste. I am
2124 sure none of these are very hard to fix, but I will
2125 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
2126 that I can get some feedback. (Lgb)
2129 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
2130 // delete the LineSeparator.
2133 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
2134 // delete the LineSeparator.
2137 // If the pos around the old_cursor were spaces, delete one of them.
2138 if (old_cursor.par() != cursor.par()
2139 || old_cursor.pos() != cursor.pos()) {
2140 // Only if the cursor has really moved
2142 if (old_cursor.pos() > 0
2143 && old_cursor.pos() < old_cursor.par()->size()
2144 && old_cursor.par()->isLineSeparator(old_cursor.pos())
2145 && old_cursor.par()->isLineSeparator(old_cursor.pos() - 1)) {
2146 old_cursor.par()->erase(old_cursor.pos() - 1);
2147 redoParagraphs(old_cursor, boost::next(old_cursor.par()));
2149 #ifdef WITH_WARNINGS
2150 #warning This will not work anymore when we have multiple views of the same buffer
2151 // In this case, we will have to correct also the cursors held by
2152 // other bufferviews. It will probably be easier to do that in a more
2153 // automated way in LyXCursor code. (JMarc 26/09/2001)
2155 // correct all cursors held by the LyXText
2156 fixCursorAfterDelete(cursor, old_cursor);
2157 fixCursorAfterDelete(selection.cursor,
2159 fixCursorAfterDelete(selection.start,
2161 fixCursorAfterDelete(selection.end, old_cursor);
2162 fixCursorAfterDelete(last_sel_cursor,
2164 fixCursorAfterDelete(toggle_cursor, old_cursor);
2165 fixCursorAfterDelete(toggle_end_cursor,
2171 // don't delete anything if this is the ONLY paragraph!
2172 if (ownerParagraphs().size() == 1)
2175 // Do not delete empty paragraphs with keepempty set.
2176 if (old_cursor.par()->allowEmpty())
2179 // only do our magic if we changed paragraph
2180 if (old_cursor.par() == cursor.par())
2183 // record if we have deleted a paragraph
2184 // we can't possibly have deleted a paragraph before this point
2185 bool deleted = false;
2187 if (old_cursor.par()->empty() ||
2188 (old_cursor.par()->size() == 1 &&
2189 old_cursor.par()->isLineSeparator(0))) {
2190 // ok, we will delete anything
2191 LyXCursor tmpcursor;
2195 bool selection_position_was_oldcursor_position = (
2196 selection.cursor.par() == old_cursor.par()
2197 && selection.cursor.pos() == old_cursor.pos());
2199 if (getRow(old_cursor) != rows().begin()) {
2201 prevrow = boost::prior(getRow(old_cursor));
2202 postPaint(old_cursor.y() - getRow(old_cursor)->baseline() - prevrow->height());
2204 cursor = old_cursor; // that undo can restore the right cursor position
2205 #warning FIXME. --end() iterator is usable here
2206 ParagraphList::iterator endpit = boost::next(old_cursor.par());
2207 while (endpit != ownerParagraphs().end() &&
2208 endpit->getDepth()) {
2212 setUndo(bv(), Undo::DELETE, old_cursor.par(),
2213 boost::prior(endpit));
2217 removeRow(getRow(old_cursor));
2219 ownerParagraphs().erase(old_cursor.par());
2221 /* Breakagain the next par. Needed because of
2222 * the parindent that can occur or dissappear.
2223 * The next row can change its height, if
2224 * there is another layout before */
2225 RowList::iterator tmprit = boost::next(prevrow);
2226 if (tmprit != rows().end()) {
2230 setHeightOfRow(prevrow);
2232 RowList::iterator nextrow = boost::next(getRow(old_cursor));
2233 postPaint(old_cursor.y() - getRow(old_cursor)->baseline());
2236 cursor = old_cursor; // that undo can restore the right cursor position
2237 #warning FIXME. --end() iterator is usable here
2238 ParagraphList::iterator endpit = boost::next(old_cursor.par());
2239 while (endpit != ownerParagraphs().end() &&
2240 endpit->getDepth()) {
2244 setUndo(bv(), Undo::DELETE, old_cursor.par(), boost::prior(endpit));
2248 removeRow(getRow(old_cursor));
2250 ownerParagraphs().erase(old_cursor.par());
2252 /* Breakagain the next par. Needed because of
2253 the parindent that can occur or dissappear.
2254 The next row can change its height, if
2255 there is another layout before */
2256 if (nextrow != rows().end()) {
2257 breakAgain(nextrow);
2263 setCursorIntern(cursor.par(), cursor.pos());
2265 if (selection_position_was_oldcursor_position) {
2266 // correct selection
2267 selection.cursor = cursor;
2271 if (old_cursor.par()->stripLeadingSpaces()) {
2272 redoParagraphs(old_cursor, boost::next(old_cursor.par()));
2274 setCursorIntern(cursor.par(), cursor.pos());
2275 selection.cursor = cursor;
2282 ParagraphList & LyXText::ownerParagraphs() const
2285 return inset_owner->paragraphs;
2287 return bv_owner->buffer()->paragraphs;
2291 LyXText::refresh_status LyXText::refreshStatus() const
2293 return refresh_status_;
2297 void LyXText::clearPaint()
2299 refresh_status_ = REFRESH_NONE;
2300 refresh_row = rows().end();
2305 void LyXText::postPaint(int start_y)
2307 refresh_status old = refresh_status_;
2309 refresh_status_ = REFRESH_AREA;
2310 refresh_row = rows().end();
2312 if (old != REFRESH_NONE && refresh_y < start_y)
2315 refresh_y = start_y;
2320 // We are an inset's lyxtext. Tell the top-level lyxtext
2321 // it needs to update the row we're in.
2322 LyXText * t = bv()->text;
2323 t->postRowPaint(t->cursorRow(), t->cursor.y() - t->cursorRow()->baseline());
2327 // FIXME: we should probably remove this y parameter,
2328 // make refresh_y be 0, and use row->y etc.
2329 void LyXText::postRowPaint(RowList::iterator rit, int start_y)
2331 if (refresh_status_ != REFRESH_NONE && refresh_y < start_y) {
2332 refresh_status_ = REFRESH_AREA;
2335 refresh_y = start_y;
2338 if (refresh_status_ == REFRESH_AREA)
2341 refresh_status_ = REFRESH_ROW;
2347 // We are an inset's lyxtext. Tell the top-level lyxtext
2348 // it needs to update the row we're in.
2349 LyXText * t = bv()->text;
2350 t->postRowPaint(t->cursorRow(), t->cursor.y() - t->cursorRow()->baseline());
2354 bool LyXText::isInInset() const
2356 // Sub-level has non-null bv owner and
2357 // non-null inset owner.
2358 return inset_owner != 0 && bv_owner != 0;
2362 int defaultRowHeight()
2364 LyXFont const font(LyXFont::ALL_SANE);
2365 return int(font_metrics::maxAscent(font)
2366 + font_metrics::maxDescent(font) * 1.5);