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_ += anchor_row_->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 void LyXText::insertParagraph(ParagraphList::iterator pit,
315 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 ParagraphList::iterator
388 LyXText::setLayout(LyXCursor & cur, LyXCursor & sstart_cur,
389 LyXCursor & send_cur,
390 string const & layout)
392 Paragraph * endpar = send_cur.par()->next();
393 Paragraph * undoendpar = endpar;
395 if (endpar && endpar->getDepth()) {
396 while (endpar && endpar->getDepth()) {
397 endpar = endpar->next();
401 endpar = endpar->next(); // because of parindents etc.
404 setUndo(bv(), Undo::EDIT, &*sstart_cur.par(), undoendpar);
406 // ok we have a selection. This is always between sstart_cur
407 // and sel_end cursor
409 ParagraphList::iterator pit = sstart_cur.par();
410 ParagraphList::iterator epit = boost::next(send_cur.par());
412 LyXLayout_ptr const & lyxlayout =
413 bv()->buffer()->params.getLyXTextClass()[layout];
416 pit->applyLayout(lyxlayout);
417 makeFontEntriesLayoutSpecific(*bv()->buffer(), *pit);
418 ParagraphList::iterator fppit = pit;
419 fppit->params().spaceTop(lyxlayout->fill_top ?
420 VSpace(VSpace::VFILL)
421 : VSpace(VSpace::NONE));
422 fppit->params().spaceBottom(lyxlayout->fill_bottom ?
423 VSpace(VSpace::VFILL)
424 : VSpace(VSpace::NONE));
425 if (lyxlayout->margintype == MARGIN_MANUAL)
426 pit->setLabelWidthString(lyxlayout->labelstring());
429 } while (pit != epit);
435 // set layout over selection and make a total rebreak of those paragraphs
436 void LyXText::setLayout(string const & layout)
438 LyXCursor tmpcursor = cursor; /* store the current cursor */
440 // if there is no selection just set the layout
441 // of the current paragraph */
442 if (!selection.set()) {
443 selection.start = cursor; // dummy selection
444 selection.end = cursor;
446 ParagraphList::iterator endpit = setLayout(cursor, selection.start,
447 selection.end, layout);
448 redoParagraphs(selection.start, endpit);
450 // we have to reset the selection, because the
451 // geometry could have changed
452 setCursor(selection.start.par(),
453 selection.start.pos(), false);
454 selection.cursor = cursor;
455 setCursor(selection.end.par(), selection.end.pos(), false);
459 setCursor(tmpcursor.par(), tmpcursor.pos(), true);
463 bool LyXText::changeDepth(bv_funcs::DEPTH_CHANGE type, bool test_only)
465 ParagraphList::iterator pit(cursor.par());
466 ParagraphList::iterator end(cursor.par());
467 ParagraphList::iterator start = pit;
469 if (selection.set()) {
470 pit = selection.start.par();
471 end = selection.end.par();
475 ParagraphList::iterator pastend = boost::next(end);
478 setUndo(bv(), Undo::EDIT, &(*start), &(*pastend));
480 bool changed = false;
482 int prev_after_depth = 0;
483 #warning parlist ... could be nicer ?
484 if (start != ownerParagraphs().begin()) {
485 prev_after_depth = boost::prior(start)->getMaxDepthAfter();
489 int const depth = pit->params().depth();
490 if (type == bv_funcs::INC_DEPTH) {
491 if (depth < prev_after_depth
492 && pit->layout()->labeltype != LABEL_BIBLIO) {
495 pit->params().depth(depth + 1);
502 pit->params().depth(depth - 1);
505 prev_after_depth = pit->getMaxDepthAfter();
517 // Wow, redoParagraphs is stupid.
519 setCursor(tmpcursor, &(*start), 0);
521 //redoParagraphs(tmpcursor, &(*pastend));
522 redoParagraphs(tmpcursor, &(*pastend));
524 // We need to actually move the text->cursor. I don't
525 // understand why ...
528 // we have to reset the visual selection because the
529 // geometry could have changed
530 if (selection.set()) {
531 setCursor(selection.start.par(), selection.start.pos());
532 selection.cursor = cursor;
533 setCursor(selection.end.par(), selection.end.pos());
536 // this handles the counter labels, and also fixes up
537 // depth values for follow-on (child) paragraphs
541 setCursor(tmpcursor.par(), tmpcursor.pos());
547 // set font over selection and make a total rebreak of those paragraphs
548 void LyXText::setFont(LyXFont const & font, bool toggleall)
550 // if there is no selection just set the current_font
551 if (!selection.set()) {
552 // Determine basis font
554 if (cursor.pos() < cursor.par()->beginningOfBody()) {
555 layoutfont = getLabelFont(bv()->buffer(),
558 layoutfont = getLayoutFont(bv()->buffer(),
561 // Update current font
562 real_current_font.update(font,
563 bv()->buffer()->params.language,
566 // Reduce to implicit settings
567 current_font = real_current_font;
568 current_font.reduce(layoutfont);
569 // And resolve it completely
570 real_current_font.realize(layoutfont);
575 LyXCursor tmpcursor = cursor; // store the current cursor
577 // ok we have a selection. This is always between sel_start_cursor
578 // and sel_end cursor
580 setUndo(bv(), Undo::EDIT,
581 &*selection.start.par(), &*boost::next(selection.end.par()));
583 cursor = selection.start;
584 while (cursor.par() != selection.end.par() ||
585 cursor.pos() < selection.end.pos())
587 if (cursor.pos() < cursor.par()->size()) {
588 // an open footnote should behave like a closed one
589 setCharFont(&*cursor.par(), cursor.pos(),
591 cursor.pos(cursor.pos() + 1);
594 cursor.par(cursor.par()->next());
599 redoParagraphs(selection.start, selection.end.par()->next());
601 // we have to reset the selection, because the
602 // geometry could have changed, but we keep
603 // it for user convenience
604 setCursor(selection.start.par(), selection.start.pos());
605 selection.cursor = cursor;
606 setCursor(selection.end.par(), selection.end.pos());
608 setCursor(tmpcursor.par(), tmpcursor.pos(), true,
609 tmpcursor.boundary());
613 void LyXText::redoHeightOfParagraph()
615 RowList::iterator tmprow = cursor.row();
616 int y = cursor.y() - tmprow->baseline();
618 setHeightOfRow(tmprow);
620 while (tmprow != rows().begin()
621 && boost::prior(tmprow)->par() == tmprow->par()) {
623 y -= tmprow->height();
624 setHeightOfRow(tmprow);
629 setCursor(cursor.par(), cursor.pos(), false, cursor.boundary());
633 void LyXText::redoDrawingOfParagraph(LyXCursor const & cur)
635 RowList::iterator tmprow = cur.row();
637 int y = cur.y() - tmprow->baseline();
638 setHeightOfRow(tmprow);
640 while (tmprow != rows().begin()
641 && boost::prior(tmprow)->par() == tmprow->par()) {
643 y -= tmprow->height();
647 setCursor(cur.par(), cur.pos());
651 // deletes and inserts again all paragaphs between the cursor
652 // and the specified par
653 // This function is needed after SetLayout and SetFont etc.
654 void LyXText::redoParagraphs(LyXCursor const & cur,
655 ParagraphList::iterator endpit)
657 RowList::iterator tmprit = cur.row();
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, ParagraphList::iterator pit)
1051 LyXTextClass const & textclass = buf->params.getLyXTextClass();
1052 LyXLayout_ptr const & layout = pit->layout();
1054 if (pit != ownerParagraphs().begin()) {
1056 pit->params().appendix(boost::prior(pit)->params().appendix());
1057 if (!pit->params().appendix() &&
1058 pit->params().startOfAppendix()) {
1059 pit->params().appendix(true);
1060 textclass.counters().reset();
1062 pit->enumdepth = boost::prior(pit)->enumdepth;
1063 pit->itemdepth = boost::prior(pit)->itemdepth;
1065 pit->params().appendix(pit->params().startOfAppendix());
1070 /* Maybe we have to increment the enumeration depth.
1071 * BUT, enumeration in a footnote is considered in isolation from its
1072 * surrounding paragraph so don't increment if this is the
1073 * first line of the footnote
1074 * AND, bibliographies can't have their depth changed ie. they
1075 * are always of depth 0
1077 if (pit != ownerParagraphs().begin()
1078 && boost::prior(pit)->getDepth() < pit->getDepth()
1079 && boost::prior(pit)->layout()->labeltype == LABEL_COUNTER_ENUMI
1080 && pit->enumdepth < 3
1081 && layout->labeltype != LABEL_BIBLIO) {
1085 // Maybe we have to decrement the enumeration depth, see note above
1086 if (pit != ownerParagraphs().begin()
1087 && boost::prior(pit)->getDepth() > pit->getDepth()
1088 && layout->labeltype != LABEL_BIBLIO) {
1089 pit->enumdepth = pit->depthHook(pit->getDepth())->enumdepth;
1092 if (!pit->params().labelString().empty()) {
1093 pit->params().labelString(string());
1096 if (layout->margintype == MARGIN_MANUAL) {
1097 if (pit->params().labelWidthString().empty()) {
1098 pit->setLabelWidthString(layout->labelstring());
1101 pit->setLabelWidthString(string());
1104 // is it a layout that has an automatic label?
1105 if (layout->labeltype >= LABEL_COUNTER_CHAPTER) {
1106 int const i = layout->labeltype - LABEL_COUNTER_CHAPTER;
1110 if (i >= 0 && i <= buf->params.secnumdepth) {
1114 textclass.counters().step(layout->latexname());
1116 // Is there a label? Useful for Chapter layout
1117 if (!pit->params().appendix()) {
1118 s << layout->labelstring();
1120 s << layout->labelstring_appendix();
1123 // Use of an integer is here less than elegant. For now.
1124 int head = textclass.maxcounter() - LABEL_COUNTER_CHAPTER;
1125 if (!pit->params().appendix()) {
1126 numbertype = "sectioning";
1128 numbertype = "appendix";
1129 if (pit->isRightToLeftPar(buf->params))
1130 langtype = "hebrew";
1135 s << textclass.counters()
1136 .numberLabel(layout->latexname(),
1137 numbertype, langtype, head);
1139 pit->params().labelString(STRCONV(s.str()));
1141 // reset enum counters
1142 textclass.counters().reset("enum");
1143 } else if (layout->labeltype < LABEL_COUNTER_ENUMI) {
1144 textclass.counters().reset("enum");
1145 } else if (layout->labeltype == LABEL_COUNTER_ENUMI) {
1147 // Yes I know this is a really, really! bad solution
1149 string enumcounter("enum");
1151 switch (pit->enumdepth) {
1160 enumcounter += "iv";
1163 // not a valid enumdepth...
1167 textclass.counters().step(enumcounter);
1169 s << textclass.counters()
1170 .numberLabel(enumcounter, "enumeration");
1171 pit->params().labelString(STRCONV(s.str()));
1173 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
1174 textclass.counters().step("bibitem");
1175 int number = textclass.counters().value("bibitem");
1176 if (pit->bibitem()) {
1177 pit->bibitem()->setCounter(number);
1178 pit->params().labelString(layout->labelstring());
1180 // In biblio should't be following counters but...
1182 string s = layout->labelstring();
1184 // the caption hack:
1185 if (layout->labeltype == LABEL_SENSITIVE) {
1186 ParagraphList::iterator tmppit = pit;
1189 while (tmppit != ownerParagraphs().end() &&
1191 // the single '=' is intended below
1192 && (in = tmppit->inInset()->owner())) {
1193 if (in->lyxCode() == Inset::FLOAT_CODE ||
1194 in->lyxCode() == Inset::WRAP_CODE) {
1198 tmppit = in->parOwner();
1204 = textclass.floats().getType(static_cast<InsetFloat*>(in)->type());
1206 textclass.counters().step(fl.type());
1208 // Doesn't work... yet.
1209 #if USE_BOOST_FORMAT
1210 s = boost::io::str(boost::format(_("%1$s #:")) % fl.name());
1211 // s << boost::format(_("%1$s %1$d:")
1213 // % buf->counters().value(fl.name());
1216 //o << fl.name() << ' ' << buf->counters().value(fl.name()) << ":";
1217 o << fl.name() << " #:";
1218 s = STRCONV(o.str());
1221 // par->SetLayout(0);
1222 // s = layout->labelstring;
1223 s = _("Senseless: ");
1226 pit->params().labelString(s);
1228 // reset the enumeration counter. They are always reset
1229 // when there is any other layout between
1230 // Just fall-through between the cases so that all
1231 // enum counters deeper than enumdepth is also reset.
1232 switch (pit->enumdepth) {
1234 textclass.counters().reset("enumi");
1236 textclass.counters().reset("enumii");
1238 textclass.counters().reset("enumiii");
1240 textclass.counters().reset("enumiv");
1246 // Updates all counters. Paragraphs with changed label string will be rebroken
1247 void LyXText::updateCounters()
1249 RowList::iterator rowit = rows().begin();
1250 ParagraphList::iterator pit = rowit->par();
1252 // CHECK if this is really needed. (Lgb)
1253 bv()->buffer()->params.getLyXTextClass().counters().reset();
1255 while (pit != ownerParagraphs().end()) {
1256 while (rowit->par() != pit)
1259 string const oldLabel = pit->params().labelString();
1262 if (pit != ownerParagraphs().begin())
1263 maxdepth = boost::prior(pit)->getMaxDepthAfter();
1265 if (pit->params().depth() > maxdepth)
1266 pit->params().depth(maxdepth);
1268 // setCounter can potentially change the labelString.
1269 setCounter(bv()->buffer(), &*pit);
1271 string const & newLabel = pit->params().labelString();
1273 if (oldLabel.empty() && !newLabel.empty()) {
1274 removeParagraph(rowit);
1275 appendParagraph(rowit);
1283 void LyXText::insertInset(Inset * inset)
1285 if (!cursor.par()->insetAllowed(inset->lyxCode()))
1287 setUndo(bv(), Undo::FINISH, &*cursor.par(),
1288 &*boost::next(cursor.par()));
1290 cursor.par()->insertInset(cursor.pos(), inset);
1291 // Just to rebreak and refresh correctly.
1292 // The character will not be inserted a second time
1293 insertChar(Paragraph::META_INSET);
1294 // If we enter a highly editable inset the cursor should be to before
1295 // the inset. This couldn't happen before as Undo was not handled inside
1296 // inset now after the Undo LyX tries to call inset->Edit(...) again
1297 // and cannot do this as the cursor is behind the inset and GetInset
1298 // does not return the inset!
1299 if (isHighlyEditableInset(inset)) {
1306 void LyXText::copyEnvironmentType()
1308 copylayouttype = cursor.par()->layout()->name();
1312 void LyXText::pasteEnvironmentType()
1314 // do nothing if there has been no previous copyEnvironmentType()
1315 if (!copylayouttype.empty())
1316 setLayout(copylayouttype);
1320 void LyXText::cutSelection(bool doclear, bool realcut)
1322 // Stuff what we got on the clipboard. Even if there is no selection.
1324 // There is a problem with having the stuffing here in that the
1325 // larger the selection the slower LyX will get. This can be
1326 // solved by running the line below only when the selection has
1327 // finished. The solution used currently just works, to make it
1328 // faster we need to be more clever and probably also have more
1329 // calls to stuffClipboard. (Lgb)
1330 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1332 // This doesn't make sense, if there is no selection
1333 if (!selection.set())
1336 // OK, we have a selection. This is always between selection.start
1337 // and selection.end
1339 // make sure that the depth behind the selection are restored, too
1340 Paragraph * endpar = selection.end.par()->next();
1341 Paragraph * undoendpar = endpar;
1343 if (endpar && endpar->getDepth()) {
1344 while (endpar && endpar->getDepth()) {
1345 endpar = endpar->next();
1346 undoendpar = endpar;
1348 } else if (endpar) {
1349 endpar = endpar->next(); // because of parindents etc.
1352 setUndo(bv(), Undo::DELETE,
1353 &*selection.start.par(), undoendpar);
1355 // there are two cases: cut only within one paragraph or
1356 // more than one paragraph
1357 if (selection.start.par() == selection.end.par()) {
1358 // only within one paragraph
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,
1365 selection.end.pos(pos);
1367 endpar = &*selection.end.par();
1368 int pos = selection.end.pos();
1369 CutAndPaste::cutSelection(&*selection.start.par(), &endpar,
1370 selection.start.pos(), pos,
1371 bv()->buffer()->params.textclass,
1374 selection.end.par(endpar);
1375 selection.end.pos(pos);
1376 cursor.pos(selection.end.pos());
1378 endpar = endpar->next();
1380 // sometimes necessary
1382 selection.start.par()->stripLeadingSpaces();
1384 redoParagraphs(selection.start, endpar);
1386 // cutSelection can invalidate the cursor so we need to set
1388 // we prefer the end for when tracking changes
1389 cursor = selection.end;
1391 // need a valid cursor. (Lgb)
1394 setCursor(cursor.par(), cursor.pos());
1395 selection.cursor = cursor;
1400 void LyXText::copySelection()
1402 // stuff the selection onto the X clipboard, from an explicit copy request
1403 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1405 // this doesnt make sense, if there is no selection
1406 if (!selection.set())
1409 // ok we have a selection. This is always between selection.start
1410 // and sel_end cursor
1412 // copy behind a space if there is one
1413 while (selection.start.par()->size() > selection.start.pos()
1414 && selection.start.par()->isLineSeparator(selection.start.pos())
1415 && (selection.start.par() != selection.end.par()
1416 || selection.start.pos() < selection.end.pos()))
1417 selection.start.pos(selection.start.pos() + 1);
1419 CutAndPaste::copySelection(&*selection.start.par(),
1420 &*selection.end.par(),
1421 selection.start.pos(), selection.end.pos(),
1422 bv()->buffer()->params.textclass);
1426 void LyXText::pasteSelection()
1428 // this does not make sense, if there is nothing to paste
1429 if (!CutAndPaste::checkPastePossible())
1432 setUndo(bv(), Undo::INSERT,
1433 &*cursor.par(), &*boost::next(cursor.par()));
1436 ParagraphList::iterator actpit = cursor.par();
1437 int pos = cursor.pos();
1439 Paragraph * actpar = &*actpit;
1440 CutAndPaste::pasteSelection(&actpar, &endpar, pos,
1441 bv()->buffer()->params.textclass);
1443 redoParagraphs(cursor, endpar);
1445 setCursor(cursor.par(), cursor.pos());
1448 selection.cursor = cursor;
1449 setCursor(actpit, pos);
1455 void LyXText::setSelectionRange(lyx::pos_type length)
1460 selection.cursor = cursor;
1467 // simple replacing. The font of the first selected character is used
1468 void LyXText::replaceSelectionWithString(string const & str)
1470 setCursorParUndo(bv());
1473 if (!selection.set()) { // create a dummy selection
1474 selection.end = cursor;
1475 selection.start = cursor;
1478 // Get font setting before we cut
1479 pos_type pos = selection.end.pos();
1480 LyXFont const font = selection.start.par()
1481 ->getFontSettings(bv()->buffer()->params,
1482 selection.start.pos());
1484 // Insert the new string
1485 for (string::const_iterator cit = str.begin(); cit != str.end(); ++cit) {
1486 selection.end.par()->insertChar(pos, (*cit), font);
1490 // Cut the selection
1491 cutSelection(true, false);
1497 // needed to insert the selection
1498 void LyXText::insertStringAsLines(string const & str)
1500 ParagraphList::iterator pit = cursor.par();
1501 pos_type pos = cursor.pos();
1502 ParagraphList::iterator endpit = boost::next(cursor.par());
1504 setCursorParUndo(bv());
1506 // only to be sure, should not be neccessary
1509 Paragraph * par = &*pit;
1510 bv()->buffer()->insertStringAsLines(par, pos, current_font, str);
1512 redoParagraphs(cursor, &*endpit);
1513 setCursor(cursor.par(), cursor.pos());
1514 selection.cursor = cursor;
1515 setCursor(pit, pos);
1520 // turns double-CR to single CR, others where converted into one
1521 // blank. Then InsertStringAsLines is called
1522 void LyXText::insertStringAsParagraphs(string const & str)
1524 string linestr(str);
1525 bool newline_inserted = false;
1526 for (string::size_type i = 0; i < linestr.length(); ++i) {
1527 if (linestr[i] == '\n') {
1528 if (newline_inserted) {
1529 // we know that \r will be ignored by
1530 // InsertStringA. Of course, it is a dirty
1531 // trick, but it works...
1532 linestr[i - 1] = '\r';
1536 newline_inserted = true;
1538 } else if (IsPrintable(linestr[i])) {
1539 newline_inserted = false;
1542 insertStringAsLines(linestr);
1546 void LyXText::checkParagraph(ParagraphList::iterator pit, pos_type pos)
1548 LyXCursor tmpcursor;
1552 RowList::iterator row = getRow(pit, pos, y);
1553 RowList::iterator beg = rows().begin();
1555 // is there a break one row above
1557 && boost::prior(row)->par() == row->par()) {
1558 z = rowBreakPoint(*boost::prior(row));
1559 if (z >= row->pos()) {
1560 // set the dimensions of the row above
1561 y -= boost::prior(row)->height();
1564 breakAgain(boost::prior(row));
1566 // set the cursor again. Otherwise
1567 // dangling pointers are possible
1568 setCursor(cursor.par(), cursor.pos(),
1569 false, cursor.boundary());
1570 selection.cursor = cursor;
1575 int const tmpheight = row->height();
1576 pos_type const tmplast = lastPos(*this, row);
1579 if (row->height() == tmpheight && lastPos(*this, row) == tmplast) {
1580 postRowPaint(row, y);
1585 // check the special right address boxes
1586 if (pit->layout()->margintype == MARGIN_RIGHT_ADDRESS_BOX) {
1593 redoDrawingOfParagraph(tmpcursor);
1596 // set the cursor again. Otherwise dangling pointers are possible
1597 // also set the selection
1599 if (selection.set()) {
1601 setCursorIntern(selection.cursor.par(), selection.cursor.pos(),
1602 false, selection.cursor.boundary());
1603 selection.cursor = cursor;
1604 setCursorIntern(selection.start.par(),
1605 selection.start.pos(),
1606 false, selection.start.boundary());
1607 selection.start = cursor;
1608 setCursorIntern(selection.end.par(),
1609 selection.end.pos(),
1610 false, selection.end.boundary());
1611 selection.end = cursor;
1612 setCursorIntern(last_sel_cursor.par(),
1613 last_sel_cursor.pos(),
1614 false, last_sel_cursor.boundary());
1615 last_sel_cursor = cursor;
1618 setCursorIntern(cursor.par(), cursor.pos(),
1619 false, cursor.boundary());
1623 // returns false if inset wasn't found
1624 bool LyXText::updateInset(Inset * inset)
1626 // first check the current paragraph
1627 int pos = cursor.par()->getPositionOfInset(inset);
1629 checkParagraph(&*cursor.par(), pos);
1633 // check every paragraph
1635 ParagraphList::iterator par = ownerParagraphs().begin();
1636 ParagraphList::iterator end = ownerParagraphs().end();
1639 pos = par->getPositionOfInset(inset);
1641 checkParagraph(&*par, pos);
1645 } while (par != end);
1651 bool LyXText::setCursor(ParagraphList::iterator pit,
1653 bool setfont, bool boundary)
1655 LyXCursor old_cursor = cursor;
1656 setCursorIntern(pit, pos, setfont, boundary);
1657 return deleteEmptyParagraphMechanism(old_cursor);
1661 void LyXText::setCursor(LyXCursor & cur, ParagraphList::iterator pit,
1662 pos_type pos, bool boundary)
1664 lyx::Assert(pit != ownerParagraphs().end());
1668 cur.boundary(boundary);
1670 // get the cursor y position in text
1672 RowList::iterator row = getRow(pit, pos, y);
1673 RowList::iterator beg = rows().begin();
1675 RowList::iterator old_row = row;
1677 // if we are before the first char of this row and are still in the
1678 // same paragraph and there is a previous row then put the cursor on
1679 // the end of the previous row
1680 cur.iy(y + row->baseline());
1682 if (row != beg && pos &&
1683 boost::prior(row)->par() == row->par() &&
1684 pos < pit->size() &&
1685 pit->getChar(pos) == Paragraph::META_INSET &&
1686 (ins = pit->getInset(pos)) && (ins->needFullRow() || ins->display()))
1693 // y is now the beginning of the cursor row
1694 y += row->baseline();
1695 // y is now the cursor baseline
1698 pos_type last = lastPrintablePos(*this, old_row);
1700 // None of these should happen, but we're scaredy-cats
1701 if (pos > pit->size()) {
1702 lyxerr << "dont like 1 please report" << endl;
1705 } else if (pos > last + 1) {
1706 lyxerr << "dont like 2 please report" << endl;
1707 // This shouldn't happen.
1710 } else if (pos < row->pos()) {
1711 lyxerr << "dont like 3 please report" << endl;
1716 // now get the cursors x position
1717 float x = getCursorX(row, pos, last, boundary);
1720 if (old_row != row) {
1721 x = getCursorX(old_row, pos, last, boundary);
1725 /* We take out this for the time being because 1) the redraw code is not
1726 prepared to this yet and 2) because some good policy has yet to be decided
1727 while editting: for instance how to act on rows being created/deleted
1731 //if the cursor is in a visible row, anchor to it
1733 if (topy < y && y < topy + bv()->workHeight())
1739 float LyXText::getCursorX(RowList::iterator rit,
1740 pos_type pos, pos_type last, bool boundary) const
1742 pos_type cursor_vpos = 0;
1744 float fill_separator;
1746 float fill_label_hfill;
1747 // This call HAS to be here because of the BidiTables!!!
1748 prepareToPrint(rit, x, fill_separator, fill_hfill,
1751 if (last < rit->pos())
1752 cursor_vpos = rit->pos();
1753 else if (pos > last && !boundary)
1754 cursor_vpos = (rit->par()->isRightToLeftPar(bv()->buffer()->params))
1755 ? rit->pos() : last + 1;
1756 else if (pos > rit->pos() &&
1757 (pos > last || boundary))
1758 /// Place cursor after char at (logical) position pos - 1
1759 cursor_vpos = (bidi_level(pos - 1) % 2 == 0)
1760 ? log2vis(pos - 1) + 1 : log2vis(pos - 1);
1762 /// Place cursor before char at (logical) position pos
1763 cursor_vpos = (bidi_level(pos) % 2 == 0)
1764 ? log2vis(pos) : log2vis(pos) + 1;
1766 pos_type body_pos = rit->par()->beginningOfBody();
1767 if ((body_pos > 0) &&
1768 ((body_pos - 1 > last) ||
1769 !rit->par()->isLineSeparator(body_pos - 1)))
1772 for (pos_type vpos = rit->pos(); vpos < cursor_vpos; ++vpos) {
1773 pos_type pos = vis2log(vpos);
1774 if (body_pos > 0 && pos == body_pos - 1) {
1775 x += fill_label_hfill +
1776 font_metrics::width(
1777 rit->par()->layout()->labelsep,
1778 getLabelFont(bv()->buffer(),
1780 if (rit->par()->isLineSeparator(body_pos - 1))
1781 x -= singleWidth(rit->par(), body_pos - 1);
1784 if (hfillExpansion(*this, rit, pos)) {
1785 x += singleWidth(rit->par(), pos);
1786 if (pos >= body_pos)
1789 x += fill_label_hfill;
1790 } else if (rit->par()->isSeparator(pos)) {
1791 x += singleWidth(rit->par(), pos);
1792 if (pos >= body_pos)
1793 x += fill_separator;
1795 x += singleWidth(rit->par(), pos);
1801 void LyXText::setCursorIntern(ParagraphList::iterator pit,
1802 pos_type pos, bool setfont, bool boundary)
1804 InsetText * it = static_cast<InsetText *>(pit->inInset());
1806 if (it != inset_owner) {
1807 lyxerr[Debug::INSETS] << "InsetText is " << it
1809 << "inset_owner is "
1810 << inset_owner << endl;
1811 #ifdef WITH_WARNINGS
1812 #warning I believe this code is wrong. (Lgb)
1813 #warning Jürgen, have a look at this. (Lgb)
1814 #warning Hmmm, I guess you are right but we
1815 #warning should verify when this is needed
1817 // Jürgen, would you like to have a look?
1818 // I guess we need to move the outer cursor
1819 // and open and lock the inset (bla bla bla)
1820 // stuff I don't know... so can you have a look?
1822 // I moved the lyxerr stuff in here so we can see if
1823 // this is actually really needed and where!
1825 // it->getLyXText(bv())->setCursorIntern(bv(), par, pos, setfont, boundary);
1830 setCursor(cursor, pit, pos, boundary);
1836 void LyXText::setCurrentFont()
1838 pos_type pos = cursor.pos();
1839 if (cursor.boundary() && pos > 0)
1843 if (pos == cursor.par()->size())
1845 else // potentional bug... BUG (Lgb)
1846 if (cursor.par()->isSeparator(pos)) {
1847 if (pos > cursor.row()->pos() &&
1848 bidi_level(pos) % 2 ==
1849 bidi_level(pos - 1) % 2)
1851 else if (pos + 1 < cursor.par()->size())
1857 cursor.par()->getFontSettings(bv()->buffer()->params, pos);
1858 real_current_font = getFont(bv()->buffer(), cursor.par(), pos);
1860 if (cursor.pos() == cursor.par()->size() &&
1861 isBoundary(bv()->buffer(), &*cursor.par(), cursor.pos()) &&
1862 !cursor.boundary()) {
1863 Language const * lang =
1864 cursor.par()->getParLanguage(bv()->buffer()->params);
1865 current_font.setLanguage(lang);
1866 current_font.setNumber(LyXFont::OFF);
1867 real_current_font.setLanguage(lang);
1868 real_current_font.setNumber(LyXFont::OFF);
1873 // returns the column near the specified x-coordinate of the row
1874 // x is set to the real beginning of this column
1876 LyXText::getColumnNearX(RowList::iterator rit, int & x, bool & boundary) const
1879 float fill_separator;
1881 float fill_label_hfill;
1883 prepareToPrint(rit, tmpx, fill_separator,
1884 fill_hfill, fill_label_hfill);
1886 pos_type vc = rit->pos();
1887 pos_type last = lastPrintablePos(*this, rit);
1890 LyXLayout_ptr const & layout = rit->par()->layout();
1892 bool left_side = false;
1894 pos_type body_pos = rit->par()->beginningOfBody();
1895 float last_tmpx = tmpx;
1898 (body_pos - 1 > last ||
1899 !rit->par()->isLineSeparator(body_pos - 1)))
1902 // check for empty row
1903 if (!rit->par()->size()) {
1908 while (vc <= last && tmpx <= x) {
1911 if (body_pos > 0 && c == body_pos - 1) {
1912 tmpx += fill_label_hfill +
1913 font_metrics::width(layout->labelsep,
1914 getLabelFont(bv()->buffer(), &*rit->par()));
1915 if (rit->par()->isLineSeparator(body_pos - 1))
1916 tmpx -= singleWidth(rit->par(), body_pos - 1);
1919 if (hfillExpansion(*this, rit, c)) {
1920 tmpx += singleWidth(rit->par(), c);
1924 tmpx += fill_label_hfill;
1925 } else if (rit->par()->isSeparator(c)) {
1926 tmpx += singleWidth(rit->par(), c);
1928 tmpx+= fill_separator;
1930 tmpx += singleWidth(rit->par(), c);
1935 if ((tmpx + last_tmpx) / 2 > x) {
1940 if (vc > last + 1) // This shouldn't happen.
1944 // This (rtl_support test) is not needed, but gives
1945 // some speedup if rtl_support=false
1946 bool const lastrow = lyxrc.rtl_support &&
1947 (boost::next(rit) == rowlist_.end() ||
1948 boost::next(rit)->par() != rit->par());
1949 // If lastrow is false, we don't need to compute
1950 // the value of rtl.
1951 bool const rtl = (lastrow)
1952 ? rit->par()->isRightToLeftPar(bv()->buffer()->params)
1955 ((rtl && left_side && vc == rit->pos() && x < tmpx - 5) ||
1956 (!rtl && !left_side && vc == last + 1 && x > tmpx + 5)))
1958 else if (vc == rit->pos()) {
1960 if (bidi_level(c) % 2 == 1)
1963 c = vis2log(vc - 1);
1964 bool const rtl = (bidi_level(c) % 2 == 1);
1965 if (left_side == rtl) {
1967 boundary = isBoundary(bv()->buffer(), &*rit->par(), c);
1971 if (rit->pos() <= last && c > last
1972 && rit->par()->isNewline(last)) {
1973 if (bidi_level(last) % 2 == 0)
1974 tmpx -= singleWidth(rit->par(), last);
1976 tmpx += singleWidth(rit->par(), last);
1986 void LyXText::setCursorFromCoordinates(int x, int y)
1988 LyXCursor old_cursor = cursor;
1990 setCursorFromCoordinates(cursor, x, y);
1992 deleteEmptyParagraphMechanism(old_cursor);
1999 * return true if the cursor given is at the end of a row,
2000 * and the next row is filled by an inset that spans an entire
2003 bool beforeFullRowInset(LyXText & lt, RowList::iterator row,
2005 if (boost::next(row) == lt.rows().end())
2007 Row const & next = *boost::next(row);
2009 if (next.pos() != cur.pos() || next.par() != cur.par())
2011 if (!cur.par()->isInset(cur.pos()))
2013 Inset const * inset = cur.par()->getInset(cur.pos());
2014 if (inset->needFullRow() || inset->display())
2021 void LyXText::setCursorFromCoordinates(LyXCursor & cur, int x, int y)
2023 // Get the row first.
2025 RowList::iterator row = getRowNearY(y);
2027 pos_type const column = getColumnNearX(row, x, bound);
2028 cur.par(&*row->par());
2029 cur.pos(row->pos() + column);
2031 cur.y(y + row->baseline());
2034 if (beforeFullRowInset(*this, row, cur)) {
2035 pos_type last = lastPrintablePos(*this, row);
2036 float x = getCursorX(boost::next(row), cur.pos(), last, bound);
2038 cur.iy(y + row->height() + boost::next(row)->baseline());
2039 cur.irow(boost::next(row));
2045 cur.boundary(bound);
2049 void LyXText::cursorLeft(bool internal)
2051 if (cursor.pos() > 0) {
2052 bool boundary = cursor.boundary();
2053 setCursor(cursor.par(), cursor.pos() - 1, true, false);
2054 if (!internal && !boundary &&
2055 isBoundary(bv()->buffer(), &*cursor.par(), cursor.pos() + 1))
2056 setCursor(cursor.par(), cursor.pos() + 1, true, true);
2057 } else if (cursor.par()->previous()) { // steps into the above paragraph.
2058 Paragraph * par = cursor.par()->previous();
2059 setCursor(par, par->size());
2064 void LyXText::cursorRight(bool internal)
2066 bool const at_end = (cursor.pos() == cursor.par()->size());
2067 bool const at_newline = !at_end &&
2068 cursor.par()->isNewline(cursor.pos());
2070 if (!internal && cursor.boundary() && !at_newline)
2071 setCursor(cursor.par(), cursor.pos(), true, false);
2073 setCursor(cursor.par(), cursor.pos() + 1, true, false);
2075 isBoundary(bv()->buffer(), &*cursor.par(), cursor.pos()))
2076 setCursor(cursor.par(), cursor.pos(), true, true);
2077 } else if (cursor.par()->next())
2078 setCursor(cursor.par()->next(), 0);
2082 void LyXText::cursorUp(bool selecting)
2085 int x = cursor.x_fix();
2086 int y = cursor.y() - cursor.row()->baseline() - 1;
2087 setCursorFromCoordinates(x, y);
2090 int y1 = cursor.iy() - topy;
2093 Inset * inset_hit = checkInsetHit(x, y1);
2094 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2095 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2099 setCursorFromCoordinates(bv(), cursor.x_fix(),
2100 cursor.y() - cursor.row()->baseline() - 1);
2105 void LyXText::cursorDown(bool selecting)
2108 int x = cursor.x_fix();
2109 int y = cursor.y() - cursor.row()->baseline() +
2110 cursor.row()->height() + 1;
2111 setCursorFromCoordinates(x, y);
2112 if (!selecting && cursor.row() == cursor.irow()) {
2114 int y1 = cursor.iy() - topy;
2117 Inset * inset_hit = checkInsetHit(x, y1);
2118 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2119 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2123 setCursorFromCoordinates(bv(), cursor.x_fix(),
2124 cursor.y() - cursor.row()->baseline()
2125 + cursor.row()->height() + 1);
2130 void LyXText::cursorUpParagraph()
2132 if (cursor.pos() > 0) {
2133 setCursor(cursor.par(), 0);
2135 else if (cursor.par()->previous()) {
2136 setCursor(cursor.par()->previous(), 0);
2141 void LyXText::cursorDownParagraph()
2143 if (cursor.par()->next()) {
2144 setCursor(cursor.par()->next(), 0);
2146 setCursor(cursor.par(), cursor.par()->size());
2150 // fix the cursor `cur' after a characters has been deleted at `where'
2151 // position. Called by deleteEmptyParagraphMechanism
2152 void LyXText::fixCursorAfterDelete(LyXCursor & cur,
2153 LyXCursor const & where)
2155 // if cursor is not in the paragraph where the delete occured,
2157 if (cur.par() != where.par())
2160 // if cursor position is after the place where the delete occured,
2162 if (cur.pos() > where.pos())
2163 cur.pos(cur.pos()-1);
2165 // check also if we don't want to set the cursor on a spot behind the
2166 // pagragraph because we erased the last character.
2167 if (cur.pos() > cur.par()->size())
2168 cur.pos(cur.par()->size());
2170 // recompute row et al. for this cursor
2171 setCursor(cur, cur.par(), cur.pos(), cur.boundary());
2175 bool LyXText::deleteEmptyParagraphMechanism(LyXCursor const & old_cursor)
2177 // Would be wrong to delete anything if we have a selection.
2178 if (selection.set())
2181 // We allow all kinds of "mumbo-jumbo" when freespacing.
2182 if (old_cursor.par()->layout()->free_spacing
2183 || old_cursor.par()->isFreeSpacing()) {
2187 /* Ok I'll put some comments here about what is missing.
2188 I have fixed BackSpace (and thus Delete) to not delete
2189 double-spaces automagically. I have also changed Cut,
2190 Copy and Paste to hopefully do some sensible things.
2191 There are still some small problems that can lead to
2192 double spaces stored in the document file or space at
2193 the beginning of paragraphs. This happens if you have
2194 the cursor betwenn to spaces and then save. Or if you
2195 cut and paste and the selection have a space at the
2196 beginning and then save right after the paste. I am
2197 sure none of these are very hard to fix, but I will
2198 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
2199 that I can get some feedback. (Lgb)
2202 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
2203 // delete the LineSeparator.
2206 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
2207 // delete the LineSeparator.
2210 // If the pos around the old_cursor were spaces, delete one of them.
2211 if (old_cursor.par() != cursor.par()
2212 || old_cursor.pos() != cursor.pos()) {
2213 // Only if the cursor has really moved
2215 if (old_cursor.pos() > 0
2216 && old_cursor.pos() < old_cursor.par()->size()
2217 && old_cursor.par()->isLineSeparator(old_cursor.pos())
2218 && old_cursor.par()->isLineSeparator(old_cursor.pos() - 1)) {
2219 old_cursor.par()->erase(old_cursor.pos() - 1);
2220 redoParagraphs(old_cursor, old_cursor.par()->next());
2222 #ifdef WITH_WARNINGS
2223 #warning This will not work anymore when we have multiple views of the same buffer
2224 // In this case, we will have to correct also the cursors held by
2225 // other bufferviews. It will probably be easier to do that in a more
2226 // automated way in LyXCursor code. (JMarc 26/09/2001)
2228 // correct all cursors held by the LyXText
2229 fixCursorAfterDelete(cursor, old_cursor);
2230 fixCursorAfterDelete(selection.cursor,
2232 fixCursorAfterDelete(selection.start,
2234 fixCursorAfterDelete(selection.end, old_cursor);
2235 fixCursorAfterDelete(last_sel_cursor,
2237 fixCursorAfterDelete(toggle_cursor, old_cursor);
2238 fixCursorAfterDelete(toggle_end_cursor,
2244 // don't delete anything if this is the ONLY paragraph!
2245 if (!old_cursor.par()->next() && !old_cursor.par()->previous())
2248 // Do not delete empty paragraphs with keepempty set.
2249 if (old_cursor.par()->layout()->keepempty)
2252 // only do our magic if we changed paragraph
2253 if (old_cursor.par() == cursor.par())
2256 // record if we have deleted a paragraph
2257 // we can't possibly have deleted a paragraph before this point
2258 bool deleted = false;
2260 if ((old_cursor.par()->empty()
2261 || (old_cursor.par()->size() == 1
2262 && old_cursor.par()->isLineSeparator(0)))) {
2263 // ok, we will delete anything
2264 LyXCursor tmpcursor;
2268 if (old_cursor.row() != rows().begin()) {
2270 prevrow = boost::prior(old_cursor.row());
2271 const_cast<LyXText *>(this)->postPaint(old_cursor.y() - old_cursor.row()->baseline() - prevrow->height());
2273 cursor = old_cursor; // that undo can restore the right cursor position
2274 Paragraph * endpar = old_cursor.par()->next();
2275 #warning FIXME This if clause looks very redundant. (Lgb)
2276 if (endpar && endpar->getDepth()) {
2277 while (endpar && endpar->getDepth()) {
2278 endpar = endpar->next();
2281 setUndo(bv(), Undo::DELETE, &*old_cursor.par(), endpar);
2285 removeRow(old_cursor.row());
2286 if (ownerParagraphs().begin() == old_cursor.par()) {
2287 ownerParagraph(&*boost::next(ownerParagraphs().begin()));
2289 #warning FIXME Do the proper ParagraphList operation here (Lgb)
2291 delete &*old_cursor.par();
2293 /* Breakagain the next par. Needed because of
2294 * the parindent that can occur or dissappear.
2295 * The next row can change its height, if
2296 * there is another layout before */
2297 if (boost::next(prevrow) != rows().end()) {
2298 breakAgain(boost::next(prevrow));
2301 setHeightOfRow(prevrow);
2303 RowList::iterator nextrow = boost::next(old_cursor.row());
2304 const_cast<LyXText *>(this)->postPaint(
2305 old_cursor.y() - old_cursor.row()->baseline());
2308 cursor = old_cursor; // that undo can restore the right cursor position
2309 Paragraph * endpar = old_cursor.par()->next();
2310 if (endpar && endpar->getDepth()) {
2311 while (endpar && endpar->getDepth()) {
2312 endpar = endpar->next();
2315 setUndo(bv(), Undo::DELETE, &*old_cursor.par(), endpar);
2319 removeRow(old_cursor.row());
2321 if (ownerParagraphs().begin() == old_cursor.par()) {
2322 ownerParagraph(&*boost::next(ownerParagraphs().begin()));
2324 #warning FIXME Do the proper ParagraphList operations here. (Lgb)
2325 delete &*old_cursor.par();
2327 /* Breakagain the next par. Needed because of
2328 the parindent that can occur or dissappear.
2329 The next row can change its height, if
2330 there is another layout before */
2331 if (nextrow != rows().end()) {
2332 breakAgain(nextrow);
2338 setCursorIntern(cursor.par(), cursor.pos());
2340 if (selection.cursor.par() == old_cursor.par()
2341 && selection.cursor.pos() == old_cursor.pos()) {
2342 // correct selection
2343 selection.cursor = cursor;
2347 if (old_cursor.par()->stripLeadingSpaces()) {
2348 redoParagraphs(old_cursor,
2349 old_cursor.par()->next());
2351 setCursorIntern(cursor.par(), cursor.pos());
2352 selection.cursor = cursor;
2359 ParagraphList & LyXText::ownerParagraphs() const
2362 return inset_owner->paragraphs;
2364 return bv_owner->buffer()->paragraphs;
2368 void LyXText::ownerParagraph(Paragraph * p) const
2371 inset_owner->paragraph(p);
2373 bv_owner->buffer()->paragraphs.set(p);
2378 void LyXText::ownerParagraph(int id, Paragraph * p) const
2380 Paragraph * op = bv_owner->buffer()->getParFromID(id);
2381 if (op && op->inInset()) {
2382 static_cast<InsetText *>(op->inInset())->paragraph(p);
2389 LyXText::refresh_status LyXText::refreshStatus() const
2391 return refresh_status_;
2395 void LyXText::clearPaint()
2397 refresh_status_ = REFRESH_NONE;
2398 refresh_row = rows().end();
2403 void LyXText::postPaint(int start_y)
2405 refresh_status old = refresh_status_;
2407 refresh_status_ = REFRESH_AREA;
2408 refresh_row = rows().end();
2410 if (old != REFRESH_NONE && refresh_y < start_y)
2413 refresh_y = start_y;
2418 // We are an inset's lyxtext. Tell the top-level lyxtext
2419 // it needs to update the row we're in.
2420 LyXText * t = bv()->text;
2421 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2425 // FIXME: we should probably remove this y parameter,
2426 // make refresh_y be 0, and use row->y etc.
2427 void LyXText::postRowPaint(RowList::iterator rit, int start_y)
2429 if (refresh_status_ != REFRESH_NONE && refresh_y < start_y) {
2430 refresh_status_ = REFRESH_AREA;
2433 refresh_y = start_y;
2436 if (refresh_status_ == REFRESH_AREA)
2439 refresh_status_ = REFRESH_ROW;
2445 // We are an inset's lyxtext. Tell the top-level lyxtext
2446 // it needs to update the row we're in.
2447 LyXText * t = bv()->text;
2448 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2452 bool LyXText::isInInset() const
2454 // Sub-level has non-null bv owner and
2455 // non-null inset owner.
2456 return inset_owner != 0 && bv_owner != 0;
2460 int defaultRowHeight()
2462 LyXFont const font(LyXFont::ALL_SANE);
2463 return int(font_metrics::maxAscent(font)
2464 + font_metrics::maxDescent(font) * 1.5);