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 * ====================================================== */
15 #include "paragraph.h"
16 #include "frontends/LyXView.h"
17 #include "undo_funcs.h"
19 #include "bufferparams.h"
21 #include "BufferView.h"
22 #include "CutAndPaste.h"
23 #include "frontends/Painter.h"
24 #include "frontends/font_metrics.h"
28 #include "FloatList.h"
30 #include "ParagraphParameters.h"
32 #include "lyxrow_funcs.h"
34 #include "insets/insetbibitem.h"
35 #include "insets/insetfloat.h"
37 #include "support/LAssert.h"
38 #include "support/textutils.h"
39 #include "support/lstrings.h"
41 #include "support/BoostFormat.h"
51 LyXText::LyXText(BufferView * bv)
52 : height(0), width(0), anchor_row_offset_(0),
53 inset_owner(0), the_locking_inset(0), bv_owner(bv)
55 anchor_row_ = rows().end();
56 need_break_row = rows().end();
57 refresh_row = rows().end();
63 LyXText::LyXText(BufferView * bv, InsetText * inset)
64 : height(0), width(0), anchor_row_offset_(0),
65 inset_owner(inset), the_locking_inset(0), bv_owner(bv)
67 anchor_row_ = rows().end();
68 need_break_row = rows().end();
69 refresh_row = rows().end();
75 void LyXText::init(BufferView * bview, bool reinit)
79 need_break_row = rows().end();
81 copylayouttype.erase();
84 } else if (!rowlist_.empty())
87 ParagraphList::iterator par = ownerParagraphs().begin();
88 ParagraphList::iterator end = ownerParagraphs().end();
90 current_font = getFont(bview->buffer(), &*par, 0);
92 for (; par != end; ++par) {
93 insertParagraph(&*par, rowlist_.end());
95 setCursorIntern(&*rowlist_.begin()->par(), 0);
96 selection.cursor = cursor;
104 LyXFont const realizeFont(LyXFont const & font,
108 LyXTextClass const & tclass = buf->params.getLyXTextClass();
109 LyXFont tmpfont(font);
110 Paragraph::depth_type par_depth = par->getDepth();
112 // Resolve against environment font information
113 while (par && par_depth && !tmpfont.resolved()) {
114 par = par->outerHook();
116 tmpfont.realize(par->layout()->font);
117 par_depth = par->getDepth();
121 tmpfont.realize(tclass.defaultfont());
129 // Gets the fully instantiated font at a given position in a paragraph
130 // Basically the same routine as Paragraph::getFont() in paragraph.C.
131 // The difference is that this one is used for displaying, and thus we
132 // are allowed to make cosmetic improvements. For instance make footnotes
134 // If position is -1, we get the layout font of the paragraph.
135 // If position is -2, we get the font of the manual label of the paragraph.
136 LyXFont const LyXText::getFont(Buffer const * buf, ParagraphList::iterator pit,
139 lyx::Assert(pos >= 0);
141 LyXLayout_ptr const & layout = pit->layout();
143 // We specialize the 95% common case:
144 if (!pit->getDepth()) {
145 if (layout->labeltype == LABEL_MANUAL
146 && pos < pit->beginningOfBody()) {
148 LyXFont f = pit->getFontSettings(buf->params, pos);
150 pit->inInset()->getDrawFont(f);
151 return f.realize(layout->reslabelfont);
153 LyXFont f = pit->getFontSettings(buf->params, pos);
155 pit->inInset()->getDrawFont(f);
156 return f.realize(layout->resfont);
160 // The uncommon case need not be optimized as much
164 if (pos < pit->beginningOfBody()) {
166 layoutfont = layout->labelfont;
169 layoutfont = layout->font;
172 LyXFont tmpfont = pit->getFontSettings(buf->params, pos);
173 tmpfont.realize(layoutfont);
176 pit->inInset()->getDrawFont(tmpfont);
178 return realizeFont(tmpfont, buf, &*pit);
182 LyXFont const LyXText::getLayoutFont(Buffer const * buf, Paragraph * par) const
184 LyXLayout_ptr const & layout = par->layout();
186 if (!par->getDepth()) {
187 return layout->resfont;
190 return realizeFont(layout->font, buf, par);
194 LyXFont const LyXText::getLabelFont(Buffer const * buf, Paragraph * par) const
196 LyXLayout_ptr const & layout = par->layout();
198 if (!par->getDepth()) {
199 return layout->reslabelfont;
202 return realizeFont(layout->labelfont, buf, par);
206 void LyXText::setCharFont(Paragraph * par,
207 pos_type pos, LyXFont const & fnt,
210 Buffer const * buf = bv()->buffer();
211 LyXFont font = getFont(buf, par, pos);
212 font.update(fnt, buf->params.language, toggleall);
213 // Let the insets convert their font
214 if (par->isInset(pos)) {
215 Inset * inset = par->getInset(pos);
216 if (isEditableInset(inset)) {
217 UpdatableInset * uinset =
218 static_cast<UpdatableInset *>(inset);
219 uinset->setFont(bv(), fnt, toggleall, true);
223 // Plug thru to version below:
224 setCharFont(buf, par, pos, font);
228 void LyXText::setCharFont(Buffer const * buf, Paragraph * par,
229 pos_type pos, LyXFont const & fnt)
233 LyXTextClass const & tclass = buf->params.getLyXTextClass();
234 LyXLayout_ptr const & layout = par->layout();
236 // Get concrete layout font to reduce against
239 if (pos < par->beginningOfBody())
240 layoutfont = layout->labelfont;
242 layoutfont = layout->font;
244 // Realize against environment font information
245 if (par->getDepth()) {
246 Paragraph * tp = par;
247 while (!layoutfont.resolved() && tp && tp->getDepth()) {
248 tp = tp->outerHook();
250 layoutfont.realize(tp->layout()->font);
254 layoutfont.realize(tclass.defaultfont());
256 // Now, reduce font against full layout font
257 font.reduce(layoutfont);
259 par->setFont(pos, font);
263 // removes the row and reset the touched counters
264 void LyXText::removeRow(RowList::iterator rit)
266 /* FIXME: when we cache the bview, this should just
267 * become a postPaint(), I think */
268 if (refresh_row == rit) {
269 if (rit == rows().begin())
270 refresh_row = boost::next(rit);
272 refresh_row = boost::prior(rit);
274 // what about refresh_y
277 if (anchor_row_ == rit) {
278 if (rit != rows().begin()) {
279 anchor_row_ = boost::prior(rit);
280 anchor_row_offset_ += boost::prior(rit)->height();
282 anchor_row_ = boost::next(rit);
283 anchor_row_offset_ -= rit->height();
287 // the text becomes smaller
288 height -= rit->height();
294 // remove all following rows of the paragraph of the specified row.
295 void LyXText::removeParagraph(RowList::iterator rit)
297 ParagraphList::iterator tmppit = rit->par();
300 while (rit != rows().end() && rit->par() == tmppit) {
301 RowList::iterator tmprit = boost::next(rit);
308 #warning FIXME Convert this to ParagraphList::iterator
309 void LyXText::insertParagraph(Paragraph * par, RowList::iterator rowit)
311 // insert a new row, starting at position 0
313 RowList::iterator rit = rowlist_.insert(rowit, newrow);
315 // and now append the whole paragraph before the new row
316 appendParagraph(rit);
320 Inset * LyXText::getInset() const
322 if (cursor.pos() < cursor.par()->size()
323 && cursor.par()->isInset(cursor.pos())) {
324 return cursor.par()->getInset(cursor.pos());
330 void LyXText::toggleInset()
332 Inset * inset = getInset();
333 // is there an editable inset at cursor position?
334 if (!isEditableInset(inset)) {
335 // No, try to see if we are inside a collapsable inset
336 if (inset_owner && inset_owner->owner()
337 && inset_owner->owner()->isOpen()) {
338 bv()->unlockInset(static_cast<UpdatableInset *>(inset_owner->owner()));
339 inset_owner->owner()->close(bv());
340 bv()->getLyXText()->cursorRight(bv());
344 //bv()->owner()->message(inset->editMessage());
346 // do we want to keep this?? (JMarc)
347 if (!isHighlyEditableInset(inset))
348 setCursorParUndo(bv());
350 if (inset->isOpen()) {
356 bv()->updateInset(inset);
360 /* used in setlayout */
361 // Asger is not sure we want to do this...
362 void LyXText::makeFontEntriesLayoutSpecific(Buffer const & buf,
365 LyXLayout_ptr const & layout = par.layout();
368 for (pos_type pos = 0; pos < par.size(); ++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 Paragraph * LyXText::setLayout(LyXCursor & cur, LyXCursor & sstart_cur,
382 LyXCursor & send_cur,
383 string const & layout)
385 Paragraph * endpar = send_cur.par()->next();
386 Paragraph * undoendpar = endpar;
388 if (endpar && endpar->getDepth()) {
389 while (endpar && endpar->getDepth()) {
390 endpar = endpar->next();
394 endpar = endpar->next(); // because of parindents etc.
397 setUndo(bv(), Undo::EDIT, &*sstart_cur.par(), undoendpar);
399 // ok we have a selection. This is always between sstart_cur
400 // and sel_end cursor
402 ParagraphList::iterator pit = sstart_cur.par();
403 ParagraphList::iterator epit = boost::next(send_cur.par());
405 LyXLayout_ptr const & lyxlayout =
406 bv()->buffer()->params.getLyXTextClass()[layout];
409 pit->applyLayout(lyxlayout);
410 makeFontEntriesLayoutSpecific(*bv()->buffer(), *pit);
411 ParagraphList::iterator fppit = pit;
412 fppit->params().spaceTop(lyxlayout->fill_top ?
413 VSpace(VSpace::VFILL)
414 : VSpace(VSpace::NONE));
415 fppit->params().spaceBottom(lyxlayout->fill_bottom ?
416 VSpace(VSpace::VFILL)
417 : VSpace(VSpace::NONE));
418 if (lyxlayout->margintype == MARGIN_MANUAL)
419 pit->setLabelWidthString(lyxlayout->labelstring());
422 } while (pit != epit);
428 // set layout over selection and make a total rebreak of those paragraphs
429 void LyXText::setLayout(string const & layout)
431 LyXCursor tmpcursor = cursor; /* store the current cursor */
433 // if there is no selection just set the layout
434 // of the current paragraph */
435 if (!selection.set()) {
436 selection.start = cursor; // dummy selection
437 selection.end = cursor;
439 Paragraph * endpar = setLayout(cursor, selection.start,
440 selection.end, layout);
441 redoParagraphs(selection.start, endpar);
443 // we have to reset the selection, because the
444 // geometry could have changed
445 setCursor(selection.start.par(),
446 selection.start.pos(), false);
447 selection.cursor = cursor;
448 setCursor(selection.end.par(), selection.end.pos(), false);
452 setCursor(tmpcursor.par(), tmpcursor.pos(), true);
456 bool LyXText::changeDepth(bv_funcs::DEPTH_CHANGE type, bool test_only)
458 ParagraphList::iterator pit(cursor.par());
459 ParagraphList::iterator end(cursor.par());
460 ParagraphList::iterator start = pit;
462 if (selection.set()) {
463 pit = selection.start.par();
464 end = selection.end.par();
468 ParagraphList::iterator pastend = boost::next(end);
471 setUndo(bv(), Undo::EDIT, &(*start), &(*pastend));
473 bool changed = false;
475 int prev_after_depth = 0;
476 #warning parlist ... could be nicer ?
477 if (start != ownerParagraphs().begin()) {
478 prev_after_depth = boost::prior(start)->getMaxDepthAfter();
482 int const depth = pit->params().depth();
483 if (type == bv_funcs::INC_DEPTH) {
484 if (depth < prev_after_depth
485 && pit->layout()->labeltype != LABEL_BIBLIO) {
488 pit->params().depth(depth + 1);
495 pit->params().depth(depth - 1);
498 prev_after_depth = pit->getMaxDepthAfter();
510 // Wow, redoParagraphs is stupid.
512 setCursor(tmpcursor, &(*start), 0);
514 //redoParagraphs(tmpcursor, &(*pastend));
515 redoParagraphs(tmpcursor, &(*pastend));
517 // We need to actually move the text->cursor. I don't
518 // understand why ...
521 // we have to reset the visual selection because the
522 // geometry could have changed
523 if (selection.set()) {
524 setCursor(selection.start.par(), selection.start.pos());
525 selection.cursor = cursor;
526 setCursor(selection.end.par(), selection.end.pos());
529 // this handles the counter labels, and also fixes up
530 // depth values for follow-on (child) paragraphs
534 setCursor(tmpcursor.par(), tmpcursor.pos());
540 // set font over selection and make a total rebreak of those paragraphs
541 void LyXText::setFont(LyXFont const & font, bool toggleall)
543 // if there is no selection just set the current_font
544 if (!selection.set()) {
545 // Determine basis font
547 if (cursor.pos() < cursor.par()->beginningOfBody()) {
548 layoutfont = getLabelFont(bv()->buffer(),
551 layoutfont = getLayoutFont(bv()->buffer(),
554 // Update current font
555 real_current_font.update(font,
556 bv()->buffer()->params.language,
559 // Reduce to implicit settings
560 current_font = real_current_font;
561 current_font.reduce(layoutfont);
562 // And resolve it completely
563 real_current_font.realize(layoutfont);
568 LyXCursor tmpcursor = cursor; // store the current cursor
570 // ok we have a selection. This is always between sel_start_cursor
571 // and sel_end cursor
573 setUndo(bv(), Undo::EDIT,
574 &*selection.start.par(), &*boost::next(selection.end.par()));
576 cursor = selection.start;
577 while (cursor.par() != selection.end.par() ||
578 cursor.pos() < selection.end.pos())
580 if (cursor.pos() < cursor.par()->size()) {
581 // an open footnote should behave like a closed one
582 setCharFont(&*cursor.par(), cursor.pos(),
584 cursor.pos(cursor.pos() + 1);
587 cursor.par(cursor.par()->next());
592 redoParagraphs(selection.start, selection.end.par()->next());
594 // we have to reset the selection, because the
595 // geometry could have changed, but we keep
596 // it for user convenience
597 setCursor(selection.start.par(), selection.start.pos());
598 selection.cursor = cursor;
599 setCursor(selection.end.par(), selection.end.pos());
601 setCursor(tmpcursor.par(), tmpcursor.pos(), true,
602 tmpcursor.boundary());
606 void LyXText::redoHeightOfParagraph()
608 RowList::iterator tmprow = cursor.row();
609 int y = cursor.y() - tmprow->baseline();
611 setHeightOfRow(tmprow);
613 while (tmprow != rows().begin()
614 && boost::prior(tmprow)->par() == tmprow->par()) {
616 y -= tmprow->height();
617 setHeightOfRow(tmprow);
622 setCursor(cursor.par(), cursor.pos(), false, cursor.boundary());
626 void LyXText::redoDrawingOfParagraph(LyXCursor const & cur)
628 RowList::iterator tmprow = cur.row();
630 int y = cur.y() - tmprow->baseline();
631 setHeightOfRow(tmprow);
633 while (tmprow != rows().begin()
634 && boost::prior(tmprow)->par() == tmprow->par()) {
636 y -= tmprow->height();
640 setCursor(cur.par(), cur.pos());
644 // deletes and inserts again all paragaphs between the cursor
645 // and the specified par
646 // This function is needed after SetLayout and SetFont etc.
647 void LyXText::redoParagraphs(LyXCursor const & cur,
648 Paragraph const * ep)
650 RowList::iterator tmprit = cur.row();
651 ParagraphList::iterator endpit(const_cast<Paragraph*>(ep));
652 int y = cur.y() - tmprit->baseline();
654 ParagraphList::iterator first_phys_pit;
655 if (tmprit == rows().begin()) {
656 // A trick/hack for UNDO.
657 // This is needed because in an UNDO/REDO we could have
658 // changed the ownerParagrah() so the paragraph inside
659 // the row is NOT my really first par anymore.
660 // Got it Lars ;) (Jug 20011206)
661 first_phys_pit = ownerParagraphs().begin();
663 // In here prevrit could be set to rows().end(). (Lgb)
665 first_phys_pit = tmprit->par();
666 while (tmprit != rows().begin()
667 && boost::prior(tmprit)->par() == first_phys_pit)
670 y -= tmprit->height();
673 // Is it possible to put the prevrit setting in here? (Lgb)
676 RowList::iterator prevrit;
677 bool good_prevrit = false;
679 // It seems to mee that good_prevrit is not needed if we let
680 // a bad prevrit have the value rows().end() (Lgb)
681 if (tmprit != rows().begin()) {
682 prevrit = boost::prior(tmprit);
687 while (tmprit != rows().end() && tmprit->par() != endpit) {
688 RowList::iterator tmprit2 = tmprit++;
692 // Reinsert the paragraphs.
693 ParagraphList::iterator tmppit = first_phys_pit;
695 // See if this loop can be rewritten as a while loop instead.
696 // That should also make the code a bit easier to read. (Lgb)
698 if (tmppit != ownerParagraphs().end()) {
699 insertParagraph(&*tmppit, tmprit);
700 while (tmprit != rows().end()
701 && tmprit->par() == tmppit) {
706 } while (tmppit != ownerParagraphs().end() && tmppit != endpit);
709 // If the above changes are done, then we can compare prevrit
710 // with rows().end() here. (Lgb)
712 setHeightOfRow(prevrit);
713 const_cast<LyXText *>(this)->postPaint(y - prevrit->height());
715 setHeightOfRow(rows().begin());
716 const_cast<LyXText *>(this)->postPaint(0);
718 if (tmprit != rows().end())
719 setHeightOfRow(tmprit);
725 void LyXText::fullRebreak()
727 if (rows().empty()) {
731 if (need_break_row != rows().end()) {
732 breakAgain(need_break_row);
733 need_break_row = rows().end();
739 // important for the screen
742 // the cursor set functions have a special mechanism. When they
743 // realize, that you left an empty paragraph, they will delete it.
744 // They also delete the corresponding row
746 // need the selection cursor:
747 void LyXText::setSelection()
749 bool const lsel = selection.set();
751 if (!selection.set()) {
752 last_sel_cursor = selection.cursor;
753 selection.start = selection.cursor;
754 selection.end = selection.cursor;
759 // first the toggling area
760 if (cursor.y() < last_sel_cursor.y()
761 || (cursor.y() == last_sel_cursor.y()
762 && cursor.x() < last_sel_cursor.x())) {
763 toggle_end_cursor = last_sel_cursor;
764 toggle_cursor = cursor;
766 toggle_end_cursor = cursor;
767 toggle_cursor = last_sel_cursor;
770 last_sel_cursor = cursor;
772 // and now the whole selection
774 if (selection.cursor.par() == cursor.par())
775 if (selection.cursor.pos() < cursor.pos()) {
776 selection.end = cursor;
777 selection.start = selection.cursor;
779 selection.end = selection.cursor;
780 selection.start = cursor;
782 else if (selection.cursor.y() < cursor.y() ||
783 (selection.cursor.y() == cursor.y()
784 && selection.cursor.x() < cursor.x())) {
785 selection.end = cursor;
786 selection.start = selection.cursor;
789 selection.end = selection.cursor;
790 selection.start = cursor;
793 // a selection with no contents is not a selection
794 if (selection.start.par() == selection.end.par() &&
795 selection.start.pos() == selection.end.pos())
796 selection.set(false);
798 if (inset_owner && (selection.set() || lsel))
799 inset_owner->setUpdateStatus(bv(), InsetText::SELECTION);
803 string const LyXText::selectionAsString(Buffer const * buffer,
806 if (!selection.set()) return string();
808 // should be const ...
809 ParagraphList::iterator startpit = selection.start.par();
810 ParagraphList::iterator endpit = selection.end.par();
811 pos_type const startpos(selection.start.pos());
812 pos_type const endpos(selection.end.pos());
814 if (startpit == endpit) {
815 return startpit->asString(buffer, startpos, endpos, label);
820 // First paragraph in selection
821 result += startpit->asString(buffer, startpos, startpit->size(), label) + "\n\n";
823 // The paragraphs in between (if any)
824 #warning FIXME Why isnt ParagraphList::iterator used here?
826 LyXCursor tmpcur(selection.start);
827 tmpcur.par(tmpcur.par()->next());
828 while (tmpcur.par() != endpit) {
829 result += tmpcur.par()->asString(buffer, 0,
830 tmpcur.par()->size(),
832 tmpcur.par(boost::next(tmpcur.par()));
835 // Last paragraph in selection
836 result += endpit->asString(buffer, 0, endpos, label);
842 void LyXText::clearSelection()
844 selection.set(false);
845 selection.mark(false);
846 last_sel_cursor = selection.end = selection.start = selection.cursor = cursor;
847 // reset this in the bv_owner!
848 if (bv_owner && bv_owner->text)
849 bv_owner->text->xsel_cache.set(false);
853 void LyXText::cursorHome()
855 setCursor(cursor.par(), cursor.row()->pos());
859 void LyXText::cursorEnd()
861 if (cursor.par()->empty())
864 // There is a lot of unneeded recalculation going on here:
865 // - boost::next(curosr.row())
866 // - lastPost(*this, cursor.row())
868 if (boost::next(cursor.row()) == rows().end()
869 || boost::next(cursor.row())->par() != cursor.row()->par()) {
870 setCursor(cursor.par(), lastPos(*this, cursor.row()) + 1);
872 if (!cursor.par()->empty() &&
873 (cursor.par()->getChar(lastPos(*this, cursor.row())) == ' '
874 || cursor.par()->isNewline(lastPos(*this, cursor.row())))) {
875 setCursor(cursor.par(), lastPos(*this, cursor.row()));
877 setCursor(cursor.par(),
878 lastPos(*this, cursor.row()) + 1);
884 void LyXText::cursorTop()
886 while (cursor.par()->previous())
887 cursor.par(cursor.par()->previous());
888 setCursor(cursor.par(), 0);
892 void LyXText::cursorBottom()
894 while (cursor.par()->next())
895 cursor.par(cursor.par()->next());
896 setCursor(cursor.par(), cursor.par()->size());
900 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
902 // If the mask is completely neutral, tell user
903 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
904 // Could only happen with user style
905 bv()->owner()->message(_("No font change defined. Use Character under the Layout menu to define font change."));
909 // Try implicit word selection
910 // If there is a change in the language the implicit word selection
912 LyXCursor resetCursor = cursor;
913 bool implicitSelection = (font.language() == ignore_language
914 && font.number() == LyXFont::IGNORE)
915 ? selectWordWhenUnderCursor(WHOLE_WORD_STRICT) : false;
918 setFont(font, toggleall);
920 // Implicit selections are cleared afterwards
921 //and cursor is set to the original position.
922 if (implicitSelection) {
924 cursor = resetCursor;
925 setCursor(cursor.par(), cursor.pos());
926 selection.cursor = cursor;
929 inset_owner->setUpdateStatus(bv(), InsetText::CURSOR_PAR);
933 string LyXText::getStringToIndex()
935 // Try implicit word selection
936 // If there is a change in the language the implicit word selection
938 LyXCursor const reset_cursor = cursor;
939 bool const implicitSelection = selectWordWhenUnderCursor(PREVIOUS_WORD);
942 if (!selection.set())
943 bv()->owner()->message(_("Nothing to index!"));
944 else if (selection.start.par() != selection.end.par())
945 bv()->owner()->message(_("Cannot index more than one paragraph!"));
947 idxstring = selectionAsString(bv()->buffer(), false);
949 // Reset cursors to their original position.
950 cursor = reset_cursor;
951 setCursor(cursor.par(), cursor.pos());
952 selection.cursor = cursor;
954 // Clear the implicit selection.
955 if (implicitSelection)
962 // the DTP switches for paragraphs. LyX will store them in the first
963 // physicla paragraph. When a paragraph is broken, the top settings rest,
964 // the bottom settings are given to the new one. So I can make shure,
965 // they do not duplicate themself and you cannnot make dirty things with
968 void LyXText::setParagraph(bool line_top, bool line_bottom,
969 bool pagebreak_top, bool pagebreak_bottom,
970 VSpace const & space_top,
971 VSpace const & space_bottom,
972 Spacing const & spacing,
974 string const & labelwidthstring,
977 LyXCursor tmpcursor = cursor;
978 if (!selection.set()) {
979 selection.start = cursor;
980 selection.end = cursor;
983 // make sure that the depth behind the selection are restored, too
984 Paragraph * endpar = selection.end.par()->next();
985 Paragraph * undoendpar = endpar;
987 if (endpar && endpar->getDepth()) {
988 while (endpar && endpar->getDepth()) {
989 endpar = endpar->next();
994 // because of parindents etc.
995 endpar = endpar->next();
998 setUndo(bv(), Undo::EDIT, &*selection.start.par(), undoendpar);
1001 ParagraphList::iterator tmppit = selection.end.par();
1003 while (tmppit != boost::prior(selection.start.par())) {
1004 setCursor(tmppit, 0);
1005 postPaint(cursor.y() - cursor.row()->baseline());
1006 cursor.par()->params().lineTop(line_top);
1007 cursor.par()->params().lineBottom(line_bottom);
1008 cursor.par()->params().pagebreakTop(pagebreak_top);
1009 cursor.par()->params().pagebreakBottom(pagebreak_bottom);
1010 cursor.par()->params().spaceTop(space_top);
1011 cursor.par()->params().spaceBottom(space_bottom);
1012 cursor.par()->params().spacing(spacing);
1013 // does the layout allow the new alignment?
1014 LyXLayout_ptr const & layout = cursor.par()->layout();
1016 if (align == LYX_ALIGN_LAYOUT)
1017 align = layout->align;
1018 if (align & layout->alignpossible) {
1019 if (align == layout->align)
1020 cursor.par()->params().align(LYX_ALIGN_LAYOUT);
1022 cursor.par()->params().align(align);
1024 cursor.par()->setLabelWidthString(labelwidthstring);
1025 cursor.par()->params().noindent(noindent);
1026 tmppit = boost::prior(cursor.par());
1029 redoParagraphs(selection.start, endpar);
1032 setCursor(selection.start.par(), selection.start.pos());
1033 selection.cursor = cursor;
1034 setCursor(selection.end.par(), selection.end.pos());
1036 setCursor(tmpcursor.par(), tmpcursor.pos());
1038 bv()->updateInset(inset_owner);
1042 // set the counter of a paragraph. This includes the labels
1043 void LyXText::setCounter(Buffer const * buf, Paragraph * par)
1045 LyXTextClass const & textclass = buf->params.getLyXTextClass();
1046 LyXLayout_ptr const & layout = par->layout();
1048 if (par->previous()) {
1050 par->params().appendix(par->previous()->params().appendix());
1051 if (!par->params().appendix() && par->params().startOfAppendix()) {
1052 par->params().appendix(true);
1053 textclass.counters().reset();
1055 par->enumdepth = par->previous()->enumdepth;
1056 par->itemdepth = par->previous()->itemdepth;
1058 par->params().appendix(par->params().startOfAppendix());
1063 /* Maybe we have to increment the enumeration depth.
1064 * BUT, enumeration in a footnote is considered in isolation from its
1065 * surrounding paragraph so don't increment if this is the
1066 * first line of the footnote
1067 * AND, bibliographies can't have their depth changed ie. they
1068 * are always of depth 0
1071 && par->previous()->getDepth() < par->getDepth()
1072 && par->previous()->layout()->labeltype == LABEL_COUNTER_ENUMI
1073 && par->enumdepth < 3
1074 && layout->labeltype != LABEL_BIBLIO) {
1078 // Maybe we have to decrement the enumeration depth, see note above
1080 && par->previous()->getDepth() > par->getDepth()
1081 && layout->labeltype != LABEL_BIBLIO) {
1082 par->enumdepth = par->depthHook(par->getDepth())->enumdepth;
1085 if (!par->params().labelString().empty()) {
1086 par->params().labelString(string());
1089 if (layout->margintype == MARGIN_MANUAL) {
1090 if (par->params().labelWidthString().empty()) {
1091 par->setLabelWidthString(layout->labelstring());
1094 par->setLabelWidthString(string());
1097 // is it a layout that has an automatic label?
1098 if (layout->labeltype >= LABEL_COUNTER_CHAPTER) {
1099 int const i = layout->labeltype - LABEL_COUNTER_CHAPTER;
1103 if (i >= 0 && i <= buf->params.secnumdepth) {
1107 textclass.counters().step(layout->latexname());
1109 // Is there a label? Useful for Chapter layout
1110 if (!par->params().appendix()) {
1111 s << layout->labelstring();
1113 s << layout->labelstring_appendix();
1116 // Use of an integer is here less than elegant. For now.
1117 int head = textclass.maxcounter() - LABEL_COUNTER_CHAPTER;
1118 if (!par->params().appendix()) {
1119 numbertype = "sectioning";
1121 numbertype = "appendix";
1122 if (par->isRightToLeftPar(buf->params))
1123 langtype = "hebrew";
1128 s << textclass.counters()
1129 .numberLabel(layout->latexname(),
1130 numbertype, langtype, head);
1132 par->params().labelString(STRCONV(s.str()));
1134 // reset enum counters
1135 textclass.counters().reset("enum");
1136 } else if (layout->labeltype < LABEL_COUNTER_ENUMI) {
1137 textclass.counters().reset("enum");
1138 } else if (layout->labeltype == LABEL_COUNTER_ENUMI) {
1140 // Yes I know this is a really, really! bad solution
1142 string enumcounter("enum");
1144 switch (par->enumdepth) {
1153 enumcounter += "iv";
1156 // not a valid enumdepth...
1160 textclass.counters().step(enumcounter);
1162 s << textclass.counters()
1163 .numberLabel(enumcounter, "enumeration");
1164 par->params().labelString(STRCONV(s.str()));
1166 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
1167 textclass.counters().step("bibitem");
1168 int number = textclass.counters().value("bibitem");
1169 if (par->bibitem()) {
1170 par->bibitem()->setCounter(number);
1171 par->params().labelString(layout->labelstring());
1173 // In biblio should't be following counters but...
1175 string s = layout->labelstring();
1177 // the caption hack:
1178 if (layout->labeltype == LABEL_SENSITIVE) {
1179 Paragraph * tmppar = par;
1182 while (tmppar && tmppar->inInset()
1183 // the single '=' is intended below
1184 && (in = tmppar->inInset()->owner())) {
1185 if (in->lyxCode() == Inset::FLOAT_CODE ||
1186 in->lyxCode() == Inset::WRAP_CODE) {
1190 tmppar = in->parOwner();
1196 = textclass.floats().getType(static_cast<InsetFloat*>(in)->type());
1198 textclass.counters().step(fl.type());
1200 // Doesn't work... yet.
1201 #if USE_BOOST_FORMAT
1202 s = boost::io::str(boost::format(_("%1$s #:")) % fl.name());
1203 // s << boost::format(_("%1$s %1$d:")
1205 // % buf->counters().value(fl.name());
1208 //o << fl.name() << ' ' << buf->counters().value(fl.name()) << ":";
1209 o << fl.name() << " #:";
1210 s = STRCONV(o.str());
1213 // par->SetLayout(0);
1214 // s = layout->labelstring;
1215 s = _("Senseless: ");
1218 par->params().labelString(s);
1220 // reset the enumeration counter. They are always reset
1221 // when there is any other layout between
1222 // Just fall-through between the cases so that all
1223 // enum counters deeper than enumdepth is also reset.
1224 switch (par->enumdepth) {
1226 textclass.counters().reset("enumi");
1228 textclass.counters().reset("enumii");
1230 textclass.counters().reset("enumiii");
1232 textclass.counters().reset("enumiv");
1238 // Updates all counters. Paragraphs with changed label string will be rebroken
1239 void LyXText::updateCounters()
1241 RowList::iterator rowit = rows().begin();
1242 ParagraphList::iterator pit = rowit->par();
1244 // CHECK if this is really needed. (Lgb)
1245 bv()->buffer()->params.getLyXTextClass().counters().reset();
1247 while (pit != ownerParagraphs().end()) {
1248 while (rowit->par() != pit)
1251 string const oldLabel = pit->params().labelString();
1254 if (pit != ownerParagraphs().begin())
1255 maxdepth = boost::prior(pit)->getMaxDepthAfter();
1257 if (pit->params().depth() > maxdepth)
1258 pit->params().depth(maxdepth);
1260 // setCounter can potentially change the labelString.
1261 setCounter(bv()->buffer(), &*pit);
1263 string const & newLabel = pit->params().labelString();
1265 if (oldLabel.empty() && !newLabel.empty()) {
1266 removeParagraph(rowit);
1267 appendParagraph(rowit);
1275 void LyXText::insertInset(Inset * inset)
1277 if (!cursor.par()->insetAllowed(inset->lyxCode()))
1279 setUndo(bv(), Undo::FINISH, &*cursor.par(),
1280 &*boost::next(cursor.par()));
1282 cursor.par()->insertInset(cursor.pos(), inset);
1283 // Just to rebreak and refresh correctly.
1284 // The character will not be inserted a second time
1285 insertChar(Paragraph::META_INSET);
1286 // If we enter a highly editable inset the cursor should be to before
1287 // the inset. This couldn't happen before as Undo was not handled inside
1288 // inset now after the Undo LyX tries to call inset->Edit(...) again
1289 // and cannot do this as the cursor is behind the inset and GetInset
1290 // does not return the inset!
1291 if (isHighlyEditableInset(inset)) {
1298 void LyXText::copyEnvironmentType()
1300 copylayouttype = cursor.par()->layout()->name();
1304 void LyXText::pasteEnvironmentType()
1306 // do nothing if there has been no previous copyEnvironmentType()
1307 if (!copylayouttype.empty())
1308 setLayout(copylayouttype);
1312 void LyXText::cutSelection(bool doclear, bool realcut)
1314 // Stuff what we got on the clipboard. Even if there is no selection.
1316 // There is a problem with having the stuffing here in that the
1317 // larger the selection the slower LyX will get. This can be
1318 // solved by running the line below only when the selection has
1319 // finished. The solution used currently just works, to make it
1320 // faster we need to be more clever and probably also have more
1321 // calls to stuffClipboard. (Lgb)
1322 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1324 // This doesn't make sense, if there is no selection
1325 if (!selection.set())
1328 // OK, we have a selection. This is always between selection.start
1329 // and selection.end
1331 // make sure that the depth behind the selection are restored, too
1332 Paragraph * endpar = selection.end.par()->next();
1333 Paragraph * undoendpar = endpar;
1335 if (endpar && endpar->getDepth()) {
1336 while (endpar && endpar->getDepth()) {
1337 endpar = endpar->next();
1338 undoendpar = endpar;
1340 } else if (endpar) {
1341 endpar = endpar->next(); // because of parindents etc.
1344 setUndo(bv(), Undo::DELETE,
1345 &*selection.start.par(), undoendpar);
1347 // there are two cases: cut only within one paragraph or
1348 // more than one paragraph
1349 if (selection.start.par() == selection.end.par()) {
1350 // only within one paragraph
1351 endpar = &*selection.end.par();
1352 int pos = selection.end.pos();
1353 CutAndPaste::cutSelection(&*selection.start.par(), &endpar,
1354 selection.start.pos(), pos,
1355 bv()->buffer()->params.textclass,
1357 selection.end.pos(pos);
1359 endpar = &*selection.end.par();
1360 int pos = selection.end.pos();
1361 CutAndPaste::cutSelection(&*selection.start.par(), &endpar,
1362 selection.start.pos(), pos,
1363 bv()->buffer()->params.textclass,
1366 selection.end.par(endpar);
1367 selection.end.pos(pos);
1368 cursor.pos(selection.end.pos());
1370 endpar = endpar->next();
1372 // sometimes necessary
1374 selection.start.par()->stripLeadingSpaces();
1376 redoParagraphs(selection.start, endpar);
1378 // cutSelection can invalidate the cursor so we need to set
1380 // we prefer the end for when tracking changes
1381 cursor = selection.end;
1383 // need a valid cursor. (Lgb)
1386 setCursor(cursor.par(), cursor.pos());
1387 selection.cursor = cursor;
1392 void LyXText::copySelection()
1394 // stuff the selection onto the X clipboard, from an explicit copy request
1395 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1397 // this doesnt make sense, if there is no selection
1398 if (!selection.set())
1401 // ok we have a selection. This is always between selection.start
1402 // and sel_end cursor
1404 // copy behind a space if there is one
1405 while (selection.start.par()->size() > selection.start.pos()
1406 && selection.start.par()->isLineSeparator(selection.start.pos())
1407 && (selection.start.par() != selection.end.par()
1408 || selection.start.pos() < selection.end.pos()))
1409 selection.start.pos(selection.start.pos() + 1);
1411 CutAndPaste::copySelection(&*selection.start.par(),
1412 &*selection.end.par(),
1413 selection.start.pos(), selection.end.pos(),
1414 bv()->buffer()->params.textclass);
1418 void LyXText::pasteSelection()
1420 // this does not make sense, if there is nothing to paste
1421 if (!CutAndPaste::checkPastePossible())
1424 setUndo(bv(), Undo::INSERT,
1425 &*cursor.par(), &*boost::next(cursor.par()));
1428 ParagraphList::iterator actpit = cursor.par();
1429 int pos = cursor.pos();
1431 Paragraph * actpar = &*actpit;
1432 CutAndPaste::pasteSelection(&actpar, &endpar, pos,
1433 bv()->buffer()->params.textclass);
1435 redoParagraphs(cursor, endpar);
1437 setCursor(cursor.par(), cursor.pos());
1440 selection.cursor = cursor;
1441 setCursor(actpit, pos);
1447 void LyXText::setSelectionRange(lyx::pos_type length)
1452 selection.cursor = cursor;
1459 // simple replacing. The font of the first selected character is used
1460 void LyXText::replaceSelectionWithString(string const & str)
1462 setCursorParUndo(bv());
1465 if (!selection.set()) { // create a dummy selection
1466 selection.end = cursor;
1467 selection.start = cursor;
1470 // Get font setting before we cut
1471 pos_type pos = selection.end.pos();
1472 LyXFont const font = selection.start.par()
1473 ->getFontSettings(bv()->buffer()->params,
1474 selection.start.pos());
1476 // Insert the new string
1477 for (string::const_iterator cit = str.begin(); cit != str.end(); ++cit) {
1478 selection.end.par()->insertChar(pos, (*cit), font);
1482 // Cut the selection
1483 cutSelection(true, false);
1489 // needed to insert the selection
1490 void LyXText::insertStringAsLines(string const & str)
1492 ParagraphList::iterator pit = cursor.par();
1493 pos_type pos = cursor.pos();
1494 ParagraphList::iterator endpit = boost::next(cursor.par());
1496 setCursorParUndo(bv());
1498 // only to be sure, should not be neccessary
1501 Paragraph * par = &*pit;
1502 bv()->buffer()->insertStringAsLines(par, pos, current_font, str);
1504 redoParagraphs(cursor, &*endpit);
1505 setCursor(cursor.par(), cursor.pos());
1506 selection.cursor = cursor;
1507 setCursor(pit, pos);
1512 // turns double-CR to single CR, others where converted into one
1513 // blank. Then InsertStringAsLines is called
1514 void LyXText::insertStringAsParagraphs(string const & str)
1516 string linestr(str);
1517 bool newline_inserted = false;
1518 for (string::size_type i = 0; i < linestr.length(); ++i) {
1519 if (linestr[i] == '\n') {
1520 if (newline_inserted) {
1521 // we know that \r will be ignored by
1522 // InsertStringA. Of course, it is a dirty
1523 // trick, but it works...
1524 linestr[i - 1] = '\r';
1528 newline_inserted = true;
1530 } else if (IsPrintable(linestr[i])) {
1531 newline_inserted = false;
1534 insertStringAsLines(linestr);
1538 void LyXText::checkParagraph(Paragraph * par, pos_type pos)
1540 LyXCursor tmpcursor;
1544 RowList::iterator row = getRow(par, pos, y);
1545 RowList::iterator beg = rows().begin();
1547 // is there a break one row above
1549 && boost::prior(row)->par() == row->par()) {
1550 z = rowBreakPoint(*boost::prior(row));
1551 if (z >= row->pos()) {
1552 // set the dimensions of the row above
1553 y -= boost::prior(row)->height();
1556 breakAgain(boost::prior(row));
1558 // set the cursor again. Otherwise
1559 // dangling pointers are possible
1560 setCursor(cursor.par(), cursor.pos(),
1561 false, cursor.boundary());
1562 selection.cursor = cursor;
1567 int const tmpheight = row->height();
1568 pos_type const tmplast = lastPos(*this, row);
1571 if (row->height() == tmpheight && lastPos(*this, row) == tmplast) {
1572 postRowPaint(row, y);
1577 // check the special right address boxes
1578 if (par->layout()->margintype == MARGIN_RIGHT_ADDRESS_BOX) {
1585 redoDrawingOfParagraph(tmpcursor);
1588 // set the cursor again. Otherwise dangling pointers are possible
1589 // also set the selection
1591 if (selection.set()) {
1593 setCursorIntern(selection.cursor.par(), selection.cursor.pos(),
1594 false, selection.cursor.boundary());
1595 selection.cursor = cursor;
1596 setCursorIntern(selection.start.par(),
1597 selection.start.pos(),
1598 false, selection.start.boundary());
1599 selection.start = cursor;
1600 setCursorIntern(selection.end.par(),
1601 selection.end.pos(),
1602 false, selection.end.boundary());
1603 selection.end = cursor;
1604 setCursorIntern(last_sel_cursor.par(),
1605 last_sel_cursor.pos(),
1606 false, last_sel_cursor.boundary());
1607 last_sel_cursor = cursor;
1610 setCursorIntern(cursor.par(), cursor.pos(),
1611 false, cursor.boundary());
1615 // returns false if inset wasn't found
1616 bool LyXText::updateInset(Inset * inset)
1618 // first check the current paragraph
1619 int pos = cursor.par()->getPositionOfInset(inset);
1621 checkParagraph(&*cursor.par(), pos);
1625 // check every paragraph
1627 ParagraphList::iterator par = ownerParagraphs().begin();
1628 ParagraphList::iterator end = ownerParagraphs().end();
1631 pos = par->getPositionOfInset(inset);
1633 checkParagraph(&*par, pos);
1637 } while (par != end);
1643 bool LyXText::setCursor(ParagraphList::iterator pit,
1645 bool setfont, bool boundary)
1647 LyXCursor old_cursor = cursor;
1648 setCursorIntern(pit, pos, setfont, boundary);
1649 return deleteEmptyParagraphMechanism(old_cursor);
1653 void LyXText::setCursor(LyXCursor & cur, ParagraphList::iterator pit,
1654 pos_type pos, bool boundary)
1656 lyx::Assert(pit != ownerParagraphs().end());
1660 cur.boundary(boundary);
1662 // get the cursor y position in text
1664 RowList::iterator row = getRow(&*pit, pos, y);
1665 RowList::iterator beg = rows().begin();
1667 RowList::iterator old_row = row;
1669 // if we are before the first char of this row and are still in the
1670 // same paragraph and there is a previous row then put the cursor on
1671 // the end of the previous row
1672 cur.iy(y + row->baseline());
1674 if (row != beg && pos &&
1675 boost::prior(row)->par() == row->par() &&
1676 pos < pit->size() &&
1677 pit->getChar(pos) == Paragraph::META_INSET &&
1678 (ins = pit->getInset(pos)) && (ins->needFullRow() || ins->display()))
1685 // y is now the beginning of the cursor row
1686 y += row->baseline();
1687 // y is now the cursor baseline
1690 pos_type last = lastPrintablePos(*this, old_row);
1692 // None of these should happen, but we're scaredy-cats
1693 if (pos > pit->size()) {
1694 lyxerr << "dont like 1 please report" << endl;
1697 } else if (pos > last + 1) {
1698 lyxerr << "dont like 2 please report" << endl;
1699 // This shouldn't happen.
1702 } else if (pos < row->pos()) {
1703 lyxerr << "dont like 3 please report" << endl;
1708 // now get the cursors x position
1709 float x = getCursorX(row, pos, last, boundary);
1712 if (old_row != row) {
1713 x = getCursorX(old_row, pos, last, boundary);
1717 /* We take out this for the time being because 1) the redraw code is not
1718 prepared to this yet and 2) because some good policy has yet to be decided
1719 while editting: for instance how to act on rows being created/deleted
1723 //if the cursor is in a visible row, anchor to it
1725 if (topy < y && y < topy + bv()->workHeight())
1731 float LyXText::getCursorX(RowList::iterator rit,
1732 pos_type pos, pos_type last, bool boundary) const
1734 pos_type cursor_vpos = 0;
1736 float fill_separator;
1738 float fill_label_hfill;
1739 // This call HAS to be here because of the BidiTables!!!
1740 prepareToPrint(rit, x, fill_separator, fill_hfill,
1743 if (last < rit->pos())
1744 cursor_vpos = rit->pos();
1745 else if (pos > last && !boundary)
1746 cursor_vpos = (rit->par()->isRightToLeftPar(bv()->buffer()->params))
1747 ? rit->pos() : last + 1;
1748 else if (pos > rit->pos() &&
1749 (pos > last || boundary))
1750 /// Place cursor after char at (logical) position pos - 1
1751 cursor_vpos = (bidi_level(pos - 1) % 2 == 0)
1752 ? log2vis(pos - 1) + 1 : log2vis(pos - 1);
1754 /// Place cursor before char at (logical) position pos
1755 cursor_vpos = (bidi_level(pos) % 2 == 0)
1756 ? log2vis(pos) : log2vis(pos) + 1;
1758 pos_type body_pos = rit->par()->beginningOfBody();
1759 if ((body_pos > 0) &&
1760 ((body_pos - 1 > last) ||
1761 !rit->par()->isLineSeparator(body_pos - 1)))
1764 for (pos_type vpos = rit->pos(); vpos < cursor_vpos; ++vpos) {
1765 pos_type pos = vis2log(vpos);
1766 if (body_pos > 0 && pos == body_pos - 1) {
1767 x += fill_label_hfill +
1768 font_metrics::width(
1769 rit->par()->layout()->labelsep,
1770 getLabelFont(bv()->buffer(),
1772 if (rit->par()->isLineSeparator(body_pos - 1))
1773 x -= singleWidth(rit->par(), body_pos - 1);
1776 if (hfillExpansion(*this, rit, pos)) {
1777 x += singleWidth(rit->par(), pos);
1778 if (pos >= body_pos)
1781 x += fill_label_hfill;
1782 } else if (rit->par()->isSeparator(pos)) {
1783 x += singleWidth(rit->par(), pos);
1784 if (pos >= body_pos)
1785 x += fill_separator;
1787 x += singleWidth(rit->par(), pos);
1793 void LyXText::setCursorIntern(ParagraphList::iterator pit,
1794 pos_type pos, bool setfont, bool boundary)
1796 InsetText * it = static_cast<InsetText *>(pit->inInset());
1798 if (it != inset_owner) {
1799 lyxerr[Debug::INSETS] << "InsetText is " << it
1801 << "inset_owner is "
1802 << inset_owner << endl;
1803 #ifdef WITH_WARNINGS
1804 #warning I believe this code is wrong. (Lgb)
1805 #warning Jürgen, have a look at this. (Lgb)
1806 #warning Hmmm, I guess you are right but we
1807 #warning should verify when this is needed
1809 // Jürgen, would you like to have a look?
1810 // I guess we need to move the outer cursor
1811 // and open and lock the inset (bla bla bla)
1812 // stuff I don't know... so can you have a look?
1814 // I moved the lyxerr stuff in here so we can see if
1815 // this is actually really needed and where!
1817 // it->getLyXText(bv())->setCursorIntern(bv(), par, pos, setfont, boundary);
1822 setCursor(cursor, pit, pos, boundary);
1828 void LyXText::setCurrentFont()
1830 pos_type pos = cursor.pos();
1831 if (cursor.boundary() && pos > 0)
1835 if (pos == cursor.par()->size())
1837 else // potentional bug... BUG (Lgb)
1838 if (cursor.par()->isSeparator(pos)) {
1839 if (pos > cursor.row()->pos() &&
1840 bidi_level(pos) % 2 ==
1841 bidi_level(pos - 1) % 2)
1843 else if (pos + 1 < cursor.par()->size())
1849 cursor.par()->getFontSettings(bv()->buffer()->params, pos);
1850 real_current_font = getFont(bv()->buffer(), &*cursor.par(), pos);
1852 if (cursor.pos() == cursor.par()->size() &&
1853 isBoundary(bv()->buffer(), &*cursor.par(), cursor.pos()) &&
1854 !cursor.boundary()) {
1855 Language const * lang =
1856 cursor.par()->getParLanguage(bv()->buffer()->params);
1857 current_font.setLanguage(lang);
1858 current_font.setNumber(LyXFont::OFF);
1859 real_current_font.setLanguage(lang);
1860 real_current_font.setNumber(LyXFont::OFF);
1865 // returns the column near the specified x-coordinate of the row
1866 // x is set to the real beginning of this column
1868 LyXText::getColumnNearX(RowList::iterator rit, int & x, bool & boundary) const
1871 float fill_separator;
1873 float fill_label_hfill;
1875 prepareToPrint(rit, tmpx, fill_separator,
1876 fill_hfill, fill_label_hfill);
1878 pos_type vc = rit->pos();
1879 pos_type last = lastPrintablePos(*this, rit);
1882 LyXLayout_ptr const & layout = rit->par()->layout();
1884 bool left_side = false;
1886 pos_type body_pos = rit->par()->beginningOfBody();
1887 float last_tmpx = tmpx;
1890 (body_pos - 1 > last ||
1891 !rit->par()->isLineSeparator(body_pos - 1)))
1894 // check for empty row
1895 if (!rit->par()->size()) {
1900 while (vc <= last && tmpx <= x) {
1903 if (body_pos > 0 && c == body_pos - 1) {
1904 tmpx += fill_label_hfill +
1905 font_metrics::width(layout->labelsep,
1906 getLabelFont(bv()->buffer(), &*rit->par()));
1907 if (rit->par()->isLineSeparator(body_pos - 1))
1908 tmpx -= singleWidth(rit->par(), body_pos - 1);
1911 if (hfillExpansion(*this, rit, c)) {
1912 tmpx += singleWidth(rit->par(), c);
1916 tmpx += fill_label_hfill;
1917 } else if (rit->par()->isSeparator(c)) {
1918 tmpx += singleWidth(rit->par(), c);
1920 tmpx+= fill_separator;
1922 tmpx += singleWidth(rit->par(), c);
1927 if ((tmpx + last_tmpx) / 2 > x) {
1932 if (vc > last + 1) // This shouldn't happen.
1936 // This (rtl_support test) is not needed, but gives
1937 // some speedup if rtl_support=false
1938 bool const lastrow = lyxrc.rtl_support &&
1939 (boost::next(rit) == rowlist_.end() ||
1940 boost::next(rit)->par() != rit->par());
1941 // If lastrow is false, we don't need to compute
1942 // the value of rtl.
1943 bool const rtl = (lastrow)
1944 ? rit->par()->isRightToLeftPar(bv()->buffer()->params)
1947 ((rtl && left_side && vc == rit->pos() && x < tmpx - 5) ||
1948 (!rtl && !left_side && vc == last + 1 && x > tmpx + 5)))
1950 else if (vc == rit->pos()) {
1952 if (bidi_level(c) % 2 == 1)
1955 c = vis2log(vc - 1);
1956 bool const rtl = (bidi_level(c) % 2 == 1);
1957 if (left_side == rtl) {
1959 boundary = isBoundary(bv()->buffer(), &*rit->par(), c);
1963 if (rit->pos() <= last && c > last
1964 && rit->par()->isNewline(last)) {
1965 if (bidi_level(last) % 2 == 0)
1966 tmpx -= singleWidth(rit->par(), last);
1968 tmpx += singleWidth(rit->par(), last);
1978 void LyXText::setCursorFromCoordinates(int x, int y)
1980 LyXCursor old_cursor = cursor;
1982 setCursorFromCoordinates(cursor, x, y);
1984 deleteEmptyParagraphMechanism(old_cursor);
1991 * return true if the cursor given is at the end of a row,
1992 * and the next row is filled by an inset that spans an entire
1995 bool beforeFullRowInset(LyXText & lt, RowList::iterator row,
1997 if (boost::next(row) == lt.rows().end())
1999 Row const & next = *boost::next(row);
2001 if (next.pos() != cur.pos() || next.par() != cur.par())
2003 if (!cur.par()->isInset(cur.pos()))
2005 Inset const * inset = cur.par()->getInset(cur.pos());
2006 if (inset->needFullRow() || inset->display())
2013 void LyXText::setCursorFromCoordinates(LyXCursor & cur, int x, int y)
2015 // Get the row first.
2017 RowList::iterator row = getRowNearY(y);
2019 pos_type const column = getColumnNearX(row, x, bound);
2020 cur.par(&*row->par());
2021 cur.pos(row->pos() + column);
2023 cur.y(y + row->baseline());
2026 if (beforeFullRowInset(*this, row, cur)) {
2027 pos_type last = lastPrintablePos(*this, row);
2028 float x = getCursorX(boost::next(row), cur.pos(), last, bound);
2030 cur.iy(y + row->height() + boost::next(row)->baseline());
2031 cur.irow(boost::next(row));
2037 cur.boundary(bound);
2041 void LyXText::cursorLeft(bool internal)
2043 if (cursor.pos() > 0) {
2044 bool boundary = cursor.boundary();
2045 setCursor(cursor.par(), cursor.pos() - 1, true, false);
2046 if (!internal && !boundary &&
2047 isBoundary(bv()->buffer(), &*cursor.par(), cursor.pos() + 1))
2048 setCursor(cursor.par(), cursor.pos() + 1, true, true);
2049 } else if (cursor.par()->previous()) { // steps into the above paragraph.
2050 Paragraph * par = cursor.par()->previous();
2051 setCursor(par, par->size());
2056 void LyXText::cursorRight(bool internal)
2058 bool const at_end = (cursor.pos() == cursor.par()->size());
2059 bool const at_newline = !at_end &&
2060 cursor.par()->isNewline(cursor.pos());
2062 if (!internal && cursor.boundary() && !at_newline)
2063 setCursor(cursor.par(), cursor.pos(), true, false);
2065 setCursor(cursor.par(), cursor.pos() + 1, true, false);
2067 isBoundary(bv()->buffer(), &*cursor.par(), cursor.pos()))
2068 setCursor(cursor.par(), cursor.pos(), true, true);
2069 } else if (cursor.par()->next())
2070 setCursor(cursor.par()->next(), 0);
2074 void LyXText::cursorUp(bool selecting)
2077 int x = cursor.x_fix();
2078 int y = cursor.y() - cursor.row()->baseline() - 1;
2079 setCursorFromCoordinates(x, y);
2082 int y1 = cursor.iy() - topy;
2085 Inset * inset_hit = checkInsetHit(x, y1);
2086 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2087 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2091 setCursorFromCoordinates(bv(), cursor.x_fix(),
2092 cursor.y() - cursor.row()->baseline() - 1);
2097 void LyXText::cursorDown(bool selecting)
2100 int x = cursor.x_fix();
2101 int y = cursor.y() - cursor.row()->baseline() +
2102 cursor.row()->height() + 1;
2103 setCursorFromCoordinates(x, y);
2104 if (!selecting && cursor.row() == cursor.irow()) {
2106 int y1 = cursor.iy() - topy;
2109 Inset * inset_hit = checkInsetHit(x, y1);
2110 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2111 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2115 setCursorFromCoordinates(bv(), cursor.x_fix(),
2116 cursor.y() - cursor.row()->baseline()
2117 + cursor.row()->height() + 1);
2122 void LyXText::cursorUpParagraph()
2124 if (cursor.pos() > 0) {
2125 setCursor(cursor.par(), 0);
2127 else if (cursor.par()->previous()) {
2128 setCursor(cursor.par()->previous(), 0);
2133 void LyXText::cursorDownParagraph()
2135 if (cursor.par()->next()) {
2136 setCursor(cursor.par()->next(), 0);
2138 setCursor(cursor.par(), cursor.par()->size());
2142 // fix the cursor `cur' after a characters has been deleted at `where'
2143 // position. Called by deleteEmptyParagraphMechanism
2144 void LyXText::fixCursorAfterDelete(LyXCursor & cur,
2145 LyXCursor const & where)
2147 // if cursor is not in the paragraph where the delete occured,
2149 if (cur.par() != where.par())
2152 // if cursor position is after the place where the delete occured,
2154 if (cur.pos() > where.pos())
2155 cur.pos(cur.pos()-1);
2157 // check also if we don't want to set the cursor on a spot behind the
2158 // pagragraph because we erased the last character.
2159 if (cur.pos() > cur.par()->size())
2160 cur.pos(cur.par()->size());
2162 // recompute row et al. for this cursor
2163 setCursor(cur, cur.par(), cur.pos(), cur.boundary());
2167 bool LyXText::deleteEmptyParagraphMechanism(LyXCursor const & old_cursor)
2169 // Would be wrong to delete anything if we have a selection.
2170 if (selection.set())
2173 // We allow all kinds of "mumbo-jumbo" when freespacing.
2174 if (old_cursor.par()->layout()->free_spacing
2175 || old_cursor.par()->isFreeSpacing()) {
2179 /* Ok I'll put some comments here about what is missing.
2180 I have fixed BackSpace (and thus Delete) to not delete
2181 double-spaces automagically. I have also changed Cut,
2182 Copy and Paste to hopefully do some sensible things.
2183 There are still some small problems that can lead to
2184 double spaces stored in the document file or space at
2185 the beginning of paragraphs. This happens if you have
2186 the cursor betwenn to spaces and then save. Or if you
2187 cut and paste and the selection have a space at the
2188 beginning and then save right after the paste. I am
2189 sure none of these are very hard to fix, but I will
2190 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
2191 that I can get some feedback. (Lgb)
2194 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
2195 // delete the LineSeparator.
2198 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
2199 // delete the LineSeparator.
2202 // If the pos around the old_cursor were spaces, delete one of them.
2203 if (old_cursor.par() != cursor.par()
2204 || old_cursor.pos() != cursor.pos()) {
2205 // Only if the cursor has really moved
2207 if (old_cursor.pos() > 0
2208 && old_cursor.pos() < old_cursor.par()->size()
2209 && old_cursor.par()->isLineSeparator(old_cursor.pos())
2210 && old_cursor.par()->isLineSeparator(old_cursor.pos() - 1)) {
2211 old_cursor.par()->erase(old_cursor.pos() - 1);
2212 redoParagraphs(old_cursor, old_cursor.par()->next());
2214 #ifdef WITH_WARNINGS
2215 #warning This will not work anymore when we have multiple views of the same buffer
2216 // In this case, we will have to correct also the cursors held by
2217 // other bufferviews. It will probably be easier to do that in a more
2218 // automated way in LyXCursor code. (JMarc 26/09/2001)
2220 // correct all cursors held by the LyXText
2221 fixCursorAfterDelete(cursor, old_cursor);
2222 fixCursorAfterDelete(selection.cursor,
2224 fixCursorAfterDelete(selection.start,
2226 fixCursorAfterDelete(selection.end, old_cursor);
2227 fixCursorAfterDelete(last_sel_cursor,
2229 fixCursorAfterDelete(toggle_cursor, old_cursor);
2230 fixCursorAfterDelete(toggle_end_cursor,
2236 // don't delete anything if this is the ONLY paragraph!
2237 if (!old_cursor.par()->next() && !old_cursor.par()->previous())
2240 // Do not delete empty paragraphs with keepempty set.
2241 if (old_cursor.par()->layout()->keepempty)
2244 // only do our magic if we changed paragraph
2245 if (old_cursor.par() == cursor.par())
2248 // record if we have deleted a paragraph
2249 // we can't possibly have deleted a paragraph before this point
2250 bool deleted = false;
2252 if ((old_cursor.par()->empty()
2253 || (old_cursor.par()->size() == 1
2254 && old_cursor.par()->isLineSeparator(0)))) {
2255 // ok, we will delete anything
2256 LyXCursor tmpcursor;
2260 if (old_cursor.row() != rows().begin()) {
2262 prevrow = boost::prior(old_cursor.row());
2263 const_cast<LyXText *>(this)->postPaint(old_cursor.y() - old_cursor.row()->baseline() - prevrow->height());
2265 cursor = old_cursor; // that undo can restore the right cursor position
2266 Paragraph * endpar = old_cursor.par()->next();
2267 if (endpar && endpar->getDepth()) {
2268 while (endpar && endpar->getDepth()) {
2269 endpar = endpar->next();
2272 setUndo(bv(), Undo::DELETE, &*old_cursor.par(), endpar);
2276 removeRow(old_cursor.row());
2277 if (ownerParagraphs().begin() == old_cursor.par()) {
2278 ownerParagraph(&*boost::next(ownerParagraphs().begin()));
2280 #warning FIXME Do the proper ParagraphList operation here (Lgb)
2282 delete &*old_cursor.par();
2284 /* Breakagain the next par. Needed because of
2285 * the parindent that can occur or dissappear.
2286 * The next row can change its height, if
2287 * there is another layout before */
2288 if (boost::next(prevrow) != rows().end()) {
2289 breakAgain(boost::next(prevrow));
2292 setHeightOfRow(prevrow);
2294 RowList::iterator nextrow = boost::next(old_cursor.row());
2295 const_cast<LyXText *>(this)->postPaint(
2296 old_cursor.y() - old_cursor.row()->baseline());
2299 cursor = old_cursor; // that undo can restore the right cursor position
2300 Paragraph * endpar = old_cursor.par()->next();
2301 if (endpar && endpar->getDepth()) {
2302 while (endpar && endpar->getDepth()) {
2303 endpar = endpar->next();
2306 setUndo(bv(), Undo::DELETE, &*old_cursor.par(), endpar);
2310 removeRow(old_cursor.row());
2312 if (ownerParagraphs().begin() == old_cursor.par()) {
2313 ownerParagraph(&*boost::next(ownerParagraphs().begin()));
2315 #warning FIXME Do the proper ParagraphList operations here. (Lgb)
2316 delete &*old_cursor.par();
2318 /* Breakagain the next par. Needed because of
2319 the parindent that can occur or dissappear.
2320 The next row can change its height, if
2321 there is another layout before */
2322 if (nextrow != rows().end()) {
2323 breakAgain(nextrow);
2329 setCursorIntern(cursor.par(), cursor.pos());
2331 if (selection.cursor.par() == old_cursor.par()
2332 && selection.cursor.pos() == old_cursor.pos()) {
2333 // correct selection
2334 selection.cursor = cursor;
2338 if (old_cursor.par()->stripLeadingSpaces()) {
2339 redoParagraphs(old_cursor,
2340 old_cursor.par()->next());
2342 setCursorIntern(cursor.par(), cursor.pos());
2343 selection.cursor = cursor;
2350 ParagraphList & LyXText::ownerParagraphs() const
2353 return inset_owner->paragraphs;
2355 return bv_owner->buffer()->paragraphs;
2359 void LyXText::ownerParagraph(Paragraph * p) const
2362 inset_owner->paragraph(p);
2364 bv_owner->buffer()->paragraphs.set(p);
2369 void LyXText::ownerParagraph(int id, Paragraph * p) const
2371 Paragraph * op = bv_owner->buffer()->getParFromID(id);
2372 if (op && op->inInset()) {
2373 static_cast<InsetText *>(op->inInset())->paragraph(p);
2380 LyXText::refresh_status LyXText::refreshStatus() const
2382 return refresh_status_;
2386 void LyXText::clearPaint()
2388 refresh_status_ = REFRESH_NONE;
2389 refresh_row = rows().end();
2394 void LyXText::postPaint(int start_y)
2396 refresh_status old = refresh_status_;
2398 refresh_status_ = REFRESH_AREA;
2399 refresh_row = rows().end();
2401 if (old != REFRESH_NONE && refresh_y < start_y)
2404 refresh_y = start_y;
2409 // We are an inset's lyxtext. Tell the top-level lyxtext
2410 // it needs to update the row we're in.
2411 LyXText * t = bv()->text;
2412 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2416 // FIXME: we should probably remove this y parameter,
2417 // make refresh_y be 0, and use row->y etc.
2418 void LyXText::postRowPaint(RowList::iterator rit, int start_y)
2420 if (refresh_status_ != REFRESH_NONE && refresh_y < start_y) {
2421 refresh_status_ = REFRESH_AREA;
2424 refresh_y = start_y;
2427 if (refresh_status_ == REFRESH_AREA)
2430 refresh_status_ = REFRESH_ROW;
2436 // We are an inset's lyxtext. Tell the top-level lyxtext
2437 // it needs to update the row we're in.
2438 LyXText * t = bv()->text;
2439 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2443 bool LyXText::isInInset() const
2445 // Sub-level has non-null bv owner and
2446 // non-null inset owner.
2447 return inset_owner != 0 && bv_owner != 0;
2451 int defaultRowHeight()
2453 LyXFont const font(LyXFont::ALL_SANE);
2454 return int(font_metrics::maxAscent(font)
2455 + font_metrics::maxDescent(font) * 1.5);