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(Row * row,
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(row, x, fill_separator, fill_hfill,
1756 if (last < row->pos())
1757 cursor_vpos = row->pos();
1758 else if (pos > last && !boundary)
1759 cursor_vpos = (row->par()->isRightToLeftPar(bv()->buffer()->params))
1760 ? row->pos() : last + 1;
1761 else if (pos > row->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 = row->par()->beginningOfBody();
1772 if ((body_pos > 0) &&
1773 ((body_pos-1 > last) ||
1774 !row->par()->isLineSeparator(body_pos - 1)))
1777 for (pos_type vpos = row->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 row->par()->layout()->labelsep,
1783 getLabelFont(bv()->buffer(),
1785 if (row->par()->isLineSeparator(body_pos - 1))
1787 row->par(), body_pos - 1);
1789 if (row->hfillExpansion(pos)) {
1790 x += singleWidth(row->par(), pos);
1791 if (pos >= body_pos)
1794 x += fill_label_hfill;
1795 } else if (row->par()->isSeparator(pos)) {
1796 x += singleWidth(row->par(), pos);
1797 if (pos >= body_pos)
1798 x += fill_separator;
1800 x += singleWidth(row->par(), pos);
1806 void LyXText::setCursorIntern(Paragraph * par,
1807 pos_type pos, bool setfont, bool boundary)
1809 InsetText * it = static_cast<InsetText *>(par->inInset());
1811 if (it != inset_owner) {
1812 lyxerr[Debug::INSETS] << "InsetText is " << it
1814 << "inset_owner is "
1815 << inset_owner << endl;
1816 #ifdef WITH_WARNINGS
1817 #warning I believe this code is wrong. (Lgb)
1818 #warning Jürgen, have a look at this. (Lgb)
1819 #warning Hmmm, I guess you are right but we
1820 #warning should verify when this is needed
1822 // Jürgen, would you like to have a look?
1823 // I guess we need to move the outer cursor
1824 // and open and lock the inset (bla bla bla)
1825 // stuff I don't know... so can you have a look?
1827 // I moved the lyxerr stuff in here so we can see if
1828 // this is actually really needed and where!
1830 // it->getLyXText(bv())->setCursorIntern(bv(), par, pos, setfont, boundary);
1835 setCursor(cursor, par, pos, boundary);
1841 void LyXText::setCurrentFont()
1843 pos_type pos = cursor.pos();
1844 if (cursor.boundary() && pos > 0)
1848 if (pos == cursor.par()->size())
1850 else // potentional bug... BUG (Lgb)
1851 if (cursor.par()->isSeparator(pos)) {
1852 if (pos > cursor.row()->pos() &&
1853 bidi_level(pos) % 2 ==
1854 bidi_level(pos - 1) % 2)
1856 else if (pos + 1 < cursor.par()->size())
1862 cursor.par()->getFontSettings(bv()->buffer()->params, pos);
1863 real_current_font = getFont(bv()->buffer(), cursor.par(), pos);
1865 if (cursor.pos() == cursor.par()->size() &&
1866 isBoundary(bv()->buffer(), cursor.par(), cursor.pos()) &&
1867 !cursor.boundary()) {
1868 Language const * lang =
1869 cursor.par()->getParLanguage(bv()->buffer()->params);
1870 current_font.setLanguage(lang);
1871 current_font.setNumber(LyXFont::OFF);
1872 real_current_font.setLanguage(lang);
1873 real_current_font.setNumber(LyXFont::OFF);
1878 // returns the column near the specified x-coordinate of the row
1879 // x is set to the real beginning of this column
1881 LyXText::getColumnNearX(RowList::iterator rit, int & x,
1882 bool & boundary) const
1885 float fill_separator;
1887 float fill_label_hfill;
1889 prepareToPrint(rit, tmpx, fill_separator,
1890 fill_hfill, fill_label_hfill);
1892 pos_type vc = rit->pos();
1893 pos_type last = rit->lastPrintablePos();
1896 LyXLayout_ptr const & layout = rit->par()->layout();
1898 bool left_side = false;
1900 pos_type body_pos = rit->par()->beginningOfBody();
1901 float last_tmpx = tmpx;
1904 (body_pos - 1 > last ||
1905 !rit->par()->isLineSeparator(body_pos - 1)))
1908 // check for empty row
1909 if (!rit->par()->size()) {
1914 while (vc <= last && tmpx <= x) {
1917 if (body_pos > 0 && c == body_pos - 1) {
1918 tmpx += fill_label_hfill +
1919 font_metrics::width(layout->labelsep,
1920 getLabelFont(bv()->buffer(), rit->par()));
1921 if (rit->par()->isLineSeparator(body_pos - 1))
1922 tmpx -= singleWidth(rit->par(), body_pos - 1);
1925 if (rit->hfillExpansion(c)) {
1926 tmpx += singleWidth(rit->par(), c);
1930 tmpx += fill_label_hfill;
1931 } else if (rit->par()->isSeparator(c)) {
1932 tmpx += singleWidth(rit->par(), c);
1934 tmpx+= fill_separator;
1936 tmpx += singleWidth(rit->par(), c);
1941 if ((tmpx + last_tmpx) / 2 > x) {
1946 if (vc > last + 1) // This shouldn't happen.
1950 bool const lastrow = lyxrc.rtl_support // This is not needed, but gives
1951 // some speedup if rtl_support=false
1952 && (boost::next(rit) == rows().end() ||
1953 boost::next(rit)->par() != rit->par());
1954 bool const rtl = (lastrow)
1955 ? rit->par()->isRightToLeftPar(bv()->buffer()->params)
1956 : false; // If lastrow is false, we don't need to compute
1957 // the value of rtl.
1960 ((rtl && left_side && vc == rit->pos() && x < tmpx - 5) ||
1961 (!rtl && !left_side && vc == last + 1 && x > tmpx + 5)))
1963 else if (vc == rit->pos()) {
1965 if (bidi_level(c) % 2 == 1)
1968 c = vis2log(vc - 1);
1969 bool const rtl = (bidi_level(c) % 2 == 1);
1970 if (left_side == rtl) {
1972 boundary = isBoundary(bv()->buffer(), rit->par(), c);
1976 if (rit->pos() <= last && c > last
1977 && rit->par()->isNewline(last)) {
1978 if (bidi_level(last) % 2 == 0)
1979 tmpx -= singleWidth(rit->par(), last);
1981 tmpx += singleWidth(rit->par(), last);
1991 void LyXText::setCursorFromCoordinates(int x, int y)
1993 LyXCursor old_cursor = cursor;
1995 setCursorFromCoordinates(cursor, x, y);
1997 deleteEmptyParagraphMechanism(old_cursor);
2004 * return true if the cursor given is at the end of a row,
2005 * and the next row is filled by an inset that spans an entire
2008 bool beforeFullRowInset(Row & row, LyXCursor & cur) {
2011 Row const & next = *row.next();
2013 if (next.pos() != cur.pos() || next.par() != cur.par())
2015 if (!cur.par()->isInset(cur.pos()))
2017 Inset const * inset = cur.par()->getInset(cur.pos());
2018 if (inset->needFullRow() || inset->display())
2025 void LyXText::setCursorFromCoordinates(LyXCursor & cur, int x, int y)
2027 // Get the row first.
2029 RowList::iterator row = getRowNearY(y);
2031 pos_type const column = getColumnNearX(&*row, x, bound);
2032 cur.par(row->par());
2033 cur.pos(row->pos() + column);
2035 cur.y(y + row->baseline());
2038 if (beforeFullRowInset(*row, cur)) {
2039 pos_type last = row->lastPrintablePos();
2040 float x = getCursorX(row->next(), cur.pos(), last, bound);
2042 cur.iy(y + row->height() + row->next()->baseline());
2043 cur.irow(row->next());
2049 cur.boundary(bound);
2053 void LyXText::cursorLeft(bool internal)
2055 if (cursor.pos() > 0) {
2056 bool boundary = cursor.boundary();
2057 setCursor(cursor.par(), cursor.pos() - 1, true, false);
2058 if (!internal && !boundary &&
2059 isBoundary(bv()->buffer(), cursor.par(), cursor.pos() + 1))
2060 setCursor(cursor.par(), cursor.pos() + 1, true, true);
2061 } else if (cursor.par()->previous()) { // steps into the above paragraph.
2062 Paragraph * par = cursor.par()->previous();
2063 setCursor(par, par->size());
2068 void LyXText::cursorRight(bool internal)
2070 if (!internal && cursor.boundary() &&
2071 !cursor.par()->isNewline(cursor.pos()))
2072 setCursor(cursor.par(), cursor.pos(), true, false);
2073 else if (cursor.pos() < cursor.par()->size()) {
2074 setCursor(cursor.par(), cursor.pos() + 1, true, false);
2076 isBoundary(bv()->buffer(), cursor.par(), cursor.pos()))
2077 setCursor(cursor.par(), cursor.pos(), true, true);
2078 } else if (cursor.par()->next())
2079 setCursor(cursor.par()->next(), 0);
2083 void LyXText::cursorUp(bool selecting)
2086 int x = cursor.x_fix();
2087 int y = cursor.y() - cursor.row()->baseline() - 1;
2088 setCursorFromCoordinates(x, y);
2091 int y1 = cursor.iy() - topy;
2094 Inset * inset_hit = checkInsetHit(x, y1);
2095 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2096 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2100 setCursorFromCoordinates(bv(), cursor.x_fix(),
2101 cursor.y() - cursor.row()->baseline() - 1);
2106 void LyXText::cursorDown(bool selecting)
2109 int x = cursor.x_fix();
2110 int y = cursor.y() - cursor.row()->baseline() +
2111 cursor.row()->height() + 1;
2112 setCursorFromCoordinates(x, y);
2113 if (!selecting && cursor.row() == cursor.irow()) {
2115 int y1 = cursor.iy() - topy;
2118 Inset * inset_hit = checkInsetHit(x, y1);
2119 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2120 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2124 setCursorFromCoordinates(bv(), cursor.x_fix(),
2125 cursor.y() - cursor.row()->baseline()
2126 + cursor.row()->height() + 1);
2131 void LyXText::cursorUpParagraph()
2133 if (cursor.pos() > 0) {
2134 setCursor(cursor.par(), 0);
2136 else if (cursor.par()->previous()) {
2137 setCursor(cursor.par()->previous(), 0);
2142 void LyXText::cursorDownParagraph()
2144 if (cursor.par()->next()) {
2145 setCursor(cursor.par()->next(), 0);
2147 setCursor(cursor.par(), cursor.par()->size());
2151 // fix the cursor `cur' after a characters has been deleted at `where'
2152 // position. Called by deleteEmptyParagraphMechanism
2153 void LyXText::fixCursorAfterDelete(LyXCursor & cur,
2154 LyXCursor const & where)
2156 // if cursor is not in the paragraph where the delete occured,
2158 if (cur.par() != where.par())
2161 // if cursor position is after the place where the delete occured,
2163 if (cur.pos() > where.pos())
2164 cur.pos(cur.pos()-1);
2166 // check also if we don't want to set the cursor on a spot behind the
2167 // pagragraph because we erased the last character.
2168 if (cur.pos() > cur.par()->size())
2169 cur.pos(cur.par()->size());
2171 // recompute row et al. for this cursor
2172 setCursor(cur, cur.par(), cur.pos(), cur.boundary());
2176 bool LyXText::deleteEmptyParagraphMechanism(LyXCursor const & old_cursor)
2178 // Would be wrong to delete anything if we have a selection.
2179 if (selection.set())
2182 // We allow all kinds of "mumbo-jumbo" when freespacing.
2183 if (old_cursor.par()->layout()->free_spacing
2184 || old_cursor.par()->isFreeSpacing()) {
2188 /* Ok I'll put some comments here about what is missing.
2189 I have fixed BackSpace (and thus Delete) to not delete
2190 double-spaces automagically. I have also changed Cut,
2191 Copy and Paste to hopefully do some sensible things.
2192 There are still some small problems that can lead to
2193 double spaces stored in the document file or space at
2194 the beginning of paragraphs. This happens if you have
2195 the cursor betwenn to spaces and then save. Or if you
2196 cut and paste and the selection have a space at the
2197 beginning and then save right after the paste. I am
2198 sure none of these are very hard to fix, but I will
2199 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
2200 that I can get some feedback. (Lgb)
2203 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
2204 // delete the LineSeparator.
2207 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
2208 // delete the LineSeparator.
2211 // If the pos around the old_cursor were spaces, delete one of them.
2212 if (old_cursor.par() != cursor.par()
2213 || old_cursor.pos() != cursor.pos()) {
2214 // Only if the cursor has really moved
2216 if (old_cursor.pos() > 0
2217 && old_cursor.pos() < old_cursor.par()->size()
2218 && old_cursor.par()->isLineSeparator(old_cursor.pos())
2219 && old_cursor.par()->isLineSeparator(old_cursor.pos() - 1)) {
2220 old_cursor.par()->erase(old_cursor.pos() - 1);
2221 redoParagraphs(old_cursor, old_cursor.par()->next());
2223 #ifdef WITH_WARNINGS
2224 #warning This will not work anymore when we have multiple views of the same buffer
2225 // In this case, we will have to correct also the cursors held by
2226 // other bufferviews. It will probably be easier to do that in a more
2227 // automated way in LyXCursor code. (JMarc 26/09/2001)
2229 // correct all cursors held by the LyXText
2230 fixCursorAfterDelete(cursor, old_cursor);
2231 fixCursorAfterDelete(selection.cursor,
2233 fixCursorAfterDelete(selection.start,
2235 fixCursorAfterDelete(selection.end, old_cursor);
2236 fixCursorAfterDelete(last_sel_cursor,
2238 fixCursorAfterDelete(toggle_cursor, old_cursor);
2239 fixCursorAfterDelete(toggle_end_cursor,
2245 // don't delete anything if this is the ONLY paragraph!
2246 if (!old_cursor.par()->next() && !old_cursor.par()->previous())
2249 // Do not delete empty paragraphs with keepempty set.
2250 if (old_cursor.par()->layout()->keepempty)
2253 // only do our magic if we changed paragraph
2254 if (old_cursor.par() == cursor.par())
2257 // record if we have deleted a paragraph
2258 // we can't possibly have deleted a paragraph before this point
2259 bool deleted = false;
2261 if ((old_cursor.par()->empty()
2262 || (old_cursor.par()->size() == 1
2263 && old_cursor.par()->isLineSeparator(0)))) {
2264 // ok, we will delete anything
2265 LyXCursor tmpcursor;
2269 if (old_cursor.row()->previous()) {
2270 const_cast<LyXText *>(this)->postPaint(old_cursor.y() - old_cursor.row()->baseline()
2271 - old_cursor.row()->previous()->height());
2273 cursor = old_cursor; // that undo can restore the right cursor position
2274 Paragraph * endpar = old_cursor.par()->next();
2275 if (endpar && endpar->getDepth()) {
2276 while (endpar && endpar->getDepth()) {
2277 endpar = endpar->next();
2280 setUndo(bv(), Undo::DELETE, old_cursor.par(), endpar);
2284 removeRow(old_cursor.row());
2285 if (ownerParagraph() == old_cursor.par()) {
2286 ownerParagraph(ownerParagraph()->next());
2289 delete old_cursor.par();
2291 /* Breakagain the next par. Needed because of
2292 * the parindent that can occur or dissappear.
2293 * The next row can change its height, if
2294 * there is another layout before */
2295 if (refresh_row != rows().end()) {
2296 if (refresh_row->next()) {
2297 breakAgain(refresh_row->next());
2300 setHeightOfRow(refresh_row);
2303 Row * nextrow = old_cursor.row()->next();
2304 const_cast<LyXText *>(this)->postPaint(
2305 old_cursor.y() - old_cursor.row()->baseline());
2308 cursor = old_cursor; // that undo can restore the right cursor position
2309 Paragraph * endpar = old_cursor.par()->next();
2310 if (endpar && endpar->getDepth()) {
2311 while (endpar && endpar->getDepth()) {
2312 endpar = endpar->next();
2315 setUndo(bv(), Undo::DELETE, old_cursor.par(), endpar);
2319 removeRow(old_cursor.row());
2321 if (ownerParagraph() == old_cursor.par()) {
2322 ownerParagraph(ownerParagraph()->next());
2325 delete old_cursor.par();
2327 /* Breakagain the next par. Needed because of
2328 the parindent that can occur or dissappear.
2329 The next row can change its height, if
2330 there is another layout before */
2332 breakAgain(nextrow);
2338 setCursorIntern(cursor.par(), cursor.pos());
2340 if (selection.cursor.par() == old_cursor.par()
2341 && selection.cursor.pos() == old_cursor.pos()) {
2342 // correct selection
2343 selection.cursor = cursor;
2347 if (old_cursor.par()->stripLeadingSpaces()) {
2348 redoParagraphs(old_cursor,
2349 old_cursor.par()->next());
2351 setCursorIntern(cursor.par(), cursor.pos());
2352 selection.cursor = cursor;
2359 Paragraph * LyXText::ownerParagraph() const
2362 return inset_owner->paragraph();
2364 return &*(bv_owner->buffer()->paragraphs.begin());
2368 void LyXText::ownerParagraph(Paragraph * p) const
2371 inset_owner->paragraph(p);
2373 bv_owner->buffer()->paragraphs.set(p);
2378 void LyXText::ownerParagraph(int id, Paragraph * p) const
2380 Paragraph * op = bv_owner->buffer()->getParFromID(id);
2381 if (op && op->inInset()) {
2382 static_cast<InsetText *>(op->inInset())->paragraph(p);
2389 LyXText::refresh_status LyXText::refreshStatus() const
2391 return refresh_status_;
2395 void LyXText::clearPaint()
2397 refresh_status_ = REFRESH_NONE;
2398 refresh_row = rows().end();
2403 void LyXText::postPaint(int start_y)
2405 refresh_status old = refresh_status_;
2407 refresh_status_ = REFRESH_AREA;
2408 refresh_row = rows().end();
2410 if (old != REFRESH_NONE && refresh_y < start_y)
2413 refresh_y = start_y;
2418 // We are an inset's lyxtext. Tell the top-level lyxtext
2419 // it needs to update the row we're in.
2420 LyXText * t = bv()->text;
2421 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2425 // FIXME: we should probably remove this y parameter,
2426 // make refresh_y be 0, and use row->y etc.
2427 void LyXText::postRowPaint(RowList::iterator rit, int start_y)
2429 if (refresh_status_ != REFRESH_NONE && refresh_y < start_y) {
2430 refresh_status_ = REFRESH_AREA;
2433 refresh_y = start_y;
2436 if (refresh_status_ == REFRESH_AREA)
2439 refresh_status_ = REFRESH_ROW;
2445 // We are an inset's lyxtext. Tell the top-level lyxtext
2446 // it needs to update the row we're in.
2447 LyXText * t = bv()->text;
2448 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2452 bool LyXText::isInInset() const
2454 // Sub-level has non-null bv owner and
2455 // non-null inset owner.
2456 return inset_owner != 0 && bv_owner != 0;
2460 int defaultRowHeight()
2462 LyXFont const font(LyXFont::ALL_SANE);
2463 return int(font_metrics::maxAscent(font)
2464 + font_metrics::maxDescent(font) * 1.5);