1 /* This file is part of
2 * ======================================================
4 * LyX, The Document Processor
6 * Copyright 1995 Matthias Ettrich
7 * Copyright 1995-2001 The LyX Team.
9 * ====================================================== */
15 #include "paragraph.h"
16 #include "frontends/LyXView.h"
17 #include "undo_funcs.h"
19 #include "bufferparams.h"
21 #include "BufferView.h"
22 #include "CutAndPaste.h"
23 #include "frontends/Painter.h"
24 #include "frontends/font_metrics.h"
28 #include "FloatList.h"
30 #include "ParagraphParameters.h"
32 #include "lyxrow_funcs.h"
34 #include "insets/insetbibitem.h"
35 #include "insets/insetfloat.h"
37 #include "support/LAssert.h"
38 #include "support/textutils.h"
39 #include "support/lstrings.h"
41 #include "support/BoostFormat.h"
51 LyXText::LyXText(BufferView * bv)
52 : height(0), width(0), anchor_row_offset_(0),
53 inset_owner(0), the_locking_inset(0), bv_owner(bv)
55 anchor_row_ = rows().end();
56 need_break_row = rows().end();
57 refresh_row = rows().end();
63 LyXText::LyXText(BufferView * bv, InsetText * inset)
64 : height(0), width(0), anchor_row_offset_(0),
65 inset_owner(inset), the_locking_inset(0), bv_owner(bv)
67 anchor_row_ = rows().end();
68 need_break_row = rows().end();
69 refresh_row = rows().end();
75 void LyXText::init(BufferView * bview, bool reinit)
79 need_break_row = rows().end();
81 copylayouttype.erase();
84 } else if (!rowlist_.empty())
87 ParagraphList::iterator pit = ownerParagraphs().begin();
88 ParagraphList::iterator end = ownerParagraphs().end();
90 current_font = getFont(bview->buffer(), pit, 0);
92 for (; pit != end; ++pit) {
93 insertParagraph(&*pit, rowlist_.end());
95 setCursorIntern(rowlist_.begin()->par(), 0);
96 selection.cursor = cursor;
104 LyXFont const realizeFont(LyXFont const & font,
106 ParagraphList & /*plist*/,
107 ParagraphList::iterator pit)
109 LyXTextClass const & tclass = buf->params.getLyXTextClass();
110 LyXFont tmpfont(font);
111 Paragraph::depth_type par_depth = pit->getDepth();
113 Paragraph * par = &*pit;
115 // Resolve against environment font information
116 while (par && par_depth && !tmpfont.resolved()) {
117 par = par->outerHook();
119 tmpfont.realize(par->layout()->font);
120 par_depth = par->getDepth();
124 tmpfont.realize(tclass.defaultfont());
132 // Gets the fully instantiated font at a given position in a paragraph
133 // Basically the same routine as Paragraph::getFont() in paragraph.C.
134 // The difference is that this one is used for displaying, and thus we
135 // are allowed to make cosmetic improvements. For instance make footnotes
137 // If position is -1, we get the layout font of the paragraph.
138 // If position is -2, we get the font of the manual label of the paragraph.
139 LyXFont const LyXText::getFont(Buffer const * buf, ParagraphList::iterator pit,
142 lyx::Assert(pos >= 0);
144 LyXLayout_ptr const & layout = pit->layout();
146 // We specialize the 95% common case:
147 if (!pit->getDepth()) {
148 if (layout->labeltype == LABEL_MANUAL
149 && pos < pit->beginningOfBody()) {
151 LyXFont f = pit->getFontSettings(buf->params, pos);
153 pit->inInset()->getDrawFont(f);
154 return f.realize(layout->reslabelfont);
156 LyXFont f = pit->getFontSettings(buf->params, pos);
158 pit->inInset()->getDrawFont(f);
159 return f.realize(layout->resfont);
163 // The uncommon case need not be optimized as much
167 if (pos < pit->beginningOfBody()) {
169 layoutfont = layout->labelfont;
172 layoutfont = layout->font;
175 LyXFont tmpfont = pit->getFontSettings(buf->params, pos);
176 tmpfont.realize(layoutfont);
179 pit->inInset()->getDrawFont(tmpfont);
181 return realizeFont(tmpfont, buf, ownerParagraphs(), pit);
185 LyXFont const LyXText::getLayoutFont(Buffer const * buf,
186 ParagraphList::iterator pit) const
188 LyXLayout_ptr const & layout = pit->layout();
190 if (!pit->getDepth()) {
191 return layout->resfont;
194 return realizeFont(layout->font, buf, ownerParagraphs(), pit);
198 LyXFont const LyXText::getLabelFont(Buffer const * buf,
199 ParagraphList::iterator pit) const
201 LyXLayout_ptr const & layout = pit->layout();
203 if (!pit->getDepth()) {
204 return layout->reslabelfont;
207 return realizeFont(layout->labelfont, buf, ownerParagraphs(), pit);
211 void LyXText::setCharFont(ParagraphList::iterator pit,
212 pos_type pos, LyXFont const & fnt,
215 Buffer const * buf = bv()->buffer();
216 LyXFont font = getFont(buf, pit, pos);
217 font.update(fnt, buf->params.language, toggleall);
218 // Let the insets convert their font
219 if (pit->isInset(pos)) {
220 Inset * inset = pit->getInset(pos);
221 if (isEditableInset(inset)) {
222 UpdatableInset * uinset =
223 static_cast<UpdatableInset *>(inset);
224 uinset->setFont(bv(), fnt, toggleall, true);
228 // Plug thru to version below:
229 setCharFont(buf, pit, pos, font);
233 void LyXText::setCharFont(Buffer const * buf, ParagraphList::iterator pit,
234 pos_type pos, LyXFont const & fnt)
238 LyXTextClass const & tclass = buf->params.getLyXTextClass();
239 LyXLayout_ptr const & layout = pit->layout();
241 // Get concrete layout font to reduce against
244 if (pos < pit->beginningOfBody())
245 layoutfont = layout->labelfont;
247 layoutfont = layout->font;
249 // Realize against environment font information
250 if (pit->getDepth()) {
251 #warning FIXME I think I hate this outerHood stuff.
252 Paragraph * tp = &*pit;
253 while (!layoutfont.resolved() && tp && tp->getDepth()) {
254 tp = tp->outerHook();
256 layoutfont.realize(tp->layout()->font);
260 layoutfont.realize(tclass.defaultfont());
262 // Now, reduce font against full layout font
263 font.reduce(layoutfont);
265 pit->setFont(pos, font);
269 // removes the row and reset the touched counters
270 void LyXText::removeRow(RowList::iterator rit)
272 /* FIXME: when we cache the bview, this should just
273 * become a postPaint(), I think */
274 if (refresh_row == rit) {
275 if (rit == rows().begin())
276 refresh_row = boost::next(rit);
278 refresh_row = boost::prior(rit);
280 // what about refresh_y
283 if (anchor_row_ == rit) {
284 if (rit != rows().begin()) {
285 anchor_row_ = boost::prior(rit);
286 anchor_row_offset_ += anchor_row_->height();
288 anchor_row_ = boost::next(rit);
289 anchor_row_offset_ -= rit->height();
293 // the text becomes smaller
294 height -= rit->height();
300 // remove all following rows of the paragraph of the specified row.
301 void LyXText::removeParagraph(RowList::iterator rit)
303 ParagraphList::iterator tmppit = rit->par();
306 while (rit != rows().end() && rit->par() == tmppit) {
307 RowList::iterator tmprit = boost::next(rit);
314 void LyXText::insertParagraph(ParagraphList::iterator pit,
315 RowList::iterator rowit)
317 // insert a new row, starting at position 0
319 RowList::iterator rit = rowlist_.insert(rowit, newrow);
321 // and now append the whole paragraph before the new row
322 appendParagraph(rit);
326 Inset * LyXText::getInset() const
328 if (cursor.pos() < cursor.par()->size()
329 && cursor.par()->isInset(cursor.pos())) {
330 return cursor.par()->getInset(cursor.pos());
336 void LyXText::toggleInset()
338 Inset * inset = getInset();
339 // is there an editable inset at cursor position?
340 if (!isEditableInset(inset)) {
341 // No, try to see if we are inside a collapsable inset
342 if (inset_owner && inset_owner->owner()
343 && inset_owner->owner()->isOpen()) {
344 bv()->unlockInset(static_cast<UpdatableInset *>(inset_owner->owner()));
345 inset_owner->owner()->close(bv());
346 bv()->getLyXText()->cursorRight(bv());
350 //bv()->owner()->message(inset->editMessage());
352 // do we want to keep this?? (JMarc)
353 if (!isHighlyEditableInset(inset))
354 setCursorParUndo(bv());
356 if (inset->isOpen()) {
362 bv()->updateInset(inset);
366 /* used in setlayout */
367 // Asger is not sure we want to do this...
368 void LyXText::makeFontEntriesLayoutSpecific(Buffer const & buf,
371 LyXLayout_ptr const & layout = par.layout();
374 for (pos_type pos = 0; pos < par.size(); ++pos) {
375 if (pos < par.beginningOfBody())
376 layoutfont = layout->labelfont;
378 layoutfont = layout->font;
380 LyXFont tmpfont = par.getFontSettings(buf.params, pos);
381 tmpfont.reduce(layoutfont);
382 par.setFont(pos, tmpfont);
387 ParagraphList::iterator
388 LyXText::setLayout(LyXCursor & cur, LyXCursor & sstart_cur,
389 LyXCursor & send_cur,
390 string const & layout)
392 Paragraph * endpar = send_cur.par()->next();
393 Paragraph * undoendpar = endpar;
395 if (endpar && endpar->getDepth()) {
396 while (endpar && endpar->getDepth()) {
397 endpar = endpar->next();
401 endpar = endpar->next(); // because of parindents etc.
404 setUndo(bv(), Undo::EDIT, &*sstart_cur.par(), undoendpar);
406 // ok we have a selection. This is always between sstart_cur
407 // and sel_end cursor
409 ParagraphList::iterator pit = sstart_cur.par();
410 ParagraphList::iterator epit = boost::next(send_cur.par());
412 LyXLayout_ptr const & lyxlayout =
413 bv()->buffer()->params.getLyXTextClass()[layout];
416 pit->applyLayout(lyxlayout);
417 makeFontEntriesLayoutSpecific(*bv()->buffer(), *pit);
418 ParagraphList::iterator fppit = pit;
419 fppit->params().spaceTop(lyxlayout->fill_top ?
420 VSpace(VSpace::VFILL)
421 : VSpace(VSpace::NONE));
422 fppit->params().spaceBottom(lyxlayout->fill_bottom ?
423 VSpace(VSpace::VFILL)
424 : VSpace(VSpace::NONE));
425 if (lyxlayout->margintype == MARGIN_MANUAL)
426 pit->setLabelWidthString(lyxlayout->labelstring());
429 } while (pit != epit);
435 // set layout over selection and make a total rebreak of those paragraphs
436 void LyXText::setLayout(string const & layout)
438 LyXCursor tmpcursor = cursor; /* store the current cursor */
440 // if there is no selection just set the layout
441 // of the current paragraph */
442 if (!selection.set()) {
443 selection.start = cursor; // dummy selection
444 selection.end = cursor;
446 ParagraphList::iterator endpit = setLayout(cursor, selection.start,
447 selection.end, layout);
448 redoParagraphs(selection.start, endpit);
450 // we have to reset the selection, because the
451 // geometry could have changed
452 setCursor(selection.start.par(),
453 selection.start.pos(), false);
454 selection.cursor = cursor;
455 setCursor(selection.end.par(), selection.end.pos(), false);
459 setCursor(tmpcursor.par(), tmpcursor.pos(), true);
463 bool LyXText::changeDepth(bv_funcs::DEPTH_CHANGE type, bool test_only)
465 ParagraphList::iterator pit(cursor.par());
466 ParagraphList::iterator end(cursor.par());
467 ParagraphList::iterator start = pit;
469 if (selection.set()) {
470 pit = selection.start.par();
471 end = selection.end.par();
475 ParagraphList::iterator pastend = boost::next(end);
478 setUndo(bv(), Undo::EDIT, &(*start), &(*pastend));
480 bool changed = false;
482 int prev_after_depth = 0;
483 #warning parlist ... could be nicer ?
484 if (start != ownerParagraphs().begin()) {
485 prev_after_depth = boost::prior(start)->getMaxDepthAfter();
489 int const depth = pit->params().depth();
490 if (type == bv_funcs::INC_DEPTH) {
491 if (depth < prev_after_depth
492 && pit->layout()->labeltype != LABEL_BIBLIO) {
495 pit->params().depth(depth + 1);
502 pit->params().depth(depth - 1);
505 prev_after_depth = pit->getMaxDepthAfter();
517 // Wow, redoParagraphs is stupid.
519 setCursor(tmpcursor, &(*start), 0);
521 //redoParagraphs(tmpcursor, &(*pastend));
522 redoParagraphs(tmpcursor, &(*pastend));
524 // We need to actually move the text->cursor. I don't
525 // understand why ...
528 // we have to reset the visual selection because the
529 // geometry could have changed
530 if (selection.set()) {
531 setCursor(selection.start.par(), selection.start.pos());
532 selection.cursor = cursor;
533 setCursor(selection.end.par(), selection.end.pos());
536 // this handles the counter labels, and also fixes up
537 // depth values for follow-on (child) paragraphs
541 setCursor(tmpcursor.par(), tmpcursor.pos());
547 // set font over selection and make a total rebreak of those paragraphs
548 void LyXText::setFont(LyXFont const & font, bool toggleall)
550 // if there is no selection just set the current_font
551 if (!selection.set()) {
552 // Determine basis font
554 if (cursor.pos() < cursor.par()->beginningOfBody()) {
555 layoutfont = getLabelFont(bv()->buffer(),
558 layoutfont = getLayoutFont(bv()->buffer(),
561 // Update current font
562 real_current_font.update(font,
563 bv()->buffer()->params.language,
566 // Reduce to implicit settings
567 current_font = real_current_font;
568 current_font.reduce(layoutfont);
569 // And resolve it completely
570 real_current_font.realize(layoutfont);
575 LyXCursor tmpcursor = cursor; // store the current cursor
577 // ok we have a selection. This is always between sel_start_cursor
578 // and sel_end cursor
580 setUndo(bv(), Undo::EDIT,
581 &*selection.start.par(), &*boost::next(selection.end.par()));
583 cursor = selection.start;
584 while (cursor.par() != selection.end.par() ||
585 cursor.pos() < selection.end.pos())
587 if (cursor.pos() < cursor.par()->size()) {
588 // an open footnote should behave like a closed one
589 setCharFont(&*cursor.par(), cursor.pos(),
591 cursor.pos(cursor.pos() + 1);
594 cursor.par(cursor.par()->next());
599 redoParagraphs(selection.start, selection.end.par()->next());
601 // we have to reset the selection, because the
602 // geometry could have changed, but we keep
603 // it for user convenience
604 setCursor(selection.start.par(), selection.start.pos());
605 selection.cursor = cursor;
606 setCursor(selection.end.par(), selection.end.pos());
608 setCursor(tmpcursor.par(), tmpcursor.pos(), true,
609 tmpcursor.boundary());
613 void LyXText::redoHeightOfParagraph()
615 RowList::iterator tmprow = cursor.row();
616 int y = cursor.y() - tmprow->baseline();
618 setHeightOfRow(tmprow);
620 while (tmprow != rows().begin()
621 && boost::prior(tmprow)->par() == tmprow->par()) {
623 y -= tmprow->height();
624 setHeightOfRow(tmprow);
629 setCursor(cursor.par(), cursor.pos(), false, cursor.boundary());
633 void LyXText::redoDrawingOfParagraph(LyXCursor const & cur)
635 RowList::iterator tmprow = cur.row();
637 int y = cur.y() - tmprow->baseline();
638 setHeightOfRow(tmprow);
640 while (tmprow != rows().begin()
641 && boost::prior(tmprow)->par() == tmprow->par()) {
643 y -= tmprow->height();
647 setCursor(cur.par(), cur.pos());
651 // deletes and inserts again all paragaphs between the cursor
652 // and the specified par
653 // This function is needed after SetLayout and SetFont etc.
654 void LyXText::redoParagraphs(LyXCursor const & cur,
655 ParagraphList::iterator endpit)
657 RowList::iterator tmprit = cur.row();
658 int y = cur.y() - tmprit->baseline();
660 ParagraphList::iterator first_phys_pit;
661 if (tmprit == rows().begin()) {
662 // A trick/hack for UNDO.
663 // This is needed because in an UNDO/REDO we could have
664 // changed the ownerParagrah() so the paragraph inside
665 // the row is NOT my really first par anymore.
666 // Got it Lars ;) (Jug 20011206)
667 first_phys_pit = ownerParagraphs().begin();
669 // In here prevrit could be set to rows().end(). (Lgb)
671 first_phys_pit = tmprit->par();
672 while (tmprit != rows().begin()
673 && boost::prior(tmprit)->par() == first_phys_pit)
676 y -= tmprit->height();
679 // Is it possible to put the prevrit setting in here? (Lgb)
682 RowList::iterator prevrit;
683 bool good_prevrit = false;
685 // It seems to mee that good_prevrit is not needed if we let
686 // a bad prevrit have the value rows().end() (Lgb)
687 if (tmprit != rows().begin()) {
688 prevrit = boost::prior(tmprit);
693 while (tmprit != rows().end() && tmprit->par() != endpit) {
694 RowList::iterator tmprit2 = tmprit++;
698 // Reinsert the paragraphs.
699 ParagraphList::iterator tmppit = first_phys_pit;
701 // See if this loop can be rewritten as a while loop instead.
702 // That should also make the code a bit easier to read. (Lgb)
704 if (tmppit != ownerParagraphs().end()) {
705 insertParagraph(&*tmppit, tmprit);
706 while (tmprit != rows().end()
707 && tmprit->par() == tmppit) {
712 } while (tmppit != ownerParagraphs().end() && tmppit != endpit);
715 // If the above changes are done, then we can compare prevrit
716 // with rows().end() here. (Lgb)
718 setHeightOfRow(prevrit);
719 const_cast<LyXText *>(this)->postPaint(y - prevrit->height());
721 setHeightOfRow(rows().begin());
722 const_cast<LyXText *>(this)->postPaint(0);
724 if (tmprit != rows().end())
725 setHeightOfRow(tmprit);
731 void LyXText::fullRebreak()
733 if (rows().empty()) {
737 if (need_break_row != rows().end()) {
738 breakAgain(need_break_row);
739 need_break_row = rows().end();
745 // important for the screen
748 // the cursor set functions have a special mechanism. When they
749 // realize, that you left an empty paragraph, they will delete it.
750 // They also delete the corresponding row
752 // need the selection cursor:
753 void LyXText::setSelection()
755 bool const lsel = selection.set();
757 if (!selection.set()) {
758 last_sel_cursor = selection.cursor;
759 selection.start = selection.cursor;
760 selection.end = selection.cursor;
765 // first the toggling area
766 if (cursor.y() < last_sel_cursor.y()
767 || (cursor.y() == last_sel_cursor.y()
768 && cursor.x() < last_sel_cursor.x())) {
769 toggle_end_cursor = last_sel_cursor;
770 toggle_cursor = cursor;
772 toggle_end_cursor = cursor;
773 toggle_cursor = last_sel_cursor;
776 last_sel_cursor = cursor;
778 // and now the whole selection
780 if (selection.cursor.par() == cursor.par())
781 if (selection.cursor.pos() < cursor.pos()) {
782 selection.end = cursor;
783 selection.start = selection.cursor;
785 selection.end = selection.cursor;
786 selection.start = cursor;
788 else if (selection.cursor.y() < cursor.y() ||
789 (selection.cursor.y() == cursor.y()
790 && selection.cursor.x() < cursor.x())) {
791 selection.end = cursor;
792 selection.start = selection.cursor;
795 selection.end = selection.cursor;
796 selection.start = cursor;
799 // a selection with no contents is not a selection
800 if (selection.start.par() == selection.end.par() &&
801 selection.start.pos() == selection.end.pos())
802 selection.set(false);
804 if (inset_owner && (selection.set() || lsel))
805 inset_owner->setUpdateStatus(bv(), InsetText::SELECTION);
809 string const LyXText::selectionAsString(Buffer const * buffer,
812 if (!selection.set()) return string();
814 // should be const ...
815 ParagraphList::iterator startpit = selection.start.par();
816 ParagraphList::iterator endpit = selection.end.par();
817 pos_type const startpos(selection.start.pos());
818 pos_type const endpos(selection.end.pos());
820 if (startpit == endpit) {
821 return startpit->asString(buffer, startpos, endpos, label);
826 // First paragraph in selection
827 result += startpit->asString(buffer, startpos, startpit->size(), label) + "\n\n";
829 // The paragraphs in between (if any)
830 #warning FIXME Why isnt ParagraphList::iterator used here?
832 LyXCursor tmpcur(selection.start);
833 tmpcur.par(tmpcur.par()->next());
834 while (tmpcur.par() != endpit) {
835 result += tmpcur.par()->asString(buffer, 0,
836 tmpcur.par()->size(),
838 tmpcur.par(boost::next(tmpcur.par()));
841 // Last paragraph in selection
842 result += endpit->asString(buffer, 0, endpos, label);
848 void LyXText::clearSelection()
850 selection.set(false);
851 selection.mark(false);
852 last_sel_cursor = selection.end = selection.start = selection.cursor = cursor;
853 // reset this in the bv_owner!
854 if (bv_owner && bv_owner->text)
855 bv_owner->text->xsel_cache.set(false);
859 void LyXText::cursorHome()
861 setCursor(cursor.par(), cursor.row()->pos());
865 void LyXText::cursorEnd()
867 if (cursor.par()->empty())
870 // There is a lot of unneeded recalculation going on here:
871 // - boost::next(curosr.row())
872 // - lastPost(*this, cursor.row())
874 if (boost::next(cursor.row()) == rows().end()
875 || boost::next(cursor.row())->par() != cursor.row()->par()) {
876 setCursor(cursor.par(), lastPos(*this, cursor.row()) + 1);
878 if (!cursor.par()->empty() &&
879 (cursor.par()->getChar(lastPos(*this, cursor.row())) == ' '
880 || cursor.par()->isNewline(lastPos(*this, cursor.row())))) {
881 setCursor(cursor.par(), lastPos(*this, cursor.row()));
883 setCursor(cursor.par(),
884 lastPos(*this, cursor.row()) + 1);
890 void LyXText::cursorTop()
892 setCursor(ownerParagraphs().begin(), 0);
896 void LyXText::cursorBottom()
899 // This is how it should be:
900 // ParagraphList::iterator lastpit = boost::prior(ownerParagraphs().end());
901 ParagraphList::iterator lastpit = &ownerParagraphs().back();
902 int pos = lastpit->size();
903 setCursor(lastpit, pos);
907 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
909 // If the mask is completely neutral, tell user
910 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
911 // Could only happen with user style
912 bv()->owner()->message(_("No font change defined. Use Character under the Layout menu to define font change."));
916 // Try implicit word selection
917 // If there is a change in the language the implicit word selection
919 LyXCursor resetCursor = cursor;
920 bool implicitSelection = (font.language() == ignore_language
921 && font.number() == LyXFont::IGNORE)
922 ? selectWordWhenUnderCursor(WHOLE_WORD_STRICT) : false;
925 setFont(font, toggleall);
927 // Implicit selections are cleared afterwards
928 //and cursor is set to the original position.
929 if (implicitSelection) {
931 cursor = resetCursor;
932 setCursor(cursor.par(), cursor.pos());
933 selection.cursor = cursor;
936 inset_owner->setUpdateStatus(bv(), InsetText::CURSOR_PAR);
940 string LyXText::getStringToIndex()
942 // Try implicit word selection
943 // If there is a change in the language the implicit word selection
945 LyXCursor const reset_cursor = cursor;
946 bool const implicitSelection = selectWordWhenUnderCursor(PREVIOUS_WORD);
949 if (!selection.set())
950 bv()->owner()->message(_("Nothing to index!"));
951 else if (selection.start.par() != selection.end.par())
952 bv()->owner()->message(_("Cannot index more than one paragraph!"));
954 idxstring = selectionAsString(bv()->buffer(), false);
956 // Reset cursors to their original position.
957 cursor = reset_cursor;
958 setCursor(cursor.par(), cursor.pos());
959 selection.cursor = cursor;
961 // Clear the implicit selection.
962 if (implicitSelection)
969 // the DTP switches for paragraphs. LyX will store them in the first
970 // physicla paragraph. When a paragraph is broken, the top settings rest,
971 // the bottom settings are given to the new one. So I can make shure,
972 // they do not duplicate themself and you cannnot make dirty things with
975 void LyXText::setParagraph(bool line_top, bool line_bottom,
976 bool pagebreak_top, bool pagebreak_bottom,
977 VSpace const & space_top,
978 VSpace const & space_bottom,
979 Spacing const & spacing,
981 string const & labelwidthstring,
984 LyXCursor tmpcursor = cursor;
985 if (!selection.set()) {
986 selection.start = cursor;
987 selection.end = cursor;
990 // make sure that the depth behind the selection are restored, too
991 ParagraphList::iterator endpit = boost::next(selection.end.par());
992 ParagraphList::iterator undoendpit = endpit;
994 if (endpit != ownerParagraphs().end() && endpit->getDepth()) {
995 while (endpit != ownerParagraphs().end() &&
996 endpit->getDepth()) {
1001 else if (endpit != ownerParagraphs().end()) {
1002 // because of parindents etc.
1006 setUndo(bv(), Undo::EDIT, &*selection.start.par(), &*undoendpit);
1009 ParagraphList::iterator tmppit = selection.end.par();
1011 while (tmppit != boost::prior(selection.start.par())) {
1012 setCursor(tmppit, 0);
1013 postPaint(cursor.y() - cursor.row()->baseline());
1014 cursor.par()->params().lineTop(line_top);
1015 cursor.par()->params().lineBottom(line_bottom);
1016 cursor.par()->params().pagebreakTop(pagebreak_top);
1017 cursor.par()->params().pagebreakBottom(pagebreak_bottom);
1018 cursor.par()->params().spaceTop(space_top);
1019 cursor.par()->params().spaceBottom(space_bottom);
1020 cursor.par()->params().spacing(spacing);
1021 // does the layout allow the new alignment?
1022 LyXLayout_ptr const & layout = cursor.par()->layout();
1024 if (align == LYX_ALIGN_LAYOUT)
1025 align = layout->align;
1026 if (align & layout->alignpossible) {
1027 if (align == layout->align)
1028 cursor.par()->params().align(LYX_ALIGN_LAYOUT);
1030 cursor.par()->params().align(align);
1032 cursor.par()->setLabelWidthString(labelwidthstring);
1033 cursor.par()->params().noindent(noindent);
1034 tmppit = boost::prior(cursor.par());
1037 redoParagraphs(selection.start, endpit);
1040 setCursor(selection.start.par(), selection.start.pos());
1041 selection.cursor = cursor;
1042 setCursor(selection.end.par(), selection.end.pos());
1044 setCursor(tmpcursor.par(), tmpcursor.pos());
1046 bv()->updateInset(inset_owner);
1050 // set the counter of a paragraph. This includes the labels
1051 void LyXText::setCounter(Buffer const * buf, ParagraphList::iterator pit)
1053 LyXTextClass const & textclass = buf->params.getLyXTextClass();
1054 LyXLayout_ptr const & layout = pit->layout();
1056 if (pit != ownerParagraphs().begin()) {
1058 pit->params().appendix(boost::prior(pit)->params().appendix());
1059 if (!pit->params().appendix() &&
1060 pit->params().startOfAppendix()) {
1061 pit->params().appendix(true);
1062 textclass.counters().reset();
1064 pit->enumdepth = boost::prior(pit)->enumdepth;
1065 pit->itemdepth = boost::prior(pit)->itemdepth;
1067 pit->params().appendix(pit->params().startOfAppendix());
1072 /* Maybe we have to increment the enumeration depth.
1073 * BUT, enumeration in a footnote is considered in isolation from its
1074 * surrounding paragraph so don't increment if this is the
1075 * first line of the footnote
1076 * AND, bibliographies can't have their depth changed ie. they
1077 * are always of depth 0
1079 if (pit != ownerParagraphs().begin()
1080 && boost::prior(pit)->getDepth() < pit->getDepth()
1081 && boost::prior(pit)->layout()->labeltype == LABEL_COUNTER_ENUMI
1082 && pit->enumdepth < 3
1083 && layout->labeltype != LABEL_BIBLIO) {
1087 // Maybe we have to decrement the enumeration depth, see note above
1088 if (pit != ownerParagraphs().begin()
1089 && boost::prior(pit)->getDepth() > pit->getDepth()
1090 && layout->labeltype != LABEL_BIBLIO) {
1091 pit->enumdepth = pit->depthHook(pit->getDepth())->enumdepth;
1094 if (!pit->params().labelString().empty()) {
1095 pit->params().labelString(string());
1098 if (layout->margintype == MARGIN_MANUAL) {
1099 if (pit->params().labelWidthString().empty()) {
1100 pit->setLabelWidthString(layout->labelstring());
1103 pit->setLabelWidthString(string());
1106 // is it a layout that has an automatic label?
1107 if (layout->labeltype >= LABEL_COUNTER_CHAPTER) {
1108 int const i = layout->labeltype - LABEL_COUNTER_CHAPTER;
1112 if (i >= 0 && i <= buf->params.secnumdepth) {
1116 textclass.counters().step(layout->latexname());
1118 // Is there a label? Useful for Chapter layout
1119 if (!pit->params().appendix()) {
1120 s << layout->labelstring();
1122 s << layout->labelstring_appendix();
1125 // Use of an integer is here less than elegant. For now.
1126 int head = textclass.maxcounter() - LABEL_COUNTER_CHAPTER;
1127 if (!pit->params().appendix()) {
1128 numbertype = "sectioning";
1130 numbertype = "appendix";
1131 if (pit->isRightToLeftPar(buf->params))
1132 langtype = "hebrew";
1137 s << textclass.counters()
1138 .numberLabel(layout->latexname(),
1139 numbertype, langtype, head);
1141 pit->params().labelString(STRCONV(s.str()));
1143 // reset enum counters
1144 textclass.counters().reset("enum");
1145 } else if (layout->labeltype < LABEL_COUNTER_ENUMI) {
1146 textclass.counters().reset("enum");
1147 } else if (layout->labeltype == LABEL_COUNTER_ENUMI) {
1149 // Yes I know this is a really, really! bad solution
1151 string enumcounter("enum");
1153 switch (pit->enumdepth) {
1162 enumcounter += "iv";
1165 // not a valid enumdepth...
1169 textclass.counters().step(enumcounter);
1171 s << textclass.counters()
1172 .numberLabel(enumcounter, "enumeration");
1173 pit->params().labelString(STRCONV(s.str()));
1175 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
1176 textclass.counters().step("bibitem");
1177 int number = textclass.counters().value("bibitem");
1178 if (pit->bibitem()) {
1179 pit->bibitem()->setCounter(number);
1180 pit->params().labelString(layout->labelstring());
1182 // In biblio should't be following counters but...
1184 string s = layout->labelstring();
1186 // the caption hack:
1187 if (layout->labeltype == LABEL_SENSITIVE) {
1188 ParagraphList::iterator tmppit = pit;
1191 while (tmppit != ownerParagraphs().end() &&
1193 // the single '=' is intended below
1194 && (in = tmppit->inInset()->owner())) {
1195 if (in->lyxCode() == Inset::FLOAT_CODE ||
1196 in->lyxCode() == Inset::WRAP_CODE) {
1200 tmppit = in->parOwner();
1206 = textclass.floats().getType(static_cast<InsetFloat*>(in)->type());
1208 textclass.counters().step(fl.type());
1210 // Doesn't work... yet.
1211 #if USE_BOOST_FORMAT
1212 s = boost::io::str(boost::format(_("%1$s #:")) % fl.name());
1213 // s << boost::format(_("%1$s %1$d:")
1215 // % buf->counters().value(fl.name());
1218 //o << fl.name() << ' ' << buf->counters().value(fl.name()) << ":";
1219 o << fl.name() << " #:";
1220 s = STRCONV(o.str());
1223 // par->SetLayout(0);
1224 // s = layout->labelstring;
1225 s = _("Senseless: ");
1228 pit->params().labelString(s);
1230 // reset the enumeration counter. They are always reset
1231 // when there is any other layout between
1232 // Just fall-through between the cases so that all
1233 // enum counters deeper than enumdepth is also reset.
1234 switch (pit->enumdepth) {
1236 textclass.counters().reset("enumi");
1238 textclass.counters().reset("enumii");
1240 textclass.counters().reset("enumiii");
1242 textclass.counters().reset("enumiv");
1248 // Updates all counters. Paragraphs with changed label string will be rebroken
1249 void LyXText::updateCounters()
1251 RowList::iterator rowit = rows().begin();
1252 ParagraphList::iterator pit = rowit->par();
1254 // CHECK if this is really needed. (Lgb)
1255 bv()->buffer()->params.getLyXTextClass().counters().reset();
1257 while (pit != ownerParagraphs().end()) {
1258 while (rowit->par() != pit)
1261 string const oldLabel = pit->params().labelString();
1264 if (pit != ownerParagraphs().begin())
1265 maxdepth = boost::prior(pit)->getMaxDepthAfter();
1267 if (pit->params().depth() > maxdepth)
1268 pit->params().depth(maxdepth);
1270 // setCounter can potentially change the labelString.
1271 setCounter(bv()->buffer(), &*pit);
1273 string const & newLabel = pit->params().labelString();
1275 if (oldLabel.empty() && !newLabel.empty()) {
1276 removeParagraph(rowit);
1277 appendParagraph(rowit);
1285 void LyXText::insertInset(Inset * inset)
1287 if (!cursor.par()->insetAllowed(inset->lyxCode()))
1289 setUndo(bv(), Undo::FINISH, &*cursor.par(),
1290 &*boost::next(cursor.par()));
1292 cursor.par()->insertInset(cursor.pos(), inset);
1293 // Just to rebreak and refresh correctly.
1294 // The character will not be inserted a second time
1295 insertChar(Paragraph::META_INSET);
1296 // If we enter a highly editable inset the cursor should be to before
1297 // the inset. This couldn't happen before as Undo was not handled inside
1298 // inset now after the Undo LyX tries to call inset->Edit(...) again
1299 // and cannot do this as the cursor is behind the inset and GetInset
1300 // does not return the inset!
1301 if (isHighlyEditableInset(inset)) {
1308 void LyXText::copyEnvironmentType()
1310 copylayouttype = cursor.par()->layout()->name();
1314 void LyXText::pasteEnvironmentType()
1316 // do nothing if there has been no previous copyEnvironmentType()
1317 if (!copylayouttype.empty())
1318 setLayout(copylayouttype);
1322 void LyXText::cutSelection(bool doclear, bool realcut)
1324 // Stuff what we got on the clipboard. Even if there is no selection.
1326 // There is a problem with having the stuffing here in that the
1327 // larger the selection the slower LyX will get. This can be
1328 // solved by running the line below only when the selection has
1329 // finished. The solution used currently just works, to make it
1330 // faster we need to be more clever and probably also have more
1331 // calls to stuffClipboard. (Lgb)
1332 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1334 // This doesn't make sense, if there is no selection
1335 if (!selection.set())
1338 // OK, we have a selection. This is always between selection.start
1339 // and selection.end
1341 // make sure that the depth behind the selection are restored, too
1342 Paragraph * endpar = selection.end.par()->next();
1343 Paragraph * undoendpar = endpar;
1345 if (endpar && endpar->getDepth()) {
1346 while (endpar && endpar->getDepth()) {
1347 endpar = endpar->next();
1348 undoendpar = endpar;
1350 } else if (endpar) {
1351 endpar = endpar->next(); // because of parindents etc.
1354 setUndo(bv(), Undo::DELETE,
1355 &*selection.start.par(), undoendpar);
1357 // there are two cases: cut only within one paragraph or
1358 // more than one paragraph
1359 if (selection.start.par() == selection.end.par()) {
1360 // only within one paragraph
1361 endpar = &*selection.end.par();
1362 int pos = selection.end.pos();
1363 CutAndPaste::cutSelection(&*selection.start.par(), &endpar,
1364 selection.start.pos(), pos,
1365 bv()->buffer()->params.textclass,
1367 selection.end.pos(pos);
1369 endpar = &*selection.end.par();
1370 int pos = selection.end.pos();
1371 CutAndPaste::cutSelection(&*selection.start.par(), &endpar,
1372 selection.start.pos(), pos,
1373 bv()->buffer()->params.textclass,
1376 selection.end.par(endpar);
1377 selection.end.pos(pos);
1378 cursor.pos(selection.end.pos());
1380 endpar = endpar->next();
1382 // sometimes necessary
1384 selection.start.par()->stripLeadingSpaces();
1386 redoParagraphs(selection.start, endpar);
1388 // cutSelection can invalidate the cursor so we need to set
1390 // we prefer the end for when tracking changes
1391 cursor = selection.end;
1393 // need a valid cursor. (Lgb)
1396 setCursor(cursor.par(), cursor.pos());
1397 selection.cursor = cursor;
1402 void LyXText::copySelection()
1404 // stuff the selection onto the X clipboard, from an explicit copy request
1405 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1407 // this doesnt make sense, if there is no selection
1408 if (!selection.set())
1411 // ok we have a selection. This is always between selection.start
1412 // and sel_end cursor
1414 // copy behind a space if there is one
1415 while (selection.start.par()->size() > selection.start.pos()
1416 && selection.start.par()->isLineSeparator(selection.start.pos())
1417 && (selection.start.par() != selection.end.par()
1418 || selection.start.pos() < selection.end.pos()))
1419 selection.start.pos(selection.start.pos() + 1);
1421 CutAndPaste::copySelection(&*selection.start.par(),
1422 &*selection.end.par(),
1423 selection.start.pos(), selection.end.pos(),
1424 bv()->buffer()->params.textclass);
1428 void LyXText::pasteSelection()
1430 // this does not make sense, if there is nothing to paste
1431 if (!CutAndPaste::checkPastePossible())
1434 setUndo(bv(), Undo::INSERT,
1435 &*cursor.par(), &*boost::next(cursor.par()));
1438 ParagraphList::iterator actpit = cursor.par();
1439 int pos = cursor.pos();
1441 Paragraph * actpar = &*actpit;
1442 CutAndPaste::pasteSelection(&actpar, &endpar, pos,
1443 bv()->buffer()->params.textclass);
1445 redoParagraphs(cursor, endpar);
1447 setCursor(cursor.par(), cursor.pos());
1450 selection.cursor = cursor;
1451 setCursor(actpit, pos);
1457 void LyXText::setSelectionRange(lyx::pos_type length)
1462 selection.cursor = cursor;
1469 // simple replacing. The font of the first selected character is used
1470 void LyXText::replaceSelectionWithString(string const & str)
1472 setCursorParUndo(bv());
1475 if (!selection.set()) { // create a dummy selection
1476 selection.end = cursor;
1477 selection.start = cursor;
1480 // Get font setting before we cut
1481 pos_type pos = selection.end.pos();
1482 LyXFont const font = selection.start.par()
1483 ->getFontSettings(bv()->buffer()->params,
1484 selection.start.pos());
1486 // Insert the new string
1487 for (string::const_iterator cit = str.begin(); cit != str.end(); ++cit) {
1488 selection.end.par()->insertChar(pos, (*cit), font);
1492 // Cut the selection
1493 cutSelection(true, false);
1499 // needed to insert the selection
1500 void LyXText::insertStringAsLines(string const & str)
1502 ParagraphList::iterator pit = cursor.par();
1503 pos_type pos = cursor.pos();
1504 ParagraphList::iterator endpit = boost::next(cursor.par());
1506 setCursorParUndo(bv());
1508 // only to be sure, should not be neccessary
1511 Paragraph * par = &*pit;
1512 bv()->buffer()->insertStringAsLines(par, pos, current_font, str);
1514 redoParagraphs(cursor, &*endpit);
1515 setCursor(cursor.par(), cursor.pos());
1516 selection.cursor = cursor;
1517 setCursor(pit, pos);
1522 // turns double-CR to single CR, others where converted into one
1523 // blank. Then InsertStringAsLines is called
1524 void LyXText::insertStringAsParagraphs(string const & str)
1526 string linestr(str);
1527 bool newline_inserted = false;
1528 for (string::size_type i = 0; i < linestr.length(); ++i) {
1529 if (linestr[i] == '\n') {
1530 if (newline_inserted) {
1531 // we know that \r will be ignored by
1532 // InsertStringA. Of course, it is a dirty
1533 // trick, but it works...
1534 linestr[i - 1] = '\r';
1538 newline_inserted = true;
1540 } else if (IsPrintable(linestr[i])) {
1541 newline_inserted = false;
1544 insertStringAsLines(linestr);
1548 void LyXText::checkParagraph(ParagraphList::iterator pit, pos_type pos)
1550 LyXCursor tmpcursor;
1554 RowList::iterator row = getRow(pit, pos, y);
1555 RowList::iterator beg = rows().begin();
1557 // is there a break one row above
1559 && boost::prior(row)->par() == row->par()) {
1560 z = rowBreakPoint(*boost::prior(row));
1561 if (z >= row->pos()) {
1562 // set the dimensions of the row above
1563 y -= boost::prior(row)->height();
1566 breakAgain(boost::prior(row));
1568 // set the cursor again. Otherwise
1569 // dangling pointers are possible
1570 setCursor(cursor.par(), cursor.pos(),
1571 false, cursor.boundary());
1572 selection.cursor = cursor;
1577 int const tmpheight = row->height();
1578 pos_type const tmplast = lastPos(*this, row);
1581 if (row->height() == tmpheight && lastPos(*this, row) == tmplast) {
1582 postRowPaint(row, y);
1587 // check the special right address boxes
1588 if (pit->layout()->margintype == MARGIN_RIGHT_ADDRESS_BOX) {
1595 redoDrawingOfParagraph(tmpcursor);
1598 // set the cursor again. Otherwise dangling pointers are possible
1599 // also set the selection
1601 if (selection.set()) {
1603 setCursorIntern(selection.cursor.par(), selection.cursor.pos(),
1604 false, selection.cursor.boundary());
1605 selection.cursor = cursor;
1606 setCursorIntern(selection.start.par(),
1607 selection.start.pos(),
1608 false, selection.start.boundary());
1609 selection.start = cursor;
1610 setCursorIntern(selection.end.par(),
1611 selection.end.pos(),
1612 false, selection.end.boundary());
1613 selection.end = cursor;
1614 setCursorIntern(last_sel_cursor.par(),
1615 last_sel_cursor.pos(),
1616 false, last_sel_cursor.boundary());
1617 last_sel_cursor = cursor;
1620 setCursorIntern(cursor.par(), cursor.pos(),
1621 false, cursor.boundary());
1625 // returns false if inset wasn't found
1626 bool LyXText::updateInset(Inset * inset)
1628 // first check the current paragraph
1629 int pos = cursor.par()->getPositionOfInset(inset);
1631 checkParagraph(&*cursor.par(), pos);
1635 // check every paragraph
1637 ParagraphList::iterator par = ownerParagraphs().begin();
1638 ParagraphList::iterator end = ownerParagraphs().end();
1641 pos = par->getPositionOfInset(inset);
1643 checkParagraph(&*par, pos);
1647 } while (par != end);
1653 bool LyXText::setCursor(ParagraphList::iterator pit,
1655 bool setfont, bool boundary)
1657 LyXCursor old_cursor = cursor;
1658 setCursorIntern(pit, pos, setfont, boundary);
1659 return deleteEmptyParagraphMechanism(old_cursor);
1663 void LyXText::setCursor(LyXCursor & cur, ParagraphList::iterator pit,
1664 pos_type pos, bool boundary)
1666 lyx::Assert(pit != ownerParagraphs().end());
1670 cur.boundary(boundary);
1672 // get the cursor y position in text
1674 RowList::iterator row = getRow(pit, pos, y);
1675 RowList::iterator beg = rows().begin();
1677 RowList::iterator old_row = row;
1679 // if we are before the first char of this row and are still in the
1680 // same paragraph and there is a previous row then put the cursor on
1681 // the end of the previous row
1682 cur.iy(y + row->baseline());
1684 if (row != beg && pos &&
1685 boost::prior(row)->par() == row->par() &&
1686 pos < pit->size() &&
1687 pit->getChar(pos) == Paragraph::META_INSET &&
1688 (ins = pit->getInset(pos)) && (ins->needFullRow() || ins->display()))
1695 // y is now the beginning of the cursor row
1696 y += row->baseline();
1697 // y is now the cursor baseline
1700 pos_type last = lastPrintablePos(*this, old_row);
1702 // None of these should happen, but we're scaredy-cats
1703 if (pos > pit->size()) {
1704 lyxerr << "dont like 1 please report" << endl;
1707 } else if (pos > last + 1) {
1708 lyxerr << "dont like 2 please report" << endl;
1709 // This shouldn't happen.
1712 } else if (pos < row->pos()) {
1713 lyxerr << "dont like 3 please report" << endl;
1718 // now get the cursors x position
1719 float x = getCursorX(row, pos, last, boundary);
1722 if (old_row != row) {
1723 x = getCursorX(old_row, pos, last, boundary);
1727 /* We take out this for the time being because 1) the redraw code is not
1728 prepared to this yet and 2) because some good policy has yet to be decided
1729 while editting: for instance how to act on rows being created/deleted
1733 //if the cursor is in a visible row, anchor to it
1735 if (topy < y && y < topy + bv()->workHeight())
1741 float LyXText::getCursorX(RowList::iterator rit,
1742 pos_type pos, pos_type last, bool boundary) const
1744 pos_type cursor_vpos = 0;
1746 float fill_separator;
1748 float fill_label_hfill;
1749 // This call HAS to be here because of the BidiTables!!!
1750 prepareToPrint(rit, x, fill_separator, fill_hfill,
1753 if (last < rit->pos())
1754 cursor_vpos = rit->pos();
1755 else if (pos > last && !boundary)
1756 cursor_vpos = (rit->par()->isRightToLeftPar(bv()->buffer()->params))
1757 ? rit->pos() : last + 1;
1758 else if (pos > rit->pos() &&
1759 (pos > last || boundary))
1760 /// Place cursor after char at (logical) position pos - 1
1761 cursor_vpos = (bidi_level(pos - 1) % 2 == 0)
1762 ? log2vis(pos - 1) + 1 : log2vis(pos - 1);
1764 /// Place cursor before char at (logical) position pos
1765 cursor_vpos = (bidi_level(pos) % 2 == 0)
1766 ? log2vis(pos) : log2vis(pos) + 1;
1768 pos_type body_pos = rit->par()->beginningOfBody();
1769 if ((body_pos > 0) &&
1770 ((body_pos - 1 > last) ||
1771 !rit->par()->isLineSeparator(body_pos - 1)))
1774 for (pos_type vpos = rit->pos(); vpos < cursor_vpos; ++vpos) {
1775 pos_type pos = vis2log(vpos);
1776 if (body_pos > 0 && pos == body_pos - 1) {
1777 x += fill_label_hfill +
1778 font_metrics::width(
1779 rit->par()->layout()->labelsep,
1780 getLabelFont(bv()->buffer(),
1782 if (rit->par()->isLineSeparator(body_pos - 1))
1783 x -= singleWidth(rit->par(), body_pos - 1);
1786 if (hfillExpansion(*this, rit, pos)) {
1787 x += singleWidth(rit->par(), pos);
1788 if (pos >= body_pos)
1791 x += fill_label_hfill;
1792 } else if (rit->par()->isSeparator(pos)) {
1793 x += singleWidth(rit->par(), pos);
1794 if (pos >= body_pos)
1795 x += fill_separator;
1797 x += singleWidth(rit->par(), pos);
1803 void LyXText::setCursorIntern(ParagraphList::iterator pit,
1804 pos_type pos, bool setfont, bool boundary)
1806 InsetText * it = static_cast<InsetText *>(pit->inInset());
1808 if (it != inset_owner) {
1809 lyxerr[Debug::INSETS] << "InsetText is " << it
1811 << "inset_owner is "
1812 << inset_owner << endl;
1813 #ifdef WITH_WARNINGS
1814 #warning I believe this code is wrong. (Lgb)
1815 #warning Jürgen, have a look at this. (Lgb)
1816 #warning Hmmm, I guess you are right but we
1817 #warning should verify when this is needed
1819 // Jürgen, would you like to have a look?
1820 // I guess we need to move the outer cursor
1821 // and open and lock the inset (bla bla bla)
1822 // stuff I don't know... so can you have a look?
1824 // I moved the lyxerr stuff in here so we can see if
1825 // this is actually really needed and where!
1827 // it->getLyXText(bv())->setCursorIntern(bv(), par, pos, setfont, boundary);
1832 setCursor(cursor, pit, pos, boundary);
1838 void LyXText::setCurrentFont()
1840 pos_type pos = cursor.pos();
1841 if (cursor.boundary() && pos > 0)
1845 if (pos == cursor.par()->size())
1847 else // potentional bug... BUG (Lgb)
1848 if (cursor.par()->isSeparator(pos)) {
1849 if (pos > cursor.row()->pos() &&
1850 bidi_level(pos) % 2 ==
1851 bidi_level(pos - 1) % 2)
1853 else if (pos + 1 < cursor.par()->size())
1859 cursor.par()->getFontSettings(bv()->buffer()->params, pos);
1860 real_current_font = getFont(bv()->buffer(), cursor.par(), pos);
1862 if (cursor.pos() == cursor.par()->size() &&
1863 isBoundary(bv()->buffer(), *cursor.par(), cursor.pos()) &&
1864 !cursor.boundary()) {
1865 Language const * lang =
1866 cursor.par()->getParLanguage(bv()->buffer()->params);
1867 current_font.setLanguage(lang);
1868 current_font.setNumber(LyXFont::OFF);
1869 real_current_font.setLanguage(lang);
1870 real_current_font.setNumber(LyXFont::OFF);
1875 // returns the column near the specified x-coordinate of the row
1876 // x is set to the real beginning of this column
1878 LyXText::getColumnNearX(RowList::iterator rit, int & x, bool & boundary) const
1881 float fill_separator;
1883 float fill_label_hfill;
1885 prepareToPrint(rit, tmpx, fill_separator,
1886 fill_hfill, fill_label_hfill);
1888 pos_type vc = rit->pos();
1889 pos_type last = lastPrintablePos(*this, rit);
1892 LyXLayout_ptr const & layout = rit->par()->layout();
1894 bool left_side = false;
1896 pos_type body_pos = rit->par()->beginningOfBody();
1897 float last_tmpx = tmpx;
1900 (body_pos - 1 > last ||
1901 !rit->par()->isLineSeparator(body_pos - 1)))
1904 // check for empty row
1905 if (!rit->par()->size()) {
1910 while (vc <= last && tmpx <= x) {
1913 if (body_pos > 0 && c == body_pos - 1) {
1914 tmpx += fill_label_hfill +
1915 font_metrics::width(layout->labelsep,
1916 getLabelFont(bv()->buffer(), &*rit->par()));
1917 if (rit->par()->isLineSeparator(body_pos - 1))
1918 tmpx -= singleWidth(rit->par(), body_pos - 1);
1921 if (hfillExpansion(*this, rit, c)) {
1922 tmpx += singleWidth(rit->par(), c);
1926 tmpx += fill_label_hfill;
1927 } else if (rit->par()->isSeparator(c)) {
1928 tmpx += singleWidth(rit->par(), c);
1930 tmpx+= fill_separator;
1932 tmpx += singleWidth(rit->par(), c);
1937 if ((tmpx + last_tmpx) / 2 > x) {
1942 if (vc > last + 1) // This shouldn't happen.
1946 // This (rtl_support test) is not needed, but gives
1947 // some speedup if rtl_support=false
1948 bool const lastrow = lyxrc.rtl_support &&
1949 (boost::next(rit) == rowlist_.end() ||
1950 boost::next(rit)->par() != rit->par());
1951 // If lastrow is false, we don't need to compute
1952 // the value of rtl.
1953 bool const rtl = (lastrow)
1954 ? rit->par()->isRightToLeftPar(bv()->buffer()->params)
1957 ((rtl && left_side && vc == rit->pos() && x < tmpx - 5) ||
1958 (!rtl && !left_side && vc == last + 1 && x > tmpx + 5)))
1960 else if (vc == rit->pos()) {
1962 if (bidi_level(c) % 2 == 1)
1965 c = vis2log(vc - 1);
1966 bool const rtl = (bidi_level(c) % 2 == 1);
1967 if (left_side == rtl) {
1969 boundary = isBoundary(bv()->buffer(), *rit->par(), c);
1973 if (rit->pos() <= last && c > last
1974 && rit->par()->isNewline(last)) {
1975 if (bidi_level(last) % 2 == 0)
1976 tmpx -= singleWidth(rit->par(), last);
1978 tmpx += singleWidth(rit->par(), last);
1988 void LyXText::setCursorFromCoordinates(int x, int y)
1990 LyXCursor old_cursor = cursor;
1992 setCursorFromCoordinates(cursor, x, y);
1994 deleteEmptyParagraphMechanism(old_cursor);
2001 * return true if the cursor given is at the end of a row,
2002 * and the next row is filled by an inset that spans an entire
2005 bool beforeFullRowInset(LyXText & lt, RowList::iterator row,
2007 if (boost::next(row) == lt.rows().end())
2009 Row const & next = *boost::next(row);
2011 if (next.pos() != cur.pos() || next.par() != cur.par())
2013 if (!cur.par()->isInset(cur.pos()))
2015 Inset const * inset = cur.par()->getInset(cur.pos());
2016 if (inset->needFullRow() || inset->display())
2023 void LyXText::setCursorFromCoordinates(LyXCursor & cur, int x, int y)
2025 // Get the row first.
2027 RowList::iterator row = getRowNearY(y);
2029 pos_type const column = getColumnNearX(row, x, bound);
2030 cur.par(&*row->par());
2031 cur.pos(row->pos() + column);
2033 cur.y(y + row->baseline());
2036 if (beforeFullRowInset(*this, row, cur)) {
2037 pos_type last = lastPrintablePos(*this, row);
2038 float x = getCursorX(boost::next(row), cur.pos(), last, bound);
2040 cur.iy(y + row->height() + boost::next(row)->baseline());
2041 cur.irow(boost::next(row));
2047 cur.boundary(bound);
2051 void LyXText::cursorLeft(bool internal)
2053 if (cursor.pos() > 0) {
2054 bool boundary = cursor.boundary();
2055 setCursor(cursor.par(), cursor.pos() - 1, true, false);
2056 if (!internal && !boundary &&
2057 isBoundary(bv()->buffer(), *cursor.par(), cursor.pos() + 1))
2058 setCursor(cursor.par(), cursor.pos() + 1, true, true);
2059 } else if (cursor.par() != ownerParagraphs().begin()) { // steps into the above paragraph.
2060 ParagraphList::iterator pit = boost::prior(cursor.par());
2061 setCursor(pit, pit->size());
2066 void LyXText::cursorRight(bool internal)
2068 bool const at_end = (cursor.pos() == cursor.par()->size());
2069 bool const at_newline = !at_end &&
2070 cursor.par()->isNewline(cursor.pos());
2072 if (!internal && cursor.boundary() && !at_newline)
2073 setCursor(cursor.par(), cursor.pos(), true, false);
2075 setCursor(cursor.par(), cursor.pos() + 1, true, false);
2077 isBoundary(bv()->buffer(), *cursor.par(), cursor.pos()))
2078 setCursor(cursor.par(), cursor.pos(), true, true);
2079 } else if (cursor.par()->next())
2080 setCursor(cursor.par()->next(), 0);
2084 void LyXText::cursorUp(bool selecting)
2087 int x = cursor.x_fix();
2088 int y = cursor.y() - cursor.row()->baseline() - 1;
2089 setCursorFromCoordinates(x, y);
2092 int y1 = cursor.iy() - topy;
2095 Inset * inset_hit = checkInsetHit(x, y1);
2096 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2097 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2101 setCursorFromCoordinates(bv(), cursor.x_fix(),
2102 cursor.y() - cursor.row()->baseline() - 1);
2107 void LyXText::cursorDown(bool selecting)
2110 int x = cursor.x_fix();
2111 int y = cursor.y() - cursor.row()->baseline() +
2112 cursor.row()->height() + 1;
2113 setCursorFromCoordinates(x, y);
2114 if (!selecting && cursor.row() == cursor.irow()) {
2116 int y1 = cursor.iy() - topy;
2119 Inset * inset_hit = checkInsetHit(x, y1);
2120 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2121 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2125 setCursorFromCoordinates(bv(), cursor.x_fix(),
2126 cursor.y() - cursor.row()->baseline()
2127 + cursor.row()->height() + 1);
2132 void LyXText::cursorUpParagraph()
2134 if (cursor.pos() > 0) {
2135 setCursor(cursor.par(), 0);
2137 else if (cursor.par() != ownerParagraphs().begin()) {
2138 setCursor(boost::prior(cursor.par()), 0);
2143 void LyXText::cursorDownParagraph()
2145 if (cursor.par()->next()) {
2146 setCursor(cursor.par()->next(), 0);
2148 setCursor(cursor.par(), cursor.par()->size());
2152 // fix the cursor `cur' after a characters has been deleted at `where'
2153 // position. Called by deleteEmptyParagraphMechanism
2154 void LyXText::fixCursorAfterDelete(LyXCursor & cur,
2155 LyXCursor const & where)
2157 // if cursor is not in the paragraph where the delete occured,
2159 if (cur.par() != where.par())
2162 // if cursor position is after the place where the delete occured,
2164 if (cur.pos() > where.pos())
2165 cur.pos(cur.pos()-1);
2167 // check also if we don't want to set the cursor on a spot behind the
2168 // pagragraph because we erased the last character.
2169 if (cur.pos() > cur.par()->size())
2170 cur.pos(cur.par()->size());
2172 // recompute row et al. for this cursor
2173 setCursor(cur, cur.par(), cur.pos(), cur.boundary());
2177 bool LyXText::deleteEmptyParagraphMechanism(LyXCursor const & old_cursor)
2179 // Would be wrong to delete anything if we have a selection.
2180 if (selection.set())
2183 // We allow all kinds of "mumbo-jumbo" when freespacing.
2184 if (old_cursor.par()->layout()->free_spacing
2185 || old_cursor.par()->isFreeSpacing()) {
2189 /* Ok I'll put some comments here about what is missing.
2190 I have fixed BackSpace (and thus Delete) to not delete
2191 double-spaces automagically. I have also changed Cut,
2192 Copy and Paste to hopefully do some sensible things.
2193 There are still some small problems that can lead to
2194 double spaces stored in the document file or space at
2195 the beginning of paragraphs. This happens if you have
2196 the cursor betwenn to spaces and then save. Or if you
2197 cut and paste and the selection have a space at the
2198 beginning and then save right after the paste. I am
2199 sure none of these are very hard to fix, but I will
2200 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
2201 that I can get some feedback. (Lgb)
2204 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
2205 // delete the LineSeparator.
2208 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
2209 // delete the LineSeparator.
2212 // If the pos around the old_cursor were spaces, delete one of them.
2213 if (old_cursor.par() != cursor.par()
2214 || old_cursor.pos() != cursor.pos()) {
2215 // Only if the cursor has really moved
2217 if (old_cursor.pos() > 0
2218 && old_cursor.pos() < old_cursor.par()->size()
2219 && old_cursor.par()->isLineSeparator(old_cursor.pos())
2220 && old_cursor.par()->isLineSeparator(old_cursor.pos() - 1)) {
2221 old_cursor.par()->erase(old_cursor.pos() - 1);
2222 redoParagraphs(old_cursor, old_cursor.par()->next());
2224 #ifdef WITH_WARNINGS
2225 #warning This will not work anymore when we have multiple views of the same buffer
2226 // In this case, we will have to correct also the cursors held by
2227 // other bufferviews. It will probably be easier to do that in a more
2228 // automated way in LyXCursor code. (JMarc 26/09/2001)
2230 // correct all cursors held by the LyXText
2231 fixCursorAfterDelete(cursor, old_cursor);
2232 fixCursorAfterDelete(selection.cursor,
2234 fixCursorAfterDelete(selection.start,
2236 fixCursorAfterDelete(selection.end, old_cursor);
2237 fixCursorAfterDelete(last_sel_cursor,
2239 fixCursorAfterDelete(toggle_cursor, old_cursor);
2240 fixCursorAfterDelete(toggle_end_cursor,
2246 // don't delete anything if this is the ONLY paragraph!
2247 if (ownerParagraphs().size() == 1)
2250 // Do not delete empty paragraphs with keepempty set.
2251 if (old_cursor.par()->layout()->keepempty)
2254 // only do our magic if we changed paragraph
2255 if (old_cursor.par() == cursor.par())
2258 // record if we have deleted a paragraph
2259 // we can't possibly have deleted a paragraph before this point
2260 bool deleted = false;
2262 if (old_cursor.par()->empty() ||
2263 (old_cursor.par()->size() == 1 &&
2264 old_cursor.par()->isLineSeparator(0))) {
2265 // ok, we will delete anything
2266 LyXCursor tmpcursor;
2270 if (old_cursor.row() != rows().begin()) {
2272 prevrow = boost::prior(old_cursor.row());
2273 const_cast<LyXText *>(this)->postPaint(old_cursor.y() - old_cursor.row()->baseline() - prevrow->height());
2275 cursor = old_cursor; // that undo can restore the right cursor position
2276 Paragraph * endpar = old_cursor.par()->next();
2277 while (endpar && endpar->getDepth()) {
2278 endpar = endpar->next();
2281 setUndo(bv(), Undo::DELETE, &*old_cursor.par(), endpar);
2285 removeRow(old_cursor.row());
2286 if (ownerParagraphs().begin() == old_cursor.par()) {
2287 ownerParagraph(&*boost::next(ownerParagraphs().begin()));
2289 #warning FIXME Do the proper ParagraphList operation here (Lgb)
2291 delete &*old_cursor.par();
2293 /* Breakagain the next par. Needed because of
2294 * the parindent that can occur or dissappear.
2295 * The next row can change its height, if
2296 * there is another layout before */
2297 if (boost::next(prevrow) != rows().end()) {
2298 breakAgain(boost::next(prevrow));
2301 setHeightOfRow(prevrow);
2303 RowList::iterator nextrow = boost::next(old_cursor.row());
2304 const_cast<LyXText *>(this)->postPaint(
2305 old_cursor.y() - old_cursor.row()->baseline());
2308 cursor = old_cursor; // that undo can restore the right cursor position
2309 Paragraph * endpar = old_cursor.par()->next();
2310 while (endpar && endpar->getDepth()) {
2311 endpar = endpar->next();
2314 setUndo(bv(), Undo::DELETE, &*old_cursor.par(), endpar);
2318 removeRow(old_cursor.row());
2320 if (ownerParagraphs().begin() == old_cursor.par()) {
2321 ownerParagraph(&*boost::next(ownerParagraphs().begin()));
2323 #warning FIXME Do the proper ParagraphList operations here. (Lgb)
2324 delete &*old_cursor.par();
2326 /* Breakagain the next par. Needed because of
2327 the parindent that can occur or dissappear.
2328 The next row can change its height, if
2329 there is another layout before */
2330 if (nextrow != rows().end()) {
2331 breakAgain(nextrow);
2337 setCursorIntern(cursor.par(), cursor.pos());
2339 if (selection.cursor.par() == old_cursor.par()
2340 && selection.cursor.pos() == old_cursor.pos()) {
2341 // correct selection
2342 selection.cursor = cursor;
2346 if (old_cursor.par()->stripLeadingSpaces()) {
2347 redoParagraphs(old_cursor,
2348 old_cursor.par()->next());
2350 setCursorIntern(cursor.par(), cursor.pos());
2351 selection.cursor = cursor;
2358 ParagraphList & LyXText::ownerParagraphs() const
2361 return inset_owner->paragraphs;
2363 return bv_owner->buffer()->paragraphs;
2367 void LyXText::ownerParagraph(Paragraph * p) const
2370 inset_owner->paragraph(p);
2372 bv_owner->buffer()->paragraphs.set(p);
2377 void LyXText::ownerParagraph(int id, Paragraph * p) const
2379 Paragraph * op = bv_owner->buffer()->getParFromID(id);
2380 if (op && op->inInset()) {
2381 static_cast<InsetText *>(op->inInset())->paragraph(p);
2388 LyXText::refresh_status LyXText::refreshStatus() const
2390 return refresh_status_;
2394 void LyXText::clearPaint()
2396 refresh_status_ = REFRESH_NONE;
2397 refresh_row = rows().end();
2402 void LyXText::postPaint(int start_y)
2404 refresh_status old = refresh_status_;
2406 refresh_status_ = REFRESH_AREA;
2407 refresh_row = rows().end();
2409 if (old != REFRESH_NONE && refresh_y < start_y)
2412 refresh_y = start_y;
2417 // We are an inset's lyxtext. Tell the top-level lyxtext
2418 // it needs to update the row we're in.
2419 LyXText * t = bv()->text;
2420 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2424 // FIXME: we should probably remove this y parameter,
2425 // make refresh_y be 0, and use row->y etc.
2426 void LyXText::postRowPaint(RowList::iterator rit, int start_y)
2428 if (refresh_status_ != REFRESH_NONE && refresh_y < start_y) {
2429 refresh_status_ = REFRESH_AREA;
2432 refresh_y = start_y;
2435 if (refresh_status_ == REFRESH_AREA)
2438 refresh_status_ = REFRESH_ROW;
2444 // We are an inset's lyxtext. Tell the top-level lyxtext
2445 // it needs to update the row we're in.
2446 LyXText * t = bv()->text;
2447 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2451 bool LyXText::isInInset() const
2453 // Sub-level has non-null bv owner and
2454 // non-null inset owner.
2455 return inset_owner != 0 && bv_owner != 0;
2459 int defaultRowHeight()
2461 LyXFont const font(LyXFont::ALL_SANE);
2462 return int(font_metrics::maxAscent(font)
2463 + font_metrics::maxDescent(font) * 1.5);