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 RowList::iterator tmprit = cur.row();
684 int y = cur.y() - tmprit->baseline();
686 Paragraph * first_phys_par = 0;
687 if (tmprit == rows().begin()) {
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 = tmprit->par();
695 while (tmprit != rows().begin()
696 && boost::prior(tmprit)->par() == first_phys_par)
699 y -= tmprit->height();
703 RowList::iterator prevrit;
704 if (tmprit != rows().begin()) {
705 prevrit = boost::prior(tmprit);
708 y = prevrit ->height();
712 Paragraph * tmppar = 0;
713 if (boost::next(tmprit) != rows().end())
714 tmppar = boost::next(tmprit)->par();
715 while (boost::next(tmprit) != rows().end() && tmppar != endpar) {
716 removeRow(boost::next(tmprit));
717 if (boost::next(tmprit) != rows().end()) {
718 tmppar = boost::next(tmprit)->par();
724 // remove the first one
725 RowList::iterator tmprit2 = tmprit; /* this is because tmprow->previous()
730 // Reinsert the paragraphs.
731 tmppar = first_phys_par;
734 insertParagraph(tmppar, tmprit);
735 while (tmprit != rows().end()
736 && tmprit->par() == tmppar) {
739 tmppar = tmppar->next();
741 } while (tmppar && tmppar != endpar);
743 setHeightOfRow(prevrit);
744 const_cast<LyXText *>(this)->postPaint(y - prevrit->height());
746 if (tmprit != rows().end() && boost::next(tmprit) != rows().end())
747 setHeightOfRow(boost::next(tmprit));
752 void LyXText::fullRebreak()
754 if (rows().empty()) {
758 if (need_break_row != rows().end()) {
759 breakAgain(need_break_row);
760 need_break_row = rows().end();
766 // important for the screen
769 // the cursor set functions have a special mechanism. When they
770 // realize, that you left an empty paragraph, they will delete it.
771 // They also delete the corresponding row
773 // need the selection cursor:
774 void LyXText::setSelection()
776 bool const lsel = selection.set();
778 if (!selection.set()) {
779 last_sel_cursor = selection.cursor;
780 selection.start = selection.cursor;
781 selection.end = selection.cursor;
786 // first the toggling area
787 if (cursor.y() < last_sel_cursor.y()
788 || (cursor.y() == last_sel_cursor.y()
789 && cursor.x() < last_sel_cursor.x())) {
790 toggle_end_cursor = last_sel_cursor;
791 toggle_cursor = cursor;
793 toggle_end_cursor = cursor;
794 toggle_cursor = last_sel_cursor;
797 last_sel_cursor = cursor;
799 // and now the whole selection
801 if (selection.cursor.par() == cursor.par())
802 if (selection.cursor.pos() < cursor.pos()) {
803 selection.end = cursor;
804 selection.start = selection.cursor;
806 selection.end = selection.cursor;
807 selection.start = cursor;
809 else if (selection.cursor.y() < cursor.y() ||
810 (selection.cursor.y() == cursor.y()
811 && selection.cursor.x() < cursor.x())) {
812 selection.end = cursor;
813 selection.start = selection.cursor;
816 selection.end = selection.cursor;
817 selection.start = cursor;
820 // a selection with no contents is not a selection
821 if (selection.start.par() == selection.end.par() &&
822 selection.start.pos() == selection.end.pos())
823 selection.set(false);
825 if (inset_owner && (selection.set() || lsel))
826 inset_owner->setUpdateStatus(bv(), InsetText::SELECTION);
830 string const LyXText::selectionAsString(Buffer const * buffer,
833 if (!selection.set()) return string();
835 // should be const ...
836 Paragraph * startpar(selection.start.par());
837 Paragraph * endpar(selection.end.par());
838 pos_type const startpos(selection.start.pos());
839 pos_type const endpos(selection.end.pos());
841 if (startpar == endpar) {
842 return startpar->asString(buffer, startpos, endpos, label);
847 // First paragraph in selection
848 result += startpar->asString(buffer, startpos, startpar->size(), label) + "\n\n";
850 // The paragraphs in between (if any)
851 LyXCursor tmpcur(selection.start);
852 tmpcur.par(tmpcur.par()->next());
853 while (tmpcur.par() != endpar) {
854 result += tmpcur.par()->asString(buffer, 0,
855 tmpcur.par()->size(),
857 tmpcur.par(tmpcur.par()->next());
860 // Last paragraph in selection
861 result += endpar->asString(buffer, 0, endpos, label);
867 void LyXText::clearSelection()
869 selection.set(false);
870 selection.mark(false);
871 last_sel_cursor = selection.end = selection.start = selection.cursor = cursor;
872 // reset this in the bv_owner!
873 if (bv_owner && bv_owner->text)
874 bv_owner->text->xsel_cache.set(false);
878 void LyXText::cursorHome()
880 setCursor(cursor.par(), cursor.row()->pos());
884 void LyXText::cursorEnd()
886 if (cursor.par()->empty())
889 if (!cursor.row()->next()
890 || cursor.row()->next()->par() != cursor.row()->par()) {
891 setCursor(cursor.par(), cursor.row()->lastPos() + 1);
893 if (!cursor.par()->empty() &&
894 (cursor.par()->getChar(cursor.row()->lastPos()) == ' '
895 || cursor.par()->isNewline(cursor.row()->lastPos()))) {
896 setCursor(cursor.par(), cursor.row()->lastPos());
898 setCursor(cursor.par(),
899 cursor.row()->lastPos() + 1);
905 void LyXText::cursorTop()
907 while (cursor.par()->previous())
908 cursor.par(cursor.par()->previous());
909 setCursor(cursor.par(), 0);
913 void LyXText::cursorBottom()
915 while (cursor.par()->next())
916 cursor.par(cursor.par()->next());
917 setCursor(cursor.par(), cursor.par()->size());
921 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
923 // If the mask is completely neutral, tell user
924 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
925 // Could only happen with user style
926 bv()->owner()->message(_("No font change defined. Use Character under the Layout menu to define font change."));
930 // Try implicit word selection
931 // If there is a change in the language the implicit word selection
933 LyXCursor resetCursor = cursor;
934 bool implicitSelection = (font.language() == ignore_language
935 && font.number() == LyXFont::IGNORE)
936 ? selectWordWhenUnderCursor(WHOLE_WORD_STRICT) : false;
939 setFont(font, toggleall);
941 // Implicit selections are cleared afterwards
942 //and cursor is set to the original position.
943 if (implicitSelection) {
945 cursor = resetCursor;
946 setCursor(cursor.par(), cursor.pos());
947 selection.cursor = cursor;
950 inset_owner->setUpdateStatus(bv(), InsetText::CURSOR_PAR);
954 string LyXText::getStringToIndex()
956 // Try implicit word selection
957 // If there is a change in the language the implicit word selection
959 LyXCursor const reset_cursor = cursor;
960 bool const implicitSelection = selectWordWhenUnderCursor(PREVIOUS_WORD);
963 if (!selection.set())
964 bv()->owner()->message(_("Nothing to index!"));
965 else if (selection.start.par() != selection.end.par())
966 bv()->owner()->message(_("Cannot index more than one paragraph!"));
968 idxstring = selectionAsString(bv()->buffer(), false);
970 // Reset cursors to their original position.
971 cursor = reset_cursor;
972 setCursor(cursor.par(), cursor.pos());
973 selection.cursor = cursor;
975 // Clear the implicit selection.
976 if (implicitSelection)
983 // the DTP switches for paragraphs. LyX will store them in the first
984 // physicla paragraph. When a paragraph is broken, the top settings rest,
985 // the bottom settings are given to the new one. So I can make shure,
986 // they do not duplicate themself and you cannnot make dirty things with
989 void LyXText::setParagraph(bool line_top, bool line_bottom,
990 bool pagebreak_top, bool pagebreak_bottom,
991 VSpace const & space_top,
992 VSpace const & space_bottom,
993 Spacing const & spacing,
995 string const & labelwidthstring,
998 LyXCursor tmpcursor = cursor;
999 if (!selection.set()) {
1000 selection.start = cursor;
1001 selection.end = cursor;
1004 // make sure that the depth behind the selection are restored, too
1005 Paragraph * endpar = selection.end.par()->next();
1006 Paragraph * undoendpar = endpar;
1008 if (endpar && endpar->getDepth()) {
1009 while (endpar && endpar->getDepth()) {
1010 endpar = endpar->next();
1011 undoendpar = endpar;
1015 // because of parindents etc.
1016 endpar = endpar->next();
1019 setUndo(bv(), Undo::EDIT, selection.start.par(), undoendpar);
1022 Paragraph * tmppar = selection.end.par();
1024 while (tmppar != selection.start.par()->previous()) {
1025 setCursor(tmppar, 0);
1026 postPaint(cursor.y() - cursor.row()->baseline());
1027 cursor.par()->params().lineTop(line_top);
1028 cursor.par()->params().lineBottom(line_bottom);
1029 cursor.par()->params().pagebreakTop(pagebreak_top);
1030 cursor.par()->params().pagebreakBottom(pagebreak_bottom);
1031 cursor.par()->params().spaceTop(space_top);
1032 cursor.par()->params().spaceBottom(space_bottom);
1033 cursor.par()->params().spacing(spacing);
1034 // does the layout allow the new alignment?
1035 LyXLayout_ptr const & layout = cursor.par()->layout();
1037 if (align == LYX_ALIGN_LAYOUT)
1038 align = layout->align;
1039 if (align & layout->alignpossible) {
1040 if (align == layout->align)
1041 cursor.par()->params().align(LYX_ALIGN_LAYOUT);
1043 cursor.par()->params().align(align);
1045 cursor.par()->setLabelWidthString(labelwidthstring);
1046 cursor.par()->params().noindent(noindent);
1047 tmppar = cursor.par()->previous();
1050 redoParagraphs(selection.start, endpar);
1053 setCursor(selection.start.par(), selection.start.pos());
1054 selection.cursor = cursor;
1055 setCursor(selection.end.par(), selection.end.pos());
1057 setCursor(tmpcursor.par(), tmpcursor.pos());
1059 bv()->updateInset(inset_owner);
1063 // set the counter of a paragraph. This includes the labels
1064 void LyXText::setCounter(Buffer const * buf, Paragraph * par)
1066 LyXTextClass const & textclass = buf->params.getLyXTextClass();
1067 LyXLayout_ptr const & layout = par->layout();
1069 if (par->previous()) {
1071 par->params().appendix(par->previous()->params().appendix());
1072 if (!par->params().appendix() && par->params().startOfAppendix()) {
1073 par->params().appendix(true);
1074 textclass.counters().reset();
1076 par->enumdepth = par->previous()->enumdepth;
1077 par->itemdepth = par->previous()->itemdepth;
1079 par->params().appendix(par->params().startOfAppendix());
1084 /* Maybe we have to increment the enumeration depth.
1085 * BUT, enumeration in a footnote is considered in isolation from its
1086 * surrounding paragraph so don't increment if this is the
1087 * first line of the footnote
1088 * AND, bibliographies can't have their depth changed ie. they
1089 * are always of depth 0
1092 && par->previous()->getDepth() < par->getDepth()
1093 && par->previous()->layout()->labeltype == LABEL_COUNTER_ENUMI
1094 && par->enumdepth < 3
1095 && layout->labeltype != LABEL_BIBLIO) {
1099 // Maybe we have to decrement the enumeration depth, see note above
1101 && par->previous()->getDepth() > par->getDepth()
1102 && layout->labeltype != LABEL_BIBLIO) {
1103 par->enumdepth = par->depthHook(par->getDepth())->enumdepth;
1106 if (!par->params().labelString().empty()) {
1107 par->params().labelString(string());
1110 if (layout->margintype == MARGIN_MANUAL) {
1111 if (par->params().labelWidthString().empty()) {
1112 par->setLabelWidthString(layout->labelstring());
1115 par->setLabelWidthString(string());
1118 // is it a layout that has an automatic label?
1119 if (layout->labeltype >= LABEL_COUNTER_CHAPTER) {
1120 int const i = layout->labeltype - LABEL_COUNTER_CHAPTER;
1124 if (i >= 0 && i <= buf->params.secnumdepth) {
1128 textclass.counters().step(layout->latexname());
1130 // Is there a label? Useful for Chapter layout
1131 if (!par->params().appendix()) {
1132 s << layout->labelstring();
1134 s << layout->labelstring_appendix();
1137 // Use of an integer is here less than elegant. For now.
1138 int head = textclass.maxcounter() - LABEL_COUNTER_CHAPTER;
1139 if (!par->params().appendix()) {
1140 numbertype = "sectioning";
1142 numbertype = "appendix";
1143 if (par->isRightToLeftPar(buf->params))
1144 langtype = "hebrew";
1149 s << textclass.counters()
1150 .numberLabel(layout->latexname(),
1151 numbertype, langtype, head);
1153 par->params().labelString(STRCONV(s.str()));
1155 // reset enum counters
1156 textclass.counters().reset("enum");
1157 } else if (layout->labeltype < LABEL_COUNTER_ENUMI) {
1158 textclass.counters().reset("enum");
1159 } else if (layout->labeltype == LABEL_COUNTER_ENUMI) {
1161 // Yes I know this is a really, really! bad solution
1163 string enumcounter("enum");
1165 switch (par->enumdepth) {
1174 enumcounter += "iv";
1177 // not a valid enumdepth...
1181 textclass.counters().step(enumcounter);
1183 s << textclass.counters()
1184 .numberLabel(enumcounter, "enumeration");
1185 par->params().labelString(STRCONV(s.str()));
1187 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
1188 textclass.counters().step("bibitem");
1189 int number = textclass.counters().value("bibitem");
1190 if (par->bibitem()) {
1191 par->bibitem()->setCounter(number);
1192 par->params().labelString(layout->labelstring());
1194 // In biblio should't be following counters but...
1196 string s = layout->labelstring();
1198 // the caption hack:
1199 if (layout->labeltype == LABEL_SENSITIVE) {
1200 Paragraph * tmppar = par;
1203 while (tmppar && tmppar->inInset()
1204 // the single '=' is intended below
1205 && (in = tmppar->inInset()->owner())) {
1206 if (in->lyxCode() == Inset::FLOAT_CODE ||
1207 in->lyxCode() == Inset::WRAP_CODE) {
1211 tmppar = in->parOwner();
1217 = textclass.floats().getType(static_cast<InsetFloat*>(in)->type());
1219 textclass.counters().step(fl.type());
1221 // Doesn't work... yet.
1222 #if USE_BOOST_FORMAT
1223 s = boost::io::str(boost::format(_("%1$s #:")) % fl.name());
1224 // s << boost::format(_("%1$s %1$d:")
1226 // % buf->counters().value(fl.name());
1229 //o << fl.name() << ' ' << buf->counters().value(fl.name()) << ":";
1230 o << fl.name() << " #:";
1231 s = STRCONV(o.str());
1234 // par->SetLayout(0);
1235 // s = layout->labelstring;
1236 s = _("Senseless: ");
1239 par->params().labelString(s);
1241 // reset the enumeration counter. They are always reset
1242 // when there is any other layout between
1243 // Just fall-through between the cases so that all
1244 // enum counters deeper than enumdepth is also reset.
1245 switch (par->enumdepth) {
1247 textclass.counters().reset("enumi");
1249 textclass.counters().reset("enumii");
1251 textclass.counters().reset("enumiii");
1253 textclass.counters().reset("enumiv");
1259 // Updates all counters. Paragraphs with changed label string will be rebroken
1260 void LyXText::updateCounters()
1262 RowList::iterator rowit = rows().begin();
1263 Paragraph * par = rowit->par();
1265 // CHECK if this is really needed. (Lgb)
1266 bv()->buffer()->params.getLyXTextClass().counters().reset();
1269 while (rowit->par() != par)
1272 string const oldLabel = par->params().labelString();
1274 // setCounter can potentially change the labelString.
1275 setCounter(bv()->buffer(), par);
1277 string const & newLabel = par->params().labelString();
1279 if (oldLabel.empty() && !newLabel.empty()) {
1280 removeParagraph(rowit);
1281 appendParagraph(rowit);
1289 void LyXText::insertInset(Inset * inset)
1291 if (!cursor.par()->insetAllowed(inset->lyxCode()))
1293 setUndo(bv(), Undo::FINISH, cursor.par(), cursor.par()->next());
1295 cursor.par()->insertInset(cursor.pos(), inset);
1296 // Just to rebreak and refresh correctly.
1297 // The character will not be inserted a second time
1298 insertChar(Paragraph::META_INSET);
1299 // If we enter a highly editable inset the cursor should be to before
1300 // the inset. This couldn't happen before as Undo was not handled inside
1301 // inset now after the Undo LyX tries to call inset->Edit(...) again
1302 // and cannot do this as the cursor is behind the inset and GetInset
1303 // does not return the inset!
1304 if (isHighlyEditableInset(inset)) {
1311 void LyXText::copyEnvironmentType()
1313 copylayouttype = cursor.par()->layout()->name();
1317 void LyXText::pasteEnvironmentType()
1319 // do nothing if there has been no previous copyEnvironmentType()
1320 if (!copylayouttype.empty())
1321 setLayout(copylayouttype);
1325 void LyXText::cutSelection(bool doclear, bool realcut)
1327 // Stuff what we got on the clipboard. Even if there is no selection.
1329 // There is a problem with having the stuffing here in that the
1330 // larger the selection the slower LyX will get. This can be
1331 // solved by running the line below only when the selection has
1332 // finished. The solution used currently just works, to make it
1333 // faster we need to be more clever and probably also have more
1334 // calls to stuffClipboard. (Lgb)
1335 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1337 // This doesn't make sense, if there is no selection
1338 if (!selection.set())
1341 // OK, we have a selection. This is always between selection.start
1342 // and selection.end
1344 // make sure that the depth behind the selection are restored, too
1345 Paragraph * endpar = selection.end.par()->next();
1346 Paragraph * undoendpar = endpar;
1348 if (endpar && endpar->getDepth()) {
1349 while (endpar && endpar->getDepth()) {
1350 endpar = endpar->next();
1351 undoendpar = endpar;
1353 } else if (endpar) {
1354 endpar = endpar->next(); // because of parindents etc.
1357 setUndo(bv(), Undo::DELETE,
1358 selection.start.par(), undoendpar);
1360 // there are two cases: cut only within one paragraph or
1361 // more than one paragraph
1362 if (selection.start.par() == selection.end.par()) {
1363 // only within one paragraph
1364 endpar = selection.end.par();
1365 int pos = selection.end.pos();
1366 CutAndPaste::cutSelection(selection.start.par(), &endpar,
1367 selection.start.pos(), pos,
1368 bv()->buffer()->params.textclass,
1370 selection.end.pos(pos);
1372 endpar = selection.end.par();
1373 int pos = selection.end.pos();
1374 CutAndPaste::cutSelection(selection.start.par(), &endpar,
1375 selection.start.pos(), pos,
1376 bv()->buffer()->params.textclass,
1379 selection.end.par(endpar);
1380 selection.end.pos(pos);
1381 cursor.pos(selection.end.pos());
1383 endpar = endpar->next();
1385 // sometimes necessary
1387 selection.start.par()->stripLeadingSpaces();
1389 redoParagraphs(selection.start, endpar);
1391 // cutSelection can invalidate the cursor so we need to set
1393 // we prefer the end for when tracking changes
1394 cursor = selection.end;
1396 // need a valid cursor. (Lgb)
1399 setCursor(cursor.par(), cursor.pos());
1400 selection.cursor = cursor;
1405 void LyXText::copySelection()
1407 // stuff the selection onto the X clipboard, from an explicit copy request
1408 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1410 // this doesnt make sense, if there is no selection
1411 if (!selection.set())
1414 // ok we have a selection. This is always between selection.start
1415 // and sel_end cursor
1417 // copy behind a space if there is one
1418 while (selection.start.par()->size() > selection.start.pos()
1419 && selection.start.par()->isLineSeparator(selection.start.pos())
1420 && (selection.start.par() != selection.end.par()
1421 || selection.start.pos() < selection.end.pos()))
1422 selection.start.pos(selection.start.pos() + 1);
1424 CutAndPaste::copySelection(selection.start.par(), selection.end.par(),
1425 selection.start.pos(), selection.end.pos(),
1426 bv()->buffer()->params.textclass);
1430 void LyXText::pasteSelection()
1432 // this does not make sense, if there is nothing to paste
1433 if (!CutAndPaste::checkPastePossible())
1436 setUndo(bv(), Undo::INSERT,
1437 cursor.par(), cursor.par()->next());
1440 Paragraph * actpar = cursor.par();
1441 int pos = cursor.pos();
1443 CutAndPaste::pasteSelection(&actpar, &endpar, pos,
1444 bv()->buffer()->params.textclass);
1446 redoParagraphs(cursor, endpar);
1448 setCursor(cursor.par(), cursor.pos());
1451 selection.cursor = cursor;
1452 setCursor(actpar, pos);
1458 void LyXText::setSelectionRange(lyx::pos_type length)
1463 selection.cursor = cursor;
1470 // simple replacing. The font of the first selected character is used
1471 void LyXText::replaceSelectionWithString(string const & str)
1473 setCursorParUndo(bv());
1476 if (!selection.set()) { // create a dummy selection
1477 selection.end = cursor;
1478 selection.start = cursor;
1481 // Get font setting before we cut
1482 pos_type pos = selection.end.pos();
1483 LyXFont const font = selection.start.par()
1484 ->getFontSettings(bv()->buffer()->params,
1485 selection.start.pos());
1487 // Insert the new string
1488 for (string::const_iterator cit = str.begin(); cit != str.end(); ++cit) {
1489 selection.end.par()->insertChar(pos, (*cit), font);
1493 // Cut the selection
1494 cutSelection(true, false);
1500 // needed to insert the selection
1501 void LyXText::insertStringAsLines(string const & str)
1503 Paragraph * par = cursor.par();
1504 pos_type pos = cursor.pos();
1505 Paragraph * endpar = cursor.par()->next();
1507 setCursorParUndo(bv());
1509 // only to be sure, should not be neccessary
1512 bv()->buffer()->insertStringAsLines(par, pos, current_font, str);
1514 redoParagraphs(cursor, endpar);
1515 setCursor(cursor.par(), cursor.pos());
1516 selection.cursor = cursor;
1517 setCursor(par, pos);
1522 // turns double-CR to single CR, others where converted into one
1523 // blank. Then InsertStringAsLines is called
1524 void LyXText::insertStringAsParagraphs(string const & str)
1526 string linestr(str);
1527 bool newline_inserted = false;
1528 for (string::size_type i = 0; i < linestr.length(); ++i) {
1529 if (linestr[i] == '\n') {
1530 if (newline_inserted) {
1531 // we know that \r will be ignored by
1532 // InsertStringA. Of course, it is a dirty
1533 // trick, but it works...
1534 linestr[i - 1] = '\r';
1538 newline_inserted = true;
1540 } else if (IsPrintable(linestr[i])) {
1541 newline_inserted = false;
1544 insertStringAsLines(linestr);
1548 void LyXText::checkParagraph(Paragraph * par, pos_type pos)
1550 LyXCursor tmpcursor;
1554 RowList::iterator row = getRow(par, pos, y);
1555 RowList::iterator beg = rows().begin();
1557 // is there a break one row above
1559 && boost::prior(row)->par() == row->par()) {
1560 z = rowBreakPoint(*boost::prior(row));
1561 if (z >= row->pos()) {
1562 // set the dimensions of the row above
1563 y -= boost::prior(row)->height();
1566 breakAgain(boost::prior(row));
1568 // set the cursor again. Otherwise
1569 // dangling pointers are possible
1570 setCursor(cursor.par(), cursor.pos(),
1571 false, cursor.boundary());
1572 selection.cursor = cursor;
1577 int const tmpheight = row->height();
1578 pos_type const tmplast = row->lastPos();
1581 if (row->height() == tmpheight && row->lastPos() == tmplast) {
1582 postRowPaint(&*row, y);
1587 // check the special right address boxes
1588 if (par->layout()->margintype == MARGIN_RIGHT_ADDRESS_BOX) {
1590 tmpcursor.row(&*row);
1595 redoDrawingOfParagraph(tmpcursor);
1598 // set the cursor again. Otherwise dangling pointers are possible
1599 // also set the selection
1601 if (selection.set()) {
1603 setCursorIntern(selection.cursor.par(), selection.cursor.pos(),
1604 false, selection.cursor.boundary());
1605 selection.cursor = cursor;
1606 setCursorIntern(selection.start.par(),
1607 selection.start.pos(),
1608 false, selection.start.boundary());
1609 selection.start = cursor;
1610 setCursorIntern(selection.end.par(),
1611 selection.end.pos(),
1612 false, selection.end.boundary());
1613 selection.end = cursor;
1614 setCursorIntern(last_sel_cursor.par(),
1615 last_sel_cursor.pos(),
1616 false, last_sel_cursor.boundary());
1617 last_sel_cursor = cursor;
1620 setCursorIntern(cursor.par(), cursor.pos(),
1621 false, cursor.boundary());
1625 // returns false if inset wasn't found
1626 bool LyXText::updateInset(Inset * inset)
1628 // first check the current paragraph
1629 int pos = cursor.par()->getPositionOfInset(inset);
1631 checkParagraph(cursor.par(), pos);
1635 // check every paragraph
1637 Paragraph * par = ownerParagraph();
1639 pos = par->getPositionOfInset(inset);
1641 checkParagraph(par, pos);
1651 bool LyXText::setCursor(Paragraph * par,
1653 bool setfont, bool boundary)
1655 LyXCursor old_cursor = cursor;
1656 setCursorIntern(par, pos, setfont, boundary);
1657 return deleteEmptyParagraphMechanism(old_cursor);
1661 void LyXText::setCursor(LyXCursor & cur, Paragraph * par,
1662 pos_type pos, bool boundary)
1668 cur.boundary(boundary);
1670 // get the cursor y position in text
1672 RowList::iterator row = getRow(par, 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 < par->size() &&
1685 par->getChar(pos) == Paragraph::META_INSET &&
1686 (ins = par->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 = old_row->lastPrintablePos();
1700 // None of these should happen, but we're scaredy-cats
1701 if (pos > par->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 //if the cursor is in a visible row, anchor to it
1727 if (topy < y && y < topy + bv()->workHeight())
1732 float LyXText::getCursorX(RowList::iterator rit,
1733 pos_type pos, pos_type last, bool boundary) const
1735 pos_type cursor_vpos = 0;
1737 float fill_separator;
1739 float fill_label_hfill;
1740 // This call HAS to be here because of the BidiTables!!!
1741 prepareToPrint(rit, x, fill_separator, fill_hfill,
1744 if (last < rit->pos())
1745 cursor_vpos = rit->pos();
1746 else if (pos > last && !boundary)
1747 cursor_vpos = (rit->par()->isRightToLeftPar(bv()->buffer()->params))
1748 ? rit->pos() : last + 1;
1749 else if (pos > rit->pos() &&
1750 (pos > last || boundary))
1751 /// Place cursor after char at (logical) position pos - 1
1752 cursor_vpos = (bidi_level(pos - 1) % 2 == 0)
1753 ? log2vis(pos - 1) + 1 : log2vis(pos - 1);
1755 /// Place cursor before char at (logical) position pos
1756 cursor_vpos = (bidi_level(pos) % 2 == 0)
1757 ? log2vis(pos) : log2vis(pos) + 1;
1759 pos_type body_pos = rit->par()->beginningOfBody();
1760 if ((body_pos > 0) &&
1761 ((body_pos - 1 > last) ||
1762 !rit->par()->isLineSeparator(body_pos - 1)))
1765 for (pos_type vpos = rit->pos(); vpos < cursor_vpos; ++vpos) {
1766 pos_type pos = vis2log(vpos);
1767 if (body_pos > 0 && pos == body_pos - 1) {
1768 x += fill_label_hfill +
1769 font_metrics::width(
1770 rit->par()->layout()->labelsep,
1771 getLabelFont(bv()->buffer(),
1773 if (rit->par()->isLineSeparator(body_pos - 1))
1774 x -= singleWidth(rit->par(), body_pos - 1);
1776 if (rit->hfillExpansion(pos)) {
1777 x += singleWidth(rit->par(), pos);
1778 if (pos >= body_pos)
1781 x += fill_label_hfill;
1782 } else if (rit->par()->isSeparator(pos)) {
1783 x += singleWidth(rit->par(), pos);
1784 if (pos >= body_pos)
1785 x += fill_separator;
1787 x += singleWidth(rit->par(), pos);
1793 void LyXText::setCursorIntern(Paragraph * par,
1794 pos_type pos, bool setfont, bool boundary)
1796 InsetText * it = static_cast<InsetText *>(par->inInset());
1798 if (it != inset_owner) {
1799 lyxerr[Debug::INSETS] << "InsetText is " << it
1801 << "inset_owner is "
1802 << inset_owner << endl;
1803 #ifdef WITH_WARNINGS
1804 #warning I believe this code is wrong. (Lgb)
1805 #warning Jürgen, have a look at this. (Lgb)
1806 #warning Hmmm, I guess you are right but we
1807 #warning should verify when this is needed
1809 // Jürgen, would you like to have a look?
1810 // I guess we need to move the outer cursor
1811 // and open and lock the inset (bla bla bla)
1812 // stuff I don't know... so can you have a look?
1814 // I moved the lyxerr stuff in here so we can see if
1815 // this is actually really needed and where!
1817 // it->getLyXText(bv())->setCursorIntern(bv(), par, pos, setfont, boundary);
1822 setCursor(cursor, par, pos, boundary);
1828 void LyXText::setCurrentFont()
1830 pos_type pos = cursor.pos();
1831 if (cursor.boundary() && pos > 0)
1835 if (pos == cursor.par()->size())
1837 else // potentional bug... BUG (Lgb)
1838 if (cursor.par()->isSeparator(pos)) {
1839 if (pos > cursor.row()->pos() &&
1840 bidi_level(pos) % 2 ==
1841 bidi_level(pos - 1) % 2)
1843 else if (pos + 1 < cursor.par()->size())
1849 cursor.par()->getFontSettings(bv()->buffer()->params, pos);
1850 real_current_font = getFont(bv()->buffer(), cursor.par(), pos);
1852 if (cursor.pos() == cursor.par()->size() &&
1853 isBoundary(bv()->buffer(), cursor.par(), cursor.pos()) &&
1854 !cursor.boundary()) {
1855 Language const * lang =
1856 cursor.par()->getParLanguage(bv()->buffer()->params);
1857 current_font.setLanguage(lang);
1858 current_font.setNumber(LyXFont::OFF);
1859 real_current_font.setLanguage(lang);
1860 real_current_font.setNumber(LyXFont::OFF);
1865 // returns the column near the specified x-coordinate of the row
1866 // x is set to the real beginning of this column
1868 LyXText::getColumnNearX(RowList::iterator rit, int & x, bool & boundary) const
1871 float fill_separator;
1873 float fill_label_hfill;
1875 prepareToPrint(rit, tmpx, fill_separator,
1876 fill_hfill, fill_label_hfill);
1878 pos_type vc = rit->pos();
1879 pos_type last = rit->lastPrintablePos();
1882 LyXLayout_ptr const & layout = rit->par()->layout();
1884 bool left_side = false;
1886 pos_type body_pos = rit->par()->beginningOfBody();
1887 float last_tmpx = tmpx;
1890 (body_pos - 1 > last ||
1891 !rit->par()->isLineSeparator(body_pos - 1)))
1894 // check for empty row
1895 if (!rit->par()->size()) {
1900 while (vc <= last && tmpx <= x) {
1903 if (body_pos > 0 && c == body_pos - 1) {
1904 tmpx += fill_label_hfill +
1905 font_metrics::width(layout->labelsep,
1906 getLabelFont(bv()->buffer(), rit->par()));
1907 if (rit->par()->isLineSeparator(body_pos - 1))
1908 tmpx -= singleWidth(rit->par(), body_pos - 1);
1911 if (rit->hfillExpansion(c)) {
1912 tmpx += singleWidth(rit->par(), c);
1916 tmpx += fill_label_hfill;
1917 } else if (rit->par()->isSeparator(c)) {
1918 tmpx += singleWidth(rit->par(), c);
1920 tmpx+= fill_separator;
1922 tmpx += singleWidth(rit->par(), c);
1927 if ((tmpx + last_tmpx) / 2 > x) {
1932 if (vc > last + 1) // This shouldn't happen.
1936 // This (rtl_support test) is not needed, but gives
1937 // some speedup if rtl_support=false
1938 bool const lastrow = lyxrc.rtl_support &&
1939 (boost::next(rit) == rowlist_.end() ||
1940 boost::next(rit)->par() != rit->par());
1941 // If lastrow is false, we don't need to compute
1942 // the value of rtl.
1943 bool const rtl = (lastrow)
1944 ? rit->par()->isRightToLeftPar(bv()->buffer()->params)
1947 ((rtl && left_side && vc == rit->pos() && x < tmpx - 5) ||
1948 (!rtl && !left_side && vc == last + 1 && x > tmpx + 5)))
1950 else if (vc == rit->pos()) {
1952 if (bidi_level(c) % 2 == 1)
1955 c = vis2log(vc - 1);
1956 bool const rtl = (bidi_level(c) % 2 == 1);
1957 if (left_side == rtl) {
1959 boundary = isBoundary(bv()->buffer(), rit->par(), c);
1963 if (rit->pos() <= last && c > last
1964 && rit->par()->isNewline(last)) {
1965 if (bidi_level(last) % 2 == 0)
1966 tmpx -= singleWidth(rit->par(), last);
1968 tmpx += singleWidth(rit->par(), last);
1978 void LyXText::setCursorFromCoordinates(int x, int y)
1980 LyXCursor old_cursor = cursor;
1982 setCursorFromCoordinates(cursor, x, y);
1984 deleteEmptyParagraphMechanism(old_cursor);
1991 * return true if the cursor given is at the end of a row,
1992 * and the next row is filled by an inset that spans an entire
1995 bool beforeFullRowInset(Row & row, LyXCursor & cur) {
1998 Row const & next = *row.next();
2000 if (next.pos() != cur.pos() || next.par() != cur.par())
2002 if (!cur.par()->isInset(cur.pos()))
2004 Inset const * inset = cur.par()->getInset(cur.pos());
2005 if (inset->needFullRow() || inset->display())
2012 void LyXText::setCursorFromCoordinates(LyXCursor & cur, int x, int y)
2014 // Get the row first.
2016 RowList::iterator row = getRowNearY(y);
2018 pos_type const column = getColumnNearX(&*row, x, bound);
2019 cur.par(row->par());
2020 cur.pos(row->pos() + column);
2022 cur.y(y + row->baseline());
2025 if (beforeFullRowInset(*row, cur)) {
2026 pos_type last = row->lastPrintablePos();
2027 float x = getCursorX(row->next(), cur.pos(), last, bound);
2029 cur.iy(y + row->height() + row->next()->baseline());
2030 cur.irow(row->next());
2036 cur.boundary(bound);
2040 void LyXText::cursorLeft(bool internal)
2042 if (cursor.pos() > 0) {
2043 bool boundary = cursor.boundary();
2044 setCursor(cursor.par(), cursor.pos() - 1, true, false);
2045 if (!internal && !boundary &&
2046 isBoundary(bv()->buffer(), cursor.par(), cursor.pos() + 1))
2047 setCursor(cursor.par(), cursor.pos() + 1, true, true);
2048 } else if (cursor.par()->previous()) { // steps into the above paragraph.
2049 Paragraph * par = cursor.par()->previous();
2050 setCursor(par, par->size());
2055 void LyXText::cursorRight(bool internal)
2057 if (!internal && cursor.boundary() &&
2058 !cursor.par()->isNewline(cursor.pos()))
2059 setCursor(cursor.par(), cursor.pos(), true, false);
2060 else if (cursor.pos() < cursor.par()->size()) {
2061 setCursor(cursor.par(), cursor.pos() + 1, true, false);
2063 isBoundary(bv()->buffer(), cursor.par(), cursor.pos()))
2064 setCursor(cursor.par(), cursor.pos(), true, true);
2065 } else if (cursor.par()->next())
2066 setCursor(cursor.par()->next(), 0);
2070 void LyXText::cursorUp(bool selecting)
2073 int x = cursor.x_fix();
2074 int y = cursor.y() - cursor.row()->baseline() - 1;
2075 setCursorFromCoordinates(x, y);
2078 int y1 = cursor.iy() - topy;
2081 Inset * inset_hit = checkInsetHit(x, y1);
2082 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2083 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2087 setCursorFromCoordinates(bv(), cursor.x_fix(),
2088 cursor.y() - cursor.row()->baseline() - 1);
2093 void LyXText::cursorDown(bool selecting)
2096 int x = cursor.x_fix();
2097 int y = cursor.y() - cursor.row()->baseline() +
2098 cursor.row()->height() + 1;
2099 setCursorFromCoordinates(x, y);
2100 if (!selecting && cursor.row() == cursor.irow()) {
2102 int y1 = cursor.iy() - topy;
2105 Inset * inset_hit = checkInsetHit(x, y1);
2106 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2107 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2111 setCursorFromCoordinates(bv(), cursor.x_fix(),
2112 cursor.y() - cursor.row()->baseline()
2113 + cursor.row()->height() + 1);
2118 void LyXText::cursorUpParagraph()
2120 if (cursor.pos() > 0) {
2121 setCursor(cursor.par(), 0);
2123 else if (cursor.par()->previous()) {
2124 setCursor(cursor.par()->previous(), 0);
2129 void LyXText::cursorDownParagraph()
2131 if (cursor.par()->next()) {
2132 setCursor(cursor.par()->next(), 0);
2134 setCursor(cursor.par(), cursor.par()->size());
2138 // fix the cursor `cur' after a characters has been deleted at `where'
2139 // position. Called by deleteEmptyParagraphMechanism
2140 void LyXText::fixCursorAfterDelete(LyXCursor & cur,
2141 LyXCursor const & where)
2143 // if cursor is not in the paragraph where the delete occured,
2145 if (cur.par() != where.par())
2148 // if cursor position is after the place where the delete occured,
2150 if (cur.pos() > where.pos())
2151 cur.pos(cur.pos()-1);
2153 // check also if we don't want to set the cursor on a spot behind the
2154 // pagragraph because we erased the last character.
2155 if (cur.pos() > cur.par()->size())
2156 cur.pos(cur.par()->size());
2158 // recompute row et al. for this cursor
2159 setCursor(cur, cur.par(), cur.pos(), cur.boundary());
2163 bool LyXText::deleteEmptyParagraphMechanism(LyXCursor const & old_cursor)
2165 // Would be wrong to delete anything if we have a selection.
2166 if (selection.set())
2169 // We allow all kinds of "mumbo-jumbo" when freespacing.
2170 if (old_cursor.par()->layout()->free_spacing
2171 || old_cursor.par()->isFreeSpacing()) {
2175 /* Ok I'll put some comments here about what is missing.
2176 I have fixed BackSpace (and thus Delete) to not delete
2177 double-spaces automagically. I have also changed Cut,
2178 Copy and Paste to hopefully do some sensible things.
2179 There are still some small problems that can lead to
2180 double spaces stored in the document file or space at
2181 the beginning of paragraphs. This happens if you have
2182 the cursor betwenn to spaces and then save. Or if you
2183 cut and paste and the selection have a space at the
2184 beginning and then save right after the paste. I am
2185 sure none of these are very hard to fix, but I will
2186 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
2187 that I can get some feedback. (Lgb)
2190 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
2191 // delete the LineSeparator.
2194 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
2195 // delete the LineSeparator.
2198 // If the pos around the old_cursor were spaces, delete one of them.
2199 if (old_cursor.par() != cursor.par()
2200 || old_cursor.pos() != cursor.pos()) {
2201 // Only if the cursor has really moved
2203 if (old_cursor.pos() > 0
2204 && old_cursor.pos() < old_cursor.par()->size()
2205 && old_cursor.par()->isLineSeparator(old_cursor.pos())
2206 && old_cursor.par()->isLineSeparator(old_cursor.pos() - 1)) {
2207 old_cursor.par()->erase(old_cursor.pos() - 1);
2208 redoParagraphs(old_cursor, old_cursor.par()->next());
2210 #ifdef WITH_WARNINGS
2211 #warning This will not work anymore when we have multiple views of the same buffer
2212 // In this case, we will have to correct also the cursors held by
2213 // other bufferviews. It will probably be easier to do that in a more
2214 // automated way in LyXCursor code. (JMarc 26/09/2001)
2216 // correct all cursors held by the LyXText
2217 fixCursorAfterDelete(cursor, old_cursor);
2218 fixCursorAfterDelete(selection.cursor,
2220 fixCursorAfterDelete(selection.start,
2222 fixCursorAfterDelete(selection.end, old_cursor);
2223 fixCursorAfterDelete(last_sel_cursor,
2225 fixCursorAfterDelete(toggle_cursor, old_cursor);
2226 fixCursorAfterDelete(toggle_end_cursor,
2232 // don't delete anything if this is the ONLY paragraph!
2233 if (!old_cursor.par()->next() && !old_cursor.par()->previous())
2236 // Do not delete empty paragraphs with keepempty set.
2237 if (old_cursor.par()->layout()->keepempty)
2240 // only do our magic if we changed paragraph
2241 if (old_cursor.par() == cursor.par())
2244 // record if we have deleted a paragraph
2245 // we can't possibly have deleted a paragraph before this point
2246 bool deleted = false;
2248 if ((old_cursor.par()->empty()
2249 || (old_cursor.par()->size() == 1
2250 && old_cursor.par()->isLineSeparator(0)))) {
2251 // ok, we will delete anything
2252 LyXCursor tmpcursor;
2256 if (old_cursor.row()->previous()) {
2257 const_cast<LyXText *>(this)->postPaint(old_cursor.y() - old_cursor.row()->baseline()
2258 - old_cursor.row()->previous()->height());
2260 cursor = old_cursor; // that undo can restore the right cursor position
2261 Paragraph * endpar = old_cursor.par()->next();
2262 if (endpar && endpar->getDepth()) {
2263 while (endpar && endpar->getDepth()) {
2264 endpar = endpar->next();
2267 setUndo(bv(), Undo::DELETE, old_cursor.par(), endpar);
2271 removeRow(old_cursor.row());
2272 if (ownerParagraph() == old_cursor.par()) {
2273 ownerParagraph(ownerParagraph()->next());
2276 delete old_cursor.par();
2278 /* Breakagain the next par. Needed because of
2279 * the parindent that can occur or dissappear.
2280 * The next row can change its height, if
2281 * there is another layout before */
2282 if (refresh_row != rows().end()) {
2283 if (refresh_row->next()) {
2284 breakAgain(refresh_row->next());
2287 setHeightOfRow(refresh_row);
2290 Row * nextrow = old_cursor.row()->next();
2291 const_cast<LyXText *>(this)->postPaint(
2292 old_cursor.y() - old_cursor.row()->baseline());
2295 cursor = old_cursor; // that undo can restore the right cursor position
2296 Paragraph * endpar = old_cursor.par()->next();
2297 if (endpar && endpar->getDepth()) {
2298 while (endpar && endpar->getDepth()) {
2299 endpar = endpar->next();
2302 setUndo(bv(), Undo::DELETE, old_cursor.par(), endpar);
2306 removeRow(old_cursor.row());
2308 if (ownerParagraph() == old_cursor.par()) {
2309 ownerParagraph(ownerParagraph()->next());
2312 delete old_cursor.par();
2314 /* Breakagain the next par. Needed because of
2315 the parindent that can occur or dissappear.
2316 The next row can change its height, if
2317 there is another layout before */
2319 breakAgain(nextrow);
2325 setCursorIntern(cursor.par(), cursor.pos());
2327 if (selection.cursor.par() == old_cursor.par()
2328 && selection.cursor.pos() == old_cursor.pos()) {
2329 // correct selection
2330 selection.cursor = cursor;
2334 if (old_cursor.par()->stripLeadingSpaces()) {
2335 redoParagraphs(old_cursor,
2336 old_cursor.par()->next());
2338 setCursorIntern(cursor.par(), cursor.pos());
2339 selection.cursor = cursor;
2346 Paragraph * LyXText::ownerParagraph() const
2349 return inset_owner->paragraph();
2351 return &*(bv_owner->buffer()->paragraphs.begin());
2355 void LyXText::ownerParagraph(Paragraph * p) const
2358 inset_owner->paragraph(p);
2360 bv_owner->buffer()->paragraphs.set(p);
2365 void LyXText::ownerParagraph(int id, Paragraph * p) const
2367 Paragraph * op = bv_owner->buffer()->getParFromID(id);
2368 if (op && op->inInset()) {
2369 static_cast<InsetText *>(op->inInset())->paragraph(p);
2376 LyXText::refresh_status LyXText::refreshStatus() const
2378 return refresh_status_;
2382 void LyXText::clearPaint()
2384 refresh_status_ = REFRESH_NONE;
2385 refresh_row = rows().end();
2390 void LyXText::postPaint(int start_y)
2392 refresh_status old = refresh_status_;
2394 refresh_status_ = REFRESH_AREA;
2395 refresh_row = rows().end();
2397 if (old != REFRESH_NONE && refresh_y < start_y)
2400 refresh_y = start_y;
2405 // We are an inset's lyxtext. Tell the top-level lyxtext
2406 // it needs to update the row we're in.
2407 LyXText * t = bv()->text;
2408 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2412 // FIXME: we should probably remove this y parameter,
2413 // make refresh_y be 0, and use row->y etc.
2414 void LyXText::postRowPaint(RowList::iterator rit, int start_y)
2416 if (refresh_status_ != REFRESH_NONE && refresh_y < start_y) {
2417 refresh_status_ = REFRESH_AREA;
2420 refresh_y = start_y;
2423 if (refresh_status_ == REFRESH_AREA)
2426 refresh_status_ = REFRESH_ROW;
2432 // We are an inset's lyxtext. Tell the top-level lyxtext
2433 // it needs to update the row we're in.
2434 LyXText * t = bv()->text;
2435 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2439 bool LyXText::isInInset() const
2441 // Sub-level has non-null bv owner and
2442 // non-null inset owner.
2443 return inset_owner != 0 && bv_owner != 0;
2447 int defaultRowHeight()
2449 LyXFont const font(LyXFont::ALL_SANE);
2450 return int(font_metrics::maxAscent(font)
2451 + font_metrics::maxDescent(font) * 1.5);