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();
92 } else if (!rowlist_.empty())
95 anchor_row_ = rows().end();
96 anchor_row_offset_ = 0;
98 ParagraphList::iterator pit = ownerParagraphs().begin();
99 ParagraphList::iterator end = ownerParagraphs().end();
101 current_font = getFont(bview->buffer(), pit, 0);
103 for (; pit != end; ++pit) {
104 insertParagraph(pit, rowlist_.end());
106 setCursorIntern(rowlist_.begin()->par(), 0);
107 selection.cursor = cursor;
113 // Gets the fully instantiated font at a given position in a paragraph
114 // Basically the same routine as Paragraph::getFont() in paragraph.C.
115 // The difference is that this one is used for displaying, and thus we
116 // are allowed to make cosmetic improvements. For instance make footnotes
118 // If position is -1, we get the layout font of the paragraph.
119 // If position is -2, we get the font of the manual label of the paragraph.
120 LyXFont const LyXText::getFont(Buffer const * buf, ParagraphList::iterator pit,
125 LyXLayout_ptr const & layout = pit->layout();
127 // We specialize the 95% common case:
128 if (!pit->getDepth()) {
129 if (layout->labeltype == LABEL_MANUAL
130 && pos < pit->beginningOfBody()) {
132 LyXFont f = pit->getFontSettings(buf->params, pos);
134 pit->inInset()->getDrawFont(f);
135 return f.realize(layout->reslabelfont);
137 LyXFont f = pit->getFontSettings(buf->params, pos);
139 pit->inInset()->getDrawFont(f);
140 return f.realize(layout->resfont);
144 // The uncommon case need not be optimized as much
148 if (pos < pit->beginningOfBody()) {
150 layoutfont = layout->labelfont;
153 layoutfont = layout->font;
156 LyXFont tmpfont = pit->getFontSettings(buf->params, pos);
157 tmpfont.realize(layoutfont);
160 pit->inInset()->getDrawFont(tmpfont);
162 // Realize with the fonts of lesser depth.
163 tmpfont.realize(outerFont(pit, ownerParagraphs()));
165 return realizeFont(tmpfont, buf->params);
169 LyXFont const LyXText::getLayoutFont(Buffer const * buf,
170 ParagraphList::iterator pit) const
172 LyXLayout_ptr const & layout = pit->layout();
174 if (!pit->getDepth()) {
175 return layout->resfont;
178 LyXFont font(layout->font);
179 // Realize with the fonts of lesser depth.
180 font.realize(outerFont(pit, ownerParagraphs()));
182 return realizeFont(font, buf->params);
186 LyXFont const LyXText::getLabelFont(Buffer const * buf,
187 ParagraphList::iterator pit) const
189 LyXLayout_ptr const & layout = pit->layout();
191 if (!pit->getDepth()) {
192 return layout->reslabelfont;
195 LyXFont font(layout->labelfont);
196 // Realize with the fonts of lesser depth.
197 font.realize(outerFont(pit, ownerParagraphs()));
199 return realizeFont(layout->labelfont, buf->params);
203 void LyXText::setCharFont(ParagraphList::iterator pit,
204 pos_type pos, LyXFont const & fnt,
207 Buffer const * buf = bv()->buffer();
208 LyXFont font = getFont(buf, pit, pos);
209 font.update(fnt, buf->params.language, toggleall);
210 // Let the insets convert their font
211 if (pit->isInset(pos)) {
212 Inset * inset = pit->getInset(pos);
213 if (isEditableInset(inset)) {
214 UpdatableInset * uinset =
215 static_cast<UpdatableInset *>(inset);
216 uinset->setFont(bv(), fnt, toggleall, true);
220 // Plug thru to version below:
221 setCharFont(buf, pit, pos, font);
225 void LyXText::setCharFont(Buffer const * buf, ParagraphList::iterator pit,
226 pos_type pos, LyXFont const & fnt)
230 LyXTextClass const & tclass = buf->params.getLyXTextClass();
231 LyXLayout_ptr const & layout = pit->layout();
233 // Get concrete layout font to reduce against
236 if (pos < pit->beginningOfBody())
237 layoutfont = layout->labelfont;
239 layoutfont = layout->font;
241 // Realize against environment font information
242 if (pit->getDepth()) {
243 ParagraphList::iterator tp = pit;
244 while (!layoutfont.resolved() &&
245 tp != ownerParagraphs().end() &&
247 tp = outerHook(tp, ownerParagraphs());
248 if (tp != ownerParagraphs().end())
249 layoutfont.realize(tp->layout()->font);
253 layoutfont.realize(tclass.defaultfont());
255 // Now, reduce font against full layout font
256 font.reduce(layoutfont);
258 pit->setFont(pos, font);
262 // removes the row and reset the touched counters
263 void LyXText::removeRow(RowList::iterator rit)
265 /* FIXME: when we cache the bview, this should just
266 * become a postPaint(), I think */
267 if (refresh_row == rit) {
268 if (rit == rows().begin())
269 refresh_row = boost::next(rit);
271 refresh_row = boost::prior(rit);
273 // what about refresh_y
276 if (anchor_row_ == rit) {
277 if (rit != rows().begin()) {
278 anchor_row_ = boost::prior(rit);
279 anchor_row_offset_ += anchor_row_->height();
281 anchor_row_ = boost::next(rit);
282 anchor_row_offset_ -= rit->height();
286 // the text becomes smaller
287 height -= rit->height();
293 // remove all following rows of the paragraph of the specified row.
294 void LyXText::removeParagraph(RowList::iterator rit)
296 ParagraphList::iterator tmppit = rit->par();
299 while (rit != rows().end() && rit->par() == tmppit) {
300 RowList::iterator tmprit = boost::next(rit);
307 void LyXText::insertParagraph(ParagraphList::iterator pit,
308 RowList::iterator rowit)
310 // insert a new row, starting at position 0
312 RowList::iterator rit = rowlist_.insert(rowit, newrow);
314 // and now append the whole paragraph before the new row
315 appendParagraph(rit);
319 Inset * LyXText::getInset() const
321 ParagraphList::iterator pit = cursor.par();
322 pos_type const pos = cursor.pos();
324 if (pos < pit->size() && pit->isInset(pos)) {
325 return pit->getInset(pos);
331 void LyXText::toggleInset()
333 Inset * inset = getInset();
334 // is there an editable inset at cursor position?
335 if (!isEditableInset(inset)) {
336 // No, try to see if we are inside a collapsable inset
337 if (inset_owner && inset_owner->owner()
338 && inset_owner->owner()->isOpen()) {
339 bv()->unlockInset(inset_owner->owner());
340 inset_owner->owner()->close(bv());
341 bv()->getLyXText()->cursorRight(bv());
345 //bv()->owner()->message(inset->editMessage());
347 // do we want to keep this?? (JMarc)
348 if (!isHighlyEditableInset(inset))
349 setCursorParUndo(bv());
351 if (inset->isOpen()) {
357 bv()->updateInset(inset);
361 /* used in setlayout */
362 // Asger is not sure we want to do this...
363 void LyXText::makeFontEntriesLayoutSpecific(Buffer const & buf,
366 LyXLayout_ptr const & layout = par.layout();
367 pos_type const psize = par.size();
370 for (pos_type pos = 0; pos < psize; ++pos) {
371 if (pos < par.beginningOfBody())
372 layoutfont = layout->labelfont;
374 layoutfont = layout->font;
376 LyXFont tmpfont = par.getFontSettings(buf.params, pos);
377 tmpfont.reduce(layoutfont);
378 par.setFont(pos, tmpfont);
383 ParagraphList::iterator
384 LyXText::setLayout(LyXCursor & cur, LyXCursor & sstart_cur,
385 LyXCursor & send_cur,
386 string const & layout)
388 ParagraphList::iterator endpit = boost::next(send_cur.par());
389 ParagraphList::iterator undoendpit = endpit;
390 ParagraphList::iterator pars_end = ownerParagraphs().end();
392 if (endpit != pars_end && endpit->getDepth()) {
393 while (endpit != pars_end && endpit->getDepth()) {
397 } else if (endpit != pars_end) {
398 // because of parindents etc.
402 setUndo(bv(), Undo::EDIT, sstart_cur.par(), boost::prior(undoendpit));
404 // ok we have a selection. This is always between sstart_cur
405 // and sel_end cursor
407 ParagraphList::iterator pit = sstart_cur.par();
408 ParagraphList::iterator epit = boost::next(send_cur.par());
410 LyXLayout_ptr const & lyxlayout =
411 bv()->buffer()->params.getLyXTextClass()[layout];
414 pit->applyLayout(lyxlayout);
415 makeFontEntriesLayoutSpecific(*bv()->buffer(), *pit);
416 ParagraphList::iterator fppit = pit;
417 fppit->params().spaceTop(lyxlayout->fill_top ?
418 VSpace(VSpace::VFILL)
419 : VSpace(VSpace::NONE));
420 fppit->params().spaceBottom(lyxlayout->fill_bottom ?
421 VSpace(VSpace::VFILL)
422 : VSpace(VSpace::NONE));
423 if (lyxlayout->margintype == MARGIN_MANUAL)
424 pit->setLabelWidthString(lyxlayout->labelstring());
427 } while (pit != epit);
433 // set layout over selection and make a total rebreak of those paragraphs
434 void LyXText::setLayout(string const & layout)
436 LyXCursor tmpcursor = cursor; // store the current cursor
438 // if there is no selection just set the layout
439 // of the current paragraph
440 if (!selection.set()) {
441 selection.start = cursor; // dummy selection
442 selection.end = cursor;
445 // special handling of new environment insets
446 BufferParams const & params = bv()->buffer()->params;
447 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
448 if (lyxlayout->is_environment) {
449 // move everything in a new environment inset
450 lyxerr << "setting layout " << layout << endl;
451 bv()->owner()->dispatch(FuncRequest(LFUN_HOME));
452 bv()->owner()->dispatch(FuncRequest(LFUN_ENDSEL));
453 bv()->owner()->dispatch(FuncRequest(LFUN_CUT));
454 Inset * inset = new InsetEnvironment(params, layout);
455 if (bv()->insertInset(inset)) {
457 //bv()->owner()->dispatch(FuncRequest(LFUN_PASTE));
464 ParagraphList::iterator endpit = setLayout(cursor, selection.start,
465 selection.end, layout);
466 redoParagraphs(selection.start, endpit);
468 // we have to reset the selection, because the
469 // geometry could have changed
470 setCursor(selection.start.par(), selection.start.pos(), false);
471 selection.cursor = cursor;
472 setCursor(selection.end.par(), selection.end.pos(), false);
476 setCursor(tmpcursor.par(), tmpcursor.pos(), true);
480 bool LyXText::changeDepth(bv_funcs::DEPTH_CHANGE type, bool test_only)
482 ParagraphList::iterator pit(cursor.par());
483 ParagraphList::iterator end(cursor.par());
484 ParagraphList::iterator start = pit;
486 if (selection.set()) {
487 pit = selection.start.par();
488 end = selection.end.par();
492 ParagraphList::iterator pastend = boost::next(end);
495 setUndo(bv(), Undo::EDIT, start, end);
497 bool changed = false;
499 int prev_after_depth = 0;
500 #warning parlist ... could be nicer ?
501 if (start != ownerParagraphs().begin()) {
502 prev_after_depth = boost::prior(start)->getMaxDepthAfter();
506 int const depth = pit->params().depth();
507 if (type == bv_funcs::INC_DEPTH) {
508 if (depth < prev_after_depth
509 && pit->layout()->labeltype != LABEL_BIBLIO) {
512 pit->params().depth(depth + 1);
519 pit->params().depth(depth - 1);
522 prev_after_depth = pit->getMaxDepthAfter();
534 // Wow, redoParagraphs is stupid.
536 setCursor(tmpcursor, start, 0);
538 redoParagraphs(tmpcursor, pastend);
540 // We need to actually move the text->cursor. I don't
541 // understand why ...
544 // we have to reset the visual selection because the
545 // geometry could have changed
546 if (selection.set()) {
547 setCursor(selection.start.par(), selection.start.pos());
548 selection.cursor = cursor;
549 setCursor(selection.end.par(), selection.end.pos());
552 // this handles the counter labels, and also fixes up
553 // depth values for follow-on (child) paragraphs
557 setCursor(tmpcursor.par(), tmpcursor.pos());
563 // set font over selection and make a total rebreak of those paragraphs
564 void LyXText::setFont(LyXFont const & font, bool toggleall)
566 // if there is no selection just set the current_font
567 if (!selection.set()) {
568 // Determine basis font
570 if (cursor.pos() < cursor.par()->beginningOfBody()) {
571 layoutfont = getLabelFont(bv()->buffer(),
574 layoutfont = getLayoutFont(bv()->buffer(),
577 // Update current font
578 real_current_font.update(font,
579 bv()->buffer()->params.language,
582 // Reduce to implicit settings
583 current_font = real_current_font;
584 current_font.reduce(layoutfont);
585 // And resolve it completely
586 real_current_font.realize(layoutfont);
591 LyXCursor tmpcursor = cursor; // store the current cursor
593 // ok we have a selection. This is always between sel_start_cursor
594 // and sel_end cursor
596 setUndo(bv(), Undo::EDIT, selection.start.par(), selection.end.par());
598 cursor = selection.start;
599 while (cursor.par() != selection.end.par() ||
600 cursor.pos() < selection.end.pos())
602 if (cursor.pos() < cursor.par()->size()) {
603 // an open footnote should behave like a closed one
604 setCharFont(cursor.par(), cursor.pos(),
606 cursor.pos(cursor.pos() + 1);
609 cursor.par(boost::next(cursor.par()));
614 redoParagraphs(selection.start, boost::next(selection.end.par()));
616 // we have to reset the selection, because the
617 // geometry could have changed, but we keep
618 // it for user convenience
619 setCursor(selection.start.par(), selection.start.pos());
620 selection.cursor = cursor;
621 setCursor(selection.end.par(), selection.end.pos());
623 setCursor(tmpcursor.par(), tmpcursor.pos(), true,
624 tmpcursor.boundary());
628 void LyXText::redoHeightOfParagraph()
630 RowList::iterator tmprow = cursorRow();
631 int y = cursor.y() - tmprow->baseline();
633 setHeightOfRow(tmprow);
635 while (tmprow != rows().begin()
636 && boost::prior(tmprow)->par() == tmprow->par()) {
638 y -= tmprow->height();
639 setHeightOfRow(tmprow);
644 setCursor(cursor.par(), cursor.pos(), false, cursor.boundary());
648 void LyXText::redoDrawingOfParagraph(LyXCursor const & cur)
650 RowList::iterator tmprow = getRow(cur);
652 int y = cur.y() - tmprow->baseline();
653 setHeightOfRow(tmprow);
655 while (tmprow != rows().begin()
656 && boost::prior(tmprow)->par() == tmprow->par()) {
658 y -= tmprow->height();
662 setCursor(cur.par(), cur.pos());
666 // deletes and inserts again all paragraphs between the cursor
667 // and the specified par
668 // This function is needed after SetLayout and SetFont etc.
669 void LyXText::redoParagraphs(LyXCursor const & cur,
670 ParagraphList::iterator endpit)
672 RowList::iterator tmprit = getRow(cur);
673 int y = cur.y() - tmprit->baseline();
675 ParagraphList::iterator first_phys_pit;
676 RowList::iterator prevrit;
677 if (tmprit == rows().begin()) {
678 // A trick/hack for UNDO.
679 // This is needed because in an UNDO/REDO we could have
680 // changed the ownerParagraph() so the paragraph inside
681 // the row is NOT my really first par anymore.
682 // Got it Lars ;) (Jug 20011206)
683 first_phys_pit = ownerParagraphs().begin();
684 prevrit = rows().end();
686 first_phys_pit = tmprit->par();
687 while (tmprit != rows().begin()
688 && boost::prior(tmprit)->par() == first_phys_pit)
691 y -= tmprit->height();
693 prevrit = boost::prior(tmprit);
697 while (tmprit != rows().end() && tmprit->par() != endpit) {
698 RowList::iterator tmprit2 = tmprit++;
702 // Reinsert the paragraphs.
703 ParagraphList::iterator tmppit = first_phys_pit;
705 while (tmppit != ownerParagraphs().end()) {
706 insertParagraph(tmppit, tmprit);
707 while (tmprit != rows().end()
708 && tmprit->par() == tmppit) {
712 if (tmppit == endpit)
715 if (prevrit != rows().end()) {
716 setHeightOfRow(prevrit);
717 postPaint(y - prevrit->height());
719 setHeightOfRow(rows().begin());
722 if (tmprit != rows().end())
723 setHeightOfRow(tmprit);
729 void LyXText::fullRebreak()
733 setCursorIntern(cursor.par(), cursor.pos());
737 void LyXText::partialRebreak()
739 if (rows().empty()) {
744 RowList::iterator rows_end = rows().end();
746 if (need_break_row != rows_end) {
747 breakAgain(need_break_row);
748 need_break_row = rows_end;
754 // important for the screen
757 // the cursor set functions have a special mechanism. When they
758 // realize, that you left an empty paragraph, they will delete it.
759 // They also delete the corresponding row
761 // need the selection cursor:
762 void LyXText::setSelection()
764 bool const lsel = TextCursor::setSelection();
766 if (inset_owner && (selection.set() || lsel))
767 inset_owner->setUpdateStatus(InsetText::SELECTION);
772 void LyXText::clearSelection()
774 TextCursor::clearSelection();
776 // reset this in the bv_owner!
777 if (bv_owner && bv_owner->text)
778 bv_owner->text->xsel_cache.set(false);
782 void LyXText::cursorHome()
784 setCursor(cursor.par(), cursorRow()->pos());
788 void LyXText::cursorEnd()
790 if (cursor.par()->empty())
793 RowList::iterator rit = cursorRow();
794 RowList::iterator next_rit = boost::next(rit);
795 ParagraphList::iterator pit = rit->par();
796 pos_type last_pos = lastPos(*this, rit);
798 if (next_rit == rows().end() || next_rit->par() != pit) {
802 (pit->getChar(last_pos) != ' ' && !pit->isNewline(last_pos))) {
807 setCursor(pit, last_pos);
811 void LyXText::cursorTop()
813 setCursor(ownerParagraphs().begin(), 0);
817 void LyXText::cursorBottom()
819 ParagraphList::iterator lastpit =
820 boost::prior(ownerParagraphs().end());
821 setCursor(lastpit, lastpit->size());
825 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
827 // If the mask is completely neutral, tell user
828 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
829 // Could only happen with user style
830 bv()->owner()->message(_("No font change defined. Use Character under the Layout menu to define font change."));
834 // Try implicit word selection
835 // If there is a change in the language the implicit word selection
837 LyXCursor resetCursor = cursor;
838 bool implicitSelection = (font.language() == ignore_language
839 && font.number() == LyXFont::IGNORE)
840 ? selectWordWhenUnderCursor(lyx::WHOLE_WORD_STRICT) : false;
843 setFont(font, toggleall);
845 // Implicit selections are cleared afterwards
846 //and cursor is set to the original position.
847 if (implicitSelection) {
849 cursor = resetCursor;
850 setCursor(cursor.par(), cursor.pos());
851 selection.cursor = cursor;
854 inset_owner->setUpdateStatus(InsetText::CURSOR_PAR);
858 string LyXText::getStringToIndex()
860 // Try implicit word selection
861 // If there is a change in the language the implicit word selection
863 LyXCursor const reset_cursor = cursor;
864 bool const implicitSelection =
865 selectWordWhenUnderCursor(lyx::PREVIOUS_WORD);
868 if (!selection.set())
869 bv()->owner()->message(_("Nothing to index!"));
870 else if (selection.start.par() != selection.end.par())
871 bv()->owner()->message(_("Cannot index more than one paragraph!"));
873 idxstring = selectionAsString(bv()->buffer(), false);
875 // Reset cursors to their original position.
876 cursor = reset_cursor;
877 setCursor(cursor.par(), cursor.pos());
878 selection.cursor = cursor;
880 // Clear the implicit selection.
881 if (implicitSelection)
888 // the DTP switches for paragraphs. LyX will store them in the first
889 // physicla paragraph. When a paragraph is broken, the top settings rest,
890 // the bottom settings are given to the new one. So I can make shure,
891 // they do not duplicate themself and you cannnot make dirty things with
894 void LyXText::setParagraph(bool line_top, bool line_bottom,
895 bool pagebreak_top, bool pagebreak_bottom,
896 VSpace const & space_top,
897 VSpace const & space_bottom,
898 Spacing const & spacing,
900 string const & labelwidthstring,
903 LyXCursor tmpcursor = cursor;
904 if (!selection.set()) {
905 selection.start = cursor;
906 selection.end = cursor;
909 // make sure that the depth behind the selection are restored, too
910 ParagraphList::iterator endpit = boost::next(selection.end.par());
911 ParagraphList::iterator undoendpit = endpit;
912 ParagraphList::iterator pars_end = ownerParagraphs().end();
914 if (endpit != pars_end && endpit->getDepth()) {
915 while (endpit != pars_end && endpit->getDepth()) {
919 } else if (endpit != pars_end) {
920 // because of parindents etc.
924 setUndo(bv(), Undo::EDIT, selection.start.par(),
925 boost::prior(undoendpit));
928 ParagraphList::iterator tmppit = selection.end.par();
930 while (tmppit != boost::prior(selection.start.par())) {
931 setCursor(tmppit, 0);
932 postPaint(cursor.y() - cursorRow()->baseline());
934 ParagraphList::iterator pit = cursor.par();
935 ParagraphParameters & params = pit->params();
937 params.lineTop(line_top);
938 params.lineBottom(line_bottom);
939 params.pagebreakTop(pagebreak_top);
940 params.pagebreakBottom(pagebreak_bottom);
941 params.spaceTop(space_top);
942 params.spaceBottom(space_bottom);
943 params.spacing(spacing);
944 // does the layout allow the new alignment?
945 LyXLayout_ptr const & layout = pit->layout();
947 if (align == LYX_ALIGN_LAYOUT)
948 align = layout->align;
949 if (align & layout->alignpossible) {
950 if (align == layout->align)
951 params.align(LYX_ALIGN_LAYOUT);
955 pit->setLabelWidthString(labelwidthstring);
956 params.noindent(noindent);
957 tmppit = boost::prior(pit);
960 redoParagraphs(selection.start, endpit);
963 setCursor(selection.start.par(), selection.start.pos());
964 selection.cursor = cursor;
965 setCursor(selection.end.par(), selection.end.pos());
967 setCursor(tmpcursor.par(), tmpcursor.pos());
969 bv()->updateInset(inset_owner);
973 // set the counter of a paragraph. This includes the labels
974 void LyXText::setCounter(Buffer const * buf, ParagraphList::iterator pit)
976 LyXTextClass const & textclass = buf->params.getLyXTextClass();
977 LyXLayout_ptr const & layout = pit->layout();
979 if (pit != ownerParagraphs().begin()) {
981 pit->params().appendix(boost::prior(pit)->params().appendix());
982 if (!pit->params().appendix() &&
983 pit->params().startOfAppendix()) {
984 pit->params().appendix(true);
985 textclass.counters().reset();
987 pit->enumdepth = boost::prior(pit)->enumdepth;
988 pit->itemdepth = boost::prior(pit)->itemdepth;
990 pit->params().appendix(pit->params().startOfAppendix());
995 /* Maybe we have to increment the enumeration depth.
996 * BUT, enumeration in a footnote is considered in isolation from its
997 * surrounding paragraph so don't increment if this is the
998 * first line of the footnote
999 * AND, bibliographies can't have their depth changed ie. they
1000 * are always of depth 0
1002 if (pit != ownerParagraphs().begin()
1003 && boost::prior(pit)->getDepth() < pit->getDepth()
1004 && boost::prior(pit)->layout()->labeltype == LABEL_COUNTER_ENUMI
1005 && pit->enumdepth < 3
1006 && layout->labeltype != LABEL_BIBLIO) {
1010 // Maybe we have to decrement the enumeration depth, see note above
1011 if (pit != ownerParagraphs().begin()
1012 && boost::prior(pit)->getDepth() > pit->getDepth()
1013 && layout->labeltype != LABEL_BIBLIO) {
1014 pit->enumdepth = depthHook(pit, ownerParagraphs(),
1015 pit->getDepth())->enumdepth;
1018 if (!pit->params().labelString().empty()) {
1019 pit->params().labelString(string());
1022 if (layout->margintype == MARGIN_MANUAL) {
1023 if (pit->params().labelWidthString().empty()) {
1024 pit->setLabelWidthString(layout->labelstring());
1027 pit->setLabelWidthString(string());
1030 // is it a layout that has an automatic label?
1031 if (layout->labeltype >= LABEL_COUNTER_CHAPTER) {
1032 int const i = layout->labeltype - LABEL_COUNTER_CHAPTER;
1036 if (i >= 0 && i <= buf->params.secnumdepth) {
1040 textclass.counters().step(layout->latexname());
1042 // Is there a label? Useful for Chapter layout
1043 if (!pit->params().appendix()) {
1044 s << buf->B_(layout->labelstring());
1046 s << buf->B_(layout->labelstring_appendix());
1049 // Use of an integer is here less than elegant. For now.
1050 int head = textclass.maxcounter() - LABEL_COUNTER_CHAPTER;
1051 if (!pit->params().appendix()) {
1052 numbertype = "sectioning";
1054 numbertype = "appendix";
1055 if (pit->isRightToLeftPar(buf->params))
1056 langtype = "hebrew";
1062 << textclass.counters()
1063 .numberLabel(layout->latexname(),
1064 numbertype, langtype, head);
1066 pit->params().labelString(STRCONV(s.str()));
1068 // reset enum counters
1069 textclass.counters().reset("enum");
1070 } else if (layout->labeltype < LABEL_COUNTER_ENUMI) {
1071 textclass.counters().reset("enum");
1072 } else if (layout->labeltype == LABEL_COUNTER_ENUMI) {
1074 // Yes I know this is a really, really! bad solution
1076 string enumcounter("enum");
1078 switch (pit->enumdepth) {
1087 enumcounter += "iv";
1090 // not a valid enumdepth...
1094 textclass.counters().step(enumcounter);
1096 s << textclass.counters()
1097 .numberLabel(enumcounter, "enumeration");
1098 pit->params().labelString(STRCONV(s.str()));
1100 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
1101 textclass.counters().step("bibitem");
1102 int number = textclass.counters().value("bibitem");
1103 if (pit->bibitem()) {
1104 pit->bibitem()->setCounter(number);
1105 pit->params().labelString(layout->labelstring());
1107 // In biblio should't be following counters but...
1109 string s = buf->B_(layout->labelstring());
1111 // the caption hack:
1112 if (layout->labeltype == LABEL_SENSITIVE) {
1113 ParagraphList::iterator tmppit = pit;
1116 while (tmppit != ownerParagraphs().end() &&
1118 // the single '=' is intended below
1119 && (in = tmppit->inInset()->owner())) {
1120 if (in->lyxCode() == Inset::FLOAT_CODE ||
1121 in->lyxCode() == Inset::WRAP_CODE) {
1125 tmppit = std::find(ownerParagraphs().begin(), ownerParagraphs().end(), *in->parOwner());
1132 if (in->lyxCode() == Inset::FLOAT_CODE)
1133 type = static_cast<InsetFloat*>(in)->params().type;
1134 else if (in->lyxCode() == Inset::WRAP_CODE)
1135 type = static_cast<InsetWrap*>(in)->params().type;
1139 Floating const & fl = textclass.floats().getType(type);
1141 textclass.counters().step(fl.type());
1143 // Doesn't work... yet.
1144 s = bformat(_("%1$s #:"), buf->B_(fl.name()));
1146 // par->SetLayout(0);
1147 // s = layout->labelstring;
1148 s = _("Senseless: ");
1151 pit->params().labelString(s);
1153 // reset the enumeration counter. They are always reset
1154 // when there is any other layout between
1155 // Just fall-through between the cases so that all
1156 // enum counters deeper than enumdepth is also reset.
1157 switch (pit->enumdepth) {
1159 textclass.counters().reset("enumi");
1161 textclass.counters().reset("enumii");
1163 textclass.counters().reset("enumiii");
1165 textclass.counters().reset("enumiv");
1171 // Updates all counters. Paragraphs with changed label string will be rebroken
1172 void LyXText::updateCounters()
1174 RowList::iterator rowit = rows().begin();
1175 ParagraphList::iterator pit = rowit->par();
1177 // CHECK if this is really needed. (Lgb)
1178 bv()->buffer()->params.getLyXTextClass().counters().reset();
1180 ParagraphList::iterator beg = ownerParagraphs().begin();
1181 ParagraphList::iterator end = ownerParagraphs().end();
1182 for (; pit != end; ++pit) {
1183 while (rowit->par() != pit)
1186 string const oldLabel = pit->params().labelString();
1188 size_t maxdepth = 0;
1190 maxdepth = boost::prior(pit)->getMaxDepthAfter();
1192 if (pit->params().depth() > maxdepth)
1193 pit->params().depth(maxdepth);
1195 // setCounter can potentially change the labelString.
1196 setCounter(bv()->buffer(), pit);
1198 string const & newLabel = pit->params().labelString();
1200 if (oldLabel.empty() && !newLabel.empty()) {
1201 removeParagraph(rowit);
1202 appendParagraph(rowit);
1208 void LyXText::insertInset(Inset * inset)
1210 if (!cursor.par()->insetAllowed(inset->lyxCode()))
1212 setUndo(bv(), Undo::FINISH, cursor.par());
1214 cursor.par()->insertInset(cursor.pos(), inset);
1215 // Just to rebreak and refresh correctly.
1216 // The character will not be inserted a second time
1217 insertChar(Paragraph::META_INSET);
1218 // If we enter a highly editable inset the cursor should be to before
1219 // the inset. This couldn't happen before as Undo was not handled inside
1220 // inset now after the Undo LyX tries to call inset->Edit(...) again
1221 // and cannot do this as the cursor is behind the inset and GetInset
1222 // does not return the inset!
1223 if (isHighlyEditableInset(inset)) {
1230 void LyXText::cutSelection(bool doclear, bool realcut)
1232 // Stuff what we got on the clipboard. Even if there is no selection.
1234 // There is a problem with having the stuffing here in that the
1235 // larger the selection the slower LyX will get. This can be
1236 // solved by running the line below only when the selection has
1237 // finished. The solution used currently just works, to make it
1238 // faster we need to be more clever and probably also have more
1239 // calls to stuffClipboard. (Lgb)
1240 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1242 // This doesn't make sense, if there is no selection
1243 if (!selection.set())
1246 // OK, we have a selection. This is always between selection.start
1247 // and selection.end
1249 // make sure that the depth behind the selection are restored, too
1250 ParagraphList::iterator endpit = boost::next(selection.end.par());
1251 ParagraphList::iterator undoendpit = endpit;
1252 ParagraphList::iterator pars_end = ownerParagraphs().end();
1254 if (endpit != pars_end && endpit->getDepth()) {
1255 while (endpit != pars_end && endpit->getDepth()) {
1257 undoendpit = endpit;
1259 } else if (endpit != pars_end) {
1260 // because of parindents etc.
1264 setUndo(bv(), Undo::DELETE, selection.start.par(),
1265 boost::prior(undoendpit));
1268 endpit = selection.end.par();
1269 int endpos = selection.end.pos();
1271 boost::tie(endpit, endpos) = realcut ?
1272 CutAndPaste::cutSelection(bv()->buffer()->params,
1274 selection.start.par(), endpit,
1275 selection.start.pos(), endpos,
1276 bv()->buffer()->params.textclass,
1278 : CutAndPaste::eraseSelection(bv()->buffer()->params,
1280 selection.start.par(), endpit,
1281 selection.start.pos(), endpos,
1283 // sometimes necessary
1285 selection.start.par()->stripLeadingSpaces();
1287 redoParagraphs(selection.start, boost::next(endpit));
1288 #warning FIXME latent bug
1289 // endpit will be invalidated on redoParagraphs once ParagraphList
1290 // becomes a std::list? There are maybe other places on which this
1291 // can happend? (Ab)
1292 // cutSelection can invalidate the cursor so we need to set
1294 // we prefer the end for when tracking changes
1298 // need a valid cursor. (Lgb)
1301 setCursor(cursor.par(), cursor.pos());
1302 selection.cursor = cursor;
1307 void LyXText::copySelection()
1309 // stuff the selection onto the X clipboard, from an explicit copy request
1310 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1312 // this doesnt make sense, if there is no selection
1313 if (!selection.set())
1316 // ok we have a selection. This is always between selection.start
1317 // and sel_end cursor
1319 // copy behind a space if there is one
1320 while (selection.start.par()->size() > selection.start.pos()
1321 && selection.start.par()->isLineSeparator(selection.start.pos())
1322 && (selection.start.par() != selection.end.par()
1323 || selection.start.pos() < selection.end.pos()))
1324 selection.start.pos(selection.start.pos() + 1);
1326 CutAndPaste::copySelection(selection.start.par(),
1327 selection.end.par(),
1328 selection.start.pos(), selection.end.pos(),
1329 bv()->buffer()->params.textclass);
1333 void LyXText::pasteSelection(size_t sel_index)
1335 // this does not make sense, if there is nothing to paste
1336 if (!CutAndPaste::checkPastePossible())
1339 setUndo(bv(), Undo::INSERT, cursor.par());
1341 ParagraphList::iterator endpit;
1346 boost::tie(ppp, endpit) =
1347 CutAndPaste::pasteSelection(*bv()->buffer(),
1349 cursor.par(), cursor.pos(),
1350 bv()->buffer()->params.textclass,
1352 bufferErrors(*bv()->buffer(), el);
1353 bv()->showErrorList(_("Paste"));
1355 redoParagraphs(cursor, endpit);
1357 setCursor(cursor.par(), cursor.pos());
1360 selection.cursor = cursor;
1361 setCursor(ppp.first, ppp.second);
1367 void LyXText::setSelectionRange(lyx::pos_type length)
1372 selection.cursor = cursor;
1379 // simple replacing. The font of the first selected character is used
1380 void LyXText::replaceSelectionWithString(string const & str)
1382 setCursorParUndo(bv());
1385 if (!selection.set()) { // create a dummy selection
1386 selection.end = cursor;
1387 selection.start = cursor;
1390 // Get font setting before we cut
1391 pos_type pos = selection.end.pos();
1392 LyXFont const font = selection.start.par()
1393 ->getFontSettings(bv()->buffer()->params,
1394 selection.start.pos());
1396 // Insert the new string
1397 string::const_iterator cit = str.begin();
1398 string::const_iterator end = str.end();
1399 for (; cit != end; ++cit) {
1400 selection.end.par()->insertChar(pos, (*cit), font);
1404 // Cut the selection
1405 cutSelection(true, false);
1411 // needed to insert the selection
1412 void LyXText::insertStringAsLines(string const & str)
1414 ParagraphList::iterator pit = cursor.par();
1415 pos_type pos = cursor.pos();
1416 ParagraphList::iterator endpit = boost::next(cursor.par());
1418 setCursorParUndo(bv());
1420 // only to be sure, should not be neccessary
1423 bv()->buffer()->insertStringAsLines(pit, pos, current_font, str);
1425 redoParagraphs(cursor, endpit);
1426 setCursor(cursor.par(), cursor.pos());
1427 selection.cursor = cursor;
1428 setCursor(pit, pos);
1433 // turns double-CR to single CR, others where converted into one
1434 // blank. Then InsertStringAsLines is called
1435 void LyXText::insertStringAsParagraphs(string const & str)
1437 string linestr(str);
1438 bool newline_inserted = false;
1439 string::size_type const siz = linestr.length();
1441 for (string::size_type i = 0; i < siz; ++i) {
1442 if (linestr[i] == '\n') {
1443 if (newline_inserted) {
1444 // we know that \r will be ignored by
1445 // InsertStringA. Of course, it is a dirty
1446 // trick, but it works...
1447 linestr[i - 1] = '\r';
1451 newline_inserted = true;
1453 } else if (IsPrintable(linestr[i])) {
1454 newline_inserted = false;
1457 insertStringAsLines(linestr);
1461 void LyXText::checkParagraph(ParagraphList::iterator pit, pos_type pos)
1463 LyXCursor tmpcursor;
1467 RowList::iterator row = getRow(pit, pos, y);
1468 RowList::iterator beg = rows().begin();
1470 // is there a break one row above
1472 && boost::prior(row)->par() == row->par()) {
1473 z = rowBreakPoint(*boost::prior(row));
1474 if (z >= row->pos()) {
1475 // set the dimensions of the row above
1476 y -= boost::prior(row)->height();
1479 breakAgain(boost::prior(row));
1481 // set the cursor again. Otherwise
1482 // dangling pointers are possible
1483 setCursor(cursor.par(), cursor.pos(),
1484 false, cursor.boundary());
1485 selection.cursor = cursor;
1490 int const tmpheight = row->height();
1491 pos_type const tmplast = lastPos(*this, row);
1494 if (row->height() == tmpheight && lastPos(*this, row) == tmplast) {
1495 postRowPaint(row, y);
1500 // check the special right address boxes
1501 if (pit->layout()->margintype == MARGIN_RIGHT_ADDRESS_BOX) {
1507 redoDrawingOfParagraph(tmpcursor);
1510 // set the cursor again. Otherwise dangling pointers are possible
1511 // also set the selection
1513 if (selection.set()) {
1515 setCursorIntern(selection.cursor.par(), selection.cursor.pos(),
1516 false, selection.cursor.boundary());
1517 selection.cursor = cursor;
1518 setCursorIntern(selection.start.par(),
1519 selection.start.pos(),
1520 false, selection.start.boundary());
1521 selection.start = cursor;
1522 setCursorIntern(selection.end.par(),
1523 selection.end.pos(),
1524 false, selection.end.boundary());
1525 selection.end = cursor;
1526 setCursorIntern(last_sel_cursor.par(),
1527 last_sel_cursor.pos(),
1528 false, last_sel_cursor.boundary());
1529 last_sel_cursor = cursor;
1532 setCursorIntern(cursor.par(), cursor.pos(),
1533 false, cursor.boundary());
1537 // returns false if inset wasn't found
1538 bool LyXText::updateInset(Inset * inset)
1540 // first check the current paragraph
1541 int pos = cursor.par()->getPositionOfInset(inset);
1543 checkParagraph(cursor.par(), pos);
1547 // check every paragraph
1549 ParagraphList::iterator par = ownerParagraphs().begin();
1550 ParagraphList::iterator end = ownerParagraphs().end();
1551 for (; par != end; ++par) {
1552 pos = par->getPositionOfInset(inset);
1554 checkParagraph(par, pos);
1563 bool LyXText::setCursor(ParagraphList::iterator pit,
1565 bool setfont, bool boundary)
1567 LyXCursor old_cursor = cursor;
1568 setCursorIntern(pit, pos, setfont, boundary);
1569 return deleteEmptyParagraphMechanism(old_cursor);
1573 void LyXText::setCursor(LyXCursor & cur, ParagraphList::iterator pit,
1574 pos_type pos, bool boundary)
1576 Assert(pit != ownerParagraphs().end());
1580 cur.boundary(boundary);
1582 // get the cursor y position in text
1584 RowList::iterator row = getRow(pit, pos, y);
1585 RowList::iterator beg = rows().begin();
1587 RowList::iterator old_row = row;
1589 // if we are before the first char of this row and are still in the
1590 // same paragraph and there is a previous row then put the cursor on
1591 // the end of the previous row
1592 cur.iy(y + row->baseline());
1595 boost::prior(row)->par() == row->par() &&
1596 pos < pit->size() &&
1597 pit->getChar(pos) == Paragraph::META_INSET) {
1598 Inset * ins = pit->getInset(pos);
1599 if (ins && (ins->needFullRow() || ins->display())) {
1605 // y is now the beginning of the cursor row
1606 y += row->baseline();
1607 // y is now the cursor baseline
1610 pos_type last = lastPrintablePos(*this, old_row);
1612 // None of these should happen, but we're scaredy-cats
1613 if (pos > pit->size()) {
1614 lyxerr << "dont like 1 please report" << endl;
1617 } else if (pos > last + 1) {
1618 lyxerr << "dont like 2 please report" << endl;
1619 // This shouldn't happen.
1622 } else if (pos < row->pos()) {
1623 lyxerr << "dont like 3 please report" << endl;
1628 // now get the cursors x position
1629 float x = getCursorX(row, pos, last, boundary);
1632 if (old_row != row) {
1633 x = getCursorX(old_row, pos, last, boundary);
1637 /* We take out this for the time being because 1) the redraw code is not
1638 prepared to this yet and 2) because some good policy has yet to be decided
1639 while editting: for instance how to act on rows being created/deleted
1643 //if the cursor is in a visible row, anchor to it
1645 if (topy < y && y < topy + bv()->workHeight())
1651 float LyXText::getCursorX(RowList::iterator rit,
1652 pos_type pos, pos_type last, bool boundary) const
1654 pos_type cursor_vpos = 0;
1656 float fill_separator;
1658 float fill_label_hfill;
1659 // This call HAS to be here because of the BidiTables!!!
1660 prepareToPrint(rit, x, fill_separator, fill_hfill,
1663 ParagraphList::iterator rit_par = rit->par();
1664 pos_type const rit_pos = rit->pos();
1667 cursor_vpos = rit_pos;
1668 else if (pos > last && !boundary)
1669 cursor_vpos = (rit_par->isRightToLeftPar(bv()->buffer()->params))
1670 ? rit_pos : last + 1;
1671 else if (pos > rit_pos && (pos > last || boundary))
1672 /// Place cursor after char at (logical) position pos - 1
1673 cursor_vpos = (bidi_level(pos - 1) % 2 == 0)
1674 ? log2vis(pos - 1) + 1 : log2vis(pos - 1);
1676 /// Place cursor before char at (logical) position pos
1677 cursor_vpos = (bidi_level(pos) % 2 == 0)
1678 ? log2vis(pos) : log2vis(pos) + 1;
1680 pos_type body_pos = rit_par->beginningOfBody();
1681 if ((body_pos > 0) &&
1682 ((body_pos - 1 > last) || !rit_par->isLineSeparator(body_pos - 1)))
1685 for (pos_type vpos = rit_pos; vpos < cursor_vpos; ++vpos) {
1686 pos_type pos = vis2log(vpos);
1687 if (body_pos > 0 && pos == body_pos - 1) {
1688 x += fill_label_hfill +
1689 font_metrics::width(
1690 rit_par->layout()->labelsep,
1691 getLabelFont(bv()->buffer(), rit_par));
1692 if (rit_par->isLineSeparator(body_pos - 1))
1693 x -= singleWidth(rit_par, body_pos - 1);
1696 if (hfillExpansion(*this, rit, pos)) {
1697 x += singleWidth(rit_par, pos);
1698 if (pos >= body_pos)
1701 x += fill_label_hfill;
1702 } else if (rit_par->isSeparator(pos)) {
1703 x += singleWidth(rit_par, pos);
1704 if (pos >= body_pos)
1705 x += fill_separator;
1707 x += singleWidth(rit_par, pos);
1713 void LyXText::setCursorIntern(ParagraphList::iterator pit,
1714 pos_type pos, bool setfont, bool boundary)
1716 UpdatableInset * it = pit->inInset();
1718 if (it != inset_owner) {
1719 lyxerr[Debug::INSETS] << "InsetText is " << it
1721 << "inset_owner is "
1722 << inset_owner << endl;
1723 #ifdef WITH_WARNINGS
1724 #warning I believe this code is wrong. (Lgb)
1725 #warning Jürgen, have a look at this. (Lgb)
1726 #warning Hmmm, I guess you are right but we
1727 #warning should verify when this is needed
1729 // Jürgen, would you like to have a look?
1730 // I guess we need to move the outer cursor
1731 // and open and lock the inset (bla bla bla)
1732 // stuff I don't know... so can you have a look?
1734 // I moved the lyxerr stuff in here so we can see if
1735 // this is actually really needed and where!
1737 // it->getLyXText(bv())->setCursorIntern(bv(), par, pos, setfont, boundary);
1742 setCursor(cursor, pit, pos, boundary);
1748 void LyXText::setCurrentFont()
1750 pos_type pos = cursor.pos();
1751 ParagraphList::iterator pit = cursor.par();
1753 if (cursor.boundary() && pos > 0)
1757 if (pos == pit->size())
1759 else // potentional bug... BUG (Lgb)
1760 if (pit->isSeparator(pos)) {
1761 if (pos > cursorRow()->pos() &&
1762 bidi_level(pos) % 2 ==
1763 bidi_level(pos - 1) % 2)
1765 else if (pos + 1 < pit->size())
1771 pit->getFontSettings(bv()->buffer()->params, pos);
1772 real_current_font = getFont(bv()->buffer(), pit, pos);
1774 if (cursor.pos() == pit->size() &&
1775 isBoundary(bv()->buffer(), *pit, cursor.pos()) &&
1776 !cursor.boundary()) {
1777 Language const * lang =
1778 pit->getParLanguage(bv()->buffer()->params);
1779 current_font.setLanguage(lang);
1780 current_font.setNumber(LyXFont::OFF);
1781 real_current_font.setLanguage(lang);
1782 real_current_font.setNumber(LyXFont::OFF);
1787 // returns the column near the specified x-coordinate of the row
1788 // x is set to the real beginning of this column
1790 LyXText::getColumnNearX(RowList::iterator rit, int & x, bool & boundary) const
1793 float fill_separator;
1795 float fill_label_hfill;
1797 prepareToPrint(rit, tmpx, fill_separator,
1798 fill_hfill, fill_label_hfill);
1800 pos_type vc = rit->pos();
1801 pos_type last = lastPrintablePos(*this, rit);
1804 ParagraphList::iterator rit_par = rit->par();
1805 LyXLayout_ptr const & layout = rit->par()->layout();
1807 bool left_side = false;
1809 pos_type body_pos = rit_par->beginningOfBody();
1810 float last_tmpx = tmpx;
1813 (body_pos - 1 > last ||
1814 !rit_par->isLineSeparator(body_pos - 1)))
1817 // check for empty row
1818 if (!rit_par->size()) {
1823 while (vc <= last && tmpx <= x) {
1826 if (body_pos > 0 && c == body_pos - 1) {
1827 tmpx += fill_label_hfill +
1828 font_metrics::width(layout->labelsep,
1829 getLabelFont(bv()->buffer(), rit_par));
1830 if (rit_par->isLineSeparator(body_pos - 1))
1831 tmpx -= singleWidth(rit_par, body_pos - 1);
1834 if (hfillExpansion(*this, rit, c)) {
1835 tmpx += singleWidth(rit_par, c);
1839 tmpx += fill_label_hfill;
1840 } else if (rit_par->isSeparator(c)) {
1841 tmpx += singleWidth(rit_par, c);
1843 tmpx+= fill_separator;
1845 tmpx += singleWidth(rit_par, c);
1850 if ((tmpx + last_tmpx) / 2 > x) {
1855 if (vc > last + 1) // This shouldn't happen.
1859 // This (rtl_support test) is not needed, but gives
1860 // some speedup if rtl_support=false
1861 RowList::iterator next_rit = boost::next(rit);
1863 bool const lastrow = lyxrc.rtl_support &&
1864 (next_rit == rowlist_.end() ||
1865 next_rit->par() != rit_par);
1867 // If lastrow is false, we don't need to compute
1868 // the value of rtl.
1869 bool const rtl = (lastrow)
1870 ? rit_par->isRightToLeftPar(bv()->buffer()->params)
1873 ((rtl && left_side && vc == rit->pos() && x < tmpx - 5) ||
1874 (!rtl && !left_side && vc == last + 1 && x > tmpx + 5)))
1876 else if (vc == rit->pos()) {
1878 if (bidi_level(c) % 2 == 1)
1881 c = vis2log(vc - 1);
1882 bool const rtl = (bidi_level(c) % 2 == 1);
1883 if (left_side == rtl) {
1885 boundary = isBoundary(bv()->buffer(), *rit_par, c);
1889 if (rit->pos() <= last && c > last
1890 && rit_par->isNewline(last)) {
1891 if (bidi_level(last) % 2 == 0)
1892 tmpx -= singleWidth(rit_par, last);
1894 tmpx += singleWidth(rit_par, last);
1904 void LyXText::setCursorFromCoordinates(int x, int y)
1906 LyXCursor old_cursor = cursor;
1908 setCursorFromCoordinates(cursor, x, y);
1910 deleteEmptyParagraphMechanism(old_cursor);
1917 * return true if the cursor given is at the end of a row,
1918 * and the next row is filled by an inset that spans an entire
1921 bool beforeFullRowInset(LyXText & lt, LyXCursor const & cur)
1923 RowList::iterator row = lt.getRow(cur);
1924 if (boost::next(row) == lt.rows().end())
1927 Row const & next = *boost::next(row);
1929 if (next.pos() != cur.pos() || next.par() != cur.par())
1932 if (cur.pos() == cur.par()->size()
1933 || !cur.par()->isInset(cur.pos()))
1936 Inset const * inset = cur.par()->getInset(cur.pos());
1937 if (inset->needFullRow() || inset->display())
1945 void LyXText::setCursorFromCoordinates(LyXCursor & cur, int x, int y)
1947 // Get the row first.
1949 RowList::iterator row = getRowNearY(y);
1951 pos_type const column = getColumnNearX(row, x, bound);
1952 cur.par(row->par());
1953 cur.pos(row->pos() + column);
1955 cur.y(y + row->baseline());
1957 if (beforeFullRowInset(*this, cur)) {
1958 pos_type const last = lastPrintablePos(*this, row);
1959 RowList::iterator next_row = boost::next(row);
1961 float x = getCursorX(next_row, cur.pos(), last, bound);
1963 cur.iy(y + row->height() + next_row->baseline());
1970 cur.boundary(bound);
1974 void LyXText::cursorLeft(bool internal)
1976 if (cursor.pos() > 0) {
1977 bool boundary = cursor.boundary();
1978 setCursor(cursor.par(), cursor.pos() - 1, true, false);
1979 if (!internal && !boundary &&
1980 isBoundary(bv()->buffer(), *cursor.par(), cursor.pos() + 1))
1981 setCursor(cursor.par(), cursor.pos() + 1, true, true);
1982 } else if (cursor.par() != ownerParagraphs().begin()) { // steps into the above paragraph.
1983 ParagraphList::iterator pit = boost::prior(cursor.par());
1984 setCursor(pit, pit->size());
1989 void LyXText::cursorRight(bool internal)
1991 bool const at_end = (cursor.pos() == cursor.par()->size());
1992 bool const at_newline = !at_end &&
1993 cursor.par()->isNewline(cursor.pos());
1995 if (!internal && cursor.boundary() && !at_newline)
1996 setCursor(cursor.par(), cursor.pos(), true, false);
1998 setCursor(cursor.par(), cursor.pos() + 1, true, false);
2000 isBoundary(bv()->buffer(), *cursor.par(), cursor.pos()))
2001 setCursor(cursor.par(), cursor.pos(), true, true);
2002 } else if (boost::next(cursor.par()) != ownerParagraphs().end())
2003 setCursor(boost::next(cursor.par()), 0);
2007 void LyXText::cursorUp(bool selecting)
2010 int x = cursor.x_fix();
2011 int y = cursor.y() - cursorRow()->baseline() - 1;
2012 setCursorFromCoordinates(x, y);
2015 int y1 = cursor.iy() - topy;
2018 Inset * inset_hit = checkInsetHit(x, y1);
2019 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2020 inset_hit->localDispatch(
2021 FuncRequest(bv(), LFUN_INSET_EDIT, x, y - (y2 - y1), mouse_button::none));
2025 setCursorFromCoordinates(bv(), cursor.x_fix(),
2026 cursor.y() - cursorRow()->baseline() - 1);
2031 void LyXText::cursorDown(bool selecting)
2034 int x = cursor.x_fix();
2035 int y = cursor.y() - cursorRow()->baseline() +
2036 cursorRow()->height() + 1;
2037 setCursorFromCoordinates(x, y);
2038 if (!selecting && cursorRow() == cursor.irow()) {
2040 int y1 = cursor.iy() - topy;
2043 Inset * inset_hit = checkInsetHit(x, y1);
2044 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2045 FuncRequest cmd(bv(), LFUN_INSET_EDIT, x, y - (y2 - y1), mouse_button::none);
2046 inset_hit->localDispatch(cmd);
2050 setCursorFromCoordinates(bv(), cursor.x_fix(),
2051 cursor.y() - cursorRow()->baseline()
2052 + cursorRow()->height() + 1);
2057 void LyXText::cursorUpParagraph()
2059 if (cursor.pos() > 0) {
2060 setCursor(cursor.par(), 0);
2062 else if (cursor.par() != ownerParagraphs().begin()) {
2063 setCursor(boost::prior(cursor.par()), 0);
2068 void LyXText::cursorDownParagraph()
2070 ParagraphList::iterator par = cursor.par();
2071 ParagraphList::iterator next_par = boost::next(par);
2073 if (next_par != ownerParagraphs().end()) {
2074 setCursor(next_par, 0);
2076 setCursor(par, par->size());
2080 // fix the cursor `cur' after a characters has been deleted at `where'
2081 // position. Called by deleteEmptyParagraphMechanism
2082 void LyXText::fixCursorAfterDelete(LyXCursor & cur,
2083 LyXCursor const & where)
2085 // if cursor is not in the paragraph where the delete occured,
2087 if (cur.par() != where.par())
2090 // if cursor position is after the place where the delete occured,
2092 if (cur.pos() > where.pos())
2093 cur.pos(cur.pos()-1);
2095 // check also if we don't want to set the cursor on a spot behind the
2096 // pagragraph because we erased the last character.
2097 if (cur.pos() > cur.par()->size())
2098 cur.pos(cur.par()->size());
2100 // recompute row et al. for this cursor
2101 setCursor(cur, cur.par(), cur.pos(), cur.boundary());
2105 bool LyXText::deleteEmptyParagraphMechanism(LyXCursor const & old_cursor)
2107 // Would be wrong to delete anything if we have a selection.
2108 if (selection.set())
2111 // We allow all kinds of "mumbo-jumbo" when freespacing.
2112 if (old_cursor.par()->layout()->free_spacing
2113 || old_cursor.par()->isFreeSpacing()) {
2117 /* Ok I'll put some comments here about what is missing.
2118 I have fixed BackSpace (and thus Delete) to not delete
2119 double-spaces automagically. I have also changed Cut,
2120 Copy and Paste to hopefully do some sensible things.
2121 There are still some small problems that can lead to
2122 double spaces stored in the document file or space at
2123 the beginning of paragraphs. This happens if you have
2124 the cursor betwenn to spaces and then save. Or if you
2125 cut and paste and the selection have a space at the
2126 beginning and then save right after the paste. I am
2127 sure none of these are very hard to fix, but I will
2128 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
2129 that I can get some feedback. (Lgb)
2132 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
2133 // delete the LineSeparator.
2136 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
2137 // delete the LineSeparator.
2140 // If the pos around the old_cursor were spaces, delete one of them.
2141 if (old_cursor.par() != cursor.par()
2142 || old_cursor.pos() != cursor.pos()) {
2143 // Only if the cursor has really moved
2145 if (old_cursor.pos() > 0
2146 && old_cursor.pos() < old_cursor.par()->size()
2147 && old_cursor.par()->isLineSeparator(old_cursor.pos())
2148 && old_cursor.par()->isLineSeparator(old_cursor.pos() - 1)) {
2149 old_cursor.par()->erase(old_cursor.pos() - 1);
2150 redoParagraphs(old_cursor, boost::next(old_cursor.par()));
2152 #ifdef WITH_WARNINGS
2153 #warning This will not work anymore when we have multiple views of the same buffer
2154 // In this case, we will have to correct also the cursors held by
2155 // other bufferviews. It will probably be easier to do that in a more
2156 // automated way in LyXCursor code. (JMarc 26/09/2001)
2158 // correct all cursors held by the LyXText
2159 fixCursorAfterDelete(cursor, old_cursor);
2160 fixCursorAfterDelete(selection.cursor,
2162 fixCursorAfterDelete(selection.start,
2164 fixCursorAfterDelete(selection.end, old_cursor);
2165 fixCursorAfterDelete(last_sel_cursor,
2167 fixCursorAfterDelete(toggle_cursor, old_cursor);
2168 fixCursorAfterDelete(toggle_end_cursor,
2174 // don't delete anything if this is the ONLY paragraph!
2175 if (ownerParagraphs().size() == 1)
2178 // Do not delete empty paragraphs with keepempty set.
2179 if (old_cursor.par()->allowEmpty())
2182 // only do our magic if we changed paragraph
2183 if (old_cursor.par() == cursor.par())
2186 // record if we have deleted a paragraph
2187 // we can't possibly have deleted a paragraph before this point
2188 bool deleted = false;
2190 if (old_cursor.par()->empty() ||
2191 (old_cursor.par()->size() == 1 &&
2192 old_cursor.par()->isLineSeparator(0))) {
2193 // ok, we will delete anything
2194 LyXCursor tmpcursor;
2198 bool selection_position_was_oldcursor_position = (
2199 selection.cursor.par() == old_cursor.par()
2200 && selection.cursor.pos() == old_cursor.pos());
2202 if (getRow(old_cursor) != rows().begin()) {
2204 prevrow = boost::prior(getRow(old_cursor));
2205 postPaint(old_cursor.y() - getRow(old_cursor)->baseline() - prevrow->height());
2207 cursor = old_cursor; // that undo can restore the right cursor position
2208 #warning FIXME. --end() iterator is usable here
2209 ParagraphList::iterator endpit = boost::next(old_cursor.par());
2210 while (endpit != ownerParagraphs().end() &&
2211 endpit->getDepth()) {
2215 setUndo(bv(), Undo::DELETE, old_cursor.par(),
2216 boost::prior(endpit));
2220 removeRow(getRow(old_cursor));
2222 ownerParagraphs().erase(old_cursor.par());
2224 /* Breakagain the next par. Needed because of
2225 * the parindent that can occur or dissappear.
2226 * The next row can change its height, if
2227 * there is another layout before */
2228 RowList::iterator tmprit = boost::next(prevrow);
2229 if (tmprit != rows().end()) {
2233 setHeightOfRow(prevrow);
2235 RowList::iterator nextrow = boost::next(getRow(old_cursor));
2236 postPaint(old_cursor.y() - getRow(old_cursor)->baseline());
2239 cursor = old_cursor; // that undo can restore the right cursor position
2240 #warning FIXME. --end() iterator is usable here
2241 ParagraphList::iterator endpit = boost::next(old_cursor.par());
2242 while (endpit != ownerParagraphs().end() &&
2243 endpit->getDepth()) {
2247 setUndo(bv(), Undo::DELETE, old_cursor.par(), boost::prior(endpit));
2251 removeRow(getRow(old_cursor));
2253 ownerParagraphs().erase(old_cursor.par());
2255 /* Breakagain the next par. Needed because of
2256 the parindent that can occur or dissappear.
2257 The next row can change its height, if
2258 there is another layout before */
2259 if (nextrow != rows().end()) {
2260 breakAgain(nextrow);
2266 setCursorIntern(cursor.par(), cursor.pos());
2268 if (selection_position_was_oldcursor_position) {
2269 // correct selection
2270 selection.cursor = cursor;
2274 if (old_cursor.par()->stripLeadingSpaces()) {
2275 redoParagraphs(old_cursor, boost::next(old_cursor.par()));
2277 setCursorIntern(cursor.par(), cursor.pos());
2278 selection.cursor = cursor;
2285 ParagraphList & LyXText::ownerParagraphs() const
2288 return inset_owner->paragraphs;
2290 return bv_owner->buffer()->paragraphs;
2294 LyXText::refresh_status LyXText::refreshStatus() const
2296 return refresh_status_;
2300 void LyXText::clearPaint()
2302 refresh_status_ = REFRESH_NONE;
2303 refresh_row = rows().end();
2308 void LyXText::postPaint(int start_y)
2310 refresh_status old = refresh_status_;
2312 refresh_status_ = REFRESH_AREA;
2313 refresh_row = rows().end();
2315 if (old != REFRESH_NONE && refresh_y < start_y)
2318 refresh_y = start_y;
2323 // We are an inset's lyxtext. Tell the top-level lyxtext
2324 // it needs to update the row we're in.
2325 LyXText * t = bv()->text;
2326 t->postRowPaint(t->cursorRow(), t->cursor.y() - t->cursorRow()->baseline());
2330 // FIXME: we should probably remove this y parameter,
2331 // make refresh_y be 0, and use row->y etc.
2332 void LyXText::postRowPaint(RowList::iterator rit, int start_y)
2334 if (refresh_status_ != REFRESH_NONE && refresh_y < start_y) {
2335 refresh_status_ = REFRESH_AREA;
2338 refresh_y = start_y;
2341 if (refresh_status_ == REFRESH_AREA)
2344 refresh_status_ = REFRESH_ROW;
2350 // We are an inset's lyxtext. Tell the top-level lyxtext
2351 // it needs to update the row we're in.
2352 LyXText * t = bv()->text;
2353 t->postRowPaint(t->cursorRow(), t->cursor.y() - t->cursorRow()->baseline());
2357 bool LyXText::isInInset() const
2359 // Sub-level has non-null bv owner and
2360 // non-null inset owner.
2361 return inset_owner != 0 && bv_owner != 0;
2365 int defaultRowHeight()
2367 LyXFont const font(LyXFont::ALL_SANE);
2368 return int(font_metrics::maxAscent(font)
2369 + font_metrics::maxDescent(font) * 1.5);