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;
471 setUndo(bv(), Undo::EDIT, &(*start), &(*pastend));
473 bool changed = false;
475 int prev_after_depth = 0;
476 #warning parlist ... could be nicer ?
477 if (start != ownerParagraphs().begin())
478 prev_after_depth = boost::prior(start)->getMaxDepthAfter();
481 int const depth = pit->params().depth();
482 if (type == bv_funcs::INC_DEPTH) {
483 if (depth < prev_after_depth
484 && pit->layout()->labeltype != LABEL_BIBLIO) {
487 pit->params().depth(depth + 1);
492 pit->params().depth(depth - 1);
495 prev_after_depth = pit->getMaxDepthAfter();
506 // Wow, redoParagraphs is stupid.
508 setCursor(tmpcursor, &(*start), 0);
509 redoParagraphs(tmpcursor, &(*pastend));
511 // We need to actually move the text->cursor. I don't
512 // understand why ...
515 // we have to reset the visual selection because the
516 // geometry could have changed
517 if (selection.set()) {
518 setCursor(selection.start.par(), selection.start.pos());
519 selection.cursor = cursor;
520 setCursor(selection.end.par(), selection.end.pos());
523 // this handles the counter labels, and also fixes up
524 // depth values for follow-on (child) paragraphs
528 setCursor(tmpcursor.par(), tmpcursor.pos());
534 // set font over selection and make a total rebreak of those paragraphs
535 void LyXText::setFont(LyXFont const & font, bool toggleall)
537 // if there is no selection just set the current_font
538 if (!selection.set()) {
539 // Determine basis font
541 if (cursor.pos() < cursor.par()->beginningOfBody()) {
542 layoutfont = getLabelFont(bv()->buffer(),
545 layoutfont = getLayoutFont(bv()->buffer(),
548 // Update current font
549 real_current_font.update(font,
550 bv()->buffer()->params.language,
553 // Reduce to implicit settings
554 current_font = real_current_font;
555 current_font.reduce(layoutfont);
556 // And resolve it completely
557 real_current_font.realize(layoutfont);
562 LyXCursor tmpcursor = cursor; // store the current cursor
564 // ok we have a selection. This is always between sel_start_cursor
565 // and sel_end cursor
567 setUndo(bv(), Undo::EDIT,
568 selection.start.par(), selection.end.par()->next());
570 cursor = selection.start;
571 while (cursor.par() != selection.end.par() ||
572 cursor.pos() < selection.end.pos())
574 if (cursor.pos() < cursor.par()->size()) {
575 // an open footnote should behave like a closed one
576 setCharFont(cursor.par(), cursor.pos(),
578 cursor.pos(cursor.pos() + 1);
581 cursor.par(cursor.par()->next());
586 redoParagraphs(selection.start, selection.end.par()->next());
588 // we have to reset the selection, because the
589 // geometry could have changed, but we keep
590 // it for user convenience
591 setCursor(selection.start.par(), selection.start.pos());
592 selection.cursor = cursor;
593 setCursor(selection.end.par(), selection.end.pos());
595 setCursor(tmpcursor.par(), tmpcursor.pos(), true,
596 tmpcursor.boundary());
600 void LyXText::redoHeightOfParagraph()
602 RowList::iterator tmprow = cursor.row();
603 int y = cursor.y() - tmprow->baseline();
605 setHeightOfRow(tmprow);
607 while (tmprow != rows().begin()
608 && boost::prior(tmprow)->par() == tmprow->par()) {
610 y -= tmprow->height();
611 setHeightOfRow(tmprow);
616 setCursor(cursor.par(), cursor.pos(), false, cursor.boundary());
620 void LyXText::redoDrawingOfParagraph(LyXCursor const & cur)
622 RowList::iterator tmprow = cur.row();
624 int y = cur.y() - tmprow->baseline();
625 setHeightOfRow(tmprow);
627 while (tmprow != rows().begin()
628 && boost::prior(tmprow)->par() == tmprow->par()) {
630 y -= tmprow->height();
634 setCursor(cur.par(), cur.pos());
638 // deletes and inserts again all paragaphs between the cursor
639 // and the specified par
640 // This function is needed after SetLayout and SetFont etc.
641 void LyXText::redoParagraphs(LyXCursor const & cur,
642 Paragraph const * ep)
644 RowList::iterator tmprit = cur.row();
645 ParagraphList::iterator endpit(const_cast<Paragraph*>(ep));
646 int y = cur.y() - tmprit->baseline();
648 ParagraphList::iterator first_phys_pit;
649 if (tmprit == rows().begin()) {
650 // A trick/hack for UNDO.
651 // This is needed because in an UNDO/REDO we could have
652 // changed the ownerParagrah() so the paragraph inside
653 // the row is NOT my really first par anymore.
654 // Got it Lars ;) (Jug 20011206)
655 first_phys_pit = ownerParagraphs().begin();
657 // In here prevrit could be set to rows().end(). (Lgb)
659 first_phys_pit = tmprit->par();
660 while (tmprit != rows().begin()
661 && boost::prior(tmprit)->par() == first_phys_pit)
664 y -= tmprit->height();
667 // Is it possible to put the prevrit setting in here? (Lgb)
670 RowList::iterator prevrit;
671 bool good_prevrit = false;
673 // It seems to mee that good_prevrit is not needed if we let
674 // a bad prevrit have the value rows().end() (Lgb)
675 if (tmprit != rows().begin()) {
676 prevrit = boost::prior(tmprit);
681 while (tmprit != rows().end() && tmprit->par() != endpit) {
682 RowList::iterator tmprit2 = tmprit++;
686 // Reinsert the paragraphs.
687 ParagraphList::iterator tmppit = first_phys_pit;
689 // See if this loop can be rewritten as a while loop instead.
690 // That should also make the code a bit easier to read. (Lgb)
692 if (tmppit != ownerParagraphs().end()) {
693 insertParagraph(&*tmppit, tmprit);
694 while (tmprit != rows().end()
695 && tmprit->par() == tmppit) {
700 } while (tmppit != ownerParagraphs().end() && tmppit != endpit);
703 // If the above changes are done, then we can compare prevrit
704 // with rows().end() here. (Lgb)
706 setHeightOfRow(prevrit);
707 const_cast<LyXText *>(this)->postPaint(y - prevrit->height());
709 setHeightOfRow(rows().begin());
710 const_cast<LyXText *>(this)->postPaint(0);
712 if (tmprit != rows().end())
713 setHeightOfRow(tmprit);
718 void LyXText::fullRebreak()
720 if (rows().empty()) {
724 if (need_break_row != rows().end()) {
725 breakAgain(need_break_row);
726 need_break_row = rows().end();
732 // important for the screen
735 // the cursor set functions have a special mechanism. When they
736 // realize, that you left an empty paragraph, they will delete it.
737 // They also delete the corresponding row
739 // need the selection cursor:
740 void LyXText::setSelection()
742 bool const lsel = selection.set();
744 if (!selection.set()) {
745 last_sel_cursor = selection.cursor;
746 selection.start = selection.cursor;
747 selection.end = selection.cursor;
752 // first the toggling area
753 if (cursor.y() < last_sel_cursor.y()
754 || (cursor.y() == last_sel_cursor.y()
755 && cursor.x() < last_sel_cursor.x())) {
756 toggle_end_cursor = last_sel_cursor;
757 toggle_cursor = cursor;
759 toggle_end_cursor = cursor;
760 toggle_cursor = last_sel_cursor;
763 last_sel_cursor = cursor;
765 // and now the whole selection
767 if (selection.cursor.par() == cursor.par())
768 if (selection.cursor.pos() < cursor.pos()) {
769 selection.end = cursor;
770 selection.start = selection.cursor;
772 selection.end = selection.cursor;
773 selection.start = cursor;
775 else if (selection.cursor.y() < cursor.y() ||
776 (selection.cursor.y() == cursor.y()
777 && selection.cursor.x() < cursor.x())) {
778 selection.end = cursor;
779 selection.start = selection.cursor;
782 selection.end = selection.cursor;
783 selection.start = cursor;
786 // a selection with no contents is not a selection
787 if (selection.start.par() == selection.end.par() &&
788 selection.start.pos() == selection.end.pos())
789 selection.set(false);
791 if (inset_owner && (selection.set() || lsel))
792 inset_owner->setUpdateStatus(bv(), InsetText::SELECTION);
796 string const LyXText::selectionAsString(Buffer const * buffer,
799 if (!selection.set()) return string();
801 // should be const ...
802 Paragraph * startpar(selection.start.par());
803 Paragraph * endpar(selection.end.par());
804 pos_type const startpos(selection.start.pos());
805 pos_type const endpos(selection.end.pos());
807 if (startpar == endpar) {
808 return startpar->asString(buffer, startpos, endpos, label);
813 // First paragraph in selection
814 result += startpar->asString(buffer, startpos, startpar->size(), label) + "\n\n";
816 // The paragraphs in between (if any)
817 LyXCursor tmpcur(selection.start);
818 tmpcur.par(tmpcur.par()->next());
819 while (tmpcur.par() != endpar) {
820 result += tmpcur.par()->asString(buffer, 0,
821 tmpcur.par()->size(),
823 tmpcur.par(tmpcur.par()->next());
826 // Last paragraph in selection
827 result += endpar->asString(buffer, 0, endpos, label);
833 void LyXText::clearSelection()
835 selection.set(false);
836 selection.mark(false);
837 last_sel_cursor = selection.end = selection.start = selection.cursor = cursor;
838 // reset this in the bv_owner!
839 if (bv_owner && bv_owner->text)
840 bv_owner->text->xsel_cache.set(false);
844 void LyXText::cursorHome()
846 setCursor(cursor.par(), cursor.row()->pos());
850 void LyXText::cursorEnd()
852 if (cursor.par()->empty())
855 // There is a lot of unneeded recalculation going on here:
856 // - boost::next(curosr.row())
857 // - lastPost(*this, cursor.row())
859 if (boost::next(cursor.row()) == rows().end()
860 || boost::next(cursor.row())->par() != cursor.row()->par()) {
861 setCursor(cursor.par(), lastPos(*this, cursor.row()) + 1);
863 if (!cursor.par()->empty() &&
864 (cursor.par()->getChar(lastPos(*this, cursor.row())) == ' '
865 || cursor.par()->isNewline(lastPos(*this, cursor.row())))) {
866 setCursor(cursor.par(), lastPos(*this, cursor.row()));
868 setCursor(cursor.par(),
869 lastPos(*this, cursor.row()) + 1);
875 void LyXText::cursorTop()
877 while (cursor.par()->previous())
878 cursor.par(cursor.par()->previous());
879 setCursor(cursor.par(), 0);
883 void LyXText::cursorBottom()
885 while (cursor.par()->next())
886 cursor.par(cursor.par()->next());
887 setCursor(cursor.par(), cursor.par()->size());
891 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
893 // If the mask is completely neutral, tell user
894 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
895 // Could only happen with user style
896 bv()->owner()->message(_("No font change defined. Use Character under the Layout menu to define font change."));
900 // Try implicit word selection
901 // If there is a change in the language the implicit word selection
903 LyXCursor resetCursor = cursor;
904 bool implicitSelection = (font.language() == ignore_language
905 && font.number() == LyXFont::IGNORE)
906 ? selectWordWhenUnderCursor(WHOLE_WORD_STRICT) : false;
909 setFont(font, toggleall);
911 // Implicit selections are cleared afterwards
912 //and cursor is set to the original position.
913 if (implicitSelection) {
915 cursor = resetCursor;
916 setCursor(cursor.par(), cursor.pos());
917 selection.cursor = cursor;
920 inset_owner->setUpdateStatus(bv(), InsetText::CURSOR_PAR);
924 string LyXText::getStringToIndex()
926 // Try implicit word selection
927 // If there is a change in the language the implicit word selection
929 LyXCursor const reset_cursor = cursor;
930 bool const implicitSelection = selectWordWhenUnderCursor(PREVIOUS_WORD);
933 if (!selection.set())
934 bv()->owner()->message(_("Nothing to index!"));
935 else if (selection.start.par() != selection.end.par())
936 bv()->owner()->message(_("Cannot index more than one paragraph!"));
938 idxstring = selectionAsString(bv()->buffer(), false);
940 // Reset cursors to their original position.
941 cursor = reset_cursor;
942 setCursor(cursor.par(), cursor.pos());
943 selection.cursor = cursor;
945 // Clear the implicit selection.
946 if (implicitSelection)
953 // the DTP switches for paragraphs. LyX will store them in the first
954 // physicla paragraph. When a paragraph is broken, the top settings rest,
955 // the bottom settings are given to the new one. So I can make shure,
956 // they do not duplicate themself and you cannnot make dirty things with
959 void LyXText::setParagraph(bool line_top, bool line_bottom,
960 bool pagebreak_top, bool pagebreak_bottom,
961 VSpace const & space_top,
962 VSpace const & space_bottom,
963 Spacing const & spacing,
965 string const & labelwidthstring,
968 LyXCursor tmpcursor = cursor;
969 if (!selection.set()) {
970 selection.start = cursor;
971 selection.end = cursor;
974 // make sure that the depth behind the selection are restored, too
975 Paragraph * endpar = selection.end.par()->next();
976 Paragraph * undoendpar = endpar;
978 if (endpar && endpar->getDepth()) {
979 while (endpar && endpar->getDepth()) {
980 endpar = endpar->next();
985 // because of parindents etc.
986 endpar = endpar->next();
989 setUndo(bv(), Undo::EDIT, selection.start.par(), undoendpar);
992 Paragraph * tmppar = selection.end.par();
994 while (tmppar != selection.start.par()->previous()) {
995 setCursor(tmppar, 0);
996 postPaint(cursor.y() - cursor.row()->baseline());
997 cursor.par()->params().lineTop(line_top);
998 cursor.par()->params().lineBottom(line_bottom);
999 cursor.par()->params().pagebreakTop(pagebreak_top);
1000 cursor.par()->params().pagebreakBottom(pagebreak_bottom);
1001 cursor.par()->params().spaceTop(space_top);
1002 cursor.par()->params().spaceBottom(space_bottom);
1003 cursor.par()->params().spacing(spacing);
1004 // does the layout allow the new alignment?
1005 LyXLayout_ptr const & layout = cursor.par()->layout();
1007 if (align == LYX_ALIGN_LAYOUT)
1008 align = layout->align;
1009 if (align & layout->alignpossible) {
1010 if (align == layout->align)
1011 cursor.par()->params().align(LYX_ALIGN_LAYOUT);
1013 cursor.par()->params().align(align);
1015 cursor.par()->setLabelWidthString(labelwidthstring);
1016 cursor.par()->params().noindent(noindent);
1017 tmppar = cursor.par()->previous();
1020 redoParagraphs(selection.start, endpar);
1023 setCursor(selection.start.par(), selection.start.pos());
1024 selection.cursor = cursor;
1025 setCursor(selection.end.par(), selection.end.pos());
1027 setCursor(tmpcursor.par(), tmpcursor.pos());
1029 bv()->updateInset(inset_owner);
1033 // set the counter of a paragraph. This includes the labels
1034 void LyXText::setCounter(Buffer const * buf, Paragraph * par)
1036 LyXTextClass const & textclass = buf->params.getLyXTextClass();
1037 LyXLayout_ptr const & layout = par->layout();
1039 if (par->previous()) {
1041 par->params().appendix(par->previous()->params().appendix());
1042 if (!par->params().appendix() && par->params().startOfAppendix()) {
1043 par->params().appendix(true);
1044 textclass.counters().reset();
1046 par->enumdepth = par->previous()->enumdepth;
1047 par->itemdepth = par->previous()->itemdepth;
1049 par->params().appendix(par->params().startOfAppendix());
1054 /* Maybe we have to increment the enumeration depth.
1055 * BUT, enumeration in a footnote is considered in isolation from its
1056 * surrounding paragraph so don't increment if this is the
1057 * first line of the footnote
1058 * AND, bibliographies can't have their depth changed ie. they
1059 * are always of depth 0
1062 && par->previous()->getDepth() < par->getDepth()
1063 && par->previous()->layout()->labeltype == LABEL_COUNTER_ENUMI
1064 && par->enumdepth < 3
1065 && layout->labeltype != LABEL_BIBLIO) {
1069 // Maybe we have to decrement the enumeration depth, see note above
1071 && par->previous()->getDepth() > par->getDepth()
1072 && layout->labeltype != LABEL_BIBLIO) {
1073 par->enumdepth = par->depthHook(par->getDepth())->enumdepth;
1076 if (!par->params().labelString().empty()) {
1077 par->params().labelString(string());
1080 if (layout->margintype == MARGIN_MANUAL) {
1081 if (par->params().labelWidthString().empty()) {
1082 par->setLabelWidthString(layout->labelstring());
1085 par->setLabelWidthString(string());
1088 // is it a layout that has an automatic label?
1089 if (layout->labeltype >= LABEL_COUNTER_CHAPTER) {
1090 int const i = layout->labeltype - LABEL_COUNTER_CHAPTER;
1094 if (i >= 0 && i <= buf->params.secnumdepth) {
1098 textclass.counters().step(layout->latexname());
1100 // Is there a label? Useful for Chapter layout
1101 if (!par->params().appendix()) {
1102 s << layout->labelstring();
1104 s << layout->labelstring_appendix();
1107 // Use of an integer is here less than elegant. For now.
1108 int head = textclass.maxcounter() - LABEL_COUNTER_CHAPTER;
1109 if (!par->params().appendix()) {
1110 numbertype = "sectioning";
1112 numbertype = "appendix";
1113 if (par->isRightToLeftPar(buf->params))
1114 langtype = "hebrew";
1119 s << textclass.counters()
1120 .numberLabel(layout->latexname(),
1121 numbertype, langtype, head);
1123 par->params().labelString(STRCONV(s.str()));
1125 // reset enum counters
1126 textclass.counters().reset("enum");
1127 } else if (layout->labeltype < LABEL_COUNTER_ENUMI) {
1128 textclass.counters().reset("enum");
1129 } else if (layout->labeltype == LABEL_COUNTER_ENUMI) {
1131 // Yes I know this is a really, really! bad solution
1133 string enumcounter("enum");
1135 switch (par->enumdepth) {
1144 enumcounter += "iv";
1147 // not a valid enumdepth...
1151 textclass.counters().step(enumcounter);
1153 s << textclass.counters()
1154 .numberLabel(enumcounter, "enumeration");
1155 par->params().labelString(STRCONV(s.str()));
1157 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
1158 textclass.counters().step("bibitem");
1159 int number = textclass.counters().value("bibitem");
1160 if (par->bibitem()) {
1161 par->bibitem()->setCounter(number);
1162 par->params().labelString(layout->labelstring());
1164 // In biblio should't be following counters but...
1166 string s = layout->labelstring();
1168 // the caption hack:
1169 if (layout->labeltype == LABEL_SENSITIVE) {
1170 Paragraph * tmppar = par;
1173 while (tmppar && tmppar->inInset()
1174 // the single '=' is intended below
1175 && (in = tmppar->inInset()->owner())) {
1176 if (in->lyxCode() == Inset::FLOAT_CODE ||
1177 in->lyxCode() == Inset::WRAP_CODE) {
1181 tmppar = in->parOwner();
1187 = textclass.floats().getType(static_cast<InsetFloat*>(in)->type());
1189 textclass.counters().step(fl.type());
1191 // Doesn't work... yet.
1192 #if USE_BOOST_FORMAT
1193 s = boost::io::str(boost::format(_("%1$s #:")) % fl.name());
1194 // s << boost::format(_("%1$s %1$d:")
1196 // % buf->counters().value(fl.name());
1199 //o << fl.name() << ' ' << buf->counters().value(fl.name()) << ":";
1200 o << fl.name() << " #:";
1201 s = STRCONV(o.str());
1204 // par->SetLayout(0);
1205 // s = layout->labelstring;
1206 s = _("Senseless: ");
1209 par->params().labelString(s);
1211 // reset the enumeration counter. They are always reset
1212 // when there is any other layout between
1213 // Just fall-through between the cases so that all
1214 // enum counters deeper than enumdepth is also reset.
1215 switch (par->enumdepth) {
1217 textclass.counters().reset("enumi");
1219 textclass.counters().reset("enumii");
1221 textclass.counters().reset("enumiii");
1223 textclass.counters().reset("enumiv");
1229 // Updates all counters. Paragraphs with changed label string will be rebroken
1230 void LyXText::updateCounters()
1232 RowList::iterator rowit = rows().begin();
1233 ParagraphList::iterator pit = rowit->par();
1235 // CHECK if this is really needed. (Lgb)
1236 bv()->buffer()->params.getLyXTextClass().counters().reset();
1238 while (pit != ownerParagraphs().end()) {
1239 while (rowit->par() != pit)
1242 string const oldLabel = pit->params().labelString();
1245 if (pit != ownerParagraphs().begin())
1246 maxdepth = boost::prior(pit)->getMaxDepthAfter();
1248 if (pit->params().depth() > maxdepth)
1249 pit->params().depth(maxdepth);
1251 // setCounter can potentially change the labelString.
1252 setCounter(bv()->buffer(), &*pit);
1254 string const & newLabel = pit->params().labelString();
1256 if (oldLabel.empty() && !newLabel.empty()) {
1257 removeParagraph(rowit);
1258 appendParagraph(rowit);
1266 void LyXText::insertInset(Inset * inset)
1268 if (!cursor.par()->insetAllowed(inset->lyxCode()))
1270 setUndo(bv(), Undo::FINISH, cursor.par(), cursor.par()->next());
1272 cursor.par()->insertInset(cursor.pos(), inset);
1273 // Just to rebreak and refresh correctly.
1274 // The character will not be inserted a second time
1275 insertChar(Paragraph::META_INSET);
1276 // If we enter a highly editable inset the cursor should be to before
1277 // the inset. This couldn't happen before as Undo was not handled inside
1278 // inset now after the Undo LyX tries to call inset->Edit(...) again
1279 // and cannot do this as the cursor is behind the inset and GetInset
1280 // does not return the inset!
1281 if (isHighlyEditableInset(inset)) {
1288 void LyXText::copyEnvironmentType()
1290 copylayouttype = cursor.par()->layout()->name();
1294 void LyXText::pasteEnvironmentType()
1296 // do nothing if there has been no previous copyEnvironmentType()
1297 if (!copylayouttype.empty())
1298 setLayout(copylayouttype);
1302 void LyXText::cutSelection(bool doclear, bool realcut)
1304 // Stuff what we got on the clipboard. Even if there is no selection.
1306 // There is a problem with having the stuffing here in that the
1307 // larger the selection the slower LyX will get. This can be
1308 // solved by running the line below only when the selection has
1309 // finished. The solution used currently just works, to make it
1310 // faster we need to be more clever and probably also have more
1311 // calls to stuffClipboard. (Lgb)
1312 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1314 // This doesn't make sense, if there is no selection
1315 if (!selection.set())
1318 // OK, we have a selection. This is always between selection.start
1319 // and selection.end
1321 // make sure that the depth behind the selection are restored, too
1322 Paragraph * endpar = selection.end.par()->next();
1323 Paragraph * undoendpar = endpar;
1325 if (endpar && endpar->getDepth()) {
1326 while (endpar && endpar->getDepth()) {
1327 endpar = endpar->next();
1328 undoendpar = endpar;
1330 } else if (endpar) {
1331 endpar = endpar->next(); // because of parindents etc.
1334 setUndo(bv(), Undo::DELETE,
1335 selection.start.par(), undoendpar);
1337 // there are two cases: cut only within one paragraph or
1338 // more than one paragraph
1339 if (selection.start.par() == selection.end.par()) {
1340 // only within one paragraph
1341 endpar = selection.end.par();
1342 int pos = selection.end.pos();
1343 CutAndPaste::cutSelection(selection.start.par(), &endpar,
1344 selection.start.pos(), pos,
1345 bv()->buffer()->params.textclass,
1347 selection.end.pos(pos);
1349 endpar = selection.end.par();
1350 int pos = selection.end.pos();
1351 CutAndPaste::cutSelection(selection.start.par(), &endpar,
1352 selection.start.pos(), pos,
1353 bv()->buffer()->params.textclass,
1356 selection.end.par(endpar);
1357 selection.end.pos(pos);
1358 cursor.pos(selection.end.pos());
1360 endpar = endpar->next();
1362 // sometimes necessary
1364 selection.start.par()->stripLeadingSpaces();
1366 redoParagraphs(selection.start, endpar);
1368 // cutSelection can invalidate the cursor so we need to set
1370 // we prefer the end for when tracking changes
1371 cursor = selection.end;
1373 // need a valid cursor. (Lgb)
1376 setCursor(cursor.par(), cursor.pos());
1377 selection.cursor = cursor;
1382 void LyXText::copySelection()
1384 // stuff the selection onto the X clipboard, from an explicit copy request
1385 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1387 // this doesnt make sense, if there is no selection
1388 if (!selection.set())
1391 // ok we have a selection. This is always between selection.start
1392 // and sel_end cursor
1394 // copy behind a space if there is one
1395 while (selection.start.par()->size() > selection.start.pos()
1396 && selection.start.par()->isLineSeparator(selection.start.pos())
1397 && (selection.start.par() != selection.end.par()
1398 || selection.start.pos() < selection.end.pos()))
1399 selection.start.pos(selection.start.pos() + 1);
1401 CutAndPaste::copySelection(selection.start.par(), selection.end.par(),
1402 selection.start.pos(), selection.end.pos(),
1403 bv()->buffer()->params.textclass);
1407 void LyXText::pasteSelection()
1409 // this does not make sense, if there is nothing to paste
1410 if (!CutAndPaste::checkPastePossible())
1413 setUndo(bv(), Undo::INSERT,
1414 cursor.par(), cursor.par()->next());
1417 Paragraph * actpar = cursor.par();
1418 int pos = cursor.pos();
1420 CutAndPaste::pasteSelection(&actpar, &endpar, pos,
1421 bv()->buffer()->params.textclass);
1423 redoParagraphs(cursor, endpar);
1425 setCursor(cursor.par(), cursor.pos());
1428 selection.cursor = cursor;
1429 setCursor(actpar, pos);
1435 void LyXText::setSelectionRange(lyx::pos_type length)
1440 selection.cursor = cursor;
1447 // simple replacing. The font of the first selected character is used
1448 void LyXText::replaceSelectionWithString(string const & str)
1450 setCursorParUndo(bv());
1453 if (!selection.set()) { // create a dummy selection
1454 selection.end = cursor;
1455 selection.start = cursor;
1458 // Get font setting before we cut
1459 pos_type pos = selection.end.pos();
1460 LyXFont const font = selection.start.par()
1461 ->getFontSettings(bv()->buffer()->params,
1462 selection.start.pos());
1464 // Insert the new string
1465 for (string::const_iterator cit = str.begin(); cit != str.end(); ++cit) {
1466 selection.end.par()->insertChar(pos, (*cit), font);
1470 // Cut the selection
1471 cutSelection(true, false);
1477 // needed to insert the selection
1478 void LyXText::insertStringAsLines(string const & str)
1480 Paragraph * par = cursor.par();
1481 pos_type pos = cursor.pos();
1482 Paragraph * endpar = cursor.par()->next();
1484 setCursorParUndo(bv());
1486 // only to be sure, should not be neccessary
1489 bv()->buffer()->insertStringAsLines(par, pos, current_font, str);
1491 redoParagraphs(cursor, endpar);
1492 setCursor(cursor.par(), cursor.pos());
1493 selection.cursor = cursor;
1494 setCursor(par, pos);
1499 // turns double-CR to single CR, others where converted into one
1500 // blank. Then InsertStringAsLines is called
1501 void LyXText::insertStringAsParagraphs(string const & str)
1503 string linestr(str);
1504 bool newline_inserted = false;
1505 for (string::size_type i = 0; i < linestr.length(); ++i) {
1506 if (linestr[i] == '\n') {
1507 if (newline_inserted) {
1508 // we know that \r will be ignored by
1509 // InsertStringA. Of course, it is a dirty
1510 // trick, but it works...
1511 linestr[i - 1] = '\r';
1515 newline_inserted = true;
1517 } else if (IsPrintable(linestr[i])) {
1518 newline_inserted = false;
1521 insertStringAsLines(linestr);
1525 void LyXText::checkParagraph(Paragraph * par, pos_type pos)
1527 LyXCursor tmpcursor;
1531 RowList::iterator row = getRow(par, pos, y);
1532 RowList::iterator beg = rows().begin();
1534 // is there a break one row above
1536 && boost::prior(row)->par() == row->par()) {
1537 z = rowBreakPoint(*boost::prior(row));
1538 if (z >= row->pos()) {
1539 // set the dimensions of the row above
1540 y -= boost::prior(row)->height();
1543 breakAgain(boost::prior(row));
1545 // set the cursor again. Otherwise
1546 // dangling pointers are possible
1547 setCursor(cursor.par(), cursor.pos(),
1548 false, cursor.boundary());
1549 selection.cursor = cursor;
1554 int const tmpheight = row->height();
1555 pos_type const tmplast = lastPos(*this, row);
1558 if (row->height() == tmpheight && lastPos(*this, row) == tmplast) {
1559 postRowPaint(row, y);
1564 // check the special right address boxes
1565 if (par->layout()->margintype == MARGIN_RIGHT_ADDRESS_BOX) {
1572 redoDrawingOfParagraph(tmpcursor);
1575 // set the cursor again. Otherwise dangling pointers are possible
1576 // also set the selection
1578 if (selection.set()) {
1580 setCursorIntern(selection.cursor.par(), selection.cursor.pos(),
1581 false, selection.cursor.boundary());
1582 selection.cursor = cursor;
1583 setCursorIntern(selection.start.par(),
1584 selection.start.pos(),
1585 false, selection.start.boundary());
1586 selection.start = cursor;
1587 setCursorIntern(selection.end.par(),
1588 selection.end.pos(),
1589 false, selection.end.boundary());
1590 selection.end = cursor;
1591 setCursorIntern(last_sel_cursor.par(),
1592 last_sel_cursor.pos(),
1593 false, last_sel_cursor.boundary());
1594 last_sel_cursor = cursor;
1597 setCursorIntern(cursor.par(), cursor.pos(),
1598 false, cursor.boundary());
1602 // returns false if inset wasn't found
1603 bool LyXText::updateInset(Inset * inset)
1605 // first check the current paragraph
1606 int pos = cursor.par()->getPositionOfInset(inset);
1608 checkParagraph(cursor.par(), pos);
1612 // check every paragraph
1614 ParagraphList::iterator par = ownerParagraphs().begin();
1615 ParagraphList::iterator end = ownerParagraphs().end();
1618 pos = par->getPositionOfInset(inset);
1620 checkParagraph(&*par, pos);
1624 } while (par != end);
1630 bool LyXText::setCursor(Paragraph * par,
1632 bool setfont, bool boundary)
1634 LyXCursor old_cursor = cursor;
1635 setCursorIntern(par, pos, setfont, boundary);
1636 return deleteEmptyParagraphMechanism(old_cursor);
1640 void LyXText::setCursor(LyXCursor & cur, Paragraph * par,
1641 pos_type pos, bool boundary)
1647 cur.boundary(boundary);
1649 // get the cursor y position in text
1651 RowList::iterator row = getRow(par, pos, y);
1652 RowList::iterator beg = rows().begin();
1654 RowList::iterator old_row = row;
1656 // if we are before the first char of this row and are still in the
1657 // same paragraph and there is a previous row then put the cursor on
1658 // the end of the previous row
1659 cur.iy(y + row->baseline());
1661 if (row != beg && pos &&
1662 boost::prior(row)->par() == row->par() &&
1663 pos < par->size() &&
1664 par->getChar(pos) == Paragraph::META_INSET &&
1665 (ins = par->getInset(pos)) && (ins->needFullRow() || ins->display()))
1672 // y is now the beginning of the cursor row
1673 y += row->baseline();
1674 // y is now the cursor baseline
1677 pos_type last = lastPrintablePos(*this, old_row);
1679 // None of these should happen, but we're scaredy-cats
1680 if (pos > par->size()) {
1681 lyxerr << "dont like 1 please report" << endl;
1684 } else if (pos > last + 1) {
1685 lyxerr << "dont like 2 please report" << endl;
1686 // This shouldn't happen.
1689 } else if (pos < row->pos()) {
1690 lyxerr << "dont like 3 please report" << endl;
1695 // now get the cursors x position
1696 float x = getCursorX(row, pos, last, boundary);
1699 if (old_row != row) {
1700 x = getCursorX(old_row, pos, last, boundary);
1704 /* We take out this for the time being because 1) the redraw code is not
1705 prepared to this yet and 2) because some good policy has yet to be decided
1706 while editting: for instance how to act on rows being created/deleted
1710 //if the cursor is in a visible row, anchor to it
1712 if (topy < y && y < topy + bv()->workHeight())
1718 float LyXText::getCursorX(RowList::iterator rit,
1719 pos_type pos, pos_type last, bool boundary) const
1721 pos_type cursor_vpos = 0;
1723 float fill_separator;
1725 float fill_label_hfill;
1726 // This call HAS to be here because of the BidiTables!!!
1727 prepareToPrint(rit, x, fill_separator, fill_hfill,
1730 if (last < rit->pos())
1731 cursor_vpos = rit->pos();
1732 else if (pos > last && !boundary)
1733 cursor_vpos = (rit->par()->isRightToLeftPar(bv()->buffer()->params))
1734 ? rit->pos() : last + 1;
1735 else if (pos > rit->pos() &&
1736 (pos > last || boundary))
1737 /// Place cursor after char at (logical) position pos - 1
1738 cursor_vpos = (bidi_level(pos - 1) % 2 == 0)
1739 ? log2vis(pos - 1) + 1 : log2vis(pos - 1);
1741 /// Place cursor before char at (logical) position pos
1742 cursor_vpos = (bidi_level(pos) % 2 == 0)
1743 ? log2vis(pos) : log2vis(pos) + 1;
1745 pos_type body_pos = rit->par()->beginningOfBody();
1746 if ((body_pos > 0) &&
1747 ((body_pos - 1 > last) ||
1748 !rit->par()->isLineSeparator(body_pos - 1)))
1751 for (pos_type vpos = rit->pos(); vpos < cursor_vpos; ++vpos) {
1752 pos_type pos = vis2log(vpos);
1753 if (body_pos > 0 && pos == body_pos - 1) {
1754 x += fill_label_hfill +
1755 font_metrics::width(
1756 rit->par()->layout()->labelsep,
1757 getLabelFont(bv()->buffer(),
1759 if (rit->par()->isLineSeparator(body_pos - 1))
1760 x -= singleWidth(&*rit->par(), body_pos - 1);
1763 if (hfillExpansion(*this, rit, pos)) {
1764 x += singleWidth(&*rit->par(), pos);
1765 if (pos >= body_pos)
1768 x += fill_label_hfill;
1769 } else if (rit->par()->isSeparator(pos)) {
1770 x += singleWidth(&*rit->par(), pos);
1771 if (pos >= body_pos)
1772 x += fill_separator;
1774 x += singleWidth(&*rit->par(), pos);
1780 void LyXText::setCursorIntern(Paragraph * par,
1781 pos_type pos, bool setfont, bool boundary)
1783 InsetText * it = static_cast<InsetText *>(par->inInset());
1785 if (it != inset_owner) {
1786 lyxerr[Debug::INSETS] << "InsetText is " << it
1788 << "inset_owner is "
1789 << inset_owner << endl;
1790 #ifdef WITH_WARNINGS
1791 #warning I believe this code is wrong. (Lgb)
1792 #warning Jürgen, have a look at this. (Lgb)
1793 #warning Hmmm, I guess you are right but we
1794 #warning should verify when this is needed
1796 // Jürgen, would you like to have a look?
1797 // I guess we need to move the outer cursor
1798 // and open and lock the inset (bla bla bla)
1799 // stuff I don't know... so can you have a look?
1801 // I moved the lyxerr stuff in here so we can see if
1802 // this is actually really needed and where!
1804 // it->getLyXText(bv())->setCursorIntern(bv(), par, pos, setfont, boundary);
1809 setCursor(cursor, par, pos, boundary);
1815 void LyXText::setCurrentFont()
1817 pos_type pos = cursor.pos();
1818 if (cursor.boundary() && pos > 0)
1822 if (pos == cursor.par()->size())
1824 else // potentional bug... BUG (Lgb)
1825 if (cursor.par()->isSeparator(pos)) {
1826 if (pos > cursor.row()->pos() &&
1827 bidi_level(pos) % 2 ==
1828 bidi_level(pos - 1) % 2)
1830 else if (pos + 1 < cursor.par()->size())
1836 cursor.par()->getFontSettings(bv()->buffer()->params, pos);
1837 real_current_font = getFont(bv()->buffer(), cursor.par(), pos);
1839 if (cursor.pos() == cursor.par()->size() &&
1840 isBoundary(bv()->buffer(), cursor.par(), cursor.pos()) &&
1841 !cursor.boundary()) {
1842 Language const * lang =
1843 cursor.par()->getParLanguage(bv()->buffer()->params);
1844 current_font.setLanguage(lang);
1845 current_font.setNumber(LyXFont::OFF);
1846 real_current_font.setLanguage(lang);
1847 real_current_font.setNumber(LyXFont::OFF);
1852 // returns the column near the specified x-coordinate of the row
1853 // x is set to the real beginning of this column
1855 LyXText::getColumnNearX(RowList::iterator rit, int & x, bool & boundary) const
1858 float fill_separator;
1860 float fill_label_hfill;
1862 prepareToPrint(rit, tmpx, fill_separator,
1863 fill_hfill, fill_label_hfill);
1865 pos_type vc = rit->pos();
1866 pos_type last = lastPrintablePos(*this, rit);
1869 LyXLayout_ptr const & layout = rit->par()->layout();
1871 bool left_side = false;
1873 pos_type body_pos = rit->par()->beginningOfBody();
1874 float last_tmpx = tmpx;
1877 (body_pos - 1 > last ||
1878 !rit->par()->isLineSeparator(body_pos - 1)))
1881 // check for empty row
1882 if (!rit->par()->size()) {
1887 while (vc <= last && tmpx <= x) {
1890 if (body_pos > 0 && c == body_pos - 1) {
1891 tmpx += fill_label_hfill +
1892 font_metrics::width(layout->labelsep,
1893 getLabelFont(bv()->buffer(), &*rit->par()));
1894 if (rit->par()->isLineSeparator(body_pos - 1))
1895 tmpx -= singleWidth(&*rit->par(), body_pos - 1);
1898 if (hfillExpansion(*this, rit, c)) {
1899 tmpx += singleWidth(&*rit->par(), c);
1903 tmpx += fill_label_hfill;
1904 } else if (rit->par()->isSeparator(c)) {
1905 tmpx += singleWidth(&*rit->par(), c);
1907 tmpx+= fill_separator;
1909 tmpx += singleWidth(&*rit->par(), c);
1914 if ((tmpx + last_tmpx) / 2 > x) {
1919 if (vc > last + 1) // This shouldn't happen.
1923 // This (rtl_support test) is not needed, but gives
1924 // some speedup if rtl_support=false
1925 bool const lastrow = lyxrc.rtl_support &&
1926 (boost::next(rit) == rowlist_.end() ||
1927 boost::next(rit)->par() != rit->par());
1928 // If lastrow is false, we don't need to compute
1929 // the value of rtl.
1930 bool const rtl = (lastrow)
1931 ? rit->par()->isRightToLeftPar(bv()->buffer()->params)
1934 ((rtl && left_side && vc == rit->pos() && x < tmpx - 5) ||
1935 (!rtl && !left_side && vc == last + 1 && x > tmpx + 5)))
1937 else if (vc == rit->pos()) {
1939 if (bidi_level(c) % 2 == 1)
1942 c = vis2log(vc - 1);
1943 bool const rtl = (bidi_level(c) % 2 == 1);
1944 if (left_side == rtl) {
1946 boundary = isBoundary(bv()->buffer(), &*rit->par(), c);
1950 if (rit->pos() <= last && c > last
1951 && rit->par()->isNewline(last)) {
1952 if (bidi_level(last) % 2 == 0)
1953 tmpx -= singleWidth(&*rit->par(), last);
1955 tmpx += singleWidth(&*rit->par(), last);
1965 void LyXText::setCursorFromCoordinates(int x, int y)
1967 LyXCursor old_cursor = cursor;
1969 setCursorFromCoordinates(cursor, x, y);
1971 deleteEmptyParagraphMechanism(old_cursor);
1978 * return true if the cursor given is at the end of a row,
1979 * and the next row is filled by an inset that spans an entire
1982 bool beforeFullRowInset(LyXText & lt, RowList::iterator row,
1984 if (boost::next(row) == lt.rows().end())
1986 Row const & next = *boost::next(row);
1988 if (next.pos() != cur.pos() || next.par() != cur.par())
1990 if (!cur.par()->isInset(cur.pos()))
1992 Inset const * inset = cur.par()->getInset(cur.pos());
1993 if (inset->needFullRow() || inset->display())
2000 void LyXText::setCursorFromCoordinates(LyXCursor & cur, int x, int y)
2002 // Get the row first.
2004 RowList::iterator row = getRowNearY(y);
2006 pos_type const column = getColumnNearX(row, x, bound);
2007 cur.par(&*row->par());
2008 cur.pos(row->pos() + column);
2010 cur.y(y + row->baseline());
2013 if (beforeFullRowInset(*this, row, cur)) {
2014 pos_type last = lastPrintablePos(*this, row);
2015 float x = getCursorX(boost::next(row), cur.pos(), last, bound);
2017 cur.iy(y + row->height() + boost::next(row)->baseline());
2018 cur.irow(boost::next(row));
2024 cur.boundary(bound);
2028 void LyXText::cursorLeft(bool internal)
2030 if (cursor.pos() > 0) {
2031 bool boundary = cursor.boundary();
2032 setCursor(cursor.par(), cursor.pos() - 1, true, false);
2033 if (!internal && !boundary &&
2034 isBoundary(bv()->buffer(), cursor.par(), cursor.pos() + 1))
2035 setCursor(cursor.par(), cursor.pos() + 1, true, true);
2036 } else if (cursor.par()->previous()) { // steps into the above paragraph.
2037 Paragraph * par = cursor.par()->previous();
2038 setCursor(par, par->size());
2043 void LyXText::cursorRight(bool internal)
2045 if (!internal && cursor.boundary() &&
2046 !cursor.par()->isNewline(cursor.pos()))
2047 setCursor(cursor.par(), cursor.pos(), true, false);
2048 else if (cursor.pos() < cursor.par()->size()) {
2049 setCursor(cursor.par(), cursor.pos() + 1, true, false);
2051 isBoundary(bv()->buffer(), cursor.par(), cursor.pos()))
2052 setCursor(cursor.par(), cursor.pos(), true, true);
2053 } else if (cursor.par()->next())
2054 setCursor(cursor.par()->next(), 0);
2058 void LyXText::cursorUp(bool selecting)
2061 int x = cursor.x_fix();
2062 int y = cursor.y() - cursor.row()->baseline() - 1;
2063 setCursorFromCoordinates(x, y);
2066 int y1 = cursor.iy() - topy;
2069 Inset * inset_hit = checkInsetHit(x, y1);
2070 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2071 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2075 setCursorFromCoordinates(bv(), cursor.x_fix(),
2076 cursor.y() - cursor.row()->baseline() - 1);
2081 void LyXText::cursorDown(bool selecting)
2084 int x = cursor.x_fix();
2085 int y = cursor.y() - cursor.row()->baseline() +
2086 cursor.row()->height() + 1;
2087 setCursorFromCoordinates(x, y);
2088 if (!selecting && cursor.row() == cursor.irow()) {
2090 int y1 = cursor.iy() - topy;
2093 Inset * inset_hit = checkInsetHit(x, y1);
2094 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2095 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2099 setCursorFromCoordinates(bv(), cursor.x_fix(),
2100 cursor.y() - cursor.row()->baseline()
2101 + cursor.row()->height() + 1);
2106 void LyXText::cursorUpParagraph()
2108 if (cursor.pos() > 0) {
2109 setCursor(cursor.par(), 0);
2111 else if (cursor.par()->previous()) {
2112 setCursor(cursor.par()->previous(), 0);
2117 void LyXText::cursorDownParagraph()
2119 if (cursor.par()->next()) {
2120 setCursor(cursor.par()->next(), 0);
2122 setCursor(cursor.par(), cursor.par()->size());
2126 // fix the cursor `cur' after a characters has been deleted at `where'
2127 // position. Called by deleteEmptyParagraphMechanism
2128 void LyXText::fixCursorAfterDelete(LyXCursor & cur,
2129 LyXCursor const & where)
2131 // if cursor is not in the paragraph where the delete occured,
2133 if (cur.par() != where.par())
2136 // if cursor position is after the place where the delete occured,
2138 if (cur.pos() > where.pos())
2139 cur.pos(cur.pos()-1);
2141 // check also if we don't want to set the cursor on a spot behind the
2142 // pagragraph because we erased the last character.
2143 if (cur.pos() > cur.par()->size())
2144 cur.pos(cur.par()->size());
2146 // recompute row et al. for this cursor
2147 setCursor(cur, cur.par(), cur.pos(), cur.boundary());
2151 bool LyXText::deleteEmptyParagraphMechanism(LyXCursor const & old_cursor)
2153 // Would be wrong to delete anything if we have a selection.
2154 if (selection.set())
2157 // We allow all kinds of "mumbo-jumbo" when freespacing.
2158 if (old_cursor.par()->layout()->free_spacing
2159 || old_cursor.par()->isFreeSpacing()) {
2163 /* Ok I'll put some comments here about what is missing.
2164 I have fixed BackSpace (and thus Delete) to not delete
2165 double-spaces automagically. I have also changed Cut,
2166 Copy and Paste to hopefully do some sensible things.
2167 There are still some small problems that can lead to
2168 double spaces stored in the document file or space at
2169 the beginning of paragraphs. This happens if you have
2170 the cursor betwenn to spaces and then save. Or if you
2171 cut and paste and the selection have a space at the
2172 beginning and then save right after the paste. I am
2173 sure none of these are very hard to fix, but I will
2174 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
2175 that I can get some feedback. (Lgb)
2178 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
2179 // delete the LineSeparator.
2182 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
2183 // delete the LineSeparator.
2186 // If the pos around the old_cursor were spaces, delete one of them.
2187 if (old_cursor.par() != cursor.par()
2188 || old_cursor.pos() != cursor.pos()) {
2189 // Only if the cursor has really moved
2191 if (old_cursor.pos() > 0
2192 && old_cursor.pos() < old_cursor.par()->size()
2193 && old_cursor.par()->isLineSeparator(old_cursor.pos())
2194 && old_cursor.par()->isLineSeparator(old_cursor.pos() - 1)) {
2195 old_cursor.par()->erase(old_cursor.pos() - 1);
2196 redoParagraphs(old_cursor, old_cursor.par()->next());
2198 #ifdef WITH_WARNINGS
2199 #warning This will not work anymore when we have multiple views of the same buffer
2200 // In this case, we will have to correct also the cursors held by
2201 // other bufferviews. It will probably be easier to do that in a more
2202 // automated way in LyXCursor code. (JMarc 26/09/2001)
2204 // correct all cursors held by the LyXText
2205 fixCursorAfterDelete(cursor, old_cursor);
2206 fixCursorAfterDelete(selection.cursor,
2208 fixCursorAfterDelete(selection.start,
2210 fixCursorAfterDelete(selection.end, old_cursor);
2211 fixCursorAfterDelete(last_sel_cursor,
2213 fixCursorAfterDelete(toggle_cursor, old_cursor);
2214 fixCursorAfterDelete(toggle_end_cursor,
2220 // don't delete anything if this is the ONLY paragraph!
2221 if (!old_cursor.par()->next() && !old_cursor.par()->previous())
2224 // Do not delete empty paragraphs with keepempty set.
2225 if (old_cursor.par()->layout()->keepempty)
2228 // only do our magic if we changed paragraph
2229 if (old_cursor.par() == cursor.par())
2232 // record if we have deleted a paragraph
2233 // we can't possibly have deleted a paragraph before this point
2234 bool deleted = false;
2236 if ((old_cursor.par()->empty()
2237 || (old_cursor.par()->size() == 1
2238 && old_cursor.par()->isLineSeparator(0)))) {
2239 // ok, we will delete anything
2240 LyXCursor tmpcursor;
2244 if (old_cursor.row() != rows().begin()) {
2246 prevrow = boost::prior(old_cursor.row());
2247 const_cast<LyXText *>(this)->postPaint(old_cursor.y() - old_cursor.row()->baseline() - prevrow->height());
2249 cursor = old_cursor; // that undo can restore the right cursor position
2250 Paragraph * endpar = old_cursor.par()->next();
2251 if (endpar && endpar->getDepth()) {
2252 while (endpar && endpar->getDepth()) {
2253 endpar = endpar->next();
2256 setUndo(bv(), Undo::DELETE, old_cursor.par(), endpar);
2260 removeRow(old_cursor.row());
2261 if (ownerParagraphs().begin() == old_cursor.par()) {
2262 ownerParagraph(&*boost::next(ownerParagraphs().begin()));
2265 delete old_cursor.par();
2267 /* Breakagain the next par. Needed because of
2268 * the parindent that can occur or dissappear.
2269 * The next row can change its height, if
2270 * there is another layout before */
2271 if (boost::next(prevrow) != rows().end()) {
2272 breakAgain(boost::next(prevrow));
2275 setHeightOfRow(prevrow);
2277 RowList::iterator nextrow = boost::next(old_cursor.row());
2278 const_cast<LyXText *>(this)->postPaint(
2279 old_cursor.y() - old_cursor.row()->baseline());
2282 cursor = old_cursor; // that undo can restore the right cursor position
2283 Paragraph * endpar = old_cursor.par()->next();
2284 if (endpar && endpar->getDepth()) {
2285 while (endpar && endpar->getDepth()) {
2286 endpar = endpar->next();
2289 setUndo(bv(), Undo::DELETE, old_cursor.par(), endpar);
2293 removeRow(old_cursor.row());
2295 if (ownerParagraphs().begin() == old_cursor.par()) {
2296 ownerParagraph(&*boost::next(ownerParagraphs().begin()));
2299 delete old_cursor.par();
2301 /* Breakagain the next par. Needed because of
2302 the parindent that can occur or dissappear.
2303 The next row can change its height, if
2304 there is another layout before */
2305 if (nextrow != rows().end()) {
2306 breakAgain(nextrow);
2312 setCursorIntern(cursor.par(), cursor.pos());
2314 if (selection.cursor.par() == old_cursor.par()
2315 && selection.cursor.pos() == old_cursor.pos()) {
2316 // correct selection
2317 selection.cursor = cursor;
2321 if (old_cursor.par()->stripLeadingSpaces()) {
2322 redoParagraphs(old_cursor,
2323 old_cursor.par()->next());
2325 setCursorIntern(cursor.par(), cursor.pos());
2326 selection.cursor = cursor;
2333 ParagraphList & LyXText::ownerParagraphs() const
2336 return inset_owner->paragraphs;
2338 return bv_owner->buffer()->paragraphs;
2342 void LyXText::ownerParagraph(Paragraph * p) const
2345 inset_owner->paragraph(p);
2347 bv_owner->buffer()->paragraphs.set(p);
2352 void LyXText::ownerParagraph(int id, Paragraph * p) const
2354 Paragraph * op = bv_owner->buffer()->getParFromID(id);
2355 if (op && op->inInset()) {
2356 static_cast<InsetText *>(op->inInset())->paragraph(p);
2363 LyXText::refresh_status LyXText::refreshStatus() const
2365 return refresh_status_;
2369 void LyXText::clearPaint()
2371 refresh_status_ = REFRESH_NONE;
2372 refresh_row = rows().end();
2377 void LyXText::postPaint(int start_y)
2379 refresh_status old = refresh_status_;
2381 refresh_status_ = REFRESH_AREA;
2382 refresh_row = rows().end();
2384 if (old != REFRESH_NONE && refresh_y < start_y)
2387 refresh_y = start_y;
2392 // We are an inset's lyxtext. Tell the top-level lyxtext
2393 // it needs to update the row we're in.
2394 LyXText * t = bv()->text;
2395 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2399 // FIXME: we should probably remove this y parameter,
2400 // make refresh_y be 0, and use row->y etc.
2401 void LyXText::postRowPaint(RowList::iterator rit, int start_y)
2403 if (refresh_status_ != REFRESH_NONE && refresh_y < start_y) {
2404 refresh_status_ = REFRESH_AREA;
2407 refresh_y = start_y;
2410 if (refresh_status_ == REFRESH_AREA)
2413 refresh_status_ = REFRESH_ROW;
2419 // We are an inset's lyxtext. Tell the top-level lyxtext
2420 // it needs to update the row we're in.
2421 LyXText * t = bv()->text;
2422 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2426 bool LyXText::isInInset() const
2428 // Sub-level has non-null bv owner and
2429 // non-null inset owner.
2430 return inset_owner != 0 && bv_owner != 0;
2434 int defaultRowHeight()
2436 LyXFont const font(LyXFont::ALL_SANE);
2437 return int(font_metrics::maxAscent(font)
2438 + font_metrics::maxDescent(font) * 1.5);