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 pit = ownerParagraphs().begin();
88 ParagraphList::iterator end = ownerParagraphs().end();
90 current_font = getFont(bview->buffer(), pit, 0);
92 for (; pit != end; ++pit) {
93 insertParagraph(&*pit, rowlist_.end());
95 setCursorIntern(rowlist_.begin()->par(), 0);
96 selection.cursor = cursor;
104 LyXFont const realizeFont(LyXFont const & font,
106 ParagraphList & /*plist*/,
107 ParagraphList::iterator pit)
109 LyXTextClass const & tclass = buf->params.getLyXTextClass();
110 LyXFont tmpfont(font);
111 Paragraph::depth_type par_depth = pit->getDepth();
113 Paragraph * par = &*pit;
115 // Resolve against environment font information
116 while (par && par_depth && !tmpfont.resolved()) {
117 par = par->outerHook();
119 tmpfont.realize(par->layout()->font);
120 par_depth = par->getDepth();
124 tmpfont.realize(tclass.defaultfont());
132 // Gets the fully instantiated font at a given position in a paragraph
133 // Basically the same routine as Paragraph::getFont() in paragraph.C.
134 // The difference is that this one is used for displaying, and thus we
135 // are allowed to make cosmetic improvements. For instance make footnotes
137 // If position is -1, we get the layout font of the paragraph.
138 // If position is -2, we get the font of the manual label of the paragraph.
139 LyXFont const LyXText::getFont(Buffer const * buf, ParagraphList::iterator pit,
142 lyx::Assert(pos >= 0);
144 LyXLayout_ptr const & layout = pit->layout();
146 // We specialize the 95% common case:
147 if (!pit->getDepth()) {
148 if (layout->labeltype == LABEL_MANUAL
149 && pos < pit->beginningOfBody()) {
151 LyXFont f = pit->getFontSettings(buf->params, pos);
153 pit->inInset()->getDrawFont(f);
154 return f.realize(layout->reslabelfont);
156 LyXFont f = pit->getFontSettings(buf->params, pos);
158 pit->inInset()->getDrawFont(f);
159 return f.realize(layout->resfont);
163 // The uncommon case need not be optimized as much
167 if (pos < pit->beginningOfBody()) {
169 layoutfont = layout->labelfont;
172 layoutfont = layout->font;
175 LyXFont tmpfont = pit->getFontSettings(buf->params, pos);
176 tmpfont.realize(layoutfont);
179 pit->inInset()->getDrawFont(tmpfont);
181 return realizeFont(tmpfont, buf, ownerParagraphs(), pit);
185 LyXFont const LyXText::getLayoutFont(Buffer const * buf,
186 ParagraphList::iterator pit) const
188 LyXLayout_ptr const & layout = pit->layout();
190 if (!pit->getDepth()) {
191 return layout->resfont;
194 return realizeFont(layout->font, buf, ownerParagraphs(), pit);
198 LyXFont const LyXText::getLabelFont(Buffer const * buf,
199 ParagraphList::iterator pit) const
201 LyXLayout_ptr const & layout = pit->layout();
203 if (!pit->getDepth()) {
204 return layout->reslabelfont;
207 return realizeFont(layout->labelfont, buf, ownerParagraphs(), pit);
211 void LyXText::setCharFont(ParagraphList::iterator pit,
212 pos_type pos, LyXFont const & fnt,
215 Buffer const * buf = bv()->buffer();
216 LyXFont font = getFont(buf, pit, pos);
217 font.update(fnt, buf->params.language, toggleall);
218 // Let the insets convert their font
219 if (pit->isInset(pos)) {
220 Inset * inset = pit->getInset(pos);
221 if (isEditableInset(inset)) {
222 UpdatableInset * uinset =
223 static_cast<UpdatableInset *>(inset);
224 uinset->setFont(bv(), fnt, toggleall, true);
228 // Plug thru to version below:
229 setCharFont(buf, pit, pos, font);
233 void LyXText::setCharFont(Buffer const * buf, ParagraphList::iterator pit,
234 pos_type pos, LyXFont const & fnt)
238 LyXTextClass const & tclass = buf->params.getLyXTextClass();
239 LyXLayout_ptr const & layout = pit->layout();
241 // Get concrete layout font to reduce against
244 if (pos < pit->beginningOfBody())
245 layoutfont = layout->labelfont;
247 layoutfont = layout->font;
249 // Realize against environment font information
250 if (pit->getDepth()) {
251 #warning FIXME I think I hate this outerHood stuff.
252 Paragraph * tp = &*pit;
253 while (!layoutfont.resolved() && tp && tp->getDepth()) {
254 tp = tp->outerHook();
256 layoutfont.realize(tp->layout()->font);
260 layoutfont.realize(tclass.defaultfont());
262 // Now, reduce font against full layout font
263 font.reduce(layoutfont);
265 pit->setFont(pos, font);
269 // removes the row and reset the touched counters
270 void LyXText::removeRow(RowList::iterator rit)
272 /* FIXME: when we cache the bview, this should just
273 * become a postPaint(), I think */
274 if (refresh_row == rit) {
275 if (rit == rows().begin())
276 refresh_row = boost::next(rit);
278 refresh_row = boost::prior(rit);
280 // what about refresh_y
283 if (anchor_row_ == rit) {
284 if (rit != rows().begin()) {
285 anchor_row_ = boost::prior(rit);
286 anchor_row_offset_ += boost::prior(rit)->height();
288 anchor_row_ = boost::next(rit);
289 anchor_row_offset_ -= rit->height();
293 // the text becomes smaller
294 height -= rit->height();
300 // remove all following rows of the paragraph of the specified row.
301 void LyXText::removeParagraph(RowList::iterator rit)
303 ParagraphList::iterator tmppit = rit->par();
306 while (rit != rows().end() && rit->par() == tmppit) {
307 RowList::iterator tmprit = boost::next(rit);
314 #warning FIXME Convert this to ParagraphList::iterator
315 void LyXText::insertParagraph(Paragraph * par, RowList::iterator rowit)
317 // insert a new row, starting at position 0
319 RowList::iterator rit = rowlist_.insert(rowit, newrow);
321 // and now append the whole paragraph before the new row
322 appendParagraph(rit);
326 Inset * LyXText::getInset() const
328 if (cursor.pos() < cursor.par()->size()
329 && cursor.par()->isInset(cursor.pos())) {
330 return cursor.par()->getInset(cursor.pos());
336 void LyXText::toggleInset()
338 Inset * inset = getInset();
339 // is there an editable inset at cursor position?
340 if (!isEditableInset(inset)) {
341 // No, try to see if we are inside a collapsable inset
342 if (inset_owner && inset_owner->owner()
343 && inset_owner->owner()->isOpen()) {
344 bv()->unlockInset(static_cast<UpdatableInset *>(inset_owner->owner()));
345 inset_owner->owner()->close(bv());
346 bv()->getLyXText()->cursorRight(bv());
350 //bv()->owner()->message(inset->editMessage());
352 // do we want to keep this?? (JMarc)
353 if (!isHighlyEditableInset(inset))
354 setCursorParUndo(bv());
356 if (inset->isOpen()) {
362 bv()->updateInset(inset);
366 /* used in setlayout */
367 // Asger is not sure we want to do this...
368 void LyXText::makeFontEntriesLayoutSpecific(Buffer const & buf,
371 LyXLayout_ptr const & layout = par.layout();
374 for (pos_type pos = 0; pos < par.size(); ++pos) {
375 if (pos < par.beginningOfBody())
376 layoutfont = layout->labelfont;
378 layoutfont = layout->font;
380 LyXFont tmpfont = par.getFontSettings(buf.params, pos);
381 tmpfont.reduce(layoutfont);
382 par.setFont(pos, tmpfont);
387 Paragraph * LyXText::setLayout(LyXCursor & cur, LyXCursor & sstart_cur,
388 LyXCursor & send_cur,
389 string const & layout)
391 Paragraph * endpar = send_cur.par()->next();
392 Paragraph * undoendpar = endpar;
394 if (endpar && endpar->getDepth()) {
395 while (endpar && endpar->getDepth()) {
396 endpar = endpar->next();
400 endpar = endpar->next(); // because of parindents etc.
403 setUndo(bv(), Undo::EDIT, &*sstart_cur.par(), undoendpar);
405 // ok we have a selection. This is always between sstart_cur
406 // and sel_end cursor
408 ParagraphList::iterator pit = sstart_cur.par();
409 ParagraphList::iterator epit = boost::next(send_cur.par());
411 LyXLayout_ptr const & lyxlayout =
412 bv()->buffer()->params.getLyXTextClass()[layout];
415 pit->applyLayout(lyxlayout);
416 makeFontEntriesLayoutSpecific(*bv()->buffer(), *pit);
417 ParagraphList::iterator fppit = pit;
418 fppit->params().spaceTop(lyxlayout->fill_top ?
419 VSpace(VSpace::VFILL)
420 : VSpace(VSpace::NONE));
421 fppit->params().spaceBottom(lyxlayout->fill_bottom ?
422 VSpace(VSpace::VFILL)
423 : VSpace(VSpace::NONE));
424 if (lyxlayout->margintype == MARGIN_MANUAL)
425 pit->setLabelWidthString(lyxlayout->labelstring());
428 } while (pit != epit);
434 // set layout over selection and make a total rebreak of those paragraphs
435 void LyXText::setLayout(string const & layout)
437 LyXCursor tmpcursor = cursor; /* store the current cursor */
439 // if there is no selection just set the layout
440 // of the current paragraph */
441 if (!selection.set()) {
442 selection.start = cursor; // dummy selection
443 selection.end = cursor;
445 Paragraph * endpar = setLayout(cursor, selection.start,
446 selection.end, layout);
447 redoParagraphs(selection.start, endpar);
449 // we have to reset the selection, because the
450 // geometry could have changed
451 setCursor(selection.start.par(),
452 selection.start.pos(), false);
453 selection.cursor = cursor;
454 setCursor(selection.end.par(), selection.end.pos(), false);
458 setCursor(tmpcursor.par(), tmpcursor.pos(), true);
462 bool LyXText::changeDepth(bv_funcs::DEPTH_CHANGE type, bool test_only)
464 ParagraphList::iterator pit(cursor.par());
465 ParagraphList::iterator end(cursor.par());
466 ParagraphList::iterator start = pit;
468 if (selection.set()) {
469 pit = selection.start.par();
470 end = selection.end.par();
474 ParagraphList::iterator pastend = boost::next(end);
477 setUndo(bv(), Undo::EDIT, &(*start), &(*pastend));
479 bool changed = false;
481 int prev_after_depth = 0;
482 #warning parlist ... could be nicer ?
483 if (start != ownerParagraphs().begin()) {
484 prev_after_depth = boost::prior(start)->getMaxDepthAfter();
488 int const depth = pit->params().depth();
489 if (type == bv_funcs::INC_DEPTH) {
490 if (depth < prev_after_depth
491 && pit->layout()->labeltype != LABEL_BIBLIO) {
494 pit->params().depth(depth + 1);
501 pit->params().depth(depth - 1);
504 prev_after_depth = pit->getMaxDepthAfter();
516 // Wow, redoParagraphs is stupid.
518 setCursor(tmpcursor, &(*start), 0);
520 //redoParagraphs(tmpcursor, &(*pastend));
521 redoParagraphs(tmpcursor, &(*pastend));
523 // We need to actually move the text->cursor. I don't
524 // understand why ...
527 // we have to reset the visual selection because the
528 // geometry could have changed
529 if (selection.set()) {
530 setCursor(selection.start.par(), selection.start.pos());
531 selection.cursor = cursor;
532 setCursor(selection.end.par(), selection.end.pos());
535 // this handles the counter labels, and also fixes up
536 // depth values for follow-on (child) paragraphs
540 setCursor(tmpcursor.par(), tmpcursor.pos());
546 // set font over selection and make a total rebreak of those paragraphs
547 void LyXText::setFont(LyXFont const & font, bool toggleall)
549 // if there is no selection just set the current_font
550 if (!selection.set()) {
551 // Determine basis font
553 if (cursor.pos() < cursor.par()->beginningOfBody()) {
554 layoutfont = getLabelFont(bv()->buffer(),
557 layoutfont = getLayoutFont(bv()->buffer(),
560 // Update current font
561 real_current_font.update(font,
562 bv()->buffer()->params.language,
565 // Reduce to implicit settings
566 current_font = real_current_font;
567 current_font.reduce(layoutfont);
568 // And resolve it completely
569 real_current_font.realize(layoutfont);
574 LyXCursor tmpcursor = cursor; // store the current cursor
576 // ok we have a selection. This is always between sel_start_cursor
577 // and sel_end cursor
579 setUndo(bv(), Undo::EDIT,
580 &*selection.start.par(), &*boost::next(selection.end.par()));
582 cursor = selection.start;
583 while (cursor.par() != selection.end.par() ||
584 cursor.pos() < selection.end.pos())
586 if (cursor.pos() < cursor.par()->size()) {
587 // an open footnote should behave like a closed one
588 setCharFont(&*cursor.par(), cursor.pos(),
590 cursor.pos(cursor.pos() + 1);
593 cursor.par(cursor.par()->next());
598 redoParagraphs(selection.start, selection.end.par()->next());
600 // we have to reset the selection, because the
601 // geometry could have changed, but we keep
602 // it for user convenience
603 setCursor(selection.start.par(), selection.start.pos());
604 selection.cursor = cursor;
605 setCursor(selection.end.par(), selection.end.pos());
607 setCursor(tmpcursor.par(), tmpcursor.pos(), true,
608 tmpcursor.boundary());
612 void LyXText::redoHeightOfParagraph()
614 RowList::iterator tmprow = cursor.row();
615 int y = cursor.y() - tmprow->baseline();
617 setHeightOfRow(tmprow);
619 while (tmprow != rows().begin()
620 && boost::prior(tmprow)->par() == tmprow->par()) {
622 y -= tmprow->height();
623 setHeightOfRow(tmprow);
628 setCursor(cursor.par(), cursor.pos(), false, cursor.boundary());
632 void LyXText::redoDrawingOfParagraph(LyXCursor const & cur)
634 RowList::iterator tmprow = cur.row();
636 int y = cur.y() - tmprow->baseline();
637 setHeightOfRow(tmprow);
639 while (tmprow != rows().begin()
640 && boost::prior(tmprow)->par() == tmprow->par()) {
642 y -= tmprow->height();
646 setCursor(cur.par(), cur.pos());
650 // deletes and inserts again all paragaphs between the cursor
651 // and the specified par
652 // This function is needed after SetLayout and SetFont etc.
653 void LyXText::redoParagraphs(LyXCursor const & cur,
654 Paragraph const * ep)
656 RowList::iterator tmprit = cur.row();
657 ParagraphList::iterator endpit(const_cast<Paragraph*>(ep));
658 int y = cur.y() - tmprit->baseline();
660 ParagraphList::iterator first_phys_pit;
661 if (tmprit == rows().begin()) {
662 // A trick/hack for UNDO.
663 // This is needed because in an UNDO/REDO we could have
664 // changed the ownerParagrah() so the paragraph inside
665 // the row is NOT my really first par anymore.
666 // Got it Lars ;) (Jug 20011206)
667 first_phys_pit = ownerParagraphs().begin();
669 // In here prevrit could be set to rows().end(). (Lgb)
671 first_phys_pit = tmprit->par();
672 while (tmprit != rows().begin()
673 && boost::prior(tmprit)->par() == first_phys_pit)
676 y -= tmprit->height();
679 // Is it possible to put the prevrit setting in here? (Lgb)
682 RowList::iterator prevrit;
683 bool good_prevrit = false;
685 // It seems to mee that good_prevrit is not needed if we let
686 // a bad prevrit have the value rows().end() (Lgb)
687 if (tmprit != rows().begin()) {
688 prevrit = boost::prior(tmprit);
693 while (tmprit != rows().end() && tmprit->par() != endpit) {
694 RowList::iterator tmprit2 = tmprit++;
698 // Reinsert the paragraphs.
699 ParagraphList::iterator tmppit = first_phys_pit;
701 // See if this loop can be rewritten as a while loop instead.
702 // That should also make the code a bit easier to read. (Lgb)
704 if (tmppit != ownerParagraphs().end()) {
705 insertParagraph(&*tmppit, tmprit);
706 while (tmprit != rows().end()
707 && tmprit->par() == tmppit) {
712 } while (tmppit != ownerParagraphs().end() && tmppit != endpit);
715 // If the above changes are done, then we can compare prevrit
716 // with rows().end() here. (Lgb)
718 setHeightOfRow(prevrit);
719 const_cast<LyXText *>(this)->postPaint(y - prevrit->height());
721 setHeightOfRow(rows().begin());
722 const_cast<LyXText *>(this)->postPaint(0);
724 if (tmprit != rows().end())
725 setHeightOfRow(tmprit);
731 void LyXText::fullRebreak()
733 if (rows().empty()) {
737 if (need_break_row != rows().end()) {
738 breakAgain(need_break_row);
739 need_break_row = rows().end();
745 // important for the screen
748 // the cursor set functions have a special mechanism. When they
749 // realize, that you left an empty paragraph, they will delete it.
750 // They also delete the corresponding row
752 // need the selection cursor:
753 void LyXText::setSelection()
755 bool const lsel = selection.set();
757 if (!selection.set()) {
758 last_sel_cursor = selection.cursor;
759 selection.start = selection.cursor;
760 selection.end = selection.cursor;
765 // first the toggling area
766 if (cursor.y() < last_sel_cursor.y()
767 || (cursor.y() == last_sel_cursor.y()
768 && cursor.x() < last_sel_cursor.x())) {
769 toggle_end_cursor = last_sel_cursor;
770 toggle_cursor = cursor;
772 toggle_end_cursor = cursor;
773 toggle_cursor = last_sel_cursor;
776 last_sel_cursor = cursor;
778 // and now the whole selection
780 if (selection.cursor.par() == cursor.par())
781 if (selection.cursor.pos() < cursor.pos()) {
782 selection.end = cursor;
783 selection.start = selection.cursor;
785 selection.end = selection.cursor;
786 selection.start = cursor;
788 else if (selection.cursor.y() < cursor.y() ||
789 (selection.cursor.y() == cursor.y()
790 && selection.cursor.x() < cursor.x())) {
791 selection.end = cursor;
792 selection.start = selection.cursor;
795 selection.end = selection.cursor;
796 selection.start = cursor;
799 // a selection with no contents is not a selection
800 if (selection.start.par() == selection.end.par() &&
801 selection.start.pos() == selection.end.pos())
802 selection.set(false);
804 if (inset_owner && (selection.set() || lsel))
805 inset_owner->setUpdateStatus(bv(), InsetText::SELECTION);
809 string const LyXText::selectionAsString(Buffer const * buffer,
812 if (!selection.set()) return string();
814 // should be const ...
815 ParagraphList::iterator startpit = selection.start.par();
816 ParagraphList::iterator endpit = selection.end.par();
817 pos_type const startpos(selection.start.pos());
818 pos_type const endpos(selection.end.pos());
820 if (startpit == endpit) {
821 return startpit->asString(buffer, startpos, endpos, label);
826 // First paragraph in selection
827 result += startpit->asString(buffer, startpos, startpit->size(), label) + "\n\n";
829 // The paragraphs in between (if any)
830 #warning FIXME Why isnt ParagraphList::iterator used here?
832 LyXCursor tmpcur(selection.start);
833 tmpcur.par(tmpcur.par()->next());
834 while (tmpcur.par() != endpit) {
835 result += tmpcur.par()->asString(buffer, 0,
836 tmpcur.par()->size(),
838 tmpcur.par(boost::next(tmpcur.par()));
841 // Last paragraph in selection
842 result += endpit->asString(buffer, 0, endpos, label);
848 void LyXText::clearSelection()
850 selection.set(false);
851 selection.mark(false);
852 last_sel_cursor = selection.end = selection.start = selection.cursor = cursor;
853 // reset this in the bv_owner!
854 if (bv_owner && bv_owner->text)
855 bv_owner->text->xsel_cache.set(false);
859 void LyXText::cursorHome()
861 setCursor(cursor.par(), cursor.row()->pos());
865 void LyXText::cursorEnd()
867 if (cursor.par()->empty())
870 // There is a lot of unneeded recalculation going on here:
871 // - boost::next(curosr.row())
872 // - lastPost(*this, cursor.row())
874 if (boost::next(cursor.row()) == rows().end()
875 || boost::next(cursor.row())->par() != cursor.row()->par()) {
876 setCursor(cursor.par(), lastPos(*this, cursor.row()) + 1);
878 if (!cursor.par()->empty() &&
879 (cursor.par()->getChar(lastPos(*this, cursor.row())) == ' '
880 || cursor.par()->isNewline(lastPos(*this, cursor.row())))) {
881 setCursor(cursor.par(), lastPos(*this, cursor.row()));
883 setCursor(cursor.par(),
884 lastPos(*this, cursor.row()) + 1);
890 void LyXText::cursorTop()
892 while (cursor.par()->previous())
893 cursor.par(cursor.par()->previous());
894 setCursor(cursor.par(), 0);
898 void LyXText::cursorBottom()
900 while (cursor.par()->next())
901 cursor.par(cursor.par()->next());
902 setCursor(cursor.par(), cursor.par()->size());
906 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
908 // If the mask is completely neutral, tell user
909 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
910 // Could only happen with user style
911 bv()->owner()->message(_("No font change defined. Use Character under the Layout menu to define font change."));
915 // Try implicit word selection
916 // If there is a change in the language the implicit word selection
918 LyXCursor resetCursor = cursor;
919 bool implicitSelection = (font.language() == ignore_language
920 && font.number() == LyXFont::IGNORE)
921 ? selectWordWhenUnderCursor(WHOLE_WORD_STRICT) : false;
924 setFont(font, toggleall);
926 // Implicit selections are cleared afterwards
927 //and cursor is set to the original position.
928 if (implicitSelection) {
930 cursor = resetCursor;
931 setCursor(cursor.par(), cursor.pos());
932 selection.cursor = cursor;
935 inset_owner->setUpdateStatus(bv(), InsetText::CURSOR_PAR);
939 string LyXText::getStringToIndex()
941 // Try implicit word selection
942 // If there is a change in the language the implicit word selection
944 LyXCursor const reset_cursor = cursor;
945 bool const implicitSelection = selectWordWhenUnderCursor(PREVIOUS_WORD);
948 if (!selection.set())
949 bv()->owner()->message(_("Nothing to index!"));
950 else if (selection.start.par() != selection.end.par())
951 bv()->owner()->message(_("Cannot index more than one paragraph!"));
953 idxstring = selectionAsString(bv()->buffer(), false);
955 // Reset cursors to their original position.
956 cursor = reset_cursor;
957 setCursor(cursor.par(), cursor.pos());
958 selection.cursor = cursor;
960 // Clear the implicit selection.
961 if (implicitSelection)
968 // the DTP switches for paragraphs. LyX will store them in the first
969 // physicla paragraph. When a paragraph is broken, the top settings rest,
970 // the bottom settings are given to the new one. So I can make shure,
971 // they do not duplicate themself and you cannnot make dirty things with
974 void LyXText::setParagraph(bool line_top, bool line_bottom,
975 bool pagebreak_top, bool pagebreak_bottom,
976 VSpace const & space_top,
977 VSpace const & space_bottom,
978 Spacing const & spacing,
980 string const & labelwidthstring,
983 LyXCursor tmpcursor = cursor;
984 if (!selection.set()) {
985 selection.start = cursor;
986 selection.end = cursor;
989 // make sure that the depth behind the selection are restored, too
990 Paragraph * endpar = selection.end.par()->next();
991 Paragraph * undoendpar = endpar;
993 if (endpar && endpar->getDepth()) {
994 while (endpar && endpar->getDepth()) {
995 endpar = endpar->next();
1000 // because of parindents etc.
1001 endpar = endpar->next();
1004 setUndo(bv(), Undo::EDIT, &*selection.start.par(), undoendpar);
1007 ParagraphList::iterator tmppit = selection.end.par();
1009 while (tmppit != boost::prior(selection.start.par())) {
1010 setCursor(tmppit, 0);
1011 postPaint(cursor.y() - cursor.row()->baseline());
1012 cursor.par()->params().lineTop(line_top);
1013 cursor.par()->params().lineBottom(line_bottom);
1014 cursor.par()->params().pagebreakTop(pagebreak_top);
1015 cursor.par()->params().pagebreakBottom(pagebreak_bottom);
1016 cursor.par()->params().spaceTop(space_top);
1017 cursor.par()->params().spaceBottom(space_bottom);
1018 cursor.par()->params().spacing(spacing);
1019 // does the layout allow the new alignment?
1020 LyXLayout_ptr const & layout = cursor.par()->layout();
1022 if (align == LYX_ALIGN_LAYOUT)
1023 align = layout->align;
1024 if (align & layout->alignpossible) {
1025 if (align == layout->align)
1026 cursor.par()->params().align(LYX_ALIGN_LAYOUT);
1028 cursor.par()->params().align(align);
1030 cursor.par()->setLabelWidthString(labelwidthstring);
1031 cursor.par()->params().noindent(noindent);
1032 tmppit = boost::prior(cursor.par());
1035 redoParagraphs(selection.start, endpar);
1038 setCursor(selection.start.par(), selection.start.pos());
1039 selection.cursor = cursor;
1040 setCursor(selection.end.par(), selection.end.pos());
1042 setCursor(tmpcursor.par(), tmpcursor.pos());
1044 bv()->updateInset(inset_owner);
1048 // set the counter of a paragraph. This includes the labels
1049 void LyXText::setCounter(Buffer const * buf, Paragraph * par)
1051 LyXTextClass const & textclass = buf->params.getLyXTextClass();
1052 LyXLayout_ptr const & layout = par->layout();
1054 if (par->previous()) {
1056 par->params().appendix(par->previous()->params().appendix());
1057 if (!par->params().appendix() && par->params().startOfAppendix()) {
1058 par->params().appendix(true);
1059 textclass.counters().reset();
1061 par->enumdepth = par->previous()->enumdepth;
1062 par->itemdepth = par->previous()->itemdepth;
1064 par->params().appendix(par->params().startOfAppendix());
1069 /* Maybe we have to increment the enumeration depth.
1070 * BUT, enumeration in a footnote is considered in isolation from its
1071 * surrounding paragraph so don't increment if this is the
1072 * first line of the footnote
1073 * AND, bibliographies can't have their depth changed ie. they
1074 * are always of depth 0
1077 && par->previous()->getDepth() < par->getDepth()
1078 && par->previous()->layout()->labeltype == LABEL_COUNTER_ENUMI
1079 && par->enumdepth < 3
1080 && layout->labeltype != LABEL_BIBLIO) {
1084 // Maybe we have to decrement the enumeration depth, see note above
1086 && par->previous()->getDepth() > par->getDepth()
1087 && layout->labeltype != LABEL_BIBLIO) {
1088 par->enumdepth = par->depthHook(par->getDepth())->enumdepth;
1091 if (!par->params().labelString().empty()) {
1092 par->params().labelString(string());
1095 if (layout->margintype == MARGIN_MANUAL) {
1096 if (par->params().labelWidthString().empty()) {
1097 par->setLabelWidthString(layout->labelstring());
1100 par->setLabelWidthString(string());
1103 // is it a layout that has an automatic label?
1104 if (layout->labeltype >= LABEL_COUNTER_CHAPTER) {
1105 int const i = layout->labeltype - LABEL_COUNTER_CHAPTER;
1109 if (i >= 0 && i <= buf->params.secnumdepth) {
1113 textclass.counters().step(layout->latexname());
1115 // Is there a label? Useful for Chapter layout
1116 if (!par->params().appendix()) {
1117 s << layout->labelstring();
1119 s << layout->labelstring_appendix();
1122 // Use of an integer is here less than elegant. For now.
1123 int head = textclass.maxcounter() - LABEL_COUNTER_CHAPTER;
1124 if (!par->params().appendix()) {
1125 numbertype = "sectioning";
1127 numbertype = "appendix";
1128 if (par->isRightToLeftPar(buf->params))
1129 langtype = "hebrew";
1134 s << textclass.counters()
1135 .numberLabel(layout->latexname(),
1136 numbertype, langtype, head);
1138 par->params().labelString(STRCONV(s.str()));
1140 // reset enum counters
1141 textclass.counters().reset("enum");
1142 } else if (layout->labeltype < LABEL_COUNTER_ENUMI) {
1143 textclass.counters().reset("enum");
1144 } else if (layout->labeltype == LABEL_COUNTER_ENUMI) {
1146 // Yes I know this is a really, really! bad solution
1148 string enumcounter("enum");
1150 switch (par->enumdepth) {
1159 enumcounter += "iv";
1162 // not a valid enumdepth...
1166 textclass.counters().step(enumcounter);
1168 s << textclass.counters()
1169 .numberLabel(enumcounter, "enumeration");
1170 par->params().labelString(STRCONV(s.str()));
1172 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
1173 textclass.counters().step("bibitem");
1174 int number = textclass.counters().value("bibitem");
1175 if (par->bibitem()) {
1176 par->bibitem()->setCounter(number);
1177 par->params().labelString(layout->labelstring());
1179 // In biblio should't be following counters but...
1181 string s = layout->labelstring();
1183 // the caption hack:
1184 if (layout->labeltype == LABEL_SENSITIVE) {
1185 Paragraph * tmppar = par;
1188 while (tmppar && tmppar->inInset()
1189 // the single '=' is intended below
1190 && (in = tmppar->inInset()->owner())) {
1191 if (in->lyxCode() == Inset::FLOAT_CODE ||
1192 in->lyxCode() == Inset::WRAP_CODE) {
1196 tmppar = in->parOwner();
1202 = textclass.floats().getType(static_cast<InsetFloat*>(in)->type());
1204 textclass.counters().step(fl.type());
1206 // Doesn't work... yet.
1207 #if USE_BOOST_FORMAT
1208 s = boost::io::str(boost::format(_("%1$s #:")) % fl.name());
1209 // s << boost::format(_("%1$s %1$d:")
1211 // % buf->counters().value(fl.name());
1214 //o << fl.name() << ' ' << buf->counters().value(fl.name()) << ":";
1215 o << fl.name() << " #:";
1216 s = STRCONV(o.str());
1219 // par->SetLayout(0);
1220 // s = layout->labelstring;
1221 s = _("Senseless: ");
1224 par->params().labelString(s);
1226 // reset the enumeration counter. They are always reset
1227 // when there is any other layout between
1228 // Just fall-through between the cases so that all
1229 // enum counters deeper than enumdepth is also reset.
1230 switch (par->enumdepth) {
1232 textclass.counters().reset("enumi");
1234 textclass.counters().reset("enumii");
1236 textclass.counters().reset("enumiii");
1238 textclass.counters().reset("enumiv");
1244 // Updates all counters. Paragraphs with changed label string will be rebroken
1245 void LyXText::updateCounters()
1247 RowList::iterator rowit = rows().begin();
1248 ParagraphList::iterator pit = rowit->par();
1250 // CHECK if this is really needed. (Lgb)
1251 bv()->buffer()->params.getLyXTextClass().counters().reset();
1253 while (pit != ownerParagraphs().end()) {
1254 while (rowit->par() != pit)
1257 string const oldLabel = pit->params().labelString();
1260 if (pit != ownerParagraphs().begin())
1261 maxdepth = boost::prior(pit)->getMaxDepthAfter();
1263 if (pit->params().depth() > maxdepth)
1264 pit->params().depth(maxdepth);
1266 // setCounter can potentially change the labelString.
1267 setCounter(bv()->buffer(), &*pit);
1269 string const & newLabel = pit->params().labelString();
1271 if (oldLabel.empty() && !newLabel.empty()) {
1272 removeParagraph(rowit);
1273 appendParagraph(rowit);
1281 void LyXText::insertInset(Inset * inset)
1283 if (!cursor.par()->insetAllowed(inset->lyxCode()))
1285 setUndo(bv(), Undo::FINISH, &*cursor.par(),
1286 &*boost::next(cursor.par()));
1288 cursor.par()->insertInset(cursor.pos(), inset);
1289 // Just to rebreak and refresh correctly.
1290 // The character will not be inserted a second time
1291 insertChar(Paragraph::META_INSET);
1292 // If we enter a highly editable inset the cursor should be to before
1293 // the inset. This couldn't happen before as Undo was not handled inside
1294 // inset now after the Undo LyX tries to call inset->Edit(...) again
1295 // and cannot do this as the cursor is behind the inset and GetInset
1296 // does not return the inset!
1297 if (isHighlyEditableInset(inset)) {
1304 void LyXText::copyEnvironmentType()
1306 copylayouttype = cursor.par()->layout()->name();
1310 void LyXText::pasteEnvironmentType()
1312 // do nothing if there has been no previous copyEnvironmentType()
1313 if (!copylayouttype.empty())
1314 setLayout(copylayouttype);
1318 void LyXText::cutSelection(bool doclear, bool realcut)
1320 // Stuff what we got on the clipboard. Even if there is no selection.
1322 // There is a problem with having the stuffing here in that the
1323 // larger the selection the slower LyX will get. This can be
1324 // solved by running the line below only when the selection has
1325 // finished. The solution used currently just works, to make it
1326 // faster we need to be more clever and probably also have more
1327 // calls to stuffClipboard. (Lgb)
1328 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1330 // This doesn't make sense, if there is no selection
1331 if (!selection.set())
1334 // OK, we have a selection. This is always between selection.start
1335 // and selection.end
1337 // make sure that the depth behind the selection are restored, too
1338 Paragraph * endpar = selection.end.par()->next();
1339 Paragraph * undoendpar = endpar;
1341 if (endpar && endpar->getDepth()) {
1342 while (endpar && endpar->getDepth()) {
1343 endpar = endpar->next();
1344 undoendpar = endpar;
1346 } else if (endpar) {
1347 endpar = endpar->next(); // because of parindents etc.
1350 setUndo(bv(), Undo::DELETE,
1351 &*selection.start.par(), undoendpar);
1353 // there are two cases: cut only within one paragraph or
1354 // more than one paragraph
1355 if (selection.start.par() == selection.end.par()) {
1356 // only within one paragraph
1357 endpar = &*selection.end.par();
1358 int pos = selection.end.pos();
1359 CutAndPaste::cutSelection(&*selection.start.par(), &endpar,
1360 selection.start.pos(), pos,
1361 bv()->buffer()->params.textclass,
1363 selection.end.pos(pos);
1365 endpar = &*selection.end.par();
1366 int pos = selection.end.pos();
1367 CutAndPaste::cutSelection(&*selection.start.par(), &endpar,
1368 selection.start.pos(), pos,
1369 bv()->buffer()->params.textclass,
1372 selection.end.par(endpar);
1373 selection.end.pos(pos);
1374 cursor.pos(selection.end.pos());
1376 endpar = endpar->next();
1378 // sometimes necessary
1380 selection.start.par()->stripLeadingSpaces();
1382 redoParagraphs(selection.start, endpar);
1384 // cutSelection can invalidate the cursor so we need to set
1386 // we prefer the end for when tracking changes
1387 cursor = selection.end;
1389 // need a valid cursor. (Lgb)
1392 setCursor(cursor.par(), cursor.pos());
1393 selection.cursor = cursor;
1398 void LyXText::copySelection()
1400 // stuff the selection onto the X clipboard, from an explicit copy request
1401 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1403 // this doesnt make sense, if there is no selection
1404 if (!selection.set())
1407 // ok we have a selection. This is always between selection.start
1408 // and sel_end cursor
1410 // copy behind a space if there is one
1411 while (selection.start.par()->size() > selection.start.pos()
1412 && selection.start.par()->isLineSeparator(selection.start.pos())
1413 && (selection.start.par() != selection.end.par()
1414 || selection.start.pos() < selection.end.pos()))
1415 selection.start.pos(selection.start.pos() + 1);
1417 CutAndPaste::copySelection(&*selection.start.par(),
1418 &*selection.end.par(),
1419 selection.start.pos(), selection.end.pos(),
1420 bv()->buffer()->params.textclass);
1424 void LyXText::pasteSelection()
1426 // this does not make sense, if there is nothing to paste
1427 if (!CutAndPaste::checkPastePossible())
1430 setUndo(bv(), Undo::INSERT,
1431 &*cursor.par(), &*boost::next(cursor.par()));
1434 ParagraphList::iterator actpit = cursor.par();
1435 int pos = cursor.pos();
1437 Paragraph * actpar = &*actpit;
1438 CutAndPaste::pasteSelection(&actpar, &endpar, pos,
1439 bv()->buffer()->params.textclass);
1441 redoParagraphs(cursor, endpar);
1443 setCursor(cursor.par(), cursor.pos());
1446 selection.cursor = cursor;
1447 setCursor(actpit, pos);
1453 void LyXText::setSelectionRange(lyx::pos_type length)
1458 selection.cursor = cursor;
1465 // simple replacing. The font of the first selected character is used
1466 void LyXText::replaceSelectionWithString(string const & str)
1468 setCursorParUndo(bv());
1471 if (!selection.set()) { // create a dummy selection
1472 selection.end = cursor;
1473 selection.start = cursor;
1476 // Get font setting before we cut
1477 pos_type pos = selection.end.pos();
1478 LyXFont const font = selection.start.par()
1479 ->getFontSettings(bv()->buffer()->params,
1480 selection.start.pos());
1482 // Insert the new string
1483 for (string::const_iterator cit = str.begin(); cit != str.end(); ++cit) {
1484 selection.end.par()->insertChar(pos, (*cit), font);
1488 // Cut the selection
1489 cutSelection(true, false);
1495 // needed to insert the selection
1496 void LyXText::insertStringAsLines(string const & str)
1498 ParagraphList::iterator pit = cursor.par();
1499 pos_type pos = cursor.pos();
1500 ParagraphList::iterator endpit = boost::next(cursor.par());
1502 setCursorParUndo(bv());
1504 // only to be sure, should not be neccessary
1507 Paragraph * par = &*pit;
1508 bv()->buffer()->insertStringAsLines(par, pos, current_font, str);
1510 redoParagraphs(cursor, &*endpit);
1511 setCursor(cursor.par(), cursor.pos());
1512 selection.cursor = cursor;
1513 setCursor(pit, pos);
1518 // turns double-CR to single CR, others where converted into one
1519 // blank. Then InsertStringAsLines is called
1520 void LyXText::insertStringAsParagraphs(string const & str)
1522 string linestr(str);
1523 bool newline_inserted = false;
1524 for (string::size_type i = 0; i < linestr.length(); ++i) {
1525 if (linestr[i] == '\n') {
1526 if (newline_inserted) {
1527 // we know that \r will be ignored by
1528 // InsertStringA. Of course, it is a dirty
1529 // trick, but it works...
1530 linestr[i - 1] = '\r';
1534 newline_inserted = true;
1536 } else if (IsPrintable(linestr[i])) {
1537 newline_inserted = false;
1540 insertStringAsLines(linestr);
1544 void LyXText::checkParagraph(Paragraph * par, pos_type pos)
1546 LyXCursor tmpcursor;
1550 RowList::iterator row = getRow(par, pos, y);
1551 RowList::iterator beg = rows().begin();
1553 // is there a break one row above
1555 && boost::prior(row)->par() == row->par()) {
1556 z = rowBreakPoint(*boost::prior(row));
1557 if (z >= row->pos()) {
1558 // set the dimensions of the row above
1559 y -= boost::prior(row)->height();
1562 breakAgain(boost::prior(row));
1564 // set the cursor again. Otherwise
1565 // dangling pointers are possible
1566 setCursor(cursor.par(), cursor.pos(),
1567 false, cursor.boundary());
1568 selection.cursor = cursor;
1573 int const tmpheight = row->height();
1574 pos_type const tmplast = lastPos(*this, row);
1577 if (row->height() == tmpheight && lastPos(*this, row) == tmplast) {
1578 postRowPaint(row, y);
1583 // check the special right address boxes
1584 if (par->layout()->margintype == MARGIN_RIGHT_ADDRESS_BOX) {
1591 redoDrawingOfParagraph(tmpcursor);
1594 // set the cursor again. Otherwise dangling pointers are possible
1595 // also set the selection
1597 if (selection.set()) {
1599 setCursorIntern(selection.cursor.par(), selection.cursor.pos(),
1600 false, selection.cursor.boundary());
1601 selection.cursor = cursor;
1602 setCursorIntern(selection.start.par(),
1603 selection.start.pos(),
1604 false, selection.start.boundary());
1605 selection.start = cursor;
1606 setCursorIntern(selection.end.par(),
1607 selection.end.pos(),
1608 false, selection.end.boundary());
1609 selection.end = cursor;
1610 setCursorIntern(last_sel_cursor.par(),
1611 last_sel_cursor.pos(),
1612 false, last_sel_cursor.boundary());
1613 last_sel_cursor = cursor;
1616 setCursorIntern(cursor.par(), cursor.pos(),
1617 false, cursor.boundary());
1621 // returns false if inset wasn't found
1622 bool LyXText::updateInset(Inset * inset)
1624 // first check the current paragraph
1625 int pos = cursor.par()->getPositionOfInset(inset);
1627 checkParagraph(&*cursor.par(), pos);
1631 // check every paragraph
1633 ParagraphList::iterator par = ownerParagraphs().begin();
1634 ParagraphList::iterator end = ownerParagraphs().end();
1637 pos = par->getPositionOfInset(inset);
1639 checkParagraph(&*par, pos);
1643 } while (par != end);
1649 bool LyXText::setCursor(ParagraphList::iterator pit,
1651 bool setfont, bool boundary)
1653 LyXCursor old_cursor = cursor;
1654 setCursorIntern(pit, pos, setfont, boundary);
1655 return deleteEmptyParagraphMechanism(old_cursor);
1659 void LyXText::setCursor(LyXCursor & cur, ParagraphList::iterator pit,
1660 pos_type pos, bool boundary)
1662 lyx::Assert(pit != ownerParagraphs().end());
1666 cur.boundary(boundary);
1668 // get the cursor y position in text
1670 RowList::iterator row = getRow(&*pit, pos, y);
1671 RowList::iterator beg = rows().begin();
1673 RowList::iterator old_row = row;
1675 // if we are before the first char of this row and are still in the
1676 // same paragraph and there is a previous row then put the cursor on
1677 // the end of the previous row
1678 cur.iy(y + row->baseline());
1680 if (row != beg && pos &&
1681 boost::prior(row)->par() == row->par() &&
1682 pos < pit->size() &&
1683 pit->getChar(pos) == Paragraph::META_INSET &&
1684 (ins = pit->getInset(pos)) && (ins->needFullRow() || ins->display()))
1691 // y is now the beginning of the cursor row
1692 y += row->baseline();
1693 // y is now the cursor baseline
1696 pos_type last = lastPrintablePos(*this, old_row);
1698 // None of these should happen, but we're scaredy-cats
1699 if (pos > pit->size()) {
1700 lyxerr << "dont like 1 please report" << endl;
1703 } else if (pos > last + 1) {
1704 lyxerr << "dont like 2 please report" << endl;
1705 // This shouldn't happen.
1708 } else if (pos < row->pos()) {
1709 lyxerr << "dont like 3 please report" << endl;
1714 // now get the cursors x position
1715 float x = getCursorX(row, pos, last, boundary);
1718 if (old_row != row) {
1719 x = getCursorX(old_row, pos, last, boundary);
1723 /* We take out this for the time being because 1) the redraw code is not
1724 prepared to this yet and 2) because some good policy has yet to be decided
1725 while editting: for instance how to act on rows being created/deleted
1729 //if the cursor is in a visible row, anchor to it
1731 if (topy < y && y < topy + bv()->workHeight())
1737 float LyXText::getCursorX(RowList::iterator rit,
1738 pos_type pos, pos_type last, bool boundary) const
1740 pos_type cursor_vpos = 0;
1742 float fill_separator;
1744 float fill_label_hfill;
1745 // This call HAS to be here because of the BidiTables!!!
1746 prepareToPrint(rit, x, fill_separator, fill_hfill,
1749 if (last < rit->pos())
1750 cursor_vpos = rit->pos();
1751 else if (pos > last && !boundary)
1752 cursor_vpos = (rit->par()->isRightToLeftPar(bv()->buffer()->params))
1753 ? rit->pos() : last + 1;
1754 else if (pos > rit->pos() &&
1755 (pos > last || boundary))
1756 /// Place cursor after char at (logical) position pos - 1
1757 cursor_vpos = (bidi_level(pos - 1) % 2 == 0)
1758 ? log2vis(pos - 1) + 1 : log2vis(pos - 1);
1760 /// Place cursor before char at (logical) position pos
1761 cursor_vpos = (bidi_level(pos) % 2 == 0)
1762 ? log2vis(pos) : log2vis(pos) + 1;
1764 pos_type body_pos = rit->par()->beginningOfBody();
1765 if ((body_pos > 0) &&
1766 ((body_pos - 1 > last) ||
1767 !rit->par()->isLineSeparator(body_pos - 1)))
1770 for (pos_type vpos = rit->pos(); vpos < cursor_vpos; ++vpos) {
1771 pos_type pos = vis2log(vpos);
1772 if (body_pos > 0 && pos == body_pos - 1) {
1773 x += fill_label_hfill +
1774 font_metrics::width(
1775 rit->par()->layout()->labelsep,
1776 getLabelFont(bv()->buffer(),
1778 if (rit->par()->isLineSeparator(body_pos - 1))
1779 x -= singleWidth(rit->par(), body_pos - 1);
1782 if (hfillExpansion(*this, rit, pos)) {
1783 x += singleWidth(rit->par(), pos);
1784 if (pos >= body_pos)
1787 x += fill_label_hfill;
1788 } else if (rit->par()->isSeparator(pos)) {
1789 x += singleWidth(rit->par(), pos);
1790 if (pos >= body_pos)
1791 x += fill_separator;
1793 x += singleWidth(rit->par(), pos);
1799 void LyXText::setCursorIntern(ParagraphList::iterator pit,
1800 pos_type pos, bool setfont, bool boundary)
1802 InsetText * it = static_cast<InsetText *>(pit->inInset());
1804 if (it != inset_owner) {
1805 lyxerr[Debug::INSETS] << "InsetText is " << it
1807 << "inset_owner is "
1808 << inset_owner << endl;
1809 #ifdef WITH_WARNINGS
1810 #warning I believe this code is wrong. (Lgb)
1811 #warning Jürgen, have a look at this. (Lgb)
1812 #warning Hmmm, I guess you are right but we
1813 #warning should verify when this is needed
1815 // Jürgen, would you like to have a look?
1816 // I guess we need to move the outer cursor
1817 // and open and lock the inset (bla bla bla)
1818 // stuff I don't know... so can you have a look?
1820 // I moved the lyxerr stuff in here so we can see if
1821 // this is actually really needed and where!
1823 // it->getLyXText(bv())->setCursorIntern(bv(), par, pos, setfont, boundary);
1828 setCursor(cursor, pit, pos, boundary);
1834 void LyXText::setCurrentFont()
1836 pos_type pos = cursor.pos();
1837 if (cursor.boundary() && pos > 0)
1841 if (pos == cursor.par()->size())
1843 else // potentional bug... BUG (Lgb)
1844 if (cursor.par()->isSeparator(pos)) {
1845 if (pos > cursor.row()->pos() &&
1846 bidi_level(pos) % 2 ==
1847 bidi_level(pos - 1) % 2)
1849 else if (pos + 1 < cursor.par()->size())
1855 cursor.par()->getFontSettings(bv()->buffer()->params, pos);
1856 real_current_font = getFont(bv()->buffer(), cursor.par(), pos);
1858 if (cursor.pos() == cursor.par()->size() &&
1859 isBoundary(bv()->buffer(), &*cursor.par(), cursor.pos()) &&
1860 !cursor.boundary()) {
1861 Language const * lang =
1862 cursor.par()->getParLanguage(bv()->buffer()->params);
1863 current_font.setLanguage(lang);
1864 current_font.setNumber(LyXFont::OFF);
1865 real_current_font.setLanguage(lang);
1866 real_current_font.setNumber(LyXFont::OFF);
1871 // returns the column near the specified x-coordinate of the row
1872 // x is set to the real beginning of this column
1874 LyXText::getColumnNearX(RowList::iterator rit, int & x, bool & boundary) const
1877 float fill_separator;
1879 float fill_label_hfill;
1881 prepareToPrint(rit, tmpx, fill_separator,
1882 fill_hfill, fill_label_hfill);
1884 pos_type vc = rit->pos();
1885 pos_type last = lastPrintablePos(*this, rit);
1888 LyXLayout_ptr const & layout = rit->par()->layout();
1890 bool left_side = false;
1892 pos_type body_pos = rit->par()->beginningOfBody();
1893 float last_tmpx = tmpx;
1896 (body_pos - 1 > last ||
1897 !rit->par()->isLineSeparator(body_pos - 1)))
1900 // check for empty row
1901 if (!rit->par()->size()) {
1906 while (vc <= last && tmpx <= x) {
1909 if (body_pos > 0 && c == body_pos - 1) {
1910 tmpx += fill_label_hfill +
1911 font_metrics::width(layout->labelsep,
1912 getLabelFont(bv()->buffer(), &*rit->par()));
1913 if (rit->par()->isLineSeparator(body_pos - 1))
1914 tmpx -= singleWidth(rit->par(), body_pos - 1);
1917 if (hfillExpansion(*this, rit, c)) {
1918 tmpx += singleWidth(rit->par(), c);
1922 tmpx += fill_label_hfill;
1923 } else if (rit->par()->isSeparator(c)) {
1924 tmpx += singleWidth(rit->par(), c);
1926 tmpx+= fill_separator;
1928 tmpx += singleWidth(rit->par(), c);
1933 if ((tmpx + last_tmpx) / 2 > x) {
1938 if (vc > last + 1) // This shouldn't happen.
1942 // This (rtl_support test) is not needed, but gives
1943 // some speedup if rtl_support=false
1944 bool const lastrow = lyxrc.rtl_support &&
1945 (boost::next(rit) == rowlist_.end() ||
1946 boost::next(rit)->par() != rit->par());
1947 // If lastrow is false, we don't need to compute
1948 // the value of rtl.
1949 bool const rtl = (lastrow)
1950 ? rit->par()->isRightToLeftPar(bv()->buffer()->params)
1953 ((rtl && left_side && vc == rit->pos() && x < tmpx - 5) ||
1954 (!rtl && !left_side && vc == last + 1 && x > tmpx + 5)))
1956 else if (vc == rit->pos()) {
1958 if (bidi_level(c) % 2 == 1)
1961 c = vis2log(vc - 1);
1962 bool const rtl = (bidi_level(c) % 2 == 1);
1963 if (left_side == rtl) {
1965 boundary = isBoundary(bv()->buffer(), &*rit->par(), c);
1969 if (rit->pos() <= last && c > last
1970 && rit->par()->isNewline(last)) {
1971 if (bidi_level(last) % 2 == 0)
1972 tmpx -= singleWidth(rit->par(), last);
1974 tmpx += singleWidth(rit->par(), last);
1984 void LyXText::setCursorFromCoordinates(int x, int y)
1986 LyXCursor old_cursor = cursor;
1988 setCursorFromCoordinates(cursor, x, y);
1990 deleteEmptyParagraphMechanism(old_cursor);
1997 * return true if the cursor given is at the end of a row,
1998 * and the next row is filled by an inset that spans an entire
2001 bool beforeFullRowInset(LyXText & lt, RowList::iterator row,
2003 if (boost::next(row) == lt.rows().end())
2005 Row const & next = *boost::next(row);
2007 if (next.pos() != cur.pos() || next.par() != cur.par())
2009 if (!cur.par()->isInset(cur.pos()))
2011 Inset const * inset = cur.par()->getInset(cur.pos());
2012 if (inset->needFullRow() || inset->display())
2019 void LyXText::setCursorFromCoordinates(LyXCursor & cur, int x, int y)
2021 // Get the row first.
2023 RowList::iterator row = getRowNearY(y);
2025 pos_type const column = getColumnNearX(row, x, bound);
2026 cur.par(&*row->par());
2027 cur.pos(row->pos() + column);
2029 cur.y(y + row->baseline());
2032 if (beforeFullRowInset(*this, row, cur)) {
2033 pos_type last = lastPrintablePos(*this, row);
2034 float x = getCursorX(boost::next(row), cur.pos(), last, bound);
2036 cur.iy(y + row->height() + boost::next(row)->baseline());
2037 cur.irow(boost::next(row));
2043 cur.boundary(bound);
2047 void LyXText::cursorLeft(bool internal)
2049 if (cursor.pos() > 0) {
2050 bool boundary = cursor.boundary();
2051 setCursor(cursor.par(), cursor.pos() - 1, true, false);
2052 if (!internal && !boundary &&
2053 isBoundary(bv()->buffer(), &*cursor.par(), cursor.pos() + 1))
2054 setCursor(cursor.par(), cursor.pos() + 1, true, true);
2055 } else if (cursor.par()->previous()) { // steps into the above paragraph.
2056 Paragraph * par = cursor.par()->previous();
2057 setCursor(par, par->size());
2062 void LyXText::cursorRight(bool internal)
2064 bool const at_end = (cursor.pos() == cursor.par()->size());
2065 bool const at_newline = !at_end &&
2066 cursor.par()->isNewline(cursor.pos());
2068 if (!internal && cursor.boundary() && !at_newline)
2069 setCursor(cursor.par(), cursor.pos(), true, false);
2071 setCursor(cursor.par(), cursor.pos() + 1, true, false);
2073 isBoundary(bv()->buffer(), &*cursor.par(), cursor.pos()))
2074 setCursor(cursor.par(), cursor.pos(), true, true);
2075 } else if (cursor.par()->next())
2076 setCursor(cursor.par()->next(), 0);
2080 void LyXText::cursorUp(bool selecting)
2083 int x = cursor.x_fix();
2084 int y = cursor.y() - cursor.row()->baseline() - 1;
2085 setCursorFromCoordinates(x, y);
2088 int y1 = cursor.iy() - topy;
2091 Inset * inset_hit = checkInsetHit(x, y1);
2092 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2093 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2097 setCursorFromCoordinates(bv(), cursor.x_fix(),
2098 cursor.y() - cursor.row()->baseline() - 1);
2103 void LyXText::cursorDown(bool selecting)
2106 int x = cursor.x_fix();
2107 int y = cursor.y() - cursor.row()->baseline() +
2108 cursor.row()->height() + 1;
2109 setCursorFromCoordinates(x, y);
2110 if (!selecting && cursor.row() == cursor.irow()) {
2112 int y1 = cursor.iy() - topy;
2115 Inset * inset_hit = checkInsetHit(x, y1);
2116 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2117 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2121 setCursorFromCoordinates(bv(), cursor.x_fix(),
2122 cursor.y() - cursor.row()->baseline()
2123 + cursor.row()->height() + 1);
2128 void LyXText::cursorUpParagraph()
2130 if (cursor.pos() > 0) {
2131 setCursor(cursor.par(), 0);
2133 else if (cursor.par()->previous()) {
2134 setCursor(cursor.par()->previous(), 0);
2139 void LyXText::cursorDownParagraph()
2141 if (cursor.par()->next()) {
2142 setCursor(cursor.par()->next(), 0);
2144 setCursor(cursor.par(), cursor.par()->size());
2148 // fix the cursor `cur' after a characters has been deleted at `where'
2149 // position. Called by deleteEmptyParagraphMechanism
2150 void LyXText::fixCursorAfterDelete(LyXCursor & cur,
2151 LyXCursor const & where)
2153 // if cursor is not in the paragraph where the delete occured,
2155 if (cur.par() != where.par())
2158 // if cursor position is after the place where the delete occured,
2160 if (cur.pos() > where.pos())
2161 cur.pos(cur.pos()-1);
2163 // check also if we don't want to set the cursor on a spot behind the
2164 // pagragraph because we erased the last character.
2165 if (cur.pos() > cur.par()->size())
2166 cur.pos(cur.par()->size());
2168 // recompute row et al. for this cursor
2169 setCursor(cur, cur.par(), cur.pos(), cur.boundary());
2173 bool LyXText::deleteEmptyParagraphMechanism(LyXCursor const & old_cursor)
2175 // Would be wrong to delete anything if we have a selection.
2176 if (selection.set())
2179 // We allow all kinds of "mumbo-jumbo" when freespacing.
2180 if (old_cursor.par()->layout()->free_spacing
2181 || old_cursor.par()->isFreeSpacing()) {
2185 /* Ok I'll put some comments here about what is missing.
2186 I have fixed BackSpace (and thus Delete) to not delete
2187 double-spaces automagically. I have also changed Cut,
2188 Copy and Paste to hopefully do some sensible things.
2189 There are still some small problems that can lead to
2190 double spaces stored in the document file or space at
2191 the beginning of paragraphs. This happens if you have
2192 the cursor betwenn to spaces and then save. Or if you
2193 cut and paste and the selection have a space at the
2194 beginning and then save right after the paste. I am
2195 sure none of these are very hard to fix, but I will
2196 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
2197 that I can get some feedback. (Lgb)
2200 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
2201 // delete the LineSeparator.
2204 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
2205 // delete the LineSeparator.
2208 // If the pos around the old_cursor were spaces, delete one of them.
2209 if (old_cursor.par() != cursor.par()
2210 || old_cursor.pos() != cursor.pos()) {
2211 // Only if the cursor has really moved
2213 if (old_cursor.pos() > 0
2214 && old_cursor.pos() < old_cursor.par()->size()
2215 && old_cursor.par()->isLineSeparator(old_cursor.pos())
2216 && old_cursor.par()->isLineSeparator(old_cursor.pos() - 1)) {
2217 old_cursor.par()->erase(old_cursor.pos() - 1);
2218 redoParagraphs(old_cursor, old_cursor.par()->next());
2220 #ifdef WITH_WARNINGS
2221 #warning This will not work anymore when we have multiple views of the same buffer
2222 // In this case, we will have to correct also the cursors held by
2223 // other bufferviews. It will probably be easier to do that in a more
2224 // automated way in LyXCursor code. (JMarc 26/09/2001)
2226 // correct all cursors held by the LyXText
2227 fixCursorAfterDelete(cursor, old_cursor);
2228 fixCursorAfterDelete(selection.cursor,
2230 fixCursorAfterDelete(selection.start,
2232 fixCursorAfterDelete(selection.end, old_cursor);
2233 fixCursorAfterDelete(last_sel_cursor,
2235 fixCursorAfterDelete(toggle_cursor, old_cursor);
2236 fixCursorAfterDelete(toggle_end_cursor,
2242 // don't delete anything if this is the ONLY paragraph!
2243 if (!old_cursor.par()->next() && !old_cursor.par()->previous())
2246 // Do not delete empty paragraphs with keepempty set.
2247 if (old_cursor.par()->layout()->keepempty)
2250 // only do our magic if we changed paragraph
2251 if (old_cursor.par() == cursor.par())
2254 // record if we have deleted a paragraph
2255 // we can't possibly have deleted a paragraph before this point
2256 bool deleted = false;
2258 if ((old_cursor.par()->empty()
2259 || (old_cursor.par()->size() == 1
2260 && old_cursor.par()->isLineSeparator(0)))) {
2261 // ok, we will delete anything
2262 LyXCursor tmpcursor;
2266 if (old_cursor.row() != rows().begin()) {
2268 prevrow = boost::prior(old_cursor.row());
2269 const_cast<LyXText *>(this)->postPaint(old_cursor.y() - old_cursor.row()->baseline() - prevrow->height());
2271 cursor = old_cursor; // that undo can restore the right cursor position
2272 Paragraph * endpar = old_cursor.par()->next();
2273 if (endpar && endpar->getDepth()) {
2274 while (endpar && endpar->getDepth()) {
2275 endpar = endpar->next();
2278 setUndo(bv(), Undo::DELETE, &*old_cursor.par(), endpar);
2282 removeRow(old_cursor.row());
2283 if (ownerParagraphs().begin() == old_cursor.par()) {
2284 ownerParagraph(&*boost::next(ownerParagraphs().begin()));
2286 #warning FIXME Do the proper ParagraphList operation here (Lgb)
2288 delete &*old_cursor.par();
2290 /* Breakagain the next par. Needed because of
2291 * the parindent that can occur or dissappear.
2292 * The next row can change its height, if
2293 * there is another layout before */
2294 if (boost::next(prevrow) != rows().end()) {
2295 breakAgain(boost::next(prevrow));
2298 setHeightOfRow(prevrow);
2300 RowList::iterator nextrow = boost::next(old_cursor.row());
2301 const_cast<LyXText *>(this)->postPaint(
2302 old_cursor.y() - old_cursor.row()->baseline());
2305 cursor = old_cursor; // that undo can restore the right cursor position
2306 Paragraph * endpar = old_cursor.par()->next();
2307 if (endpar && endpar->getDepth()) {
2308 while (endpar && endpar->getDepth()) {
2309 endpar = endpar->next();
2312 setUndo(bv(), Undo::DELETE, &*old_cursor.par(), endpar);
2316 removeRow(old_cursor.row());
2318 if (ownerParagraphs().begin() == old_cursor.par()) {
2319 ownerParagraph(&*boost::next(ownerParagraphs().begin()));
2321 #warning FIXME Do the proper ParagraphList operations here. (Lgb)
2322 delete &*old_cursor.par();
2324 /* Breakagain the next par. Needed because of
2325 the parindent that can occur or dissappear.
2326 The next row can change its height, if
2327 there is another layout before */
2328 if (nextrow != rows().end()) {
2329 breakAgain(nextrow);
2335 setCursorIntern(cursor.par(), cursor.pos());
2337 if (selection.cursor.par() == old_cursor.par()
2338 && selection.cursor.pos() == old_cursor.pos()) {
2339 // correct selection
2340 selection.cursor = cursor;
2344 if (old_cursor.par()->stripLeadingSpaces()) {
2345 redoParagraphs(old_cursor,
2346 old_cursor.par()->next());
2348 setCursorIntern(cursor.par(), cursor.pos());
2349 selection.cursor = cursor;
2356 ParagraphList & LyXText::ownerParagraphs() const
2359 return inset_owner->paragraphs;
2361 return bv_owner->buffer()->paragraphs;
2365 void LyXText::ownerParagraph(Paragraph * p) const
2368 inset_owner->paragraph(p);
2370 bv_owner->buffer()->paragraphs.set(p);
2375 void LyXText::ownerParagraph(int id, Paragraph * p) const
2377 Paragraph * op = bv_owner->buffer()->getParFromID(id);
2378 if (op && op->inInset()) {
2379 static_cast<InsetText *>(op->inInset())->paragraph(p);
2386 LyXText::refresh_status LyXText::refreshStatus() const
2388 return refresh_status_;
2392 void LyXText::clearPaint()
2394 refresh_status_ = REFRESH_NONE;
2395 refresh_row = rows().end();
2400 void LyXText::postPaint(int start_y)
2402 refresh_status old = refresh_status_;
2404 refresh_status_ = REFRESH_AREA;
2405 refresh_row = rows().end();
2407 if (old != REFRESH_NONE && refresh_y < start_y)
2410 refresh_y = start_y;
2415 // We are an inset's lyxtext. Tell the top-level lyxtext
2416 // it needs to update the row we're in.
2417 LyXText * t = bv()->text;
2418 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2422 // FIXME: we should probably remove this y parameter,
2423 // make refresh_y be 0, and use row->y etc.
2424 void LyXText::postRowPaint(RowList::iterator rit, int start_y)
2426 if (refresh_status_ != REFRESH_NONE && refresh_y < start_y) {
2427 refresh_status_ = REFRESH_AREA;
2430 refresh_y = start_y;
2433 if (refresh_status_ == REFRESH_AREA)
2436 refresh_status_ = REFRESH_ROW;
2442 // We are an inset's lyxtext. Tell the top-level lyxtext
2443 // it needs to update the row we're in.
2444 LyXText * t = bv()->text;
2445 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2449 bool LyXText::isInInset() const
2451 // Sub-level has non-null bv owner and
2452 // non-null inset owner.
2453 return inset_owner != 0 && bv_owner != 0;
2457 int defaultRowHeight()
2459 LyXFont const font(LyXFont::ALL_SANE);
2460 return int(font_metrics::maxAscent(font)
2461 + font_metrics::maxDescent(font) * 1.5);