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"
33 #include "insets/inseterror.h"
34 #include "insets/insetbibitem.h"
35 #include "insets/insetspecialchar.h"
36 #include "insets/insettext.h"
37 #include "insets/insetfloat.h"
38 #include "insets/insetwrap.h"
40 #include "support/LAssert.h"
41 #include "support/textutils.h"
42 #include "support/lstrings.h"
44 #include "support/BoostFormat.h"
54 LyXText::LyXText(BufferView * bv)
55 : height(0), width(0), anchor_row_offset_(0),
56 inset_owner(0), the_locking_inset(0), bv_owner(bv)
58 anchor_row_ = rows().end();
59 need_break_row = rows().end();
60 refresh_row = rows().end();
65 LyXText::LyXText(BufferView * bv, InsetText * inset)
66 : height(0), width(0), anchor_row_offset_(0),
67 inset_owner(inset), the_locking_inset(0), bv_owner(bv)
69 anchor_row_ = rows().end();
70 need_break_row = rows().end();
71 refresh_row = rows().end();
76 void LyXText::init(BufferView * bview, bool reinit)
80 need_break_row = rows().end();
82 copylayouttype.erase();
85 } else if (!rowlist_.empty())
88 Paragraph * par = ownerParagraph();
89 current_font = getFont(bview->buffer(), par, 0);
92 insertParagraph(par, rowlist_.end());
95 setCursorIntern(rowlist_.begin()->par(), 0);
96 selection.cursor = cursor;
104 LyXFont const realizeFont(LyXFont const & font,
108 LyXTextClass const & tclass = buf->params.getLyXTextClass();
109 LyXFont tmpfont(font);
110 Paragraph::depth_type par_depth = par->getDepth();
112 // Resolve against environment font information
113 while (par && par_depth && !tmpfont.resolved()) {
114 par = par->outerHook();
116 tmpfont.realize(par->layout()->font);
117 par_depth = par->getDepth();
121 tmpfont.realize(tclass.defaultfont());
129 // Gets the fully instantiated font at a given position in a paragraph
130 // Basically the same routine as Paragraph::getFont() in paragraph.C.
131 // The difference is that this one is used for displaying, and thus we
132 // are allowed to make cosmetic improvements. For instance make footnotes
134 // If position is -1, we get the layout font of the paragraph.
135 // If position is -2, we get the font of the manual label of the paragraph.
136 LyXFont const LyXText::getFont(Buffer const * buf, Paragraph * par,
139 lyx::Assert(pos >= 0);
141 LyXLayout_ptr const & layout = par->layout();
143 // We specialize the 95% common case:
144 if (!par->getDepth()) {
145 if (layout->labeltype == LABEL_MANUAL
146 && pos < par->beginningOfBody()) {
148 LyXFont f = par->getFontSettings(buf->params, pos);
150 par->inInset()->getDrawFont(f);
151 return f.realize(layout->reslabelfont);
153 LyXFont f = par->getFontSettings(buf->params, pos);
155 par->inInset()->getDrawFont(f);
156 return f.realize(layout->resfont);
160 // The uncommon case need not be optimized as much
164 if (pos < par->beginningOfBody()) {
166 layoutfont = layout->labelfont;
169 layoutfont = layout->font;
172 LyXFont tmpfont = par->getFontSettings(buf->params, pos);
173 tmpfont.realize(layoutfont);
176 par->inInset()->getDrawFont(tmpfont);
178 return realizeFont(tmpfont, buf, par);
182 LyXFont const LyXText::getLayoutFont(Buffer const * buf, Paragraph * par) const
184 LyXLayout_ptr const & layout = par->layout();
186 if (!par->getDepth()) {
187 return layout->resfont;
190 return realizeFont(layout->font, buf, par);
194 LyXFont const LyXText::getLabelFont(Buffer const * buf, Paragraph * par) const
196 LyXLayout_ptr const & layout = par->layout();
198 if (!par->getDepth()) {
199 return layout->reslabelfont;
202 return realizeFont(layout->labelfont, buf, par);
206 void LyXText::setCharFont(Paragraph * par,
207 pos_type pos, LyXFont const & fnt,
210 Buffer const * buf = bv()->buffer();
211 LyXFont font = getFont(buf, par, pos);
212 font.update(fnt, buf->params.language, toggleall);
213 // Let the insets convert their font
214 if (par->isInset(pos)) {
215 Inset * inset = par->getInset(pos);
216 if (isEditableInset(inset)) {
217 UpdatableInset * uinset =
218 static_cast<UpdatableInset *>(inset);
219 uinset->setFont(bv(), fnt, toggleall, true);
223 // Plug thru to version below:
224 setCharFont(buf, par, pos, font);
228 void LyXText::setCharFont(Buffer const * buf, Paragraph * par,
229 pos_type pos, LyXFont const & fnt)
233 LyXTextClass const & tclass = buf->params.getLyXTextClass();
234 LyXLayout_ptr const & layout = par->layout();
236 // Get concrete layout font to reduce against
239 if (pos < par->beginningOfBody())
240 layoutfont = layout->labelfont;
242 layoutfont = layout->font;
244 // Realize against environment font information
245 if (par->getDepth()) {
246 Paragraph * tp = par;
247 while (!layoutfont.resolved() && tp && tp->getDepth()) {
248 tp = tp->outerHook();
250 layoutfont.realize(tp->layout()->font);
254 layoutfont.realize(tclass.defaultfont());
256 // Now, reduce font against full layout font
257 font.reduce(layoutfont);
259 par->setFont(pos, font);
263 // removes the row and reset the touched counters
264 void LyXText::removeRow(RowList::iterator rit)
266 /* FIXME: when we cache the bview, this should just
267 * become a postPaint(), I think */
268 if (refresh_row == rit) {
269 if (rit == rows().begin())
270 refresh_row = boost::next(rit);
272 refresh_row = boost::prior(rit);
274 // what about refresh_y
277 if (anchor_row_ == rit) {
278 if (rit != rows().begin()) {
279 anchor_row_ = boost::prior(rit);
280 anchor_row_offset_ += boost::prior(rit)->height();
282 anchor_row_ = boost::next(rit);
283 anchor_row_offset_ -= rit->height();
287 // the text becomes smaller
288 height -= rit->height();
294 // remove all following rows of the paragraph of the specified row.
295 void LyXText::removeParagraph(RowList::iterator rit)
297 Paragraph * tmppar = rit->par();
300 while (rit != rows().end() && rit->par() == tmppar) {
301 RowList::iterator tmprit = boost::next(rit);
308 void LyXText::insertParagraph(Paragraph * par, RowList::iterator rowit)
310 // insert a new row, starting at position 0
311 RowList::iterator rit = rowlist_.insert(rowit, new Row(par, 0));
313 // and now append the whole paragraph before the new row
314 appendParagraph(rit);
318 Inset * LyXText::getInset() const
320 if (cursor.pos() < cursor.par()->size()
321 && cursor.par()->isInset(cursor.pos())) {
322 return cursor.par()->getInset(cursor.pos());
328 void LyXText::toggleInset()
330 Inset * inset = getInset();
331 // is there an editable inset at cursor position?
332 if (!isEditableInset(inset)) {
333 // No, try to see if we are inside a collapsable inset
334 if (inset_owner && inset_owner->owner()
335 && inset_owner->owner()->isOpen()) {
336 bv()->unlockInset(static_cast<UpdatableInset *>(inset_owner->owner()));
337 inset_owner->owner()->close(bv());
338 bv()->getLyXText()->cursorRight(bv());
342 //bv()->owner()->message(inset->editMessage());
344 // do we want to keep this?? (JMarc)
345 if (!isHighlyEditableInset(inset))
346 setCursorParUndo(bv());
348 if (inset->isOpen()) {
354 bv()->updateInset(inset);
358 /* used in setlayout */
359 // Asger is not sure we want to do this...
360 void LyXText::makeFontEntriesLayoutSpecific(Buffer const & buf,
363 LyXLayout_ptr const & layout = par.layout();
366 for (pos_type pos = 0; pos < par.size(); ++pos) {
367 if (pos < par.beginningOfBody())
368 layoutfont = layout->labelfont;
370 layoutfont = layout->font;
372 LyXFont tmpfont = par.getFontSettings(buf.params, pos);
373 tmpfont.reduce(layoutfont);
374 par.setFont(pos, tmpfont);
379 Paragraph * LyXText::setLayout(LyXCursor & cur, LyXCursor & sstart_cur,
380 LyXCursor & send_cur,
381 string const & layout)
383 Paragraph * endpar = send_cur.par()->next();
384 Paragraph * undoendpar = endpar;
386 if (endpar && endpar->getDepth()) {
387 while (endpar && endpar->getDepth()) {
388 endpar = endpar->next();
392 endpar = endpar->next(); // because of parindents etc.
395 setUndo(bv(), Undo::EDIT, sstart_cur.par(), undoendpar);
397 // ok we have a selection. This is always between sstart_cur
398 // and sel_end cursor
400 Paragraph * par = sstart_cur.par();
401 Paragraph * epar = send_cur.par()->next();
403 LyXLayout_ptr const & lyxlayout =
404 bv()->buffer()->params.getLyXTextClass()[layout];
407 par->applyLayout(lyxlayout);
408 makeFontEntriesLayoutSpecific(*bv()->buffer(), *par);
409 Paragraph * fppar = par;
410 fppar->params().spaceTop(lyxlayout->fill_top ?
411 VSpace(VSpace::VFILL)
412 : VSpace(VSpace::NONE));
413 fppar->params().spaceBottom(lyxlayout->fill_bottom ?
414 VSpace(VSpace::VFILL)
415 : VSpace(VSpace::NONE));
416 if (lyxlayout->margintype == MARGIN_MANUAL)
417 par->setLabelWidthString(lyxlayout->labelstring());
420 } while (par != epar);
426 // set layout over selection and make a total rebreak of those paragraphs
427 void LyXText::setLayout(string const & layout)
429 LyXCursor tmpcursor = cursor; /* store the current cursor */
431 // if there is no selection just set the layout
432 // of the current paragraph */
433 if (!selection.set()) {
434 selection.start = cursor; // dummy selection
435 selection.end = cursor;
437 Paragraph * endpar = setLayout(cursor, selection.start,
438 selection.end, layout);
439 redoParagraphs(selection.start, endpar);
441 // we have to reset the selection, because the
442 // geometry could have changed
443 setCursor(selection.start.par(),
444 selection.start.pos(), false);
445 selection.cursor = cursor;
446 setCursor(selection.end.par(), selection.end.pos(), false);
450 setCursor(tmpcursor.par(), tmpcursor.pos(), true);
454 // increment depth over selection and
455 // make a total rebreak of those paragraphs
456 void LyXText::incDepth()
458 // If there is no selection, just use the current paragraph
459 if (!selection.set()) {
460 selection.start = cursor; // dummy selection
461 selection.end = cursor;
464 // We end at the next paragraph with depth 0
465 Paragraph * endpar = selection.end.par()->next();
467 Paragraph * undoendpar = endpar;
469 if (endpar && endpar->getDepth()) {
470 while (endpar && endpar->getDepth()) {
471 endpar = endpar->next();
475 endpar = endpar->next(); // because of parindents etc.
478 setUndo(bv(), Undo::EDIT,
479 selection.start.par(), undoendpar);
481 LyXCursor tmpcursor = cursor; // store the current cursor
483 // ok we have a selection. This is always between sel_start_cursor
484 // and sel_end cursor
485 cursor = selection.start;
488 // NOTE: you can't change the depth of a bibliography entry
489 if (cursor.par()->layout()->labeltype != LABEL_BIBLIO) {
490 Paragraph * prev = cursor.par()->previous();
493 if (cursor.par()->getDepth()
494 < prev->getMaxDepthAfter()) {
495 cursor.par()->params().depth(cursor.par()->getDepth() + 1);
499 if (cursor.par() == selection.end.par())
501 cursor.par(cursor.par()->next());
504 redoParagraphs(selection.start, endpar);
506 // we have to reset visual the selection because the
507 // geometry could have changed
508 setCursor(selection.start.par(), selection.start.pos());
509 selection.cursor = cursor;
510 setCursor(selection.end.par(), selection.end.pos());
513 setCursor(tmpcursor.par(), tmpcursor.pos());
517 // decrement depth over selection and
518 // make a total rebreak of those paragraphs
519 void LyXText::decDepth()
521 // if there is no selection just set the layout
522 // of the current paragraph
523 if (!selection.set()) {
524 selection.start = cursor; // dummy selection
525 selection.end = cursor;
527 Paragraph * endpar = selection.end.par()->next();
528 Paragraph * undoendpar = endpar;
530 if (endpar && endpar->getDepth()) {
531 while (endpar && endpar->getDepth()) {
532 endpar = endpar->next();
536 endpar = endpar->next(); // because of parindents etc.
539 setUndo(bv(), Undo::EDIT,
540 selection.start.par(), undoendpar);
542 LyXCursor tmpcursor = cursor; // store the current cursor
544 // ok we have a selection. This is always between sel_start_cursor
545 // and sel_end cursor
546 cursor = selection.start;
549 if (cursor.par()->params().depth()) {
550 cursor.par()->params()
551 .depth(cursor.par()->params().depth() - 1);
553 if (cursor.par() == selection.end.par()) {
556 cursor.par(cursor.par()->next());
559 redoParagraphs(selection.start, endpar);
561 // we have to reset the visual selection because the
562 // geometry could have changed
563 setCursor(selection.start.par(), selection.start.pos());
564 selection.cursor = cursor;
565 setCursor(selection.end.par(), selection.end.pos());
568 setCursor(tmpcursor.par(), tmpcursor.pos());
572 // set font over selection and make a total rebreak of those paragraphs
573 void LyXText::setFont(LyXFont const & font, bool toggleall)
575 // if there is no selection just set the current_font
576 if (!selection.set()) {
577 // Determine basis font
579 if (cursor.pos() < cursor.par()->beginningOfBody()) {
580 layoutfont = getLabelFont(bv()->buffer(),
583 layoutfont = getLayoutFont(bv()->buffer(),
586 // Update current font
587 real_current_font.update(font,
588 bv()->buffer()->params.language,
591 // Reduce to implicit settings
592 current_font = real_current_font;
593 current_font.reduce(layoutfont);
594 // And resolve it completely
595 real_current_font.realize(layoutfont);
600 LyXCursor tmpcursor = cursor; // store the current cursor
602 // ok we have a selection. This is always between sel_start_cursor
603 // and sel_end cursor
605 setUndo(bv(), Undo::EDIT,
606 selection.start.par(), selection.end.par()->next());
608 cursor = selection.start;
609 while (cursor.par() != selection.end.par() ||
610 cursor.pos() < selection.end.pos())
612 if (cursor.pos() < cursor.par()->size()) {
613 // an open footnote should behave like a closed one
614 setCharFont(cursor.par(), cursor.pos(),
616 cursor.pos(cursor.pos() + 1);
619 cursor.par(cursor.par()->next());
624 redoParagraphs(selection.start, selection.end.par()->next());
626 // we have to reset the selection, because the
627 // geometry could have changed, but we keep
628 // it for user convenience
629 setCursor(selection.start.par(), selection.start.pos());
630 selection.cursor = cursor;
631 setCursor(selection.end.par(), selection.end.pos());
633 setCursor(tmpcursor.par(), tmpcursor.pos(), true,
634 tmpcursor.boundary());
638 void LyXText::redoHeightOfParagraph()
640 Row * tmprow = cursor.row();
641 int y = cursor.y() - tmprow->baseline();
643 setHeightOfRow(tmprow);
645 while (tmprow->previous()
646 && tmprow->previous()->par() == tmprow->par()) {
647 tmprow = tmprow->previous();
648 y -= tmprow->height();
649 setHeightOfRow(tmprow);
654 setCursor(cursor.par(), cursor.pos(), false, cursor.boundary());
658 void LyXText::redoDrawingOfParagraph(LyXCursor const & cur)
660 Row * tmprow = cur.row();
662 int y = cur.y() - tmprow->baseline();
663 setHeightOfRow(tmprow);
665 while (tmprow->previous()
666 && tmprow->previous()->par() == tmprow->par()) {
667 tmprow = tmprow->previous();
668 y -= tmprow->height();
672 setCursor(cur.par(), cur.pos());
676 // deletes and inserts again all paragaphs between the cursor
677 // and the specified par
678 // This function is needed after SetLayout and SetFont etc.
679 void LyXText::redoParagraphs(LyXCursor const & cur,
680 Paragraph const * endpar)
682 Row * tmprow = cur.row();
684 int y = cur.y() - tmprow->baseline();
686 Paragraph * first_phys_par = 0;
687 if (!tmprow->previous()) {
688 // a trick/hack for UNDO
689 // This is needed because in an UNDO/REDO we could have changed
690 // the ownerParagrah() so the paragraph inside the row is NOT
691 // my really first par anymore. Got it Lars ;) (Jug 20011206)
692 first_phys_par = ownerParagraph();
694 first_phys_par = tmprow->par();
696 // Find first row of this paragraph.
697 while (tmprow->previous()
698 && tmprow->previous()->par() == first_phys_par)
700 tmprow = tmprow->previous();
701 y -= tmprow->height();
705 Row * prevrow = tmprow->previous();
707 // Remove all the rows until we reach endpar
708 Paragraph * tmppar = 0;
710 tmppar = tmprow->next()->par();
711 while (tmprow->next() && tmppar != endpar) {
712 removeRow(tmprow->next());
713 if (tmprow->next()) {
714 tmppar = tmprow->next()->par();
720 // Remove the first of the paragraphs rows.
721 // This is because tmprow->previous() can be 0
722 Row * tmprow2 = tmprow;
723 tmprow = tmprow->previous();
726 // Reinsert the paragraphs.
727 tmppar = first_phys_par;
731 insertParagraph(tmppar, rowlist_.begin());
733 insertParagraph(tmppar, tmprow->next());
738 tmprow = &*rows().begin();
740 while (tmprow->next()
741 && tmprow->next()->par() == tmppar) {
742 tmprow = tmprow->next();
744 tmppar = tmppar->next();
746 } while (tmppar && tmppar != endpar);
748 // this is because of layout changes
750 setHeightOfRow(prevrow);
751 const_cast<LyXText *>(this)->postPaint(y - prevrow->height());
753 setHeightOfRow(&*rows().begin());
754 const_cast<LyXText *>(this)->postPaint(0);
757 if (tmprow && tmprow->next())
758 setHeightOfRow(tmprow->next());
763 void LyXText::fullRebreak()
765 if (rows().empty()) {
769 if (need_break_row != rows().end()) {
770 breakAgain(need_break_row);
771 need_break_row = rows().end();
777 // important for the screen
780 // the cursor set functions have a special mechanism. When they
781 // realize, that you left an empty paragraph, they will delete it.
782 // They also delete the corresponding row
784 // need the selection cursor:
785 void LyXText::setSelection()
787 bool const lsel = selection.set();
789 if (!selection.set()) {
790 last_sel_cursor = selection.cursor;
791 selection.start = selection.cursor;
792 selection.end = selection.cursor;
797 // first the toggling area
798 if (cursor.y() < last_sel_cursor.y()
799 || (cursor.y() == last_sel_cursor.y()
800 && cursor.x() < last_sel_cursor.x())) {
801 toggle_end_cursor = last_sel_cursor;
802 toggle_cursor = cursor;
804 toggle_end_cursor = cursor;
805 toggle_cursor = last_sel_cursor;
808 last_sel_cursor = cursor;
810 // and now the whole selection
812 if (selection.cursor.par() == cursor.par())
813 if (selection.cursor.pos() < cursor.pos()) {
814 selection.end = cursor;
815 selection.start = selection.cursor;
817 selection.end = selection.cursor;
818 selection.start = cursor;
820 else if (selection.cursor.y() < cursor.y() ||
821 (selection.cursor.y() == cursor.y()
822 && selection.cursor.x() < cursor.x())) {
823 selection.end = cursor;
824 selection.start = selection.cursor;
827 selection.end = selection.cursor;
828 selection.start = cursor;
831 // a selection with no contents is not a selection
832 if (selection.start.par() == selection.end.par() &&
833 selection.start.pos() == selection.end.pos())
834 selection.set(false);
836 if (inset_owner && (selection.set() || lsel))
837 inset_owner->setUpdateStatus(bv(), InsetText::SELECTION);
841 string const LyXText::selectionAsString(Buffer const * buffer,
844 if (!selection.set()) return string();
846 // should be const ...
847 Paragraph * startpar(selection.start.par());
848 Paragraph * endpar(selection.end.par());
849 pos_type const startpos(selection.start.pos());
850 pos_type const endpos(selection.end.pos());
852 if (startpar == endpar) {
853 return startpar->asString(buffer, startpos, endpos, label);
858 // First paragraph in selection
859 result += startpar->asString(buffer, startpos, startpar->size(), label) + "\n\n";
861 // The paragraphs in between (if any)
862 LyXCursor tmpcur(selection.start);
863 tmpcur.par(tmpcur.par()->next());
864 while (tmpcur.par() != endpar) {
865 result += tmpcur.par()->asString(buffer, 0,
866 tmpcur.par()->size(),
868 tmpcur.par(tmpcur.par()->next());
871 // Last paragraph in selection
872 result += endpar->asString(buffer, 0, endpos, label);
878 void LyXText::clearSelection()
880 selection.set(false);
881 selection.mark(false);
882 last_sel_cursor = selection.end = selection.start = selection.cursor = cursor;
883 // reset this in the bv_owner!
884 if (bv_owner && bv_owner->text)
885 bv_owner->text->xsel_cache.set(false);
889 void LyXText::cursorHome()
891 setCursor(cursor.par(), cursor.row()->pos());
895 void LyXText::cursorEnd()
897 if (cursor.par()->empty())
900 if (!cursor.row()->next()
901 || cursor.row()->next()->par() != cursor.row()->par()) {
902 setCursor(cursor.par(), cursor.row()->lastPos() + 1);
904 if (!cursor.par()->empty() &&
905 (cursor.par()->getChar(cursor.row()->lastPos()) == ' '
906 || cursor.par()->isNewline(cursor.row()->lastPos()))) {
907 setCursor(cursor.par(), cursor.row()->lastPos());
909 setCursor(cursor.par(),
910 cursor.row()->lastPos() + 1);
916 void LyXText::cursorTop()
918 while (cursor.par()->previous())
919 cursor.par(cursor.par()->previous());
920 setCursor(cursor.par(), 0);
924 void LyXText::cursorBottom()
926 while (cursor.par()->next())
927 cursor.par(cursor.par()->next());
928 setCursor(cursor.par(), cursor.par()->size());
932 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
934 // If the mask is completely neutral, tell user
935 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
936 // Could only happen with user style
937 bv()->owner()->message(_("No font change defined. Use Character under the Layout menu to define font change."));
941 // Try implicit word selection
942 // If there is a change in the language the implicit word selection
944 LyXCursor resetCursor = cursor;
945 bool implicitSelection = (font.language() == ignore_language
946 && font.number() == LyXFont::IGNORE)
947 ? selectWordWhenUnderCursor(WHOLE_WORD_STRICT) : false;
950 setFont(font, toggleall);
952 // Implicit selections are cleared afterwards
953 //and cursor is set to the original position.
954 if (implicitSelection) {
956 cursor = resetCursor;
957 setCursor(cursor.par(), cursor.pos());
958 selection.cursor = cursor;
961 inset_owner->setUpdateStatus(bv(), InsetText::CURSOR_PAR);
965 string LyXText::getStringToIndex()
967 // Try implicit word selection
968 // If there is a change in the language the implicit word selection
970 LyXCursor const reset_cursor = cursor;
971 bool const implicitSelection = selectWordWhenUnderCursor(PREVIOUS_WORD);
974 if (!selection.set())
975 bv()->owner()->message(_("Nothing to index!"));
976 else if (selection.start.par() != selection.end.par())
977 bv()->owner()->message(_("Cannot index more than one paragraph!"));
979 idxstring = selectionAsString(bv()->buffer(), false);
981 // Reset cursors to their original position.
982 cursor = reset_cursor;
983 setCursor(cursor.par(), cursor.pos());
984 selection.cursor = cursor;
986 // Clear the implicit selection.
987 if (implicitSelection)
994 // the DTP switches for paragraphs. LyX will store them in the first
995 // physicla paragraph. When a paragraph is broken, the top settings rest,
996 // the bottom settings are given to the new one. So I can make shure,
997 // they do not duplicate themself and you cannnot make dirty things with
1000 void LyXText::setParagraph(bool line_top, bool line_bottom,
1001 bool pagebreak_top, bool pagebreak_bottom,
1002 VSpace const & space_top,
1003 VSpace const & space_bottom,
1004 Spacing const & spacing,
1006 string const & labelwidthstring,
1009 LyXCursor tmpcursor = cursor;
1010 if (!selection.set()) {
1011 selection.start = cursor;
1012 selection.end = cursor;
1015 // make sure that the depth behind the selection are restored, too
1016 Paragraph * endpar = selection.end.par()->next();
1017 Paragraph * undoendpar = endpar;
1019 if (endpar && endpar->getDepth()) {
1020 while (endpar && endpar->getDepth()) {
1021 endpar = endpar->next();
1022 undoendpar = endpar;
1026 // because of parindents etc.
1027 endpar = endpar->next();
1030 setUndo(bv(), Undo::EDIT, selection.start.par(), undoendpar);
1033 Paragraph * tmppar = selection.end.par();
1035 while (tmppar != selection.start.par()->previous()) {
1036 setCursor(tmppar, 0);
1037 postPaint(cursor.y() - cursor.row()->baseline());
1038 cursor.par()->params().lineTop(line_top);
1039 cursor.par()->params().lineBottom(line_bottom);
1040 cursor.par()->params().pagebreakTop(pagebreak_top);
1041 cursor.par()->params().pagebreakBottom(pagebreak_bottom);
1042 cursor.par()->params().spaceTop(space_top);
1043 cursor.par()->params().spaceBottom(space_bottom);
1044 cursor.par()->params().spacing(spacing);
1045 // does the layout allow the new alignment?
1046 LyXLayout_ptr const & layout = cursor.par()->layout();
1048 if (align == LYX_ALIGN_LAYOUT)
1049 align = layout->align;
1050 if (align & layout->alignpossible) {
1051 if (align == layout->align)
1052 cursor.par()->params().align(LYX_ALIGN_LAYOUT);
1054 cursor.par()->params().align(align);
1056 cursor.par()->setLabelWidthString(labelwidthstring);
1057 cursor.par()->params().noindent(noindent);
1058 tmppar = cursor.par()->previous();
1061 redoParagraphs(selection.start, endpar);
1064 setCursor(selection.start.par(), selection.start.pos());
1065 selection.cursor = cursor;
1066 setCursor(selection.end.par(), selection.end.pos());
1068 setCursor(tmpcursor.par(), tmpcursor.pos());
1070 bv()->updateInset(inset_owner);
1074 // set the counter of a paragraph. This includes the labels
1075 void LyXText::setCounter(Buffer const * buf, Paragraph * par)
1077 LyXTextClass const & textclass = buf->params.getLyXTextClass();
1078 LyXLayout_ptr const & layout = par->layout();
1080 if (par->previous()) {
1082 par->params().appendix(par->previous()->params().appendix());
1083 if (!par->params().appendix() && par->params().startOfAppendix()) {
1084 par->params().appendix(true);
1085 textclass.counters().reset();
1087 par->enumdepth = par->previous()->enumdepth;
1088 par->itemdepth = par->previous()->itemdepth;
1090 par->params().appendix(par->params().startOfAppendix());
1095 /* Maybe we have to increment the enumeration depth.
1096 * BUT, enumeration in a footnote is considered in isolation from its
1097 * surrounding paragraph so don't increment if this is the
1098 * first line of the footnote
1099 * AND, bibliographies can't have their depth changed ie. they
1100 * are always of depth 0
1103 && par->previous()->getDepth() < par->getDepth()
1104 && par->previous()->layout()->labeltype == LABEL_COUNTER_ENUMI
1105 && par->enumdepth < 3
1106 && layout->labeltype != LABEL_BIBLIO) {
1110 // Maybe we have to decrement the enumeration depth, see note above
1112 && par->previous()->getDepth() > par->getDepth()
1113 && layout->labeltype != LABEL_BIBLIO) {
1114 par->enumdepth = par->depthHook(par->getDepth())->enumdepth;
1117 if (!par->params().labelString().empty()) {
1118 par->params().labelString(string());
1121 if (layout->margintype == MARGIN_MANUAL) {
1122 if (par->params().labelWidthString().empty()) {
1123 par->setLabelWidthString(layout->labelstring());
1126 par->setLabelWidthString(string());
1129 // is it a layout that has an automatic label?
1130 if (layout->labeltype >= LABEL_COUNTER_CHAPTER) {
1131 int const i = layout->labeltype - LABEL_COUNTER_CHAPTER;
1135 if (i >= 0 && i <= buf->params.secnumdepth) {
1139 textclass.counters().step(layout->latexname());
1141 // Is there a label? Useful for Chapter layout
1142 if (!par->params().appendix()) {
1143 s << layout->labelstring();
1145 s << layout->labelstring_appendix();
1148 // Use of an integer is here less than elegant. For now.
1149 int head = textclass.maxcounter() - LABEL_COUNTER_CHAPTER;
1150 if (!par->params().appendix()) {
1151 numbertype = "sectioning";
1153 numbertype = "appendix";
1154 if (par->isRightToLeftPar(buf->params))
1155 langtype = "hebrew";
1160 s << textclass.counters()
1161 .numberLabel(layout->latexname(),
1162 numbertype, langtype, head);
1164 par->params().labelString(STRCONV(s.str()));
1166 // reset enum counters
1167 textclass.counters().reset("enum");
1168 } else if (layout->labeltype < LABEL_COUNTER_ENUMI) {
1169 textclass.counters().reset("enum");
1170 } else if (layout->labeltype == LABEL_COUNTER_ENUMI) {
1172 // Yes I know this is a really, really! bad solution
1174 string enumcounter("enum");
1176 switch (par->enumdepth) {
1185 enumcounter += "iv";
1188 // not a valid enumdepth...
1192 textclass.counters().step(enumcounter);
1194 s << textclass.counters()
1195 .numberLabel(enumcounter, "enumeration");
1196 par->params().labelString(STRCONV(s.str()));
1198 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
1199 textclass.counters().step("bibitem");
1200 int number = textclass.counters().value("bibitem");
1201 if (par->bibitem()) {
1202 par->bibitem()->setCounter(number);
1203 par->params().labelString(layout->labelstring());
1205 // In biblio should't be following counters but...
1207 string s = layout->labelstring();
1209 // the caption hack:
1210 if (layout->labeltype == LABEL_SENSITIVE) {
1211 Paragraph * tmppar = par;
1214 while (tmppar && tmppar->inInset()
1215 // the single '=' is intended below
1216 && (in = tmppar->inInset()->owner())) {
1217 if (in->lyxCode() == Inset::FLOAT_CODE ||
1218 in->lyxCode() == Inset::WRAP_CODE) {
1222 tmppar = in->parOwner();
1228 = textclass.floats().getType(static_cast<InsetFloat*>(in)->type());
1230 textclass.counters().step(fl.type());
1232 // Doesn't work... yet.
1233 #warning use boost.format
1234 #if USE_BOOST_FORMAT
1235 s = boost::io::str(boost::format(_("%1$s #:")) % fl.name());
1236 // s << boost::format(_("%1$s %1$d:")
1238 // % buf->counters().value(fl.name());
1241 //o << fl.name() << ' ' << buf->counters().value(fl.name()) << ":";
1242 o << fl.name() << " #:";
1243 s = STRCONV(o.str());
1246 // par->SetLayout(0);
1247 // s = layout->labelstring;
1248 s = _("Senseless: ");
1251 par->params().labelString(s);
1253 // reset the enumeration counter. They are always reset
1254 // when there is any other layout between
1255 // Just fall-through between the cases so that all
1256 // enum counters deeper than enumdepth is also reset.
1257 switch (par->enumdepth) {
1259 textclass.counters().reset("enumi");
1261 textclass.counters().reset("enumii");
1263 textclass.counters().reset("enumiii");
1265 textclass.counters().reset("enumiv");
1271 // Updates all counters. Paragraphs with changed label string will be rebroken
1272 void LyXText::updateCounters()
1274 RowList::iterator rowit = rows().begin();
1275 Paragraph * par = rowit->par();
1277 // CHECK if this is really needed. (Lgb)
1278 bv()->buffer()->params.getLyXTextClass().counters().reset();
1281 while (rowit->par() != par)
1284 string const oldLabel = par->params().labelString();
1286 // setCounter can potentially change the labelString.
1287 setCounter(bv()->buffer(), par);
1289 string const & newLabel = par->params().labelString();
1291 if (oldLabel.empty() && !newLabel.empty()) {
1292 removeParagraph(&*rowit);
1293 appendParagraph(rowit);
1301 void LyXText::insertInset(Inset * inset)
1303 if (!cursor.par()->insetAllowed(inset->lyxCode()))
1305 setUndo(bv(), Undo::FINISH, cursor.par(), cursor.par()->next());
1307 cursor.par()->insertInset(cursor.pos(), inset);
1308 // Just to rebreak and refresh correctly.
1309 // The character will not be inserted a second time
1310 insertChar(Paragraph::META_INSET);
1311 // If we enter a highly editable inset the cursor should be to before
1312 // the inset. This couldn't happen before as Undo was not handled inside
1313 // inset now after the Undo LyX tries to call inset->Edit(...) again
1314 // and cannot do this as the cursor is behind the inset and GetInset
1315 // does not return the inset!
1316 if (isHighlyEditableInset(inset)) {
1323 void LyXText::copyEnvironmentType()
1325 copylayouttype = cursor.par()->layout()->name();
1329 void LyXText::pasteEnvironmentType()
1331 // do nothing if there has been no previous copyEnvironmentType()
1332 if (!copylayouttype.empty())
1333 setLayout(copylayouttype);
1337 void LyXText::cutSelection(bool doclear, bool realcut)
1339 // Stuff what we got on the clipboard. Even if there is no selection.
1341 // There is a problem with having the stuffing here in that the
1342 // larger the selection the slower LyX will get. This can be
1343 // solved by running the line below only when the selection has
1344 // finished. The solution used currently just works, to make it
1345 // faster we need to be more clever and probably also have more
1346 // calls to stuffClipboard. (Lgb)
1347 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1349 // This doesn't make sense, if there is no selection
1350 if (!selection.set())
1353 // OK, we have a selection. This is always between selection.start
1354 // and selection.end
1356 // make sure that the depth behind the selection are restored, too
1357 Paragraph * endpar = selection.end.par()->next();
1358 Paragraph * undoendpar = endpar;
1360 if (endpar && endpar->getDepth()) {
1361 while (endpar && endpar->getDepth()) {
1362 endpar = endpar->next();
1363 undoendpar = endpar;
1365 } else if (endpar) {
1366 endpar = endpar->next(); // because of parindents etc.
1369 setUndo(bv(), Undo::DELETE,
1370 selection.start.par(), undoendpar);
1372 // there are two cases: cut only within one paragraph or
1373 // more than one paragraph
1374 if (selection.start.par() == selection.end.par()) {
1375 // only within one paragraph
1376 endpar = selection.end.par();
1377 int pos = selection.end.pos();
1378 CutAndPaste::cutSelection(selection.start.par(), &endpar,
1379 selection.start.pos(), pos,
1380 bv()->buffer()->params.textclass,
1382 selection.end.pos(pos);
1384 endpar = selection.end.par();
1385 int pos = selection.end.pos();
1386 CutAndPaste::cutSelection(selection.start.par(), &endpar,
1387 selection.start.pos(), pos,
1388 bv()->buffer()->params.textclass,
1391 selection.end.par(endpar);
1392 selection.end.pos(pos);
1393 cursor.pos(selection.end.pos());
1395 endpar = endpar->next();
1397 // sometimes necessary
1399 selection.start.par()->stripLeadingSpaces();
1401 redoParagraphs(selection.start, endpar);
1403 // cutSelection can invalidate the cursor so we need to set
1405 // we prefer the end for when tracking changes
1406 cursor = selection.end;
1408 // need a valid cursor. (Lgb)
1411 setCursor(cursor.par(), cursor.pos());
1412 selection.cursor = cursor;
1417 void LyXText::copySelection()
1419 // stuff the selection onto the X clipboard, from an explicit copy request
1420 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1422 // this doesnt make sense, if there is no selection
1423 if (!selection.set())
1426 // ok we have a selection. This is always between selection.start
1427 // and sel_end cursor
1429 // copy behind a space if there is one
1430 while (selection.start.par()->size() > selection.start.pos()
1431 && selection.start.par()->isLineSeparator(selection.start.pos())
1432 && (selection.start.par() != selection.end.par()
1433 || selection.start.pos() < selection.end.pos()))
1434 selection.start.pos(selection.start.pos() + 1);
1436 CutAndPaste::copySelection(selection.start.par(), selection.end.par(),
1437 selection.start.pos(), selection.end.pos(),
1438 bv()->buffer()->params.textclass);
1442 void LyXText::pasteSelection()
1444 // this does not make sense, if there is nothing to paste
1445 if (!CutAndPaste::checkPastePossible())
1448 setUndo(bv(), Undo::INSERT,
1449 cursor.par(), cursor.par()->next());
1452 Paragraph * actpar = cursor.par();
1453 int pos = cursor.pos();
1455 CutAndPaste::pasteSelection(&actpar, &endpar, pos,
1456 bv()->buffer()->params.textclass);
1458 redoParagraphs(cursor, endpar);
1460 setCursor(cursor.par(), cursor.pos());
1463 selection.cursor = cursor;
1464 setCursor(actpar, pos);
1470 void LyXText::setSelectionRange(lyx::pos_type length)
1475 selection.cursor = cursor;
1482 // simple replacing. The font of the first selected character is used
1483 void LyXText::replaceSelectionWithString(string const & str)
1485 setCursorParUndo(bv());
1488 if (!selection.set()) { // create a dummy selection
1489 selection.end = cursor;
1490 selection.start = cursor;
1493 // Get font setting before we cut
1494 pos_type pos = selection.end.pos();
1495 LyXFont const font = selection.start.par()
1496 ->getFontSettings(bv()->buffer()->params,
1497 selection.start.pos());
1499 // Insert the new string
1500 for (string::const_iterator cit = str.begin(); cit != str.end(); ++cit) {
1501 selection.end.par()->insertChar(pos, (*cit), font);
1505 // Cut the selection
1506 cutSelection(true, false);
1512 // needed to insert the selection
1513 void LyXText::insertStringAsLines(string const & str)
1515 Paragraph * par = cursor.par();
1516 pos_type pos = cursor.pos();
1517 Paragraph * endpar = cursor.par()->next();
1519 setCursorParUndo(bv());
1521 // only to be sure, should not be neccessary
1524 bv()->buffer()->insertStringAsLines(par, pos, current_font, str);
1526 redoParagraphs(cursor, endpar);
1527 setCursor(cursor.par(), cursor.pos());
1528 selection.cursor = cursor;
1529 setCursor(par, pos);
1534 // turns double-CR to single CR, others where converted into one
1535 // blank. Then InsertStringAsLines is called
1536 void LyXText::insertStringAsParagraphs(string const & str)
1538 string linestr(str);
1539 bool newline_inserted = false;
1540 for (string::size_type i = 0; i < linestr.length(); ++i) {
1541 if (linestr[i] == '\n') {
1542 if (newline_inserted) {
1543 // we know that \r will be ignored by
1544 // InsertStringA. Of course, it is a dirty
1545 // trick, but it works...
1546 linestr[i - 1] = '\r';
1550 newline_inserted = true;
1552 } else if (IsPrintable(linestr[i])) {
1553 newline_inserted = false;
1556 insertStringAsLines(linestr);
1560 void LyXText::checkParagraph(Paragraph * par, pos_type pos)
1562 LyXCursor tmpcursor;
1566 RowList::iterator row = getRow(par, pos, y);
1567 RowList::iterator beg = rows().begin();
1569 // is there a break one row above
1571 && boost::prior(row)->par() == row->par()) {
1572 z = rowBreakPoint(*boost::prior(row));
1573 if (z >= row->pos()) {
1574 // set the dimensions of the row above
1575 y -= boost::prior(row)->height();
1578 breakAgain(&*boost::prior(row));
1580 // set the cursor again. Otherwise
1581 // dangling pointers are possible
1582 setCursor(cursor.par(), cursor.pos(),
1583 false, cursor.boundary());
1584 selection.cursor = cursor;
1589 int const tmpheight = row->height();
1590 pos_type const tmplast = row->lastPos();
1593 if (row->height() == tmpheight && row->lastPos() == tmplast) {
1594 postRowPaint(&*row, y);
1599 // check the special right address boxes
1600 if (par->layout()->margintype == MARGIN_RIGHT_ADDRESS_BOX) {
1602 tmpcursor.row(&*row);
1607 redoDrawingOfParagraph(tmpcursor);
1610 // set the cursor again. Otherwise dangling pointers are possible
1611 // also set the selection
1613 if (selection.set()) {
1615 setCursorIntern(selection.cursor.par(), selection.cursor.pos(),
1616 false, selection.cursor.boundary());
1617 selection.cursor = cursor;
1618 setCursorIntern(selection.start.par(),
1619 selection.start.pos(),
1620 false, selection.start.boundary());
1621 selection.start = cursor;
1622 setCursorIntern(selection.end.par(),
1623 selection.end.pos(),
1624 false, selection.end.boundary());
1625 selection.end = cursor;
1626 setCursorIntern(last_sel_cursor.par(),
1627 last_sel_cursor.pos(),
1628 false, last_sel_cursor.boundary());
1629 last_sel_cursor = cursor;
1632 setCursorIntern(cursor.par(), cursor.pos(),
1633 false, cursor.boundary());
1637 // returns false if inset wasn't found
1638 bool LyXText::updateInset(Inset * inset)
1640 // first check the current paragraph
1641 int pos = cursor.par()->getPositionOfInset(inset);
1643 checkParagraph(cursor.par(), pos);
1647 // check every paragraph
1649 Paragraph * par = ownerParagraph();
1651 pos = par->getPositionOfInset(inset);
1653 checkParagraph(par, pos);
1663 bool LyXText::setCursor(Paragraph * par,
1665 bool setfont, bool boundary)
1667 LyXCursor old_cursor = cursor;
1668 setCursorIntern(par, pos, setfont, boundary);
1669 return deleteEmptyParagraphMechanism(old_cursor);
1673 void LyXText::setCursor(LyXCursor & cur, Paragraph * par,
1674 pos_type pos, bool boundary)
1680 cur.boundary(boundary);
1682 // get the cursor y position in text
1684 RowList::iterator row = getRow(par, pos, y);
1685 RowList::iterator beg = rows().begin();
1687 RowList::iterator old_row = row;
1689 // if we are before the first char of this row and are still in the
1690 // same paragraph and there is a previous row then put the cursor on
1691 // the end of the previous row
1692 cur.iy(y + row->baseline());
1694 if (row != beg && pos &&
1695 boost::prior(row)->par() == row->par() &&
1696 pos < par->size() &&
1697 par->getChar(pos) == Paragraph::META_INSET &&
1698 (ins = par->getInset(pos)) && (ins->needFullRow() || ins->display()))
1705 // y is now the beginning of the cursor row
1706 y += row->baseline();
1707 // y is now the cursor baseline
1710 pos_type last = old_row->lastPrintablePos();
1712 // None of these should happen, but we're scaredy-cats
1713 if (pos > par->size()) {
1714 lyxerr << "dont like 1 please report" << endl;
1717 } else if (pos > last + 1) {
1718 lyxerr << "dont like 2 please report" << endl;
1719 // This shouldn't happen.
1722 } else if (pos < row->pos()) {
1723 lyxerr << "dont like 3 please report" << endl;
1728 // now get the cursors x position
1729 float x = getCursorX(&*row, pos, last, boundary);
1732 if (old_row != row) {
1733 x = getCursorX(&*old_row, pos, last, boundary);
1737 //if the cursor is in a visible row, anchor to it
1739 if (topy < y && y < topy + bv()->workHeight())
1744 float LyXText::getCursorX(RowList::iterator rit,
1745 pos_type pos, pos_type last, bool boundary) const
1747 pos_type cursor_vpos = 0;
1749 float fill_separator;
1751 float fill_label_hfill;
1752 // This call HAS to be here because of the BidiTables!!!
1753 prepareToPrint(rit, x, fill_separator, fill_hfill,
1756 if (last < rit->pos())
1757 cursor_vpos = rit->pos();
1758 else if (pos > last && !boundary)
1759 cursor_vpos = (rit->par()->isRightToLeftPar(bv()->buffer()->params))
1760 ? rit->pos() : last + 1;
1761 else if (pos > rit->pos() &&
1762 (pos > last || boundary))
1763 /// Place cursor after char at (logical) position pos - 1
1764 cursor_vpos = (bidi_level(pos - 1) % 2 == 0)
1765 ? log2vis(pos - 1) + 1 : log2vis(pos - 1);
1767 /// Place cursor before char at (logical) position pos
1768 cursor_vpos = (bidi_level(pos) % 2 == 0)
1769 ? log2vis(pos) : log2vis(pos) + 1;
1771 pos_type body_pos = rit->par()->beginningOfBody();
1772 if ((body_pos > 0) &&
1773 ((body_pos - 1 > last) ||
1774 !rit->par()->isLineSeparator(body_pos - 1)))
1777 for (pos_type vpos = rit->pos(); vpos < cursor_vpos; ++vpos) {
1778 pos_type pos = vis2log(vpos);
1779 if (body_pos > 0 && pos == body_pos - 1) {
1780 x += fill_label_hfill +
1781 font_metrics::width(
1782 rit->par()->layout()->labelsep,
1783 getLabelFont(bv()->buffer(),
1785 if (rit->par()->isLineSeparator(body_pos - 1))
1786 x -= singleWidth(rit->par(), body_pos - 1);
1788 if (rit->hfillExpansion(pos)) {
1789 x += singleWidth(rit->par(), pos);
1790 if (pos >= body_pos)
1793 x += fill_label_hfill;
1794 } else if (rit->par()->isSeparator(pos)) {
1795 x += singleWidth(rit->par(), pos);
1796 if (pos >= body_pos)
1797 x += fill_separator;
1799 x += singleWidth(rit->par(), pos);
1805 void LyXText::setCursorIntern(Paragraph * par,
1806 pos_type pos, bool setfont, bool boundary)
1808 InsetText * it = static_cast<InsetText *>(par->inInset());
1810 if (it != inset_owner) {
1811 lyxerr[Debug::INSETS] << "InsetText is " << it
1813 << "inset_owner is "
1814 << inset_owner << endl;
1815 #ifdef WITH_WARNINGS
1816 #warning I believe this code is wrong. (Lgb)
1817 #warning Jürgen, have a look at this. (Lgb)
1818 #warning Hmmm, I guess you are right but we
1819 #warning should verify when this is needed
1821 // Jürgen, would you like to have a look?
1822 // I guess we need to move the outer cursor
1823 // and open and lock the inset (bla bla bla)
1824 // stuff I don't know... so can you have a look?
1826 // I moved the lyxerr stuff in here so we can see if
1827 // this is actually really needed and where!
1829 // it->getLyXText(bv())->setCursorIntern(bv(), par, pos, setfont, boundary);
1834 setCursor(cursor, par, pos, boundary);
1840 void LyXText::setCurrentFont()
1842 pos_type pos = cursor.pos();
1843 if (cursor.boundary() && pos > 0)
1847 if (pos == cursor.par()->size())
1849 else // potentional bug... BUG (Lgb)
1850 if (cursor.par()->isSeparator(pos)) {
1851 if (pos > cursor.row()->pos() &&
1852 bidi_level(pos) % 2 ==
1853 bidi_level(pos - 1) % 2)
1855 else if (pos + 1 < cursor.par()->size())
1861 cursor.par()->getFontSettings(bv()->buffer()->params, pos);
1862 real_current_font = getFont(bv()->buffer(), cursor.par(), pos);
1864 if (cursor.pos() == cursor.par()->size() &&
1865 isBoundary(bv()->buffer(), cursor.par(), cursor.pos()) &&
1866 !cursor.boundary()) {
1867 Language const * lang =
1868 cursor.par()->getParLanguage(bv()->buffer()->params);
1869 current_font.setLanguage(lang);
1870 current_font.setNumber(LyXFont::OFF);
1871 real_current_font.setLanguage(lang);
1872 real_current_font.setNumber(LyXFont::OFF);
1877 // returns the column near the specified x-coordinate of the row
1878 // x is set to the real beginning of this column
1880 LyXText::getColumnNearX(RowList::iterator rit, int & x,
1881 bool & boundary) const
1884 float fill_separator;
1886 float fill_label_hfill;
1888 prepareToPrint(rit, tmpx, fill_separator,
1889 fill_hfill, fill_label_hfill);
1891 pos_type vc = rit->pos();
1892 pos_type last = rit->lastPrintablePos();
1895 LyXLayout_ptr const & layout = rit->par()->layout();
1897 bool left_side = false;
1899 pos_type body_pos = rit->par()->beginningOfBody();
1900 float last_tmpx = tmpx;
1903 (body_pos - 1 > last ||
1904 !rit->par()->isLineSeparator(body_pos - 1)))
1907 // check for empty row
1908 if (!rit->par()->size()) {
1913 while (vc <= last && tmpx <= x) {
1916 if (body_pos > 0 && c == body_pos - 1) {
1917 tmpx += fill_label_hfill +
1918 font_metrics::width(layout->labelsep,
1919 getLabelFont(bv()->buffer(), rit->par()));
1920 if (rit->par()->isLineSeparator(body_pos - 1))
1921 tmpx -= singleWidth(rit->par(), body_pos - 1);
1924 if (rit->hfillExpansion(c)) {
1925 tmpx += singleWidth(rit->par(), c);
1929 tmpx += fill_label_hfill;
1930 } else if (rit->par()->isSeparator(c)) {
1931 tmpx += singleWidth(rit->par(), c);
1933 tmpx+= fill_separator;
1935 tmpx += singleWidth(rit->par(), c);
1940 if ((tmpx + last_tmpx) / 2 > x) {
1945 if (vc > last + 1) // This shouldn't happen.
1949 bool const lastrow = lyxrc.rtl_support // This is not needed, but gives
1950 // some speedup if rtl_support=false
1951 && (boost::next(rit) == rows().end() ||
1952 boost::next(rit)->par() != rit->par());
1953 bool const rtl = (lastrow)
1954 ? rit->par()->isRightToLeftPar(bv()->buffer()->params)
1955 : false; // If lastrow is false, we don't need to compute
1956 // the value of rtl.
1959 ((rtl && left_side && vc == rit->pos() && x < tmpx - 5) ||
1960 (!rtl && !left_side && vc == last + 1 && x > tmpx + 5)))
1962 else if (vc == rit->pos()) {
1964 if (bidi_level(c) % 2 == 1)
1967 c = vis2log(vc - 1);
1968 bool const rtl = (bidi_level(c) % 2 == 1);
1969 if (left_side == rtl) {
1971 boundary = isBoundary(bv()->buffer(), rit->par(), c);
1975 if (rit->pos() <= last && c > last
1976 && rit->par()->isNewline(last)) {
1977 if (bidi_level(last) % 2 == 0)
1978 tmpx -= singleWidth(rit->par(), last);
1980 tmpx += singleWidth(rit->par(), last);
1990 void LyXText::setCursorFromCoordinates(int x, int y)
1992 LyXCursor old_cursor = cursor;
1994 setCursorFromCoordinates(cursor, x, y);
1996 deleteEmptyParagraphMechanism(old_cursor);
2003 * return true if the cursor given is at the end of a row,
2004 * and the next row is filled by an inset that spans an entire
2007 bool beforeFullRowInset(Row & row, LyXCursor & cur) {
2010 Row const & next = *row.next();
2012 if (next.pos() != cur.pos() || next.par() != cur.par())
2014 if (!cur.par()->isInset(cur.pos()))
2016 Inset const * inset = cur.par()->getInset(cur.pos());
2017 if (inset->needFullRow() || inset->display())
2024 void LyXText::setCursorFromCoordinates(LyXCursor & cur, int x, int y)
2026 // Get the row first.
2028 RowList::iterator row = getRowNearY(y);
2030 pos_type const column = getColumnNearX(&*row, x, bound);
2031 cur.par(row->par());
2032 cur.pos(row->pos() + column);
2034 cur.y(y + row->baseline());
2037 if (beforeFullRowInset(*row, cur)) {
2038 pos_type last = row->lastPrintablePos();
2039 float x = getCursorX(row->next(), cur.pos(), last, bound);
2041 cur.iy(y + row->height() + row->next()->baseline());
2042 cur.irow(row->next());
2048 cur.boundary(bound);
2052 void LyXText::cursorLeft(bool internal)
2054 if (cursor.pos() > 0) {
2055 bool boundary = cursor.boundary();
2056 setCursor(cursor.par(), cursor.pos() - 1, true, false);
2057 if (!internal && !boundary &&
2058 isBoundary(bv()->buffer(), cursor.par(), cursor.pos() + 1))
2059 setCursor(cursor.par(), cursor.pos() + 1, true, true);
2060 } else if (cursor.par()->previous()) { // steps into the above paragraph.
2061 Paragraph * par = cursor.par()->previous();
2062 setCursor(par, par->size());
2067 void LyXText::cursorRight(bool internal)
2069 if (!internal && cursor.boundary() &&
2070 !cursor.par()->isNewline(cursor.pos()))
2071 setCursor(cursor.par(), cursor.pos(), true, false);
2072 else if (cursor.pos() < cursor.par()->size()) {
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()->previous()) {
2269 const_cast<LyXText *>(this)->postPaint(old_cursor.y() - old_cursor.row()->baseline()
2270 - old_cursor.row()->previous()->height());
2272 cursor = old_cursor; // that undo can restore the right cursor position
2273 Paragraph * endpar = old_cursor.par()->next();
2274 if (endpar && endpar->getDepth()) {
2275 while (endpar && endpar->getDepth()) {
2276 endpar = endpar->next();
2279 setUndo(bv(), Undo::DELETE, old_cursor.par(), endpar);
2283 removeRow(old_cursor.row());
2284 if (ownerParagraph() == old_cursor.par()) {
2285 ownerParagraph(ownerParagraph()->next());
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 (refresh_row != rows().end()) {
2295 if (refresh_row->next()) {
2296 breakAgain(refresh_row->next());
2299 setHeightOfRow(refresh_row);
2302 Row * nextrow = old_cursor.row()->next();
2303 const_cast<LyXText *>(this)->postPaint(
2304 old_cursor.y() - old_cursor.row()->baseline());
2307 cursor = old_cursor; // that undo can restore the right cursor position
2308 Paragraph * endpar = old_cursor.par()->next();
2309 if (endpar && endpar->getDepth()) {
2310 while (endpar && endpar->getDepth()) {
2311 endpar = endpar->next();
2314 setUndo(bv(), Undo::DELETE, old_cursor.par(), endpar);
2318 removeRow(old_cursor.row());
2320 if (ownerParagraph() == old_cursor.par()) {
2321 ownerParagraph(ownerParagraph()->next());
2324 delete old_cursor.par();
2326 /* Breakagain the next par. Needed because of
2327 the parindent that can occur or dissappear.
2328 The next row can change its height, if
2329 there is another layout before */
2331 breakAgain(nextrow);
2337 setCursorIntern(cursor.par(), cursor.pos());
2339 if (selection.cursor.par() == old_cursor.par()
2340 && selection.cursor.pos() == old_cursor.pos()) {
2341 // correct selection
2342 selection.cursor = cursor;
2346 if (old_cursor.par()->stripLeadingSpaces()) {
2347 redoParagraphs(old_cursor,
2348 old_cursor.par()->next());
2350 setCursorIntern(cursor.par(), cursor.pos());
2351 selection.cursor = cursor;
2358 Paragraph * LyXText::ownerParagraph() const
2361 return inset_owner->paragraph();
2363 return &*(bv_owner->buffer()->paragraphs.begin());
2367 void LyXText::ownerParagraph(Paragraph * p) const
2370 inset_owner->paragraph(p);
2372 bv_owner->buffer()->paragraphs.set(p);
2377 void LyXText::ownerParagraph(int id, Paragraph * p) const
2379 Paragraph * op = bv_owner->buffer()->getParFromID(id);
2380 if (op && op->inInset()) {
2381 static_cast<InsetText *>(op->inInset())->paragraph(p);
2388 LyXText::refresh_status LyXText::refreshStatus() const
2390 return refresh_status_;
2394 void LyXText::clearPaint()
2396 refresh_status_ = REFRESH_NONE;
2397 refresh_row = rows().end();
2402 void LyXText::postPaint(int start_y)
2404 refresh_status old = refresh_status_;
2406 refresh_status_ = REFRESH_AREA;
2407 refresh_row = rows().end();
2409 if (old != REFRESH_NONE && refresh_y < start_y)
2412 refresh_y = start_y;
2417 // We are an inset's lyxtext. Tell the top-level lyxtext
2418 // it needs to update the row we're in.
2419 LyXText * t = bv()->text;
2420 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2424 // FIXME: we should probably remove this y parameter,
2425 // make refresh_y be 0, and use row->y etc.
2426 void LyXText::postRowPaint(RowList::iterator rit, int start_y)
2428 if (refresh_status_ != REFRESH_NONE && refresh_y < start_y) {
2429 refresh_status_ = REFRESH_AREA;
2432 refresh_y = start_y;
2435 if (refresh_status_ == REFRESH_AREA)
2438 refresh_status_ = REFRESH_ROW;
2444 // We are an inset's lyxtext. Tell the top-level lyxtext
2445 // it needs to update the row we're in.
2446 LyXText * t = bv()->text;
2447 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2451 bool LyXText::isInInset() const
2453 // Sub-level has non-null bv owner and
2454 // non-null inset owner.
2455 return inset_owner != 0 && bv_owner != 0;
2459 int defaultRowHeight()
2461 LyXFont const font(LyXFont::ALL_SANE);
2462 return int(font_metrics::maxAscent(font)
2463 + font_metrics::maxDescent(font) * 1.5);