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/inseterror.h"
35 #include "insets/insetbibitem.h"
36 #include "insets/insetspecialchar.h"
37 #include "insets/insettext.h"
38 #include "insets/insetfloat.h"
39 #include "insets/insetwrap.h"
41 #include "support/LAssert.h"
42 #include "support/textutils.h"
43 #include "support/lstrings.h"
45 #include "support/BoostFormat.h"
55 LyXText::LyXText(BufferView * bv)
56 : height(0), width(0), anchor_row_offset_(0),
57 inset_owner(0), the_locking_inset(0), bv_owner(bv)
59 anchor_row_ = rows().end();
60 need_break_row = rows().end();
61 refresh_row = rows().end();
67 LyXText::LyXText(BufferView * bv, InsetText * inset)
68 : height(0), width(0), anchor_row_offset_(0),
69 inset_owner(inset), the_locking_inset(0), bv_owner(bv)
71 anchor_row_ = rows().end();
72 need_break_row = rows().end();
73 refresh_row = rows().end();
79 void LyXText::init(BufferView * bview, bool reinit)
83 need_break_row = rows().end();
85 copylayouttype.erase();
88 } else if (!rowlist_.empty())
91 ParagraphList::iterator par = ownerParagraphs().begin();
92 ParagraphList::iterator end = ownerParagraphs().end();
94 current_font = getFont(bview->buffer(), &*par, 0);
96 for (; par != end; ++par) {
97 insertParagraph(&*par, rowlist_.end());
99 setCursorIntern(&*rowlist_.begin()->par(), 0);
100 selection.cursor = cursor;
108 LyXFont const realizeFont(LyXFont const & font,
112 LyXTextClass const & tclass = buf->params.getLyXTextClass();
113 LyXFont tmpfont(font);
114 Paragraph::depth_type par_depth = par->getDepth();
116 // Resolve against environment font information
117 while (par && par_depth && !tmpfont.resolved()) {
118 par = par->outerHook();
120 tmpfont.realize(par->layout()->font);
121 par_depth = par->getDepth();
125 tmpfont.realize(tclass.defaultfont());
133 // Gets the fully instantiated font at a given position in a paragraph
134 // Basically the same routine as Paragraph::getFont() in paragraph.C.
135 // The difference is that this one is used for displaying, and thus we
136 // are allowed to make cosmetic improvements. For instance make footnotes
138 // If position is -1, we get the layout font of the paragraph.
139 // If position is -2, we get the font of the manual label of the paragraph.
140 LyXFont const LyXText::getFont(Buffer const * buf, Paragraph * par,
143 lyx::Assert(pos >= 0);
145 LyXLayout_ptr const & layout = par->layout();
147 // We specialize the 95% common case:
148 if (!par->getDepth()) {
149 if (layout->labeltype == LABEL_MANUAL
150 && pos < par->beginningOfBody()) {
152 LyXFont f = par->getFontSettings(buf->params, pos);
154 par->inInset()->getDrawFont(f);
155 return f.realize(layout->reslabelfont);
157 LyXFont f = par->getFontSettings(buf->params, pos);
159 par->inInset()->getDrawFont(f);
160 return f.realize(layout->resfont);
164 // The uncommon case need not be optimized as much
168 if (pos < par->beginningOfBody()) {
170 layoutfont = layout->labelfont;
173 layoutfont = layout->font;
176 LyXFont tmpfont = par->getFontSettings(buf->params, pos);
177 tmpfont.realize(layoutfont);
180 par->inInset()->getDrawFont(tmpfont);
182 return realizeFont(tmpfont, buf, par);
186 LyXFont const LyXText::getLayoutFont(Buffer const * buf, Paragraph * par) const
188 LyXLayout_ptr const & layout = par->layout();
190 if (!par->getDepth()) {
191 return layout->resfont;
194 return realizeFont(layout->font, buf, par);
198 LyXFont const LyXText::getLabelFont(Buffer const * buf, Paragraph * par) const
200 LyXLayout_ptr const & layout = par->layout();
202 if (!par->getDepth()) {
203 return layout->reslabelfont;
206 return realizeFont(layout->labelfont, buf, par);
210 void LyXText::setCharFont(Paragraph * par,
211 pos_type pos, LyXFont const & fnt,
214 Buffer const * buf = bv()->buffer();
215 LyXFont font = getFont(buf, par, pos);
216 font.update(fnt, buf->params.language, toggleall);
217 // Let the insets convert their font
218 if (par->isInset(pos)) {
219 Inset * inset = par->getInset(pos);
220 if (isEditableInset(inset)) {
221 UpdatableInset * uinset =
222 static_cast<UpdatableInset *>(inset);
223 uinset->setFont(bv(), fnt, toggleall, true);
227 // Plug thru to version below:
228 setCharFont(buf, par, pos, font);
232 void LyXText::setCharFont(Buffer const * buf, Paragraph * par,
233 pos_type pos, LyXFont const & fnt)
237 LyXTextClass const & tclass = buf->params.getLyXTextClass();
238 LyXLayout_ptr const & layout = par->layout();
240 // Get concrete layout font to reduce against
243 if (pos < par->beginningOfBody())
244 layoutfont = layout->labelfont;
246 layoutfont = layout->font;
248 // Realize against environment font information
249 if (par->getDepth()) {
250 Paragraph * tp = par;
251 while (!layoutfont.resolved() && tp && tp->getDepth()) {
252 tp = tp->outerHook();
254 layoutfont.realize(tp->layout()->font);
258 layoutfont.realize(tclass.defaultfont());
260 // Now, reduce font against full layout font
261 font.reduce(layoutfont);
263 par->setFont(pos, font);
267 // removes the row and reset the touched counters
268 void LyXText::removeRow(RowList::iterator rit)
270 /* FIXME: when we cache the bview, this should just
271 * become a postPaint(), I think */
272 if (refresh_row == rit) {
273 if (rit == rows().begin())
274 refresh_row = boost::next(rit);
276 refresh_row = boost::prior(rit);
278 // what about refresh_y
281 if (anchor_row_ == rit) {
282 if (rit != rows().begin()) {
283 anchor_row_ = boost::prior(rit);
284 anchor_row_offset_ += boost::prior(rit)->height();
286 anchor_row_ = boost::next(rit);
287 anchor_row_offset_ -= rit->height();
291 // the text becomes smaller
292 height -= rit->height();
298 // remove all following rows of the paragraph of the specified row.
299 void LyXText::removeParagraph(RowList::iterator rit)
301 ParagraphList::iterator tmppit = rit->par();
304 while (rit != rows().end() && rit->par() == tmppit) {
305 RowList::iterator tmprit = boost::next(rit);
312 #warning FIXME Convert this to ParagraphList::iterator
313 void LyXText::insertParagraph(Paragraph * par, RowList::iterator rowit)
315 // insert a new row, starting at position 0
317 RowList::iterator rit = rowlist_.insert(rowit, newrow);
319 // and now append the whole paragraph before the new row
320 appendParagraph(rit);
324 Inset * LyXText::getInset() const
326 if (cursor.pos() < cursor.par()->size()
327 && cursor.par()->isInset(cursor.pos())) {
328 return cursor.par()->getInset(cursor.pos());
334 void LyXText::toggleInset()
336 Inset * inset = getInset();
337 // is there an editable inset at cursor position?
338 if (!isEditableInset(inset)) {
339 // No, try to see if we are inside a collapsable inset
340 if (inset_owner && inset_owner->owner()
341 && inset_owner->owner()->isOpen()) {
342 bv()->unlockInset(static_cast<UpdatableInset *>(inset_owner->owner()));
343 inset_owner->owner()->close(bv());
344 bv()->getLyXText()->cursorRight(bv());
348 //bv()->owner()->message(inset->editMessage());
350 // do we want to keep this?? (JMarc)
351 if (!isHighlyEditableInset(inset))
352 setCursorParUndo(bv());
354 if (inset->isOpen()) {
360 bv()->updateInset(inset);
364 /* used in setlayout */
365 // Asger is not sure we want to do this...
366 void LyXText::makeFontEntriesLayoutSpecific(Buffer const & buf,
369 LyXLayout_ptr const & layout = par.layout();
372 for (pos_type pos = 0; pos < par.size(); ++pos) {
373 if (pos < par.beginningOfBody())
374 layoutfont = layout->labelfont;
376 layoutfont = layout->font;
378 LyXFont tmpfont = par.getFontSettings(buf.params, pos);
379 tmpfont.reduce(layoutfont);
380 par.setFont(pos, tmpfont);
385 Paragraph * LyXText::setLayout(LyXCursor & cur, LyXCursor & sstart_cur,
386 LyXCursor & send_cur,
387 string const & layout)
389 Paragraph * endpar = send_cur.par()->next();
390 Paragraph * undoendpar = endpar;
392 if (endpar && endpar->getDepth()) {
393 while (endpar && endpar->getDepth()) {
394 endpar = endpar->next();
398 endpar = endpar->next(); // because of parindents etc.
401 setUndo(bv(), Undo::EDIT, sstart_cur.par(), undoendpar);
403 // ok we have a selection. This is always between sstart_cur
404 // and sel_end cursor
406 Paragraph * par = sstart_cur.par();
407 Paragraph * epar = send_cur.par()->next();
409 LyXLayout_ptr const & lyxlayout =
410 bv()->buffer()->params.getLyXTextClass()[layout];
413 par->applyLayout(lyxlayout);
414 makeFontEntriesLayoutSpecific(*bv()->buffer(), *par);
415 Paragraph * fppar = par;
416 fppar->params().spaceTop(lyxlayout->fill_top ?
417 VSpace(VSpace::VFILL)
418 : VSpace(VSpace::NONE));
419 fppar->params().spaceBottom(lyxlayout->fill_bottom ?
420 VSpace(VSpace::VFILL)
421 : VSpace(VSpace::NONE));
422 if (lyxlayout->margintype == MARGIN_MANUAL)
423 par->setLabelWidthString(lyxlayout->labelstring());
426 } while (par != epar);
432 // set layout over selection and make a total rebreak of those paragraphs
433 void LyXText::setLayout(string const & layout)
435 LyXCursor tmpcursor = cursor; /* store the current cursor */
437 // if there is no selection just set the layout
438 // of the current paragraph */
439 if (!selection.set()) {
440 selection.start = cursor; // dummy selection
441 selection.end = cursor;
443 Paragraph * endpar = setLayout(cursor, selection.start,
444 selection.end, layout);
445 redoParagraphs(selection.start, endpar);
447 // we have to reset the selection, because the
448 // geometry could have changed
449 setCursor(selection.start.par(),
450 selection.start.pos(), false);
451 selection.cursor = cursor;
452 setCursor(selection.end.par(), selection.end.pos(), false);
456 setCursor(tmpcursor.par(), tmpcursor.pos(), true);
460 bool LyXText::changeDepth(bv_funcs::DEPTH_CHANGE type, bool test_only)
462 ParagraphList::iterator pit(cursor.par());
463 ParagraphList::iterator end(cursor.par());
464 ParagraphList::iterator start = pit;
466 if (selection.set()) {
467 pit = selection.start.par();
468 end = selection.end.par();
472 ParagraphList::iterator pastend = end;
474 setUndo(bv(), Undo::EDIT, &(*start), &(*pastend));
476 bool changed = false;
478 int prev_after_depth = 0;
479 #warning parlist ... could be nicer ?
480 if (start != ownerParagraphs().begin())
481 prev_after_depth = boost::prior(start)->getMaxDepthAfter();
484 int const depth = pit->params().depth();
485 if (type == bv_funcs::INC_DEPTH) {
486 if (depth < prev_after_depth
487 && pit->layout()->labeltype != LABEL_BIBLIO) {
490 pit->params().depth(depth + 1);
495 pit->params().depth(depth - 1);
498 prev_after_depth = pit->getMaxDepthAfter();
509 // Wow, redoParagraphs is stupid.
511 setCursor(tmpcursor, &(*start), 0);
512 redoParagraphs(tmpcursor, &(*pastend));
514 // We need to actually move the text->cursor. I don't
515 // understand why ...
518 // we have to reset the visual selection because the
519 // geometry could have changed
520 if (selection.set()) {
521 setCursor(selection.start.par(), selection.start.pos());
522 selection.cursor = cursor;
523 setCursor(selection.end.par(), selection.end.pos());
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();
1244 // setCounter can potentially change the labelString.
1245 setCounter(bv()->buffer(), &*pit);
1247 string const & newLabel = pit->params().labelString();
1249 if (oldLabel.empty() && !newLabel.empty()) {
1250 removeParagraph(rowit);
1251 appendParagraph(rowit);
1259 void LyXText::insertInset(Inset * inset)
1261 if (!cursor.par()->insetAllowed(inset->lyxCode()))
1263 setUndo(bv(), Undo::FINISH, cursor.par(), cursor.par()->next());
1265 cursor.par()->insertInset(cursor.pos(), inset);
1266 // Just to rebreak and refresh correctly.
1267 // The character will not be inserted a second time
1268 insertChar(Paragraph::META_INSET);
1269 // If we enter a highly editable inset the cursor should be to before
1270 // the inset. This couldn't happen before as Undo was not handled inside
1271 // inset now after the Undo LyX tries to call inset->Edit(...) again
1272 // and cannot do this as the cursor is behind the inset and GetInset
1273 // does not return the inset!
1274 if (isHighlyEditableInset(inset)) {
1281 void LyXText::copyEnvironmentType()
1283 copylayouttype = cursor.par()->layout()->name();
1287 void LyXText::pasteEnvironmentType()
1289 // do nothing if there has been no previous copyEnvironmentType()
1290 if (!copylayouttype.empty())
1291 setLayout(copylayouttype);
1295 void LyXText::cutSelection(bool doclear, bool realcut)
1297 // Stuff what we got on the clipboard. Even if there is no selection.
1299 // There is a problem with having the stuffing here in that the
1300 // larger the selection the slower LyX will get. This can be
1301 // solved by running the line below only when the selection has
1302 // finished. The solution used currently just works, to make it
1303 // faster we need to be more clever and probably also have more
1304 // calls to stuffClipboard. (Lgb)
1305 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1307 // This doesn't make sense, if there is no selection
1308 if (!selection.set())
1311 // OK, we have a selection. This is always between selection.start
1312 // and selection.end
1314 // make sure that the depth behind the selection are restored, too
1315 Paragraph * endpar = selection.end.par()->next();
1316 Paragraph * undoendpar = endpar;
1318 if (endpar && endpar->getDepth()) {
1319 while (endpar && endpar->getDepth()) {
1320 endpar = endpar->next();
1321 undoendpar = endpar;
1323 } else if (endpar) {
1324 endpar = endpar->next(); // because of parindents etc.
1327 setUndo(bv(), Undo::DELETE,
1328 selection.start.par(), undoendpar);
1330 // there are two cases: cut only within one paragraph or
1331 // more than one paragraph
1332 if (selection.start.par() == selection.end.par()) {
1333 // only within one paragraph
1334 endpar = selection.end.par();
1335 int pos = selection.end.pos();
1336 CutAndPaste::cutSelection(selection.start.par(), &endpar,
1337 selection.start.pos(), pos,
1338 bv()->buffer()->params.textclass,
1340 selection.end.pos(pos);
1342 endpar = selection.end.par();
1343 int pos = selection.end.pos();
1344 CutAndPaste::cutSelection(selection.start.par(), &endpar,
1345 selection.start.pos(), pos,
1346 bv()->buffer()->params.textclass,
1349 selection.end.par(endpar);
1350 selection.end.pos(pos);
1351 cursor.pos(selection.end.pos());
1353 endpar = endpar->next();
1355 // sometimes necessary
1357 selection.start.par()->stripLeadingSpaces();
1359 redoParagraphs(selection.start, endpar);
1361 // cutSelection can invalidate the cursor so we need to set
1363 // we prefer the end for when tracking changes
1364 cursor = selection.end;
1366 // need a valid cursor. (Lgb)
1369 setCursor(cursor.par(), cursor.pos());
1370 selection.cursor = cursor;
1375 void LyXText::copySelection()
1377 // stuff the selection onto the X clipboard, from an explicit copy request
1378 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1380 // this doesnt make sense, if there is no selection
1381 if (!selection.set())
1384 // ok we have a selection. This is always between selection.start
1385 // and sel_end cursor
1387 // copy behind a space if there is one
1388 while (selection.start.par()->size() > selection.start.pos()
1389 && selection.start.par()->isLineSeparator(selection.start.pos())
1390 && (selection.start.par() != selection.end.par()
1391 || selection.start.pos() < selection.end.pos()))
1392 selection.start.pos(selection.start.pos() + 1);
1394 CutAndPaste::copySelection(selection.start.par(), selection.end.par(),
1395 selection.start.pos(), selection.end.pos(),
1396 bv()->buffer()->params.textclass);
1400 void LyXText::pasteSelection()
1402 // this does not make sense, if there is nothing to paste
1403 if (!CutAndPaste::checkPastePossible())
1406 setUndo(bv(), Undo::INSERT,
1407 cursor.par(), cursor.par()->next());
1410 Paragraph * actpar = cursor.par();
1411 int pos = cursor.pos();
1413 CutAndPaste::pasteSelection(&actpar, &endpar, pos,
1414 bv()->buffer()->params.textclass);
1416 redoParagraphs(cursor, endpar);
1418 setCursor(cursor.par(), cursor.pos());
1421 selection.cursor = cursor;
1422 setCursor(actpar, pos);
1428 void LyXText::setSelectionRange(lyx::pos_type length)
1433 selection.cursor = cursor;
1440 // simple replacing. The font of the first selected character is used
1441 void LyXText::replaceSelectionWithString(string const & str)
1443 setCursorParUndo(bv());
1446 if (!selection.set()) { // create a dummy selection
1447 selection.end = cursor;
1448 selection.start = cursor;
1451 // Get font setting before we cut
1452 pos_type pos = selection.end.pos();
1453 LyXFont const font = selection.start.par()
1454 ->getFontSettings(bv()->buffer()->params,
1455 selection.start.pos());
1457 // Insert the new string
1458 for (string::const_iterator cit = str.begin(); cit != str.end(); ++cit) {
1459 selection.end.par()->insertChar(pos, (*cit), font);
1463 // Cut the selection
1464 cutSelection(true, false);
1470 // needed to insert the selection
1471 void LyXText::insertStringAsLines(string const & str)
1473 Paragraph * par = cursor.par();
1474 pos_type pos = cursor.pos();
1475 Paragraph * endpar = cursor.par()->next();
1477 setCursorParUndo(bv());
1479 // only to be sure, should not be neccessary
1482 bv()->buffer()->insertStringAsLines(par, pos, current_font, str);
1484 redoParagraphs(cursor, endpar);
1485 setCursor(cursor.par(), cursor.pos());
1486 selection.cursor = cursor;
1487 setCursor(par, pos);
1492 // turns double-CR to single CR, others where converted into one
1493 // blank. Then InsertStringAsLines is called
1494 void LyXText::insertStringAsParagraphs(string const & str)
1496 string linestr(str);
1497 bool newline_inserted = false;
1498 for (string::size_type i = 0; i < linestr.length(); ++i) {
1499 if (linestr[i] == '\n') {
1500 if (newline_inserted) {
1501 // we know that \r will be ignored by
1502 // InsertStringA. Of course, it is a dirty
1503 // trick, but it works...
1504 linestr[i - 1] = '\r';
1508 newline_inserted = true;
1510 } else if (IsPrintable(linestr[i])) {
1511 newline_inserted = false;
1514 insertStringAsLines(linestr);
1518 void LyXText::checkParagraph(Paragraph * par, pos_type pos)
1520 LyXCursor tmpcursor;
1524 RowList::iterator row = getRow(par, pos, y);
1525 RowList::iterator beg = rows().begin();
1527 // is there a break one row above
1529 && boost::prior(row)->par() == row->par()) {
1530 z = rowBreakPoint(*boost::prior(row));
1531 if (z >= row->pos()) {
1532 // set the dimensions of the row above
1533 y -= boost::prior(row)->height();
1536 breakAgain(boost::prior(row));
1538 // set the cursor again. Otherwise
1539 // dangling pointers are possible
1540 setCursor(cursor.par(), cursor.pos(),
1541 false, cursor.boundary());
1542 selection.cursor = cursor;
1547 int const tmpheight = row->height();
1548 pos_type const tmplast = lastPos(*this, row);
1551 if (row->height() == tmpheight && lastPos(*this, row) == tmplast) {
1552 postRowPaint(row, y);
1557 // check the special right address boxes
1558 if (par->layout()->margintype == MARGIN_RIGHT_ADDRESS_BOX) {
1565 redoDrawingOfParagraph(tmpcursor);
1568 // set the cursor again. Otherwise dangling pointers are possible
1569 // also set the selection
1571 if (selection.set()) {
1573 setCursorIntern(selection.cursor.par(), selection.cursor.pos(),
1574 false, selection.cursor.boundary());
1575 selection.cursor = cursor;
1576 setCursorIntern(selection.start.par(),
1577 selection.start.pos(),
1578 false, selection.start.boundary());
1579 selection.start = cursor;
1580 setCursorIntern(selection.end.par(),
1581 selection.end.pos(),
1582 false, selection.end.boundary());
1583 selection.end = cursor;
1584 setCursorIntern(last_sel_cursor.par(),
1585 last_sel_cursor.pos(),
1586 false, last_sel_cursor.boundary());
1587 last_sel_cursor = cursor;
1590 setCursorIntern(cursor.par(), cursor.pos(),
1591 false, cursor.boundary());
1595 // returns false if inset wasn't found
1596 bool LyXText::updateInset(Inset * inset)
1598 // first check the current paragraph
1599 int pos = cursor.par()->getPositionOfInset(inset);
1601 checkParagraph(cursor.par(), pos);
1605 // check every paragraph
1607 ParagraphList::iterator par = ownerParagraphs().begin();
1608 ParagraphList::iterator end = ownerParagraphs().end();
1611 pos = par->getPositionOfInset(inset);
1613 checkParagraph(&*par, pos);
1617 } while (par != end);
1623 bool LyXText::setCursor(Paragraph * par,
1625 bool setfont, bool boundary)
1627 LyXCursor old_cursor = cursor;
1628 setCursorIntern(par, pos, setfont, boundary);
1629 return deleteEmptyParagraphMechanism(old_cursor);
1633 void LyXText::setCursor(LyXCursor & cur, Paragraph * par,
1634 pos_type pos, bool boundary)
1640 cur.boundary(boundary);
1642 // get the cursor y position in text
1644 RowList::iterator row = getRow(par, pos, y);
1645 RowList::iterator beg = rows().begin();
1647 RowList::iterator old_row = row;
1649 // if we are before the first char of this row and are still in the
1650 // same paragraph and there is a previous row then put the cursor on
1651 // the end of the previous row
1652 cur.iy(y + row->baseline());
1654 if (row != beg && pos &&
1655 boost::prior(row)->par() == row->par() &&
1656 pos < par->size() &&
1657 par->getChar(pos) == Paragraph::META_INSET &&
1658 (ins = par->getInset(pos)) && (ins->needFullRow() || ins->display()))
1665 // y is now the beginning of the cursor row
1666 y += row->baseline();
1667 // y is now the cursor baseline
1670 pos_type last = lastPrintablePos(*this, old_row);
1672 // None of these should happen, but we're scaredy-cats
1673 if (pos > par->size()) {
1674 lyxerr << "dont like 1 please report" << endl;
1677 } else if (pos > last + 1) {
1678 lyxerr << "dont like 2 please report" << endl;
1679 // This shouldn't happen.
1682 } else if (pos < row->pos()) {
1683 lyxerr << "dont like 3 please report" << endl;
1688 // now get the cursors x position
1689 float x = getCursorX(row, pos, last, boundary);
1692 if (old_row != row) {
1693 x = getCursorX(old_row, pos, last, boundary);
1697 /* We take out this for the time being because 1) the redraw code is not
1698 prepared to this yet and 2) because some good policy has yet to be decided
1699 while editting: for instance how to act on rows being created/deleted
1703 //if the cursor is in a visible row, anchor to it
1705 if (topy < y && y < topy + bv()->workHeight())
1711 float LyXText::getCursorX(RowList::iterator rit,
1712 pos_type pos, pos_type last, bool boundary) const
1714 pos_type cursor_vpos = 0;
1716 float fill_separator;
1718 float fill_label_hfill;
1719 // This call HAS to be here because of the BidiTables!!!
1720 prepareToPrint(rit, x, fill_separator, fill_hfill,
1723 if (last < rit->pos())
1724 cursor_vpos = rit->pos();
1725 else if (pos > last && !boundary)
1726 cursor_vpos = (rit->par()->isRightToLeftPar(bv()->buffer()->params))
1727 ? rit->pos() : last + 1;
1728 else if (pos > rit->pos() &&
1729 (pos > last || boundary))
1730 /// Place cursor after char at (logical) position pos - 1
1731 cursor_vpos = (bidi_level(pos - 1) % 2 == 0)
1732 ? log2vis(pos - 1) + 1 : log2vis(pos - 1);
1734 /// Place cursor before char at (logical) position pos
1735 cursor_vpos = (bidi_level(pos) % 2 == 0)
1736 ? log2vis(pos) : log2vis(pos) + 1;
1738 pos_type body_pos = rit->par()->beginningOfBody();
1739 if ((body_pos > 0) &&
1740 ((body_pos - 1 > last) ||
1741 !rit->par()->isLineSeparator(body_pos - 1)))
1744 for (pos_type vpos = rit->pos(); vpos < cursor_vpos; ++vpos) {
1745 pos_type pos = vis2log(vpos);
1746 if (body_pos > 0 && pos == body_pos - 1) {
1747 x += fill_label_hfill +
1748 font_metrics::width(
1749 rit->par()->layout()->labelsep,
1750 getLabelFont(bv()->buffer(),
1752 if (rit->par()->isLineSeparator(body_pos - 1))
1753 x -= singleWidth(&*rit->par(), body_pos - 1);
1756 if (hfillExpansion(*this, rit, pos)) {
1757 x += singleWidth(&*rit->par(), pos);
1758 if (pos >= body_pos)
1761 x += fill_label_hfill;
1762 } else if (rit->par()->isSeparator(pos)) {
1763 x += singleWidth(&*rit->par(), pos);
1764 if (pos >= body_pos)
1765 x += fill_separator;
1767 x += singleWidth(&*rit->par(), pos);
1773 void LyXText::setCursorIntern(Paragraph * par,
1774 pos_type pos, bool setfont, bool boundary)
1776 InsetText * it = static_cast<InsetText *>(par->inInset());
1778 if (it != inset_owner) {
1779 lyxerr[Debug::INSETS] << "InsetText is " << it
1781 << "inset_owner is "
1782 << inset_owner << endl;
1783 #ifdef WITH_WARNINGS
1784 #warning I believe this code is wrong. (Lgb)
1785 #warning Jürgen, have a look at this. (Lgb)
1786 #warning Hmmm, I guess you are right but we
1787 #warning should verify when this is needed
1789 // Jürgen, would you like to have a look?
1790 // I guess we need to move the outer cursor
1791 // and open and lock the inset (bla bla bla)
1792 // stuff I don't know... so can you have a look?
1794 // I moved the lyxerr stuff in here so we can see if
1795 // this is actually really needed and where!
1797 // it->getLyXText(bv())->setCursorIntern(bv(), par, pos, setfont, boundary);
1802 setCursor(cursor, par, pos, boundary);
1808 void LyXText::setCurrentFont()
1810 pos_type pos = cursor.pos();
1811 if (cursor.boundary() && pos > 0)
1815 if (pos == cursor.par()->size())
1817 else // potentional bug... BUG (Lgb)
1818 if (cursor.par()->isSeparator(pos)) {
1819 if (pos > cursor.row()->pos() &&
1820 bidi_level(pos) % 2 ==
1821 bidi_level(pos - 1) % 2)
1823 else if (pos + 1 < cursor.par()->size())
1829 cursor.par()->getFontSettings(bv()->buffer()->params, pos);
1830 real_current_font = getFont(bv()->buffer(), cursor.par(), pos);
1832 if (cursor.pos() == cursor.par()->size() &&
1833 isBoundary(bv()->buffer(), cursor.par(), cursor.pos()) &&
1834 !cursor.boundary()) {
1835 Language const * lang =
1836 cursor.par()->getParLanguage(bv()->buffer()->params);
1837 current_font.setLanguage(lang);
1838 current_font.setNumber(LyXFont::OFF);
1839 real_current_font.setLanguage(lang);
1840 real_current_font.setNumber(LyXFont::OFF);
1845 // returns the column near the specified x-coordinate of the row
1846 // x is set to the real beginning of this column
1848 LyXText::getColumnNearX(RowList::iterator rit, int & x, bool & boundary) const
1851 float fill_separator;
1853 float fill_label_hfill;
1855 prepareToPrint(rit, tmpx, fill_separator,
1856 fill_hfill, fill_label_hfill);
1858 pos_type vc = rit->pos();
1859 pos_type last = lastPrintablePos(*this, rit);
1862 LyXLayout_ptr const & layout = rit->par()->layout();
1864 bool left_side = false;
1866 pos_type body_pos = rit->par()->beginningOfBody();
1867 float last_tmpx = tmpx;
1870 (body_pos - 1 > last ||
1871 !rit->par()->isLineSeparator(body_pos - 1)))
1874 // check for empty row
1875 if (!rit->par()->size()) {
1880 while (vc <= last && tmpx <= x) {
1883 if (body_pos > 0 && c == body_pos - 1) {
1884 tmpx += fill_label_hfill +
1885 font_metrics::width(layout->labelsep,
1886 getLabelFont(bv()->buffer(), &*rit->par()));
1887 if (rit->par()->isLineSeparator(body_pos - 1))
1888 tmpx -= singleWidth(&*rit->par(), body_pos - 1);
1891 if (hfillExpansion(*this, rit, c)) {
1892 tmpx += singleWidth(&*rit->par(), c);
1896 tmpx += fill_label_hfill;
1897 } else if (rit->par()->isSeparator(c)) {
1898 tmpx += singleWidth(&*rit->par(), c);
1900 tmpx+= fill_separator;
1902 tmpx += singleWidth(&*rit->par(), c);
1907 if ((tmpx + last_tmpx) / 2 > x) {
1912 if (vc > last + 1) // This shouldn't happen.
1916 // This (rtl_support test) is not needed, but gives
1917 // some speedup if rtl_support=false
1918 bool const lastrow = lyxrc.rtl_support &&
1919 (boost::next(rit) == rowlist_.end() ||
1920 boost::next(rit)->par() != rit->par());
1921 // If lastrow is false, we don't need to compute
1922 // the value of rtl.
1923 bool const rtl = (lastrow)
1924 ? rit->par()->isRightToLeftPar(bv()->buffer()->params)
1927 ((rtl && left_side && vc == rit->pos() && x < tmpx - 5) ||
1928 (!rtl && !left_side && vc == last + 1 && x > tmpx + 5)))
1930 else if (vc == rit->pos()) {
1932 if (bidi_level(c) % 2 == 1)
1935 c = vis2log(vc - 1);
1936 bool const rtl = (bidi_level(c) % 2 == 1);
1937 if (left_side == rtl) {
1939 boundary = isBoundary(bv()->buffer(), &*rit->par(), c);
1943 if (rit->pos() <= last && c > last
1944 && rit->par()->isNewline(last)) {
1945 if (bidi_level(last) % 2 == 0)
1946 tmpx -= singleWidth(&*rit->par(), last);
1948 tmpx += singleWidth(&*rit->par(), last);
1958 void LyXText::setCursorFromCoordinates(int x, int y)
1960 LyXCursor old_cursor = cursor;
1962 setCursorFromCoordinates(cursor, x, y);
1964 deleteEmptyParagraphMechanism(old_cursor);
1971 * return true if the cursor given is at the end of a row,
1972 * and the next row is filled by an inset that spans an entire
1975 bool beforeFullRowInset(LyXText & lt, RowList::iterator row,
1977 if (boost::next(row) == lt.rows().end())
1979 Row const & next = *boost::next(row);
1981 if (next.pos() != cur.pos() || next.par() != cur.par())
1983 if (!cur.par()->isInset(cur.pos()))
1985 Inset const * inset = cur.par()->getInset(cur.pos());
1986 if (inset->needFullRow() || inset->display())
1993 void LyXText::setCursorFromCoordinates(LyXCursor & cur, int x, int y)
1995 // Get the row first.
1997 RowList::iterator row = getRowNearY(y);
1999 pos_type const column = getColumnNearX(row, x, bound);
2000 cur.par(&*row->par());
2001 cur.pos(row->pos() + column);
2003 cur.y(y + row->baseline());
2006 if (beforeFullRowInset(*this, row, cur)) {
2007 pos_type last = lastPrintablePos(*this, row);
2008 float x = getCursorX(boost::next(row), cur.pos(), last, bound);
2010 cur.iy(y + row->height() + boost::next(row)->baseline());
2011 cur.irow(boost::next(row));
2017 cur.boundary(bound);
2021 void LyXText::cursorLeft(bool internal)
2023 if (cursor.pos() > 0) {
2024 bool boundary = cursor.boundary();
2025 setCursor(cursor.par(), cursor.pos() - 1, true, false);
2026 if (!internal && !boundary &&
2027 isBoundary(bv()->buffer(), cursor.par(), cursor.pos() + 1))
2028 setCursor(cursor.par(), cursor.pos() + 1, true, true);
2029 } else if (cursor.par()->previous()) { // steps into the above paragraph.
2030 Paragraph * par = cursor.par()->previous();
2031 setCursor(par, par->size());
2036 void LyXText::cursorRight(bool internal)
2038 if (!internal && cursor.boundary() &&
2039 !cursor.par()->isNewline(cursor.pos()))
2040 setCursor(cursor.par(), cursor.pos(), true, false);
2041 else if (cursor.pos() < cursor.par()->size()) {
2042 setCursor(cursor.par(), cursor.pos() + 1, true, false);
2044 isBoundary(bv()->buffer(), cursor.par(), cursor.pos()))
2045 setCursor(cursor.par(), cursor.pos(), true, true);
2046 } else if (cursor.par()->next())
2047 setCursor(cursor.par()->next(), 0);
2051 void LyXText::cursorUp(bool selecting)
2054 int x = cursor.x_fix();
2055 int y = cursor.y() - cursor.row()->baseline() - 1;
2056 setCursorFromCoordinates(x, y);
2059 int y1 = cursor.iy() - topy;
2062 Inset * inset_hit = checkInsetHit(x, y1);
2063 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2064 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2068 setCursorFromCoordinates(bv(), cursor.x_fix(),
2069 cursor.y() - cursor.row()->baseline() - 1);
2074 void LyXText::cursorDown(bool selecting)
2077 int x = cursor.x_fix();
2078 int y = cursor.y() - cursor.row()->baseline() +
2079 cursor.row()->height() + 1;
2080 setCursorFromCoordinates(x, y);
2081 if (!selecting && cursor.row() == cursor.irow()) {
2083 int y1 = cursor.iy() - topy;
2086 Inset * inset_hit = checkInsetHit(x, y1);
2087 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2088 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2092 setCursorFromCoordinates(bv(), cursor.x_fix(),
2093 cursor.y() - cursor.row()->baseline()
2094 + cursor.row()->height() + 1);
2099 void LyXText::cursorUpParagraph()
2101 if (cursor.pos() > 0) {
2102 setCursor(cursor.par(), 0);
2104 else if (cursor.par()->previous()) {
2105 setCursor(cursor.par()->previous(), 0);
2110 void LyXText::cursorDownParagraph()
2112 if (cursor.par()->next()) {
2113 setCursor(cursor.par()->next(), 0);
2115 setCursor(cursor.par(), cursor.par()->size());
2119 // fix the cursor `cur' after a characters has been deleted at `where'
2120 // position. Called by deleteEmptyParagraphMechanism
2121 void LyXText::fixCursorAfterDelete(LyXCursor & cur,
2122 LyXCursor const & where)
2124 // if cursor is not in the paragraph where the delete occured,
2126 if (cur.par() != where.par())
2129 // if cursor position is after the place where the delete occured,
2131 if (cur.pos() > where.pos())
2132 cur.pos(cur.pos()-1);
2134 // check also if we don't want to set the cursor on a spot behind the
2135 // pagragraph because we erased the last character.
2136 if (cur.pos() > cur.par()->size())
2137 cur.pos(cur.par()->size());
2139 // recompute row et al. for this cursor
2140 setCursor(cur, cur.par(), cur.pos(), cur.boundary());
2144 bool LyXText::deleteEmptyParagraphMechanism(LyXCursor const & old_cursor)
2146 // Would be wrong to delete anything if we have a selection.
2147 if (selection.set())
2150 // We allow all kinds of "mumbo-jumbo" when freespacing.
2151 if (old_cursor.par()->layout()->free_spacing
2152 || old_cursor.par()->isFreeSpacing()) {
2156 /* Ok I'll put some comments here about what is missing.
2157 I have fixed BackSpace (and thus Delete) to not delete
2158 double-spaces automagically. I have also changed Cut,
2159 Copy and Paste to hopefully do some sensible things.
2160 There are still some small problems that can lead to
2161 double spaces stored in the document file or space at
2162 the beginning of paragraphs. This happens if you have
2163 the cursor betwenn to spaces and then save. Or if you
2164 cut and paste and the selection have a space at the
2165 beginning and then save right after the paste. I am
2166 sure none of these are very hard to fix, but I will
2167 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
2168 that I can get some feedback. (Lgb)
2171 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
2172 // delete the LineSeparator.
2175 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
2176 // delete the LineSeparator.
2179 // If the pos around the old_cursor were spaces, delete one of them.
2180 if (old_cursor.par() != cursor.par()
2181 || old_cursor.pos() != cursor.pos()) {
2182 // Only if the cursor has really moved
2184 if (old_cursor.pos() > 0
2185 && old_cursor.pos() < old_cursor.par()->size()
2186 && old_cursor.par()->isLineSeparator(old_cursor.pos())
2187 && old_cursor.par()->isLineSeparator(old_cursor.pos() - 1)) {
2188 old_cursor.par()->erase(old_cursor.pos() - 1);
2189 redoParagraphs(old_cursor, old_cursor.par()->next());
2191 #ifdef WITH_WARNINGS
2192 #warning This will not work anymore when we have multiple views of the same buffer
2193 // In this case, we will have to correct also the cursors held by
2194 // other bufferviews. It will probably be easier to do that in a more
2195 // automated way in LyXCursor code. (JMarc 26/09/2001)
2197 // correct all cursors held by the LyXText
2198 fixCursorAfterDelete(cursor, old_cursor);
2199 fixCursorAfterDelete(selection.cursor,
2201 fixCursorAfterDelete(selection.start,
2203 fixCursorAfterDelete(selection.end, old_cursor);
2204 fixCursorAfterDelete(last_sel_cursor,
2206 fixCursorAfterDelete(toggle_cursor, old_cursor);
2207 fixCursorAfterDelete(toggle_end_cursor,
2213 // don't delete anything if this is the ONLY paragraph!
2214 if (!old_cursor.par()->next() && !old_cursor.par()->previous())
2217 // Do not delete empty paragraphs with keepempty set.
2218 if (old_cursor.par()->layout()->keepempty)
2221 // only do our magic if we changed paragraph
2222 if (old_cursor.par() == cursor.par())
2225 // record if we have deleted a paragraph
2226 // we can't possibly have deleted a paragraph before this point
2227 bool deleted = false;
2229 if ((old_cursor.par()->empty()
2230 || (old_cursor.par()->size() == 1
2231 && old_cursor.par()->isLineSeparator(0)))) {
2232 // ok, we will delete anything
2233 LyXCursor tmpcursor;
2237 if (old_cursor.row() != rows().begin()) {
2239 prevrow = boost::prior(old_cursor.row());
2240 const_cast<LyXText *>(this)->postPaint(old_cursor.y() - old_cursor.row()->baseline() - prevrow->height());
2242 cursor = old_cursor; // that undo can restore the right cursor position
2243 Paragraph * endpar = old_cursor.par()->next();
2244 if (endpar && endpar->getDepth()) {
2245 while (endpar && endpar->getDepth()) {
2246 endpar = endpar->next();
2249 setUndo(bv(), Undo::DELETE, old_cursor.par(), endpar);
2253 removeRow(old_cursor.row());
2254 if (ownerParagraphs().begin() == old_cursor.par()) {
2255 ownerParagraph(&*boost::next(ownerParagraphs().begin()));
2258 delete old_cursor.par();
2260 /* Breakagain the next par. Needed because of
2261 * the parindent that can occur or dissappear.
2262 * The next row can change its height, if
2263 * there is another layout before */
2264 if (boost::next(prevrow) != rows().end()) {
2265 breakAgain(boost::next(prevrow));
2268 setHeightOfRow(prevrow);
2270 RowList::iterator nextrow = boost::next(old_cursor.row());
2271 const_cast<LyXText *>(this)->postPaint(
2272 old_cursor.y() - old_cursor.row()->baseline());
2275 cursor = old_cursor; // that undo can restore the right cursor position
2276 Paragraph * endpar = old_cursor.par()->next();
2277 if (endpar && endpar->getDepth()) {
2278 while (endpar && endpar->getDepth()) {
2279 endpar = endpar->next();
2282 setUndo(bv(), Undo::DELETE, old_cursor.par(), endpar);
2286 removeRow(old_cursor.row());
2288 if (ownerParagraphs().begin() == old_cursor.par()) {
2289 ownerParagraph(&*boost::next(ownerParagraphs().begin()));
2292 delete old_cursor.par();
2294 /* Breakagain the next par. Needed because of
2295 the parindent that can occur or dissappear.
2296 The next row can change its height, if
2297 there is another layout before */
2298 if (nextrow != rows().end()) {
2299 breakAgain(nextrow);
2305 setCursorIntern(cursor.par(), cursor.pos());
2307 if (selection.cursor.par() == old_cursor.par()
2308 && selection.cursor.pos() == old_cursor.pos()) {
2309 // correct selection
2310 selection.cursor = cursor;
2314 if (old_cursor.par()->stripLeadingSpaces()) {
2315 redoParagraphs(old_cursor,
2316 old_cursor.par()->next());
2318 setCursorIntern(cursor.par(), cursor.pos());
2319 selection.cursor = cursor;
2326 ParagraphList & LyXText::ownerParagraphs() const
2329 return inset_owner->paragraphs;
2331 return bv_owner->buffer()->paragraphs;
2335 void LyXText::ownerParagraph(Paragraph * p) const
2338 inset_owner->paragraph(p);
2340 bv_owner->buffer()->paragraphs.set(p);
2345 void LyXText::ownerParagraph(int id, Paragraph * p) const
2347 Paragraph * op = bv_owner->buffer()->getParFromID(id);
2348 if (op && op->inInset()) {
2349 static_cast<InsetText *>(op->inInset())->paragraph(p);
2356 LyXText::refresh_status LyXText::refreshStatus() const
2358 return refresh_status_;
2362 void LyXText::clearPaint()
2364 refresh_status_ = REFRESH_NONE;
2365 refresh_row = rows().end();
2370 void LyXText::postPaint(int start_y)
2372 refresh_status old = refresh_status_;
2374 refresh_status_ = REFRESH_AREA;
2375 refresh_row = rows().end();
2377 if (old != REFRESH_NONE && refresh_y < start_y)
2380 refresh_y = start_y;
2385 // We are an inset's lyxtext. Tell the top-level lyxtext
2386 // it needs to update the row we're in.
2387 LyXText * t = bv()->text;
2388 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2392 // FIXME: we should probably remove this y parameter,
2393 // make refresh_y be 0, and use row->y etc.
2394 void LyXText::postRowPaint(RowList::iterator rit, int start_y)
2396 if (refresh_status_ != REFRESH_NONE && refresh_y < start_y) {
2397 refresh_status_ = REFRESH_AREA;
2400 refresh_y = start_y;
2403 if (refresh_status_ == REFRESH_AREA)
2406 refresh_status_ = REFRESH_ROW;
2412 // We are an inset's lyxtext. Tell the top-level lyxtext
2413 // it needs to update the row we're in.
2414 LyXText * t = bv()->text;
2415 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2419 bool LyXText::isInInset() const
2421 // Sub-level has non-null bv owner and
2422 // non-null inset owner.
2423 return inset_owner != 0 && bv_owner != 0;
2427 int defaultRowHeight()
2429 LyXFont const font(LyXFont::ALL_SANE);
2430 return int(font_metrics::maxAscent(font)
2431 + font_metrics::maxDescent(font) * 1.5);