1 /* This file is part of
2 * ======================================================
4 * LyX, The Document Processor
6 * Copyright 1995 Matthias Ettrich
7 * Copyright 1995-2001 The LyX Team.
9 * ====================================================== */
15 #include "paragraph.h"
16 #include "frontends/LyXView.h"
17 #include "undo_funcs.h"
19 #include "bufferparams.h"
21 #include "BufferView.h"
22 #include "CutAndPaste.h"
23 #include "frontends/Painter.h"
24 #include "frontends/font_metrics.h"
28 #include "FloatList.h"
30 #include "ParagraphParameters.h"
32 #include "lyxrow_funcs.h"
34 #include "insets/insetbibitem.h"
35 #include "insets/insetfloat.h"
37 #include "support/LAssert.h"
38 #include "support/textutils.h"
39 #include "support/lstrings.h"
41 #include "support/BoostFormat.h"
51 LyXText::LyXText(BufferView * bv)
52 : height(0), width(0), anchor_row_offset_(0),
53 inset_owner(0), the_locking_inset(0), bv_owner(bv)
55 anchor_row_ = rows().end();
56 need_break_row = rows().end();
57 refresh_row = rows().end();
63 LyXText::LyXText(BufferView * bv, InsetText * inset)
64 : height(0), width(0), anchor_row_offset_(0),
65 inset_owner(inset), the_locking_inset(0), bv_owner(bv)
67 anchor_row_ = rows().end();
68 need_break_row = rows().end();
69 refresh_row = rows().end();
75 void LyXText::init(BufferView * bview, bool reinit)
79 need_break_row = rows().end();
81 copylayouttype.erase();
84 } else if (!rowlist_.empty())
87 ParagraphList::iterator par = ownerParagraphs().begin();
88 ParagraphList::iterator end = ownerParagraphs().end();
90 current_font = getFont(bview->buffer(), &*par, 0);
92 for (; par != end; ++par) {
93 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 ParagraphList::iterator tmppit = rit->par();
300 while (rit != rows().end() && rit->par() == tmppit) {
301 RowList::iterator tmprit = boost::next(rit);
308 #warning FIXME Convert this to ParagraphList::iterator
309 void LyXText::insertParagraph(Paragraph * par, RowList::iterator rowit)
311 // insert a new row, starting at position 0
313 RowList::iterator rit = rowlist_.insert(rowit, newrow);
315 // and now append the whole paragraph before the new row
316 appendParagraph(rit);
320 Inset * LyXText::getInset() const
322 if (cursor.pos() < cursor.par()->size()
323 && cursor.par()->isInset(cursor.pos())) {
324 return cursor.par()->getInset(cursor.pos());
330 void LyXText::toggleInset()
332 Inset * inset = getInset();
333 // is there an editable inset at cursor position?
334 if (!isEditableInset(inset)) {
335 // No, try to see if we are inside a collapsable inset
336 if (inset_owner && inset_owner->owner()
337 && inset_owner->owner()->isOpen()) {
338 bv()->unlockInset(static_cast<UpdatableInset *>(inset_owner->owner()));
339 inset_owner->owner()->close(bv());
340 bv()->getLyXText()->cursorRight(bv());
344 //bv()->owner()->message(inset->editMessage());
346 // do we want to keep this?? (JMarc)
347 if (!isHighlyEditableInset(inset))
348 setCursorParUndo(bv());
350 if (inset->isOpen()) {
356 bv()->updateInset(inset);
360 /* used in setlayout */
361 // Asger is not sure we want to do this...
362 void LyXText::makeFontEntriesLayoutSpecific(Buffer const & buf,
365 LyXLayout_ptr const & layout = par.layout();
368 for (pos_type pos = 0; pos < par.size(); ++pos) {
369 if (pos < par.beginningOfBody())
370 layoutfont = layout->labelfont;
372 layoutfont = layout->font;
374 LyXFont tmpfont = par.getFontSettings(buf.params, pos);
375 tmpfont.reduce(layoutfont);
376 par.setFont(pos, tmpfont);
381 Paragraph * LyXText::setLayout(LyXCursor & cur, LyXCursor & sstart_cur,
382 LyXCursor & send_cur,
383 string const & layout)
385 Paragraph * endpar = send_cur.par()->next();
386 Paragraph * undoendpar = endpar;
388 if (endpar && endpar->getDepth()) {
389 while (endpar && endpar->getDepth()) {
390 endpar = endpar->next();
394 endpar = endpar->next(); // because of parindents etc.
397 setUndo(bv(), Undo::EDIT, sstart_cur.par(), undoendpar);
399 // ok we have a selection. This is always between sstart_cur
400 // and sel_end cursor
402 Paragraph * par = sstart_cur.par();
403 Paragraph * epar = send_cur.par()->next();
405 LyXLayout_ptr const & lyxlayout =
406 bv()->buffer()->params.getLyXTextClass()[layout];
409 par->applyLayout(lyxlayout);
410 makeFontEntriesLayoutSpecific(*bv()->buffer(), *par);
411 Paragraph * fppar = par;
412 fppar->params().spaceTop(lyxlayout->fill_top ?
413 VSpace(VSpace::VFILL)
414 : VSpace(VSpace::NONE));
415 fppar->params().spaceBottom(lyxlayout->fill_bottom ?
416 VSpace(VSpace::VFILL)
417 : VSpace(VSpace::NONE));
418 if (lyxlayout->margintype == MARGIN_MANUAL)
419 par->setLabelWidthString(lyxlayout->labelstring());
422 } while (par != epar);
428 // set layout over selection and make a total rebreak of those paragraphs
429 void LyXText::setLayout(string const & layout)
431 LyXCursor tmpcursor = cursor; /* store the current cursor */
433 // if there is no selection just set the layout
434 // of the current paragraph */
435 if (!selection.set()) {
436 selection.start = cursor; // dummy selection
437 selection.end = cursor;
439 Paragraph * endpar = setLayout(cursor, selection.start,
440 selection.end, layout);
441 redoParagraphs(selection.start, endpar);
443 // we have to reset the selection, because the
444 // geometry could have changed
445 setCursor(selection.start.par(),
446 selection.start.pos(), false);
447 selection.cursor = cursor;
448 setCursor(selection.end.par(), selection.end.pos(), false);
452 setCursor(tmpcursor.par(), tmpcursor.pos(), true);
456 bool LyXText::changeDepth(bv_funcs::DEPTH_CHANGE type, bool test_only)
458 ParagraphList::iterator pit(cursor.par());
459 ParagraphList::iterator end(cursor.par());
460 ParagraphList::iterator start = pit;
462 if (selection.set()) {
463 pit = selection.start.par();
464 end = selection.end.par();
468 ParagraphList::iterator pastend = end;
470 setUndo(bv(), Undo::EDIT, &(*start), &(*pastend));
472 bool changed = false;
474 int prev_after_depth = 0;
475 #warning parlist ... could be nicer ?
476 if (start != ownerParagraphs().begin())
477 prev_after_depth = boost::prior(start)->getMaxDepthAfter();
480 int const depth = pit->params().depth();
481 if (type == bv_funcs::INC_DEPTH) {
482 if (depth < prev_after_depth
483 && pit->layout()->labeltype != LABEL_BIBLIO) {
486 pit->params().depth(depth + 1);
491 pit->params().depth(depth - 1);
494 prev_after_depth = pit->getMaxDepthAfter();
505 // Wow, redoParagraphs is stupid.
507 setCursor(tmpcursor, &(*start), 0);
508 redoParagraphs(tmpcursor, &(*pastend));
510 // We need to actually move the text->cursor. I don't
511 // understand why ...
514 // we have to reset the visual selection because the
515 // geometry could have changed
516 if (selection.set()) {
517 setCursor(selection.start.par(), selection.start.pos());
518 selection.cursor = cursor;
519 setCursor(selection.end.par(), selection.end.pos());
524 setCursor(tmpcursor.par(), tmpcursor.pos());
530 // set font over selection and make a total rebreak of those paragraphs
531 void LyXText::setFont(LyXFont const & font, bool toggleall)
533 // if there is no selection just set the current_font
534 if (!selection.set()) {
535 // Determine basis font
537 if (cursor.pos() < cursor.par()->beginningOfBody()) {
538 layoutfont = getLabelFont(bv()->buffer(),
541 layoutfont = getLayoutFont(bv()->buffer(),
544 // Update current font
545 real_current_font.update(font,
546 bv()->buffer()->params.language,
549 // Reduce to implicit settings
550 current_font = real_current_font;
551 current_font.reduce(layoutfont);
552 // And resolve it completely
553 real_current_font.realize(layoutfont);
558 LyXCursor tmpcursor = cursor; // store the current cursor
560 // ok we have a selection. This is always between sel_start_cursor
561 // and sel_end cursor
563 setUndo(bv(), Undo::EDIT,
564 selection.start.par(), selection.end.par()->next());
566 cursor = selection.start;
567 while (cursor.par() != selection.end.par() ||
568 cursor.pos() < selection.end.pos())
570 if (cursor.pos() < cursor.par()->size()) {
571 // an open footnote should behave like a closed one
572 setCharFont(cursor.par(), cursor.pos(),
574 cursor.pos(cursor.pos() + 1);
577 cursor.par(cursor.par()->next());
582 redoParagraphs(selection.start, selection.end.par()->next());
584 // we have to reset the selection, because the
585 // geometry could have changed, but we keep
586 // it for user convenience
587 setCursor(selection.start.par(), selection.start.pos());
588 selection.cursor = cursor;
589 setCursor(selection.end.par(), selection.end.pos());
591 setCursor(tmpcursor.par(), tmpcursor.pos(), true,
592 tmpcursor.boundary());
596 void LyXText::redoHeightOfParagraph()
598 RowList::iterator tmprow = cursor.row();
599 int y = cursor.y() - tmprow->baseline();
601 setHeightOfRow(tmprow);
603 while (tmprow != rows().begin()
604 && boost::prior(tmprow)->par() == tmprow->par()) {
606 y -= tmprow->height();
607 setHeightOfRow(tmprow);
612 setCursor(cursor.par(), cursor.pos(), false, cursor.boundary());
616 void LyXText::redoDrawingOfParagraph(LyXCursor const & cur)
618 RowList::iterator tmprow = cur.row();
620 int y = cur.y() - tmprow->baseline();
621 setHeightOfRow(tmprow);
623 while (tmprow != rows().begin()
624 && boost::prior(tmprow)->par() == tmprow->par()) {
626 y -= tmprow->height();
630 setCursor(cur.par(), cur.pos());
634 // deletes and inserts again all paragaphs between the cursor
635 // and the specified par
636 // This function is needed after SetLayout and SetFont etc.
637 void LyXText::redoParagraphs(LyXCursor const & cur,
638 Paragraph const * ep)
640 RowList::iterator tmprit = cur.row();
641 ParagraphList::iterator endpit(const_cast<Paragraph*>(ep));
642 int y = cur.y() - tmprit->baseline();
644 ParagraphList::iterator first_phys_pit;
645 if (tmprit == rows().begin()) {
646 // A trick/hack for UNDO.
647 // This is needed because in an UNDO/REDO we could have
648 // changed the ownerParagrah() so the paragraph inside
649 // the row is NOT my really first par anymore.
650 // Got it Lars ;) (Jug 20011206)
651 first_phys_pit = ownerParagraphs().begin();
653 // In here prevrit could be set to rows().end(). (Lgb)
655 first_phys_pit = tmprit->par();
656 while (tmprit != rows().begin()
657 && boost::prior(tmprit)->par() == first_phys_pit)
660 y -= tmprit->height();
663 // Is it possible to put the prevrit setting in here? (Lgb)
666 RowList::iterator prevrit;
667 bool good_prevrit = false;
669 // It seems to mee that good_prevrit is not needed if we let
670 // a bad prevrit have the value rows().end() (Lgb)
671 if (tmprit != rows().begin()) {
672 prevrit = boost::prior(tmprit);
677 while (tmprit != rows().end() && tmprit->par() != endpit) {
678 RowList::iterator tmprit2 = tmprit++;
682 // Reinsert the paragraphs.
683 ParagraphList::iterator tmppit = first_phys_pit;
685 // See if this loop can be rewritten as a while loop instead.
686 // That should also make the code a bit easier to read. (Lgb)
688 if (tmppit != ownerParagraphs().end()) {
689 insertParagraph(&*tmppit, tmprit);
690 while (tmprit != rows().end()
691 && tmprit->par() == tmppit) {
696 } while (tmppit != ownerParagraphs().end() && tmppit != endpit);
699 // If the above changes are done, then we can compare prevrit
700 // with rows().end() here. (Lgb)
702 setHeightOfRow(prevrit);
703 const_cast<LyXText *>(this)->postPaint(y - prevrit->height());
705 setHeightOfRow(rows().begin());
706 const_cast<LyXText *>(this)->postPaint(0);
708 if (tmprit != rows().end())
709 setHeightOfRow(tmprit);
714 void LyXText::fullRebreak()
716 if (rows().empty()) {
720 if (need_break_row != rows().end()) {
721 breakAgain(need_break_row);
722 need_break_row = rows().end();
728 // important for the screen
731 // the cursor set functions have a special mechanism. When they
732 // realize, that you left an empty paragraph, they will delete it.
733 // They also delete the corresponding row
735 // need the selection cursor:
736 void LyXText::setSelection()
738 bool const lsel = selection.set();
740 if (!selection.set()) {
741 last_sel_cursor = selection.cursor;
742 selection.start = selection.cursor;
743 selection.end = selection.cursor;
748 // first the toggling area
749 if (cursor.y() < last_sel_cursor.y()
750 || (cursor.y() == last_sel_cursor.y()
751 && cursor.x() < last_sel_cursor.x())) {
752 toggle_end_cursor = last_sel_cursor;
753 toggle_cursor = cursor;
755 toggle_end_cursor = cursor;
756 toggle_cursor = last_sel_cursor;
759 last_sel_cursor = cursor;
761 // and now the whole selection
763 if (selection.cursor.par() == cursor.par())
764 if (selection.cursor.pos() < cursor.pos()) {
765 selection.end = cursor;
766 selection.start = selection.cursor;
768 selection.end = selection.cursor;
769 selection.start = cursor;
771 else if (selection.cursor.y() < cursor.y() ||
772 (selection.cursor.y() == cursor.y()
773 && selection.cursor.x() < cursor.x())) {
774 selection.end = cursor;
775 selection.start = selection.cursor;
778 selection.end = selection.cursor;
779 selection.start = cursor;
782 // a selection with no contents is not a selection
783 if (selection.start.par() == selection.end.par() &&
784 selection.start.pos() == selection.end.pos())
785 selection.set(false);
787 if (inset_owner && (selection.set() || lsel))
788 inset_owner->setUpdateStatus(bv(), InsetText::SELECTION);
792 string const LyXText::selectionAsString(Buffer const * buffer,
795 if (!selection.set()) return string();
797 // should be const ...
798 Paragraph * startpar(selection.start.par());
799 Paragraph * endpar(selection.end.par());
800 pos_type const startpos(selection.start.pos());
801 pos_type const endpos(selection.end.pos());
803 if (startpar == endpar) {
804 return startpar->asString(buffer, startpos, endpos, label);
809 // First paragraph in selection
810 result += startpar->asString(buffer, startpos, startpar->size(), label) + "\n\n";
812 // The paragraphs in between (if any)
813 LyXCursor tmpcur(selection.start);
814 tmpcur.par(tmpcur.par()->next());
815 while (tmpcur.par() != endpar) {
816 result += tmpcur.par()->asString(buffer, 0,
817 tmpcur.par()->size(),
819 tmpcur.par(tmpcur.par()->next());
822 // Last paragraph in selection
823 result += endpar->asString(buffer, 0, endpos, label);
829 void LyXText::clearSelection()
831 selection.set(false);
832 selection.mark(false);
833 last_sel_cursor = selection.end = selection.start = selection.cursor = cursor;
834 // reset this in the bv_owner!
835 if (bv_owner && bv_owner->text)
836 bv_owner->text->xsel_cache.set(false);
840 void LyXText::cursorHome()
842 setCursor(cursor.par(), cursor.row()->pos());
846 void LyXText::cursorEnd()
848 if (cursor.par()->empty())
851 // There is a lot of unneeded recalculation going on here:
852 // - boost::next(curosr.row())
853 // - lastPost(*this, cursor.row())
855 if (boost::next(cursor.row()) == rows().end()
856 || boost::next(cursor.row())->par() != cursor.row()->par()) {
857 setCursor(cursor.par(), lastPos(*this, cursor.row()) + 1);
859 if (!cursor.par()->empty() &&
860 (cursor.par()->getChar(lastPos(*this, cursor.row())) == ' '
861 || cursor.par()->isNewline(lastPos(*this, cursor.row())))) {
862 setCursor(cursor.par(), lastPos(*this, cursor.row()));
864 setCursor(cursor.par(),
865 lastPos(*this, cursor.row()) + 1);
871 void LyXText::cursorTop()
873 while (cursor.par()->previous())
874 cursor.par(cursor.par()->previous());
875 setCursor(cursor.par(), 0);
879 void LyXText::cursorBottom()
881 while (cursor.par()->next())
882 cursor.par(cursor.par()->next());
883 setCursor(cursor.par(), cursor.par()->size());
887 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
889 // If the mask is completely neutral, tell user
890 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
891 // Could only happen with user style
892 bv()->owner()->message(_("No font change defined. Use Character under the Layout menu to define font change."));
896 // Try implicit word selection
897 // If there is a change in the language the implicit word selection
899 LyXCursor resetCursor = cursor;
900 bool implicitSelection = (font.language() == ignore_language
901 && font.number() == LyXFont::IGNORE)
902 ? selectWordWhenUnderCursor(WHOLE_WORD_STRICT) : false;
905 setFont(font, toggleall);
907 // Implicit selections are cleared afterwards
908 //and cursor is set to the original position.
909 if (implicitSelection) {
911 cursor = resetCursor;
912 setCursor(cursor.par(), cursor.pos());
913 selection.cursor = cursor;
916 inset_owner->setUpdateStatus(bv(), InsetText::CURSOR_PAR);
920 string LyXText::getStringToIndex()
922 // Try implicit word selection
923 // If there is a change in the language the implicit word selection
925 LyXCursor const reset_cursor = cursor;
926 bool const implicitSelection = selectWordWhenUnderCursor(PREVIOUS_WORD);
929 if (!selection.set())
930 bv()->owner()->message(_("Nothing to index!"));
931 else if (selection.start.par() != selection.end.par())
932 bv()->owner()->message(_("Cannot index more than one paragraph!"));
934 idxstring = selectionAsString(bv()->buffer(), false);
936 // Reset cursors to their original position.
937 cursor = reset_cursor;
938 setCursor(cursor.par(), cursor.pos());
939 selection.cursor = cursor;
941 // Clear the implicit selection.
942 if (implicitSelection)
949 // the DTP switches for paragraphs. LyX will store them in the first
950 // physicla paragraph. When a paragraph is broken, the top settings rest,
951 // the bottom settings are given to the new one. So I can make shure,
952 // they do not duplicate themself and you cannnot make dirty things with
955 void LyXText::setParagraph(bool line_top, bool line_bottom,
956 bool pagebreak_top, bool pagebreak_bottom,
957 VSpace const & space_top,
958 VSpace const & space_bottom,
959 Spacing const & spacing,
961 string const & labelwidthstring,
964 LyXCursor tmpcursor = cursor;
965 if (!selection.set()) {
966 selection.start = cursor;
967 selection.end = cursor;
970 // make sure that the depth behind the selection are restored, too
971 Paragraph * endpar = selection.end.par()->next();
972 Paragraph * undoendpar = endpar;
974 if (endpar && endpar->getDepth()) {
975 while (endpar && endpar->getDepth()) {
976 endpar = endpar->next();
981 // because of parindents etc.
982 endpar = endpar->next();
985 setUndo(bv(), Undo::EDIT, selection.start.par(), undoendpar);
988 Paragraph * tmppar = selection.end.par();
990 while (tmppar != selection.start.par()->previous()) {
991 setCursor(tmppar, 0);
992 postPaint(cursor.y() - cursor.row()->baseline());
993 cursor.par()->params().lineTop(line_top);
994 cursor.par()->params().lineBottom(line_bottom);
995 cursor.par()->params().pagebreakTop(pagebreak_top);
996 cursor.par()->params().pagebreakBottom(pagebreak_bottom);
997 cursor.par()->params().spaceTop(space_top);
998 cursor.par()->params().spaceBottom(space_bottom);
999 cursor.par()->params().spacing(spacing);
1000 // does the layout allow the new alignment?
1001 LyXLayout_ptr const & layout = cursor.par()->layout();
1003 if (align == LYX_ALIGN_LAYOUT)
1004 align = layout->align;
1005 if (align & layout->alignpossible) {
1006 if (align == layout->align)
1007 cursor.par()->params().align(LYX_ALIGN_LAYOUT);
1009 cursor.par()->params().align(align);
1011 cursor.par()->setLabelWidthString(labelwidthstring);
1012 cursor.par()->params().noindent(noindent);
1013 tmppar = cursor.par()->previous();
1016 redoParagraphs(selection.start, endpar);
1019 setCursor(selection.start.par(), selection.start.pos());
1020 selection.cursor = cursor;
1021 setCursor(selection.end.par(), selection.end.pos());
1023 setCursor(tmpcursor.par(), tmpcursor.pos());
1025 bv()->updateInset(inset_owner);
1029 // set the counter of a paragraph. This includes the labels
1030 void LyXText::setCounter(Buffer const * buf, Paragraph * par)
1032 LyXTextClass const & textclass = buf->params.getLyXTextClass();
1033 LyXLayout_ptr const & layout = par->layout();
1035 if (par->previous()) {
1037 par->params().appendix(par->previous()->params().appendix());
1038 if (!par->params().appendix() && par->params().startOfAppendix()) {
1039 par->params().appendix(true);
1040 textclass.counters().reset();
1042 par->enumdepth = par->previous()->enumdepth;
1043 par->itemdepth = par->previous()->itemdepth;
1045 par->params().appendix(par->params().startOfAppendix());
1050 /* Maybe we have to increment the enumeration depth.
1051 * BUT, enumeration in a footnote is considered in isolation from its
1052 * surrounding paragraph so don't increment if this is the
1053 * first line of the footnote
1054 * AND, bibliographies can't have their depth changed ie. they
1055 * are always of depth 0
1058 && par->previous()->getDepth() < par->getDepth()
1059 && par->previous()->layout()->labeltype == LABEL_COUNTER_ENUMI
1060 && par->enumdepth < 3
1061 && layout->labeltype != LABEL_BIBLIO) {
1065 // Maybe we have to decrement the enumeration depth, see note above
1067 && par->previous()->getDepth() > par->getDepth()
1068 && layout->labeltype != LABEL_BIBLIO) {
1069 par->enumdepth = par->depthHook(par->getDepth())->enumdepth;
1072 if (!par->params().labelString().empty()) {
1073 par->params().labelString(string());
1076 if (layout->margintype == MARGIN_MANUAL) {
1077 if (par->params().labelWidthString().empty()) {
1078 par->setLabelWidthString(layout->labelstring());
1081 par->setLabelWidthString(string());
1084 // is it a layout that has an automatic label?
1085 if (layout->labeltype >= LABEL_COUNTER_CHAPTER) {
1086 int const i = layout->labeltype - LABEL_COUNTER_CHAPTER;
1090 if (i >= 0 && i <= buf->params.secnumdepth) {
1094 textclass.counters().step(layout->latexname());
1096 // Is there a label? Useful for Chapter layout
1097 if (!par->params().appendix()) {
1098 s << layout->labelstring();
1100 s << layout->labelstring_appendix();
1103 // Use of an integer is here less than elegant. For now.
1104 int head = textclass.maxcounter() - LABEL_COUNTER_CHAPTER;
1105 if (!par->params().appendix()) {
1106 numbertype = "sectioning";
1108 numbertype = "appendix";
1109 if (par->isRightToLeftPar(buf->params))
1110 langtype = "hebrew";
1115 s << textclass.counters()
1116 .numberLabel(layout->latexname(),
1117 numbertype, langtype, head);
1119 par->params().labelString(STRCONV(s.str()));
1121 // reset enum counters
1122 textclass.counters().reset("enum");
1123 } else if (layout->labeltype < LABEL_COUNTER_ENUMI) {
1124 textclass.counters().reset("enum");
1125 } else if (layout->labeltype == LABEL_COUNTER_ENUMI) {
1127 // Yes I know this is a really, really! bad solution
1129 string enumcounter("enum");
1131 switch (par->enumdepth) {
1140 enumcounter += "iv";
1143 // not a valid enumdepth...
1147 textclass.counters().step(enumcounter);
1149 s << textclass.counters()
1150 .numberLabel(enumcounter, "enumeration");
1151 par->params().labelString(STRCONV(s.str()));
1153 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
1154 textclass.counters().step("bibitem");
1155 int number = textclass.counters().value("bibitem");
1156 if (par->bibitem()) {
1157 par->bibitem()->setCounter(number);
1158 par->params().labelString(layout->labelstring());
1160 // In biblio should't be following counters but...
1162 string s = layout->labelstring();
1164 // the caption hack:
1165 if (layout->labeltype == LABEL_SENSITIVE) {
1166 Paragraph * tmppar = par;
1169 while (tmppar && tmppar->inInset()
1170 // the single '=' is intended below
1171 && (in = tmppar->inInset()->owner())) {
1172 if (in->lyxCode() == Inset::FLOAT_CODE ||
1173 in->lyxCode() == Inset::WRAP_CODE) {
1177 tmppar = in->parOwner();
1183 = textclass.floats().getType(static_cast<InsetFloat*>(in)->type());
1185 textclass.counters().step(fl.type());
1187 // Doesn't work... yet.
1188 #if USE_BOOST_FORMAT
1189 s = boost::io::str(boost::format(_("%1$s #:")) % fl.name());
1190 // s << boost::format(_("%1$s %1$d:")
1192 // % buf->counters().value(fl.name());
1195 //o << fl.name() << ' ' << buf->counters().value(fl.name()) << ":";
1196 o << fl.name() << " #:";
1197 s = STRCONV(o.str());
1200 // par->SetLayout(0);
1201 // s = layout->labelstring;
1202 s = _("Senseless: ");
1205 par->params().labelString(s);
1207 // reset the enumeration counter. They are always reset
1208 // when there is any other layout between
1209 // Just fall-through between the cases so that all
1210 // enum counters deeper than enumdepth is also reset.
1211 switch (par->enumdepth) {
1213 textclass.counters().reset("enumi");
1215 textclass.counters().reset("enumii");
1217 textclass.counters().reset("enumiii");
1219 textclass.counters().reset("enumiv");
1225 // Updates all counters. Paragraphs with changed label string will be rebroken
1226 void LyXText::updateCounters()
1228 RowList::iterator rowit = rows().begin();
1229 ParagraphList::iterator pit = rowit->par();
1231 // CHECK if this is really needed. (Lgb)
1232 bv()->buffer()->params.getLyXTextClass().counters().reset();
1234 while (pit != ownerParagraphs().end()) {
1235 while (rowit->par() != pit)
1238 string const oldLabel = pit->params().labelString();
1240 // setCounter can potentially change the labelString.
1241 setCounter(bv()->buffer(), &*pit);
1243 string const & newLabel = pit->params().labelString();
1245 if (oldLabel.empty() && !newLabel.empty()) {
1246 removeParagraph(rowit);
1247 appendParagraph(rowit);
1255 void LyXText::insertInset(Inset * inset)
1257 if (!cursor.par()->insetAllowed(inset->lyxCode()))
1259 setUndo(bv(), Undo::FINISH, cursor.par(), cursor.par()->next());
1261 cursor.par()->insertInset(cursor.pos(), inset);
1262 // Just to rebreak and refresh correctly.
1263 // The character will not be inserted a second time
1264 insertChar(Paragraph::META_INSET);
1265 // If we enter a highly editable inset the cursor should be to before
1266 // the inset. This couldn't happen before as Undo was not handled inside
1267 // inset now after the Undo LyX tries to call inset->Edit(...) again
1268 // and cannot do this as the cursor is behind the inset and GetInset
1269 // does not return the inset!
1270 if (isHighlyEditableInset(inset)) {
1277 void LyXText::copyEnvironmentType()
1279 copylayouttype = cursor.par()->layout()->name();
1283 void LyXText::pasteEnvironmentType()
1285 // do nothing if there has been no previous copyEnvironmentType()
1286 if (!copylayouttype.empty())
1287 setLayout(copylayouttype);
1291 void LyXText::cutSelection(bool doclear, bool realcut)
1293 // Stuff what we got on the clipboard. Even if there is no selection.
1295 // There is a problem with having the stuffing here in that the
1296 // larger the selection the slower LyX will get. This can be
1297 // solved by running the line below only when the selection has
1298 // finished. The solution used currently just works, to make it
1299 // faster we need to be more clever and probably also have more
1300 // calls to stuffClipboard. (Lgb)
1301 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1303 // This doesn't make sense, if there is no selection
1304 if (!selection.set())
1307 // OK, we have a selection. This is always between selection.start
1308 // and selection.end
1310 // make sure that the depth behind the selection are restored, too
1311 Paragraph * endpar = selection.end.par()->next();
1312 Paragraph * undoendpar = endpar;
1314 if (endpar && endpar->getDepth()) {
1315 while (endpar && endpar->getDepth()) {
1316 endpar = endpar->next();
1317 undoendpar = endpar;
1319 } else if (endpar) {
1320 endpar = endpar->next(); // because of parindents etc.
1323 setUndo(bv(), Undo::DELETE,
1324 selection.start.par(), undoendpar);
1326 // there are two cases: cut only within one paragraph or
1327 // more than one paragraph
1328 if (selection.start.par() == selection.end.par()) {
1329 // only within one paragraph
1330 endpar = selection.end.par();
1331 int pos = selection.end.pos();
1332 CutAndPaste::cutSelection(selection.start.par(), &endpar,
1333 selection.start.pos(), pos,
1334 bv()->buffer()->params.textclass,
1336 selection.end.pos(pos);
1338 endpar = selection.end.par();
1339 int pos = selection.end.pos();
1340 CutAndPaste::cutSelection(selection.start.par(), &endpar,
1341 selection.start.pos(), pos,
1342 bv()->buffer()->params.textclass,
1345 selection.end.par(endpar);
1346 selection.end.pos(pos);
1347 cursor.pos(selection.end.pos());
1349 endpar = endpar->next();
1351 // sometimes necessary
1353 selection.start.par()->stripLeadingSpaces();
1355 redoParagraphs(selection.start, endpar);
1357 // cutSelection can invalidate the cursor so we need to set
1359 // we prefer the end for when tracking changes
1360 cursor = selection.end;
1362 // need a valid cursor. (Lgb)
1365 setCursor(cursor.par(), cursor.pos());
1366 selection.cursor = cursor;
1371 void LyXText::copySelection()
1373 // stuff the selection onto the X clipboard, from an explicit copy request
1374 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1376 // this doesnt make sense, if there is no selection
1377 if (!selection.set())
1380 // ok we have a selection. This is always between selection.start
1381 // and sel_end cursor
1383 // copy behind a space if there is one
1384 while (selection.start.par()->size() > selection.start.pos()
1385 && selection.start.par()->isLineSeparator(selection.start.pos())
1386 && (selection.start.par() != selection.end.par()
1387 || selection.start.pos() < selection.end.pos()))
1388 selection.start.pos(selection.start.pos() + 1);
1390 CutAndPaste::copySelection(selection.start.par(), selection.end.par(),
1391 selection.start.pos(), selection.end.pos(),
1392 bv()->buffer()->params.textclass);
1396 void LyXText::pasteSelection()
1398 // this does not make sense, if there is nothing to paste
1399 if (!CutAndPaste::checkPastePossible())
1402 setUndo(bv(), Undo::INSERT,
1403 cursor.par(), cursor.par()->next());
1406 Paragraph * actpar = cursor.par();
1407 int pos = cursor.pos();
1409 CutAndPaste::pasteSelection(&actpar, &endpar, pos,
1410 bv()->buffer()->params.textclass);
1412 redoParagraphs(cursor, endpar);
1414 setCursor(cursor.par(), cursor.pos());
1417 selection.cursor = cursor;
1418 setCursor(actpar, pos);
1424 void LyXText::setSelectionRange(lyx::pos_type length)
1429 selection.cursor = cursor;
1436 // simple replacing. The font of the first selected character is used
1437 void LyXText::replaceSelectionWithString(string const & str)
1439 setCursorParUndo(bv());
1442 if (!selection.set()) { // create a dummy selection
1443 selection.end = cursor;
1444 selection.start = cursor;
1447 // Get font setting before we cut
1448 pos_type pos = selection.end.pos();
1449 LyXFont const font = selection.start.par()
1450 ->getFontSettings(bv()->buffer()->params,
1451 selection.start.pos());
1453 // Insert the new string
1454 for (string::const_iterator cit = str.begin(); cit != str.end(); ++cit) {
1455 selection.end.par()->insertChar(pos, (*cit), font);
1459 // Cut the selection
1460 cutSelection(true, false);
1466 // needed to insert the selection
1467 void LyXText::insertStringAsLines(string const & str)
1469 Paragraph * par = cursor.par();
1470 pos_type pos = cursor.pos();
1471 Paragraph * endpar = cursor.par()->next();
1473 setCursorParUndo(bv());
1475 // only to be sure, should not be neccessary
1478 bv()->buffer()->insertStringAsLines(par, pos, current_font, str);
1480 redoParagraphs(cursor, endpar);
1481 setCursor(cursor.par(), cursor.pos());
1482 selection.cursor = cursor;
1483 setCursor(par, pos);
1488 // turns double-CR to single CR, others where converted into one
1489 // blank. Then InsertStringAsLines is called
1490 void LyXText::insertStringAsParagraphs(string const & str)
1492 string linestr(str);
1493 bool newline_inserted = false;
1494 for (string::size_type i = 0; i < linestr.length(); ++i) {
1495 if (linestr[i] == '\n') {
1496 if (newline_inserted) {
1497 // we know that \r will be ignored by
1498 // InsertStringA. Of course, it is a dirty
1499 // trick, but it works...
1500 linestr[i - 1] = '\r';
1504 newline_inserted = true;
1506 } else if (IsPrintable(linestr[i])) {
1507 newline_inserted = false;
1510 insertStringAsLines(linestr);
1514 void LyXText::checkParagraph(Paragraph * par, pos_type pos)
1516 LyXCursor tmpcursor;
1520 RowList::iterator row = getRow(par, pos, y);
1521 RowList::iterator beg = rows().begin();
1523 // is there a break one row above
1525 && boost::prior(row)->par() == row->par()) {
1526 z = rowBreakPoint(*boost::prior(row));
1527 if (z >= row->pos()) {
1528 // set the dimensions of the row above
1529 y -= boost::prior(row)->height();
1532 breakAgain(boost::prior(row));
1534 // set the cursor again. Otherwise
1535 // dangling pointers are possible
1536 setCursor(cursor.par(), cursor.pos(),
1537 false, cursor.boundary());
1538 selection.cursor = cursor;
1543 int const tmpheight = row->height();
1544 pos_type const tmplast = lastPos(*this, row);
1547 if (row->height() == tmpheight && lastPos(*this, row) == tmplast) {
1548 postRowPaint(row, y);
1553 // check the special right address boxes
1554 if (par->layout()->margintype == MARGIN_RIGHT_ADDRESS_BOX) {
1561 redoDrawingOfParagraph(tmpcursor);
1564 // set the cursor again. Otherwise dangling pointers are possible
1565 // also set the selection
1567 if (selection.set()) {
1569 setCursorIntern(selection.cursor.par(), selection.cursor.pos(),
1570 false, selection.cursor.boundary());
1571 selection.cursor = cursor;
1572 setCursorIntern(selection.start.par(),
1573 selection.start.pos(),
1574 false, selection.start.boundary());
1575 selection.start = cursor;
1576 setCursorIntern(selection.end.par(),
1577 selection.end.pos(),
1578 false, selection.end.boundary());
1579 selection.end = cursor;
1580 setCursorIntern(last_sel_cursor.par(),
1581 last_sel_cursor.pos(),
1582 false, last_sel_cursor.boundary());
1583 last_sel_cursor = cursor;
1586 setCursorIntern(cursor.par(), cursor.pos(),
1587 false, cursor.boundary());
1591 // returns false if inset wasn't found
1592 bool LyXText::updateInset(Inset * inset)
1594 // first check the current paragraph
1595 int pos = cursor.par()->getPositionOfInset(inset);
1597 checkParagraph(cursor.par(), pos);
1601 // check every paragraph
1603 ParagraphList::iterator par = ownerParagraphs().begin();
1604 ParagraphList::iterator end = ownerParagraphs().end();
1607 pos = par->getPositionOfInset(inset);
1609 checkParagraph(&*par, pos);
1613 } while (par != end);
1619 bool LyXText::setCursor(Paragraph * par,
1621 bool setfont, bool boundary)
1623 LyXCursor old_cursor = cursor;
1624 setCursorIntern(par, pos, setfont, boundary);
1625 return deleteEmptyParagraphMechanism(old_cursor);
1629 void LyXText::setCursor(LyXCursor & cur, Paragraph * par,
1630 pos_type pos, bool boundary)
1636 cur.boundary(boundary);
1638 // get the cursor y position in text
1640 RowList::iterator row = getRow(par, pos, y);
1641 RowList::iterator beg = rows().begin();
1643 RowList::iterator old_row = row;
1645 // if we are before the first char of this row and are still in the
1646 // same paragraph and there is a previous row then put the cursor on
1647 // the end of the previous row
1648 cur.iy(y + row->baseline());
1650 if (row != beg && pos &&
1651 boost::prior(row)->par() == row->par() &&
1652 pos < par->size() &&
1653 par->getChar(pos) == Paragraph::META_INSET &&
1654 (ins = par->getInset(pos)) && (ins->needFullRow() || ins->display()))
1661 // y is now the beginning of the cursor row
1662 y += row->baseline();
1663 // y is now the cursor baseline
1666 pos_type last = lastPrintablePos(*this, old_row);
1668 // None of these should happen, but we're scaredy-cats
1669 if (pos > par->size()) {
1670 lyxerr << "dont like 1 please report" << endl;
1673 } else if (pos > last + 1) {
1674 lyxerr << "dont like 2 please report" << endl;
1675 // This shouldn't happen.
1678 } else if (pos < row->pos()) {
1679 lyxerr << "dont like 3 please report" << endl;
1684 // now get the cursors x position
1685 float x = getCursorX(row, pos, last, boundary);
1688 if (old_row != row) {
1689 x = getCursorX(old_row, pos, last, boundary);
1693 /* We take out this for the time being because 1) the redraw code is not
1694 prepared to this yet and 2) because some good policy has yet to be decided
1695 while editting: for instance how to act on rows being created/deleted
1699 //if the cursor is in a visible row, anchor to it
1701 if (topy < y && y < topy + bv()->workHeight())
1707 float LyXText::getCursorX(RowList::iterator rit,
1708 pos_type pos, pos_type last, bool boundary) const
1710 pos_type cursor_vpos = 0;
1712 float fill_separator;
1714 float fill_label_hfill;
1715 // This call HAS to be here because of the BidiTables!!!
1716 prepareToPrint(rit, x, fill_separator, fill_hfill,
1719 if (last < rit->pos())
1720 cursor_vpos = rit->pos();
1721 else if (pos > last && !boundary)
1722 cursor_vpos = (rit->par()->isRightToLeftPar(bv()->buffer()->params))
1723 ? rit->pos() : last + 1;
1724 else if (pos > rit->pos() &&
1725 (pos > last || boundary))
1726 /// Place cursor after char at (logical) position pos - 1
1727 cursor_vpos = (bidi_level(pos - 1) % 2 == 0)
1728 ? log2vis(pos - 1) + 1 : log2vis(pos - 1);
1730 /// Place cursor before char at (logical) position pos
1731 cursor_vpos = (bidi_level(pos) % 2 == 0)
1732 ? log2vis(pos) : log2vis(pos) + 1;
1734 pos_type body_pos = rit->par()->beginningOfBody();
1735 if ((body_pos > 0) &&
1736 ((body_pos - 1 > last) ||
1737 !rit->par()->isLineSeparator(body_pos - 1)))
1740 for (pos_type vpos = rit->pos(); vpos < cursor_vpos; ++vpos) {
1741 pos_type pos = vis2log(vpos);
1742 if (body_pos > 0 && pos == body_pos - 1) {
1743 x += fill_label_hfill +
1744 font_metrics::width(
1745 rit->par()->layout()->labelsep,
1746 getLabelFont(bv()->buffer(),
1748 if (rit->par()->isLineSeparator(body_pos - 1))
1749 x -= singleWidth(&*rit->par(), body_pos - 1);
1752 if (hfillExpansion(*this, rit, pos)) {
1753 x += singleWidth(&*rit->par(), pos);
1754 if (pos >= body_pos)
1757 x += fill_label_hfill;
1758 } else if (rit->par()->isSeparator(pos)) {
1759 x += singleWidth(&*rit->par(), pos);
1760 if (pos >= body_pos)
1761 x += fill_separator;
1763 x += singleWidth(&*rit->par(), pos);
1769 void LyXText::setCursorIntern(Paragraph * par,
1770 pos_type pos, bool setfont, bool boundary)
1772 InsetText * it = static_cast<InsetText *>(par->inInset());
1774 if (it != inset_owner) {
1775 lyxerr[Debug::INSETS] << "InsetText is " << it
1777 << "inset_owner is "
1778 << inset_owner << endl;
1779 #ifdef WITH_WARNINGS
1780 #warning I believe this code is wrong. (Lgb)
1781 #warning Jürgen, have a look at this. (Lgb)
1782 #warning Hmmm, I guess you are right but we
1783 #warning should verify when this is needed
1785 // Jürgen, would you like to have a look?
1786 // I guess we need to move the outer cursor
1787 // and open and lock the inset (bla bla bla)
1788 // stuff I don't know... so can you have a look?
1790 // I moved the lyxerr stuff in here so we can see if
1791 // this is actually really needed and where!
1793 // it->getLyXText(bv())->setCursorIntern(bv(), par, pos, setfont, boundary);
1798 setCursor(cursor, par, pos, boundary);
1804 void LyXText::setCurrentFont()
1806 pos_type pos = cursor.pos();
1807 if (cursor.boundary() && pos > 0)
1811 if (pos == cursor.par()->size())
1813 else // potentional bug... BUG (Lgb)
1814 if (cursor.par()->isSeparator(pos)) {
1815 if (pos > cursor.row()->pos() &&
1816 bidi_level(pos) % 2 ==
1817 bidi_level(pos - 1) % 2)
1819 else if (pos + 1 < cursor.par()->size())
1825 cursor.par()->getFontSettings(bv()->buffer()->params, pos);
1826 real_current_font = getFont(bv()->buffer(), cursor.par(), pos);
1828 if (cursor.pos() == cursor.par()->size() &&
1829 isBoundary(bv()->buffer(), cursor.par(), cursor.pos()) &&
1830 !cursor.boundary()) {
1831 Language const * lang =
1832 cursor.par()->getParLanguage(bv()->buffer()->params);
1833 current_font.setLanguage(lang);
1834 current_font.setNumber(LyXFont::OFF);
1835 real_current_font.setLanguage(lang);
1836 real_current_font.setNumber(LyXFont::OFF);
1841 // returns the column near the specified x-coordinate of the row
1842 // x is set to the real beginning of this column
1844 LyXText::getColumnNearX(RowList::iterator rit, int & x, bool & boundary) const
1847 float fill_separator;
1849 float fill_label_hfill;
1851 prepareToPrint(rit, tmpx, fill_separator,
1852 fill_hfill, fill_label_hfill);
1854 pos_type vc = rit->pos();
1855 pos_type last = lastPrintablePos(*this, rit);
1858 LyXLayout_ptr const & layout = rit->par()->layout();
1860 bool left_side = false;
1862 pos_type body_pos = rit->par()->beginningOfBody();
1863 float last_tmpx = tmpx;
1866 (body_pos - 1 > last ||
1867 !rit->par()->isLineSeparator(body_pos - 1)))
1870 // check for empty row
1871 if (!rit->par()->size()) {
1876 while (vc <= last && tmpx <= x) {
1879 if (body_pos > 0 && c == body_pos - 1) {
1880 tmpx += fill_label_hfill +
1881 font_metrics::width(layout->labelsep,
1882 getLabelFont(bv()->buffer(), &*rit->par()));
1883 if (rit->par()->isLineSeparator(body_pos - 1))
1884 tmpx -= singleWidth(&*rit->par(), body_pos - 1);
1887 if (hfillExpansion(*this, rit, c)) {
1888 tmpx += singleWidth(&*rit->par(), c);
1892 tmpx += fill_label_hfill;
1893 } else if (rit->par()->isSeparator(c)) {
1894 tmpx += singleWidth(&*rit->par(), c);
1896 tmpx+= fill_separator;
1898 tmpx += singleWidth(&*rit->par(), c);
1903 if ((tmpx + last_tmpx) / 2 > x) {
1908 if (vc > last + 1) // This shouldn't happen.
1912 // This (rtl_support test) is not needed, but gives
1913 // some speedup if rtl_support=false
1914 bool const lastrow = lyxrc.rtl_support &&
1915 (boost::next(rit) == rowlist_.end() ||
1916 boost::next(rit)->par() != rit->par());
1917 // If lastrow is false, we don't need to compute
1918 // the value of rtl.
1919 bool const rtl = (lastrow)
1920 ? rit->par()->isRightToLeftPar(bv()->buffer()->params)
1923 ((rtl && left_side && vc == rit->pos() && x < tmpx - 5) ||
1924 (!rtl && !left_side && vc == last + 1 && x > tmpx + 5)))
1926 else if (vc == rit->pos()) {
1928 if (bidi_level(c) % 2 == 1)
1931 c = vis2log(vc - 1);
1932 bool const rtl = (bidi_level(c) % 2 == 1);
1933 if (left_side == rtl) {
1935 boundary = isBoundary(bv()->buffer(), &*rit->par(), c);
1939 if (rit->pos() <= last && c > last
1940 && rit->par()->isNewline(last)) {
1941 if (bidi_level(last) % 2 == 0)
1942 tmpx -= singleWidth(&*rit->par(), last);
1944 tmpx += singleWidth(&*rit->par(), last);
1954 void LyXText::setCursorFromCoordinates(int x, int y)
1956 LyXCursor old_cursor = cursor;
1958 setCursorFromCoordinates(cursor, x, y);
1960 deleteEmptyParagraphMechanism(old_cursor);
1967 * return true if the cursor given is at the end of a row,
1968 * and the next row is filled by an inset that spans an entire
1971 bool beforeFullRowInset(LyXText & lt, RowList::iterator row,
1973 if (boost::next(row) == lt.rows().end())
1975 Row const & next = *boost::next(row);
1977 if (next.pos() != cur.pos() || next.par() != cur.par())
1979 if (!cur.par()->isInset(cur.pos()))
1981 Inset const * inset = cur.par()->getInset(cur.pos());
1982 if (inset->needFullRow() || inset->display())
1989 void LyXText::setCursorFromCoordinates(LyXCursor & cur, int x, int y)
1991 // Get the row first.
1993 RowList::iterator row = getRowNearY(y);
1995 pos_type const column = getColumnNearX(row, x, bound);
1996 cur.par(&*row->par());
1997 cur.pos(row->pos() + column);
1999 cur.y(y + row->baseline());
2002 if (beforeFullRowInset(*this, row, cur)) {
2003 pos_type last = lastPrintablePos(*this, row);
2004 float x = getCursorX(boost::next(row), cur.pos(), last, bound);
2006 cur.iy(y + row->height() + boost::next(row)->baseline());
2007 cur.irow(boost::next(row));
2013 cur.boundary(bound);
2017 void LyXText::cursorLeft(bool internal)
2019 if (cursor.pos() > 0) {
2020 bool boundary = cursor.boundary();
2021 setCursor(cursor.par(), cursor.pos() - 1, true, false);
2022 if (!internal && !boundary &&
2023 isBoundary(bv()->buffer(), cursor.par(), cursor.pos() + 1))
2024 setCursor(cursor.par(), cursor.pos() + 1, true, true);
2025 } else if (cursor.par()->previous()) { // steps into the above paragraph.
2026 Paragraph * par = cursor.par()->previous();
2027 setCursor(par, par->size());
2032 void LyXText::cursorRight(bool internal)
2034 if (!internal && cursor.boundary() &&
2035 !cursor.par()->isNewline(cursor.pos()))
2036 setCursor(cursor.par(), cursor.pos(), true, false);
2037 else if (cursor.pos() < cursor.par()->size()) {
2038 setCursor(cursor.par(), cursor.pos() + 1, true, false);
2040 isBoundary(bv()->buffer(), cursor.par(), cursor.pos()))
2041 setCursor(cursor.par(), cursor.pos(), true, true);
2042 } else if (cursor.par()->next())
2043 setCursor(cursor.par()->next(), 0);
2047 void LyXText::cursorUp(bool selecting)
2050 int x = cursor.x_fix();
2051 int y = cursor.y() - cursor.row()->baseline() - 1;
2052 setCursorFromCoordinates(x, y);
2055 int y1 = cursor.iy() - topy;
2058 Inset * inset_hit = checkInsetHit(x, y1);
2059 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2060 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2064 setCursorFromCoordinates(bv(), cursor.x_fix(),
2065 cursor.y() - cursor.row()->baseline() - 1);
2070 void LyXText::cursorDown(bool selecting)
2073 int x = cursor.x_fix();
2074 int y = cursor.y() - cursor.row()->baseline() +
2075 cursor.row()->height() + 1;
2076 setCursorFromCoordinates(x, y);
2077 if (!selecting && cursor.row() == cursor.irow()) {
2079 int y1 = cursor.iy() - topy;
2082 Inset * inset_hit = checkInsetHit(x, y1);
2083 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2084 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2088 setCursorFromCoordinates(bv(), cursor.x_fix(),
2089 cursor.y() - cursor.row()->baseline()
2090 + cursor.row()->height() + 1);
2095 void LyXText::cursorUpParagraph()
2097 if (cursor.pos() > 0) {
2098 setCursor(cursor.par(), 0);
2100 else if (cursor.par()->previous()) {
2101 setCursor(cursor.par()->previous(), 0);
2106 void LyXText::cursorDownParagraph()
2108 if (cursor.par()->next()) {
2109 setCursor(cursor.par()->next(), 0);
2111 setCursor(cursor.par(), cursor.par()->size());
2115 // fix the cursor `cur' after a characters has been deleted at `where'
2116 // position. Called by deleteEmptyParagraphMechanism
2117 void LyXText::fixCursorAfterDelete(LyXCursor & cur,
2118 LyXCursor const & where)
2120 // if cursor is not in the paragraph where the delete occured,
2122 if (cur.par() != where.par())
2125 // if cursor position is after the place where the delete occured,
2127 if (cur.pos() > where.pos())
2128 cur.pos(cur.pos()-1);
2130 // check also if we don't want to set the cursor on a spot behind the
2131 // pagragraph because we erased the last character.
2132 if (cur.pos() > cur.par()->size())
2133 cur.pos(cur.par()->size());
2135 // recompute row et al. for this cursor
2136 setCursor(cur, cur.par(), cur.pos(), cur.boundary());
2140 bool LyXText::deleteEmptyParagraphMechanism(LyXCursor const & old_cursor)
2142 // Would be wrong to delete anything if we have a selection.
2143 if (selection.set())
2146 // We allow all kinds of "mumbo-jumbo" when freespacing.
2147 if (old_cursor.par()->layout()->free_spacing
2148 || old_cursor.par()->isFreeSpacing()) {
2152 /* Ok I'll put some comments here about what is missing.
2153 I have fixed BackSpace (and thus Delete) to not delete
2154 double-spaces automagically. I have also changed Cut,
2155 Copy and Paste to hopefully do some sensible things.
2156 There are still some small problems that can lead to
2157 double spaces stored in the document file or space at
2158 the beginning of paragraphs. This happens if you have
2159 the cursor betwenn to spaces and then save. Or if you
2160 cut and paste and the selection have a space at the
2161 beginning and then save right after the paste. I am
2162 sure none of these are very hard to fix, but I will
2163 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
2164 that I can get some feedback. (Lgb)
2167 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
2168 // delete the LineSeparator.
2171 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
2172 // delete the LineSeparator.
2175 // If the pos around the old_cursor were spaces, delete one of them.
2176 if (old_cursor.par() != cursor.par()
2177 || old_cursor.pos() != cursor.pos()) {
2178 // Only if the cursor has really moved
2180 if (old_cursor.pos() > 0
2181 && old_cursor.pos() < old_cursor.par()->size()
2182 && old_cursor.par()->isLineSeparator(old_cursor.pos())
2183 && old_cursor.par()->isLineSeparator(old_cursor.pos() - 1)) {
2184 old_cursor.par()->erase(old_cursor.pos() - 1);
2185 redoParagraphs(old_cursor, old_cursor.par()->next());
2187 #ifdef WITH_WARNINGS
2188 #warning This will not work anymore when we have multiple views of the same buffer
2189 // In this case, we will have to correct also the cursors held by
2190 // other bufferviews. It will probably be easier to do that in a more
2191 // automated way in LyXCursor code. (JMarc 26/09/2001)
2193 // correct all cursors held by the LyXText
2194 fixCursorAfterDelete(cursor, old_cursor);
2195 fixCursorAfterDelete(selection.cursor,
2197 fixCursorAfterDelete(selection.start,
2199 fixCursorAfterDelete(selection.end, old_cursor);
2200 fixCursorAfterDelete(last_sel_cursor,
2202 fixCursorAfterDelete(toggle_cursor, old_cursor);
2203 fixCursorAfterDelete(toggle_end_cursor,
2209 // don't delete anything if this is the ONLY paragraph!
2210 if (!old_cursor.par()->next() && !old_cursor.par()->previous())
2213 // Do not delete empty paragraphs with keepempty set.
2214 if (old_cursor.par()->layout()->keepempty)
2217 // only do our magic if we changed paragraph
2218 if (old_cursor.par() == cursor.par())
2221 // record if we have deleted a paragraph
2222 // we can't possibly have deleted a paragraph before this point
2223 bool deleted = false;
2225 if ((old_cursor.par()->empty()
2226 || (old_cursor.par()->size() == 1
2227 && old_cursor.par()->isLineSeparator(0)))) {
2228 // ok, we will delete anything
2229 LyXCursor tmpcursor;
2233 if (old_cursor.row() != rows().begin()) {
2235 prevrow = boost::prior(old_cursor.row());
2236 const_cast<LyXText *>(this)->postPaint(old_cursor.y() - old_cursor.row()->baseline() - prevrow->height());
2238 cursor = old_cursor; // that undo can restore the right cursor position
2239 Paragraph * endpar = old_cursor.par()->next();
2240 if (endpar && endpar->getDepth()) {
2241 while (endpar && endpar->getDepth()) {
2242 endpar = endpar->next();
2245 setUndo(bv(), Undo::DELETE, old_cursor.par(), endpar);
2249 removeRow(old_cursor.row());
2250 if (ownerParagraphs().begin() == old_cursor.par()) {
2251 ownerParagraph(&*boost::next(ownerParagraphs().begin()));
2254 delete old_cursor.par();
2256 /* Breakagain the next par. Needed because of
2257 * the parindent that can occur or dissappear.
2258 * The next row can change its height, if
2259 * there is another layout before */
2260 if (boost::next(prevrow) != rows().end()) {
2261 breakAgain(boost::next(prevrow));
2264 setHeightOfRow(prevrow);
2266 RowList::iterator nextrow = boost::next(old_cursor.row());
2267 const_cast<LyXText *>(this)->postPaint(
2268 old_cursor.y() - old_cursor.row()->baseline());
2271 cursor = old_cursor; // that undo can restore the right cursor position
2272 Paragraph * endpar = old_cursor.par()->next();
2273 if (endpar && endpar->getDepth()) {
2274 while (endpar && endpar->getDepth()) {
2275 endpar = endpar->next();
2278 setUndo(bv(), Undo::DELETE, old_cursor.par(), endpar);
2282 removeRow(old_cursor.row());
2284 if (ownerParagraphs().begin() == old_cursor.par()) {
2285 ownerParagraph(&*boost::next(ownerParagraphs().begin()));
2288 delete old_cursor.par();
2290 /* Breakagain the next par. Needed because of
2291 the parindent that can occur or dissappear.
2292 The next row can change its height, if
2293 there is another layout before */
2294 if (nextrow != rows().end()) {
2295 breakAgain(nextrow);
2301 setCursorIntern(cursor.par(), cursor.pos());
2303 if (selection.cursor.par() == old_cursor.par()
2304 && selection.cursor.pos() == old_cursor.pos()) {
2305 // correct selection
2306 selection.cursor = cursor;
2310 if (old_cursor.par()->stripLeadingSpaces()) {
2311 redoParagraphs(old_cursor,
2312 old_cursor.par()->next());
2314 setCursorIntern(cursor.par(), cursor.pos());
2315 selection.cursor = cursor;
2322 ParagraphList & LyXText::ownerParagraphs() const
2325 return inset_owner->paragraphs;
2327 return bv_owner->buffer()->paragraphs;
2331 void LyXText::ownerParagraph(Paragraph * p) const
2334 inset_owner->paragraph(p);
2336 bv_owner->buffer()->paragraphs.set(p);
2341 void LyXText::ownerParagraph(int id, Paragraph * p) const
2343 Paragraph * op = bv_owner->buffer()->getParFromID(id);
2344 if (op && op->inInset()) {
2345 static_cast<InsetText *>(op->inInset())->paragraph(p);
2352 LyXText::refresh_status LyXText::refreshStatus() const
2354 return refresh_status_;
2358 void LyXText::clearPaint()
2360 refresh_status_ = REFRESH_NONE;
2361 refresh_row = rows().end();
2366 void LyXText::postPaint(int start_y)
2368 refresh_status old = refresh_status_;
2370 refresh_status_ = REFRESH_AREA;
2371 refresh_row = rows().end();
2373 if (old != REFRESH_NONE && refresh_y < start_y)
2376 refresh_y = start_y;
2381 // We are an inset's lyxtext. Tell the top-level lyxtext
2382 // it needs to update the row we're in.
2383 LyXText * t = bv()->text;
2384 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2388 // FIXME: we should probably remove this y parameter,
2389 // make refresh_y be 0, and use row->y etc.
2390 void LyXText::postRowPaint(RowList::iterator rit, int start_y)
2392 if (refresh_status_ != REFRESH_NONE && refresh_y < start_y) {
2393 refresh_status_ = REFRESH_AREA;
2396 refresh_y = start_y;
2399 if (refresh_status_ == REFRESH_AREA)
2402 refresh_status_ = REFRESH_ROW;
2408 // We are an inset's lyxtext. Tell the top-level lyxtext
2409 // it needs to update the row we're in.
2410 LyXText * t = bv()->text;
2411 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2415 bool LyXText::isInInset() const
2417 // Sub-level has non-null bv owner and
2418 // non-null inset owner.
2419 return inset_owner != 0 && bv_owner != 0;
2423 int defaultRowHeight()
2425 LyXFont const font(LyXFont::ALL_SANE);
2426 return int(font_metrics::maxAscent(font)
2427 + font_metrics::maxDescent(font) * 1.5);