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 if (!internal && cursor.boundary() &&
2059 !cursor.par()->isNewline(cursor.pos()))
2060 setCursor(cursor.par(), cursor.pos(), true, false);
2061 else if (cursor.pos() < cursor.par()->size()) {
2062 setCursor(cursor.par(), cursor.pos() + 1, true, false);
2064 isBoundary(bv()->buffer(), &*cursor.par(), cursor.pos()))
2065 setCursor(cursor.par(), cursor.pos(), true, true);
2066 } else if (cursor.par()->next())
2067 setCursor(cursor.par()->next(), 0);
2071 void LyXText::cursorUp(bool selecting)
2074 int x = cursor.x_fix();
2075 int y = cursor.y() - cursor.row()->baseline() - 1;
2076 setCursorFromCoordinates(x, y);
2079 int y1 = cursor.iy() - topy;
2082 Inset * inset_hit = checkInsetHit(x, y1);
2083 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2084 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2088 setCursorFromCoordinates(bv(), cursor.x_fix(),
2089 cursor.y() - cursor.row()->baseline() - 1);
2094 void LyXText::cursorDown(bool selecting)
2097 int x = cursor.x_fix();
2098 int y = cursor.y() - cursor.row()->baseline() +
2099 cursor.row()->height() + 1;
2100 setCursorFromCoordinates(x, y);
2101 if (!selecting && cursor.row() == cursor.irow()) {
2103 int y1 = cursor.iy() - topy;
2106 Inset * inset_hit = checkInsetHit(x, y1);
2107 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2108 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2112 setCursorFromCoordinates(bv(), cursor.x_fix(),
2113 cursor.y() - cursor.row()->baseline()
2114 + cursor.row()->height() + 1);
2119 void LyXText::cursorUpParagraph()
2121 if (cursor.pos() > 0) {
2122 setCursor(cursor.par(), 0);
2124 else if (cursor.par()->previous()) {
2125 setCursor(cursor.par()->previous(), 0);
2130 void LyXText::cursorDownParagraph()
2132 if (cursor.par()->next()) {
2133 setCursor(cursor.par()->next(), 0);
2135 setCursor(cursor.par(), cursor.par()->size());
2139 // fix the cursor `cur' after a characters has been deleted at `where'
2140 // position. Called by deleteEmptyParagraphMechanism
2141 void LyXText::fixCursorAfterDelete(LyXCursor & cur,
2142 LyXCursor const & where)
2144 // if cursor is not in the paragraph where the delete occured,
2146 if (cur.par() != where.par())
2149 // if cursor position is after the place where the delete occured,
2151 if (cur.pos() > where.pos())
2152 cur.pos(cur.pos()-1);
2154 // check also if we don't want to set the cursor on a spot behind the
2155 // pagragraph because we erased the last character.
2156 if (cur.pos() > cur.par()->size())
2157 cur.pos(cur.par()->size());
2159 // recompute row et al. for this cursor
2160 setCursor(cur, cur.par(), cur.pos(), cur.boundary());
2164 bool LyXText::deleteEmptyParagraphMechanism(LyXCursor const & old_cursor)
2166 // Would be wrong to delete anything if we have a selection.
2167 if (selection.set())
2170 // We allow all kinds of "mumbo-jumbo" when freespacing.
2171 if (old_cursor.par()->layout()->free_spacing
2172 || old_cursor.par()->isFreeSpacing()) {
2176 /* Ok I'll put some comments here about what is missing.
2177 I have fixed BackSpace (and thus Delete) to not delete
2178 double-spaces automagically. I have also changed Cut,
2179 Copy and Paste to hopefully do some sensible things.
2180 There are still some small problems that can lead to
2181 double spaces stored in the document file or space at
2182 the beginning of paragraphs. This happens if you have
2183 the cursor betwenn to spaces and then save. Or if you
2184 cut and paste and the selection have a space at the
2185 beginning and then save right after the paste. I am
2186 sure none of these are very hard to fix, but I will
2187 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
2188 that I can get some feedback. (Lgb)
2191 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
2192 // delete the LineSeparator.
2195 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
2196 // delete the LineSeparator.
2199 // If the pos around the old_cursor were spaces, delete one of them.
2200 if (old_cursor.par() != cursor.par()
2201 || old_cursor.pos() != cursor.pos()) {
2202 // Only if the cursor has really moved
2204 if (old_cursor.pos() > 0
2205 && old_cursor.pos() < old_cursor.par()->size()
2206 && old_cursor.par()->isLineSeparator(old_cursor.pos())
2207 && old_cursor.par()->isLineSeparator(old_cursor.pos() - 1)) {
2208 old_cursor.par()->erase(old_cursor.pos() - 1);
2209 redoParagraphs(old_cursor, old_cursor.par()->next());
2211 #ifdef WITH_WARNINGS
2212 #warning This will not work anymore when we have multiple views of the same buffer
2213 // In this case, we will have to correct also the cursors held by
2214 // other bufferviews. It will probably be easier to do that in a more
2215 // automated way in LyXCursor code. (JMarc 26/09/2001)
2217 // correct all cursors held by the LyXText
2218 fixCursorAfterDelete(cursor, old_cursor);
2219 fixCursorAfterDelete(selection.cursor,
2221 fixCursorAfterDelete(selection.start,
2223 fixCursorAfterDelete(selection.end, old_cursor);
2224 fixCursorAfterDelete(last_sel_cursor,
2226 fixCursorAfterDelete(toggle_cursor, old_cursor);
2227 fixCursorAfterDelete(toggle_end_cursor,
2233 // don't delete anything if this is the ONLY paragraph!
2234 if (!old_cursor.par()->next() && !old_cursor.par()->previous())
2237 // Do not delete empty paragraphs with keepempty set.
2238 if (old_cursor.par()->layout()->keepempty)
2241 // only do our magic if we changed paragraph
2242 if (old_cursor.par() == cursor.par())
2245 // record if we have deleted a paragraph
2246 // we can't possibly have deleted a paragraph before this point
2247 bool deleted = false;
2249 if ((old_cursor.par()->empty()
2250 || (old_cursor.par()->size() == 1
2251 && old_cursor.par()->isLineSeparator(0)))) {
2252 // ok, we will delete anything
2253 LyXCursor tmpcursor;
2257 if (old_cursor.row() != rows().begin()) {
2259 prevrow = boost::prior(old_cursor.row());
2260 const_cast<LyXText *>(this)->postPaint(old_cursor.y() - old_cursor.row()->baseline() - prevrow->height());
2262 cursor = old_cursor; // that undo can restore the right cursor position
2263 Paragraph * endpar = old_cursor.par()->next();
2264 if (endpar && endpar->getDepth()) {
2265 while (endpar && endpar->getDepth()) {
2266 endpar = endpar->next();
2269 setUndo(bv(), Undo::DELETE, &*old_cursor.par(), endpar);
2273 removeRow(old_cursor.row());
2274 if (ownerParagraphs().begin() == old_cursor.par()) {
2275 ownerParagraph(&*boost::next(ownerParagraphs().begin()));
2277 #warning FIXME Do the proper ParagraphList operation here (Lgb)
2279 delete &*old_cursor.par();
2281 /* Breakagain the next par. Needed because of
2282 * the parindent that can occur or dissappear.
2283 * The next row can change its height, if
2284 * there is another layout before */
2285 if (boost::next(prevrow) != rows().end()) {
2286 breakAgain(boost::next(prevrow));
2289 setHeightOfRow(prevrow);
2291 RowList::iterator nextrow = boost::next(old_cursor.row());
2292 const_cast<LyXText *>(this)->postPaint(
2293 old_cursor.y() - old_cursor.row()->baseline());
2296 cursor = old_cursor; // that undo can restore the right cursor position
2297 Paragraph * endpar = old_cursor.par()->next();
2298 if (endpar && endpar->getDepth()) {
2299 while (endpar && endpar->getDepth()) {
2300 endpar = endpar->next();
2303 setUndo(bv(), Undo::DELETE, &*old_cursor.par(), endpar);
2307 removeRow(old_cursor.row());
2309 if (ownerParagraphs().begin() == old_cursor.par()) {
2310 ownerParagraph(&*boost::next(ownerParagraphs().begin()));
2312 #warning FIXME Do the proper ParagraphList operations here. (Lgb)
2313 delete &*old_cursor.par();
2315 /* Breakagain the next par. Needed because of
2316 the parindent that can occur or dissappear.
2317 The next row can change its height, if
2318 there is another layout before */
2319 if (nextrow != rows().end()) {
2320 breakAgain(nextrow);
2326 setCursorIntern(cursor.par(), cursor.pos());
2328 if (selection.cursor.par() == old_cursor.par()
2329 && selection.cursor.pos() == old_cursor.pos()) {
2330 // correct selection
2331 selection.cursor = cursor;
2335 if (old_cursor.par()->stripLeadingSpaces()) {
2336 redoParagraphs(old_cursor,
2337 old_cursor.par()->next());
2339 setCursorIntern(cursor.par(), cursor.pos());
2340 selection.cursor = cursor;
2347 ParagraphList & LyXText::ownerParagraphs() const
2350 return inset_owner->paragraphs;
2352 return bv_owner->buffer()->paragraphs;
2356 void LyXText::ownerParagraph(Paragraph * p) const
2359 inset_owner->paragraph(p);
2361 bv_owner->buffer()->paragraphs.set(p);
2366 void LyXText::ownerParagraph(int id, Paragraph * p) const
2368 Paragraph * op = bv_owner->buffer()->getParFromID(id);
2369 if (op && op->inInset()) {
2370 static_cast<InsetText *>(op->inInset())->paragraph(p);
2377 LyXText::refresh_status LyXText::refreshStatus() const
2379 return refresh_status_;
2383 void LyXText::clearPaint()
2385 refresh_status_ = REFRESH_NONE;
2386 refresh_row = rows().end();
2391 void LyXText::postPaint(int start_y)
2393 refresh_status old = refresh_status_;
2395 refresh_status_ = REFRESH_AREA;
2396 refresh_row = rows().end();
2398 if (old != REFRESH_NONE && refresh_y < start_y)
2401 refresh_y = start_y;
2406 // We are an inset's lyxtext. Tell the top-level lyxtext
2407 // it needs to update the row we're in.
2408 LyXText * t = bv()->text;
2409 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2413 // FIXME: we should probably remove this y parameter,
2414 // make refresh_y be 0, and use row->y etc.
2415 void LyXText::postRowPaint(RowList::iterator rit, int start_y)
2417 if (refresh_status_ != REFRESH_NONE && refresh_y < start_y) {
2418 refresh_status_ = REFRESH_AREA;
2421 refresh_y = start_y;
2424 if (refresh_status_ == REFRESH_AREA)
2427 refresh_status_ = REFRESH_ROW;
2433 // We are an inset's lyxtext. Tell the top-level lyxtext
2434 // it needs to update the row we're in.
2435 LyXText * t = bv()->text;
2436 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2440 bool LyXText::isInInset() const
2442 // Sub-level has non-null bv owner and
2443 // non-null inset owner.
2444 return inset_owner != 0 && bv_owner != 0;
2448 int defaultRowHeight()
2450 LyXFont const font(LyXFont::ALL_SANE);
2451 return int(font_metrics::maxAscent(font)
2452 + font_metrics::maxDescent(font) * 1.5);