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 void LyXText::incDepth()
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 int prev_after_depth = 0;
477 #warning parlist ... could be nicer ?
478 if (start != ownerParagraphs().begin())
479 prev_after_depth = boost::prior(start)->getMaxDepthAfter();
482 int const depth = pit->params().depth();
484 if (depth < prev_after_depth
485 && pit->layout()->labeltype != LABEL_BIBLIO) {
486 pit->params().depth(depth + 1);
489 prev_after_depth = pit->getMaxDepthAfter();
497 // Wow, redoParagraphs is stupid.
499 setCursor(tmpcursor, &(*start), 0);
500 redoParagraphs(tmpcursor, &(*pastend));
502 // We need to actually move the text->cursor. I don't
503 // understand why ...
506 // we have to reset the visual selection because the
507 // geometry could have changed
508 if (selection.set()) {
509 setCursor(selection.start.par(), selection.start.pos());
510 selection.cursor = cursor;
511 setCursor(selection.end.par(), selection.end.pos());
516 setCursor(tmpcursor.par(), tmpcursor.pos());
520 // decrement depth over selection and
521 // make a total rebreak of those paragraphs
522 void LyXText::decDepth()
524 // if there is no selection just set the layout
525 // of the current paragraph
526 if (!selection.set()) {
527 selection.start = cursor; // dummy selection
528 selection.end = cursor;
530 Paragraph * endpar = selection.end.par()->next();
531 Paragraph * undoendpar = endpar;
533 if (endpar && endpar->getDepth()) {
534 while (endpar && endpar->getDepth()) {
535 endpar = endpar->next();
539 endpar = endpar->next(); // because of parindents etc.
542 setUndo(bv(), Undo::EDIT,
543 selection.start.par(), undoendpar);
545 LyXCursor tmpcursor = cursor; // store the current cursor
547 // ok we have a selection. This is always between sel_start_cursor
548 // and sel_end cursor
549 cursor = selection.start;
552 if (cursor.par()->params().depth()) {
553 cursor.par()->params()
554 .depth(cursor.par()->params().depth() - 1);
556 if (cursor.par() == selection.end.par()) {
559 cursor.par(cursor.par()->next());
562 redoParagraphs(selection.start, endpar);
564 // we have to reset the visual selection because the
565 // geometry could have changed
566 setCursor(selection.start.par(), selection.start.pos());
567 selection.cursor = cursor;
568 setCursor(selection.end.par(), selection.end.pos());
571 setCursor(tmpcursor.par(), tmpcursor.pos());
575 // set font over selection and make a total rebreak of those paragraphs
576 void LyXText::setFont(LyXFont const & font, bool toggleall)
578 // if there is no selection just set the current_font
579 if (!selection.set()) {
580 // Determine basis font
582 if (cursor.pos() < cursor.par()->beginningOfBody()) {
583 layoutfont = getLabelFont(bv()->buffer(),
586 layoutfont = getLayoutFont(bv()->buffer(),
589 // Update current font
590 real_current_font.update(font,
591 bv()->buffer()->params.language,
594 // Reduce to implicit settings
595 current_font = real_current_font;
596 current_font.reduce(layoutfont);
597 // And resolve it completely
598 real_current_font.realize(layoutfont);
603 LyXCursor tmpcursor = cursor; // store the current cursor
605 // ok we have a selection. This is always between sel_start_cursor
606 // and sel_end cursor
608 setUndo(bv(), Undo::EDIT,
609 selection.start.par(), selection.end.par()->next());
611 cursor = selection.start;
612 while (cursor.par() != selection.end.par() ||
613 cursor.pos() < selection.end.pos())
615 if (cursor.pos() < cursor.par()->size()) {
616 // an open footnote should behave like a closed one
617 setCharFont(cursor.par(), cursor.pos(),
619 cursor.pos(cursor.pos() + 1);
622 cursor.par(cursor.par()->next());
627 redoParagraphs(selection.start, selection.end.par()->next());
629 // we have to reset the selection, because the
630 // geometry could have changed, but we keep
631 // it for user convenience
632 setCursor(selection.start.par(), selection.start.pos());
633 selection.cursor = cursor;
634 setCursor(selection.end.par(), selection.end.pos());
636 setCursor(tmpcursor.par(), tmpcursor.pos(), true,
637 tmpcursor.boundary());
641 void LyXText::redoHeightOfParagraph()
643 RowList::iterator tmprow = cursor.row();
644 int y = cursor.y() - tmprow->baseline();
646 setHeightOfRow(tmprow);
648 while (tmprow != rows().begin()
649 && boost::prior(tmprow)->par() == tmprow->par()) {
651 y -= tmprow->height();
652 setHeightOfRow(tmprow);
657 setCursor(cursor.par(), cursor.pos(), false, cursor.boundary());
661 void LyXText::redoDrawingOfParagraph(LyXCursor const & cur)
663 RowList::iterator tmprow = cur.row();
665 int y = cur.y() - tmprow->baseline();
666 setHeightOfRow(tmprow);
668 while (tmprow != rows().begin()
669 && boost::prior(tmprow)->par() == tmprow->par()) {
671 y -= tmprow->height();
675 setCursor(cur.par(), cur.pos());
679 // deletes and inserts again all paragaphs between the cursor
680 // and the specified par
681 // This function is needed after SetLayout and SetFont etc.
682 void LyXText::redoParagraphs(LyXCursor const & cur,
683 Paragraph const * ep)
685 RowList::iterator tmprit = cur.row();
686 ParagraphList::iterator endpit(const_cast<Paragraph*>(ep));
687 int y = cur.y() - tmprit->baseline();
689 ParagraphList::iterator first_phys_pit;
690 if (tmprit == rows().begin()) {
691 // A trick/hack for UNDO.
692 // This is needed because in an UNDO/REDO we could have
693 // changed the ownerParagrah() so the paragraph inside
694 // the row is NOT my really first par anymore.
695 // Got it Lars ;) (Jug 20011206)
696 first_phys_pit = ownerParagraphs().begin();
698 // In here prevrit could be set to rows().end(). (Lgb)
700 first_phys_pit = tmprit->par();
701 while (tmprit != rows().begin()
702 && boost::prior(tmprit)->par() == first_phys_pit)
705 y -= tmprit->height();
708 // Is it possible to put the prevrit setting in here? (Lgb)
711 RowList::iterator prevrit;
712 bool good_prevrit = false;
714 // It seems to mee that good_prevrit is not needed if we let
715 // a bad prevrit have the value rows().end() (Lgb)
716 if (tmprit != rows().begin()) {
717 prevrit = boost::prior(tmprit);
722 while (tmprit != rows().end() && tmprit->par() != endpit) {
723 RowList::iterator tmprit2 = tmprit++;
727 // Reinsert the paragraphs.
728 ParagraphList::iterator tmppit = first_phys_pit;
730 // See if this loop can be rewritten as a while loop instead.
731 // That should also make the code a bit easier to read. (Lgb)
733 if (tmppit != ownerParagraphs().end()) {
734 insertParagraph(&*tmppit, tmprit);
735 while (tmprit != rows().end()
736 && tmprit->par() == tmppit) {
741 } while (tmppit != ownerParagraphs().end() && tmppit != endpit);
744 // If the above changes are done, then we can compare prevrit
745 // with rows().end() here. (Lgb)
747 setHeightOfRow(prevrit);
748 const_cast<LyXText *>(this)->postPaint(y - prevrit->height());
750 setHeightOfRow(rows().begin());
751 const_cast<LyXText *>(this)->postPaint(0);
753 if (tmprit != rows().end())
754 setHeightOfRow(tmprit);
759 void LyXText::fullRebreak()
761 if (rows().empty()) {
765 if (need_break_row != rows().end()) {
766 breakAgain(need_break_row);
767 need_break_row = rows().end();
773 // important for the screen
776 // the cursor set functions have a special mechanism. When they
777 // realize, that you left an empty paragraph, they will delete it.
778 // They also delete the corresponding row
780 // need the selection cursor:
781 void LyXText::setSelection()
783 bool const lsel = selection.set();
785 if (!selection.set()) {
786 last_sel_cursor = selection.cursor;
787 selection.start = selection.cursor;
788 selection.end = selection.cursor;
793 // first the toggling area
794 if (cursor.y() < last_sel_cursor.y()
795 || (cursor.y() == last_sel_cursor.y()
796 && cursor.x() < last_sel_cursor.x())) {
797 toggle_end_cursor = last_sel_cursor;
798 toggle_cursor = cursor;
800 toggle_end_cursor = cursor;
801 toggle_cursor = last_sel_cursor;
804 last_sel_cursor = cursor;
806 // and now the whole selection
808 if (selection.cursor.par() == cursor.par())
809 if (selection.cursor.pos() < cursor.pos()) {
810 selection.end = cursor;
811 selection.start = selection.cursor;
813 selection.end = selection.cursor;
814 selection.start = cursor;
816 else if (selection.cursor.y() < cursor.y() ||
817 (selection.cursor.y() == cursor.y()
818 && selection.cursor.x() < cursor.x())) {
819 selection.end = cursor;
820 selection.start = selection.cursor;
823 selection.end = selection.cursor;
824 selection.start = cursor;
827 // a selection with no contents is not a selection
828 if (selection.start.par() == selection.end.par() &&
829 selection.start.pos() == selection.end.pos())
830 selection.set(false);
832 if (inset_owner && (selection.set() || lsel))
833 inset_owner->setUpdateStatus(bv(), InsetText::SELECTION);
837 string const LyXText::selectionAsString(Buffer const * buffer,
840 if (!selection.set()) return string();
842 // should be const ...
843 Paragraph * startpar(selection.start.par());
844 Paragraph * endpar(selection.end.par());
845 pos_type const startpos(selection.start.pos());
846 pos_type const endpos(selection.end.pos());
848 if (startpar == endpar) {
849 return startpar->asString(buffer, startpos, endpos, label);
854 // First paragraph in selection
855 result += startpar->asString(buffer, startpos, startpar->size(), label) + "\n\n";
857 // The paragraphs in between (if any)
858 LyXCursor tmpcur(selection.start);
859 tmpcur.par(tmpcur.par()->next());
860 while (tmpcur.par() != endpar) {
861 result += tmpcur.par()->asString(buffer, 0,
862 tmpcur.par()->size(),
864 tmpcur.par(tmpcur.par()->next());
867 // Last paragraph in selection
868 result += endpar->asString(buffer, 0, endpos, label);
874 void LyXText::clearSelection()
876 selection.set(false);
877 selection.mark(false);
878 last_sel_cursor = selection.end = selection.start = selection.cursor = cursor;
879 // reset this in the bv_owner!
880 if (bv_owner && bv_owner->text)
881 bv_owner->text->xsel_cache.set(false);
885 void LyXText::cursorHome()
887 setCursor(cursor.par(), cursor.row()->pos());
891 void LyXText::cursorEnd()
893 if (cursor.par()->empty())
896 // There is a lot of unneeded recalculation going on here:
897 // - boost::next(curosr.row())
898 // - lastPost(*this, cursor.row())
900 if (boost::next(cursor.row()) == rows().end()
901 || boost::next(cursor.row())->par() != cursor.row()->par()) {
902 setCursor(cursor.par(), lastPos(*this, cursor.row()) + 1);
904 if (!cursor.par()->empty() &&
905 (cursor.par()->getChar(lastPos(*this, cursor.row())) == ' '
906 || cursor.par()->isNewline(lastPos(*this, cursor.row())))) {
907 setCursor(cursor.par(), lastPos(*this, cursor.row()));
909 setCursor(cursor.par(),
910 lastPos(*this, cursor.row()) + 1);
916 void LyXText::cursorTop()
918 while (cursor.par()->previous())
919 cursor.par(cursor.par()->previous());
920 setCursor(cursor.par(), 0);
924 void LyXText::cursorBottom()
926 while (cursor.par()->next())
927 cursor.par(cursor.par()->next());
928 setCursor(cursor.par(), cursor.par()->size());
932 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
934 // If the mask is completely neutral, tell user
935 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
936 // Could only happen with user style
937 bv()->owner()->message(_("No font change defined. Use Character under the Layout menu to define font change."));
941 // Try implicit word selection
942 // If there is a change in the language the implicit word selection
944 LyXCursor resetCursor = cursor;
945 bool implicitSelection = (font.language() == ignore_language
946 && font.number() == LyXFont::IGNORE)
947 ? selectWordWhenUnderCursor(WHOLE_WORD_STRICT) : false;
950 setFont(font, toggleall);
952 // Implicit selections are cleared afterwards
953 //and cursor is set to the original position.
954 if (implicitSelection) {
956 cursor = resetCursor;
957 setCursor(cursor.par(), cursor.pos());
958 selection.cursor = cursor;
961 inset_owner->setUpdateStatus(bv(), InsetText::CURSOR_PAR);
965 string LyXText::getStringToIndex()
967 // Try implicit word selection
968 // If there is a change in the language the implicit word selection
970 LyXCursor const reset_cursor = cursor;
971 bool const implicitSelection = selectWordWhenUnderCursor(PREVIOUS_WORD);
974 if (!selection.set())
975 bv()->owner()->message(_("Nothing to index!"));
976 else if (selection.start.par() != selection.end.par())
977 bv()->owner()->message(_("Cannot index more than one paragraph!"));
979 idxstring = selectionAsString(bv()->buffer(), false);
981 // Reset cursors to their original position.
982 cursor = reset_cursor;
983 setCursor(cursor.par(), cursor.pos());
984 selection.cursor = cursor;
986 // Clear the implicit selection.
987 if (implicitSelection)
994 // the DTP switches for paragraphs. LyX will store them in the first
995 // physicla paragraph. When a paragraph is broken, the top settings rest,
996 // the bottom settings are given to the new one. So I can make shure,
997 // they do not duplicate themself and you cannnot make dirty things with
1000 void LyXText::setParagraph(bool line_top, bool line_bottom,
1001 bool pagebreak_top, bool pagebreak_bottom,
1002 VSpace const & space_top,
1003 VSpace const & space_bottom,
1004 Spacing const & spacing,
1006 string const & labelwidthstring,
1009 LyXCursor tmpcursor = cursor;
1010 if (!selection.set()) {
1011 selection.start = cursor;
1012 selection.end = cursor;
1015 // make sure that the depth behind the selection are restored, too
1016 Paragraph * endpar = selection.end.par()->next();
1017 Paragraph * undoendpar = endpar;
1019 if (endpar && endpar->getDepth()) {
1020 while (endpar && endpar->getDepth()) {
1021 endpar = endpar->next();
1022 undoendpar = endpar;
1026 // because of parindents etc.
1027 endpar = endpar->next();
1030 setUndo(bv(), Undo::EDIT, selection.start.par(), undoendpar);
1033 Paragraph * tmppar = selection.end.par();
1035 while (tmppar != selection.start.par()->previous()) {
1036 setCursor(tmppar, 0);
1037 postPaint(cursor.y() - cursor.row()->baseline());
1038 cursor.par()->params().lineTop(line_top);
1039 cursor.par()->params().lineBottom(line_bottom);
1040 cursor.par()->params().pagebreakTop(pagebreak_top);
1041 cursor.par()->params().pagebreakBottom(pagebreak_bottom);
1042 cursor.par()->params().spaceTop(space_top);
1043 cursor.par()->params().spaceBottom(space_bottom);
1044 cursor.par()->params().spacing(spacing);
1045 // does the layout allow the new alignment?
1046 LyXLayout_ptr const & layout = cursor.par()->layout();
1048 if (align == LYX_ALIGN_LAYOUT)
1049 align = layout->align;
1050 if (align & layout->alignpossible) {
1051 if (align == layout->align)
1052 cursor.par()->params().align(LYX_ALIGN_LAYOUT);
1054 cursor.par()->params().align(align);
1056 cursor.par()->setLabelWidthString(labelwidthstring);
1057 cursor.par()->params().noindent(noindent);
1058 tmppar = cursor.par()->previous();
1061 redoParagraphs(selection.start, endpar);
1064 setCursor(selection.start.par(), selection.start.pos());
1065 selection.cursor = cursor;
1066 setCursor(selection.end.par(), selection.end.pos());
1068 setCursor(tmpcursor.par(), tmpcursor.pos());
1070 bv()->updateInset(inset_owner);
1074 // set the counter of a paragraph. This includes the labels
1075 void LyXText::setCounter(Buffer const * buf, Paragraph * par)
1077 LyXTextClass const & textclass = buf->params.getLyXTextClass();
1078 LyXLayout_ptr const & layout = par->layout();
1080 if (par->previous()) {
1082 par->params().appendix(par->previous()->params().appendix());
1083 if (!par->params().appendix() && par->params().startOfAppendix()) {
1084 par->params().appendix(true);
1085 textclass.counters().reset();
1087 par->enumdepth = par->previous()->enumdepth;
1088 par->itemdepth = par->previous()->itemdepth;
1090 par->params().appendix(par->params().startOfAppendix());
1095 /* Maybe we have to increment the enumeration depth.
1096 * BUT, enumeration in a footnote is considered in isolation from its
1097 * surrounding paragraph so don't increment if this is the
1098 * first line of the footnote
1099 * AND, bibliographies can't have their depth changed ie. they
1100 * are always of depth 0
1103 && par->previous()->getDepth() < par->getDepth()
1104 && par->previous()->layout()->labeltype == LABEL_COUNTER_ENUMI
1105 && par->enumdepth < 3
1106 && layout->labeltype != LABEL_BIBLIO) {
1110 // Maybe we have to decrement the enumeration depth, see note above
1112 && par->previous()->getDepth() > par->getDepth()
1113 && layout->labeltype != LABEL_BIBLIO) {
1114 par->enumdepth = par->depthHook(par->getDepth())->enumdepth;
1117 if (!par->params().labelString().empty()) {
1118 par->params().labelString(string());
1121 if (layout->margintype == MARGIN_MANUAL) {
1122 if (par->params().labelWidthString().empty()) {
1123 par->setLabelWidthString(layout->labelstring());
1126 par->setLabelWidthString(string());
1129 // is it a layout that has an automatic label?
1130 if (layout->labeltype >= LABEL_COUNTER_CHAPTER) {
1131 int const i = layout->labeltype - LABEL_COUNTER_CHAPTER;
1135 if (i >= 0 && i <= buf->params.secnumdepth) {
1139 textclass.counters().step(layout->latexname());
1141 // Is there a label? Useful for Chapter layout
1142 if (!par->params().appendix()) {
1143 s << layout->labelstring();
1145 s << layout->labelstring_appendix();
1148 // Use of an integer is here less than elegant. For now.
1149 int head = textclass.maxcounter() - LABEL_COUNTER_CHAPTER;
1150 if (!par->params().appendix()) {
1151 numbertype = "sectioning";
1153 numbertype = "appendix";
1154 if (par->isRightToLeftPar(buf->params))
1155 langtype = "hebrew";
1160 s << textclass.counters()
1161 .numberLabel(layout->latexname(),
1162 numbertype, langtype, head);
1164 par->params().labelString(STRCONV(s.str()));
1166 // reset enum counters
1167 textclass.counters().reset("enum");
1168 } else if (layout->labeltype < LABEL_COUNTER_ENUMI) {
1169 textclass.counters().reset("enum");
1170 } else if (layout->labeltype == LABEL_COUNTER_ENUMI) {
1172 // Yes I know this is a really, really! bad solution
1174 string enumcounter("enum");
1176 switch (par->enumdepth) {
1185 enumcounter += "iv";
1188 // not a valid enumdepth...
1192 textclass.counters().step(enumcounter);
1194 s << textclass.counters()
1195 .numberLabel(enumcounter, "enumeration");
1196 par->params().labelString(STRCONV(s.str()));
1198 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
1199 textclass.counters().step("bibitem");
1200 int number = textclass.counters().value("bibitem");
1201 if (par->bibitem()) {
1202 par->bibitem()->setCounter(number);
1203 par->params().labelString(layout->labelstring());
1205 // In biblio should't be following counters but...
1207 string s = layout->labelstring();
1209 // the caption hack:
1210 if (layout->labeltype == LABEL_SENSITIVE) {
1211 Paragraph * tmppar = par;
1214 while (tmppar && tmppar->inInset()
1215 // the single '=' is intended below
1216 && (in = tmppar->inInset()->owner())) {
1217 if (in->lyxCode() == Inset::FLOAT_CODE ||
1218 in->lyxCode() == Inset::WRAP_CODE) {
1222 tmppar = in->parOwner();
1228 = textclass.floats().getType(static_cast<InsetFloat*>(in)->type());
1230 textclass.counters().step(fl.type());
1232 // Doesn't work... yet.
1233 #if USE_BOOST_FORMAT
1234 s = boost::io::str(boost::format(_("%1$s #:")) % fl.name());
1235 // s << boost::format(_("%1$s %1$d:")
1237 // % buf->counters().value(fl.name());
1240 //o << fl.name() << ' ' << buf->counters().value(fl.name()) << ":";
1241 o << fl.name() << " #:";
1242 s = STRCONV(o.str());
1245 // par->SetLayout(0);
1246 // s = layout->labelstring;
1247 s = _("Senseless: ");
1250 par->params().labelString(s);
1252 // reset the enumeration counter. They are always reset
1253 // when there is any other layout between
1254 // Just fall-through between the cases so that all
1255 // enum counters deeper than enumdepth is also reset.
1256 switch (par->enumdepth) {
1258 textclass.counters().reset("enumi");
1260 textclass.counters().reset("enumii");
1262 textclass.counters().reset("enumiii");
1264 textclass.counters().reset("enumiv");
1270 // Updates all counters. Paragraphs with changed label string will be rebroken
1271 void LyXText::updateCounters()
1273 RowList::iterator rowit = rows().begin();
1274 ParagraphList::iterator pit = rowit->par();
1276 // CHECK if this is really needed. (Lgb)
1277 bv()->buffer()->params.getLyXTextClass().counters().reset();
1279 while (pit != ownerParagraphs().end()) {
1280 while (rowit->par() != pit)
1283 string const oldLabel = pit->params().labelString();
1285 // setCounter can potentially change the labelString.
1286 setCounter(bv()->buffer(), &*pit);
1288 string const & newLabel = pit->params().labelString();
1290 if (oldLabel.empty() && !newLabel.empty()) {
1291 removeParagraph(rowit);
1292 appendParagraph(rowit);
1300 void LyXText::insertInset(Inset * inset)
1302 if (!cursor.par()->insetAllowed(inset->lyxCode()))
1304 setUndo(bv(), Undo::FINISH, cursor.par(), cursor.par()->next());
1306 cursor.par()->insertInset(cursor.pos(), inset);
1307 // Just to rebreak and refresh correctly.
1308 // The character will not be inserted a second time
1309 insertChar(Paragraph::META_INSET);
1310 // If we enter a highly editable inset the cursor should be to before
1311 // the inset. This couldn't happen before as Undo was not handled inside
1312 // inset now after the Undo LyX tries to call inset->Edit(...) again
1313 // and cannot do this as the cursor is behind the inset and GetInset
1314 // does not return the inset!
1315 if (isHighlyEditableInset(inset)) {
1322 void LyXText::copyEnvironmentType()
1324 copylayouttype = cursor.par()->layout()->name();
1328 void LyXText::pasteEnvironmentType()
1330 // do nothing if there has been no previous copyEnvironmentType()
1331 if (!copylayouttype.empty())
1332 setLayout(copylayouttype);
1336 void LyXText::cutSelection(bool doclear, bool realcut)
1338 // Stuff what we got on the clipboard. Even if there is no selection.
1340 // There is a problem with having the stuffing here in that the
1341 // larger the selection the slower LyX will get. This can be
1342 // solved by running the line below only when the selection has
1343 // finished. The solution used currently just works, to make it
1344 // faster we need to be more clever and probably also have more
1345 // calls to stuffClipboard. (Lgb)
1346 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1348 // This doesn't make sense, if there is no selection
1349 if (!selection.set())
1352 // OK, we have a selection. This is always between selection.start
1353 // and selection.end
1355 // make sure that the depth behind the selection are restored, too
1356 Paragraph * endpar = selection.end.par()->next();
1357 Paragraph * undoendpar = endpar;
1359 if (endpar && endpar->getDepth()) {
1360 while (endpar && endpar->getDepth()) {
1361 endpar = endpar->next();
1362 undoendpar = endpar;
1364 } else if (endpar) {
1365 endpar = endpar->next(); // because of parindents etc.
1368 setUndo(bv(), Undo::DELETE,
1369 selection.start.par(), undoendpar);
1371 // there are two cases: cut only within one paragraph or
1372 // more than one paragraph
1373 if (selection.start.par() == selection.end.par()) {
1374 // only within one paragraph
1375 endpar = selection.end.par();
1376 int pos = selection.end.pos();
1377 CutAndPaste::cutSelection(selection.start.par(), &endpar,
1378 selection.start.pos(), pos,
1379 bv()->buffer()->params.textclass,
1381 selection.end.pos(pos);
1383 endpar = selection.end.par();
1384 int pos = selection.end.pos();
1385 CutAndPaste::cutSelection(selection.start.par(), &endpar,
1386 selection.start.pos(), pos,
1387 bv()->buffer()->params.textclass,
1390 selection.end.par(endpar);
1391 selection.end.pos(pos);
1392 cursor.pos(selection.end.pos());
1394 endpar = endpar->next();
1396 // sometimes necessary
1398 selection.start.par()->stripLeadingSpaces();
1400 redoParagraphs(selection.start, endpar);
1402 // cutSelection can invalidate the cursor so we need to set
1404 // we prefer the end for when tracking changes
1405 cursor = selection.end;
1407 // need a valid cursor. (Lgb)
1410 setCursor(cursor.par(), cursor.pos());
1411 selection.cursor = cursor;
1416 void LyXText::copySelection()
1418 // stuff the selection onto the X clipboard, from an explicit copy request
1419 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1421 // this doesnt make sense, if there is no selection
1422 if (!selection.set())
1425 // ok we have a selection. This is always between selection.start
1426 // and sel_end cursor
1428 // copy behind a space if there is one
1429 while (selection.start.par()->size() > selection.start.pos()
1430 && selection.start.par()->isLineSeparator(selection.start.pos())
1431 && (selection.start.par() != selection.end.par()
1432 || selection.start.pos() < selection.end.pos()))
1433 selection.start.pos(selection.start.pos() + 1);
1435 CutAndPaste::copySelection(selection.start.par(), selection.end.par(),
1436 selection.start.pos(), selection.end.pos(),
1437 bv()->buffer()->params.textclass);
1441 void LyXText::pasteSelection()
1443 // this does not make sense, if there is nothing to paste
1444 if (!CutAndPaste::checkPastePossible())
1447 setUndo(bv(), Undo::INSERT,
1448 cursor.par(), cursor.par()->next());
1451 Paragraph * actpar = cursor.par();
1452 int pos = cursor.pos();
1454 CutAndPaste::pasteSelection(&actpar, &endpar, pos,
1455 bv()->buffer()->params.textclass);
1457 redoParagraphs(cursor, endpar);
1459 setCursor(cursor.par(), cursor.pos());
1462 selection.cursor = cursor;
1463 setCursor(actpar, pos);
1469 void LyXText::setSelectionRange(lyx::pos_type length)
1474 selection.cursor = cursor;
1481 // simple replacing. The font of the first selected character is used
1482 void LyXText::replaceSelectionWithString(string const & str)
1484 setCursorParUndo(bv());
1487 if (!selection.set()) { // create a dummy selection
1488 selection.end = cursor;
1489 selection.start = cursor;
1492 // Get font setting before we cut
1493 pos_type pos = selection.end.pos();
1494 LyXFont const font = selection.start.par()
1495 ->getFontSettings(bv()->buffer()->params,
1496 selection.start.pos());
1498 // Insert the new string
1499 for (string::const_iterator cit = str.begin(); cit != str.end(); ++cit) {
1500 selection.end.par()->insertChar(pos, (*cit), font);
1504 // Cut the selection
1505 cutSelection(true, false);
1511 // needed to insert the selection
1512 void LyXText::insertStringAsLines(string const & str)
1514 Paragraph * par = cursor.par();
1515 pos_type pos = cursor.pos();
1516 Paragraph * endpar = cursor.par()->next();
1518 setCursorParUndo(bv());
1520 // only to be sure, should not be neccessary
1523 bv()->buffer()->insertStringAsLines(par, pos, current_font, str);
1525 redoParagraphs(cursor, endpar);
1526 setCursor(cursor.par(), cursor.pos());
1527 selection.cursor = cursor;
1528 setCursor(par, pos);
1533 // turns double-CR to single CR, others where converted into one
1534 // blank. Then InsertStringAsLines is called
1535 void LyXText::insertStringAsParagraphs(string const & str)
1537 string linestr(str);
1538 bool newline_inserted = false;
1539 for (string::size_type i = 0; i < linestr.length(); ++i) {
1540 if (linestr[i] == '\n') {
1541 if (newline_inserted) {
1542 // we know that \r will be ignored by
1543 // InsertStringA. Of course, it is a dirty
1544 // trick, but it works...
1545 linestr[i - 1] = '\r';
1549 newline_inserted = true;
1551 } else if (IsPrintable(linestr[i])) {
1552 newline_inserted = false;
1555 insertStringAsLines(linestr);
1559 void LyXText::checkParagraph(Paragraph * par, pos_type pos)
1561 LyXCursor tmpcursor;
1565 RowList::iterator row = getRow(par, pos, y);
1566 RowList::iterator beg = rows().begin();
1568 // is there a break one row above
1570 && boost::prior(row)->par() == row->par()) {
1571 z = rowBreakPoint(*boost::prior(row));
1572 if (z >= row->pos()) {
1573 // set the dimensions of the row above
1574 y -= boost::prior(row)->height();
1577 breakAgain(boost::prior(row));
1579 // set the cursor again. Otherwise
1580 // dangling pointers are possible
1581 setCursor(cursor.par(), cursor.pos(),
1582 false, cursor.boundary());
1583 selection.cursor = cursor;
1588 int const tmpheight = row->height();
1589 pos_type const tmplast = lastPos(*this, row);
1592 if (row->height() == tmpheight && lastPos(*this, row) == tmplast) {
1593 postRowPaint(row, y);
1598 // check the special right address boxes
1599 if (par->layout()->margintype == MARGIN_RIGHT_ADDRESS_BOX) {
1606 redoDrawingOfParagraph(tmpcursor);
1609 // set the cursor again. Otherwise dangling pointers are possible
1610 // also set the selection
1612 if (selection.set()) {
1614 setCursorIntern(selection.cursor.par(), selection.cursor.pos(),
1615 false, selection.cursor.boundary());
1616 selection.cursor = cursor;
1617 setCursorIntern(selection.start.par(),
1618 selection.start.pos(),
1619 false, selection.start.boundary());
1620 selection.start = cursor;
1621 setCursorIntern(selection.end.par(),
1622 selection.end.pos(),
1623 false, selection.end.boundary());
1624 selection.end = cursor;
1625 setCursorIntern(last_sel_cursor.par(),
1626 last_sel_cursor.pos(),
1627 false, last_sel_cursor.boundary());
1628 last_sel_cursor = cursor;
1631 setCursorIntern(cursor.par(), cursor.pos(),
1632 false, cursor.boundary());
1636 // returns false if inset wasn't found
1637 bool LyXText::updateInset(Inset * inset)
1639 // first check the current paragraph
1640 int pos = cursor.par()->getPositionOfInset(inset);
1642 checkParagraph(cursor.par(), pos);
1646 // check every paragraph
1648 ParagraphList::iterator par = ownerParagraphs().begin();
1649 ParagraphList::iterator end = ownerParagraphs().end();
1652 pos = par->getPositionOfInset(inset);
1654 checkParagraph(&*par, pos);
1658 } while (par != end);
1664 bool LyXText::setCursor(Paragraph * par,
1666 bool setfont, bool boundary)
1668 LyXCursor old_cursor = cursor;
1669 setCursorIntern(par, pos, setfont, boundary);
1670 return deleteEmptyParagraphMechanism(old_cursor);
1674 void LyXText::setCursor(LyXCursor & cur, Paragraph * par,
1675 pos_type pos, bool boundary)
1681 cur.boundary(boundary);
1683 // get the cursor y position in text
1685 RowList::iterator row = getRow(par, pos, y);
1686 RowList::iterator beg = rows().begin();
1688 RowList::iterator old_row = row;
1690 // if we are before the first char of this row and are still in the
1691 // same paragraph and there is a previous row then put the cursor on
1692 // the end of the previous row
1693 cur.iy(y + row->baseline());
1695 if (row != beg && pos &&
1696 boost::prior(row)->par() == row->par() &&
1697 pos < par->size() &&
1698 par->getChar(pos) == Paragraph::META_INSET &&
1699 (ins = par->getInset(pos)) && (ins->needFullRow() || ins->display()))
1706 // y is now the beginning of the cursor row
1707 y += row->baseline();
1708 // y is now the cursor baseline
1711 pos_type last = lastPrintablePos(*this, old_row);
1713 // None of these should happen, but we're scaredy-cats
1714 if (pos > par->size()) {
1715 lyxerr << "dont like 1 please report" << endl;
1718 } else if (pos > last + 1) {
1719 lyxerr << "dont like 2 please report" << endl;
1720 // This shouldn't happen.
1723 } else if (pos < row->pos()) {
1724 lyxerr << "dont like 3 please report" << endl;
1729 // now get the cursors x position
1730 float x = getCursorX(row, pos, last, boundary);
1733 if (old_row != row) {
1734 x = getCursorX(old_row, pos, last, boundary);
1738 /* We take out this for the time being because 1) the redraw code is not
1739 prepared to this yet and 2) because some good policy has yet to be decided
1740 while editting: for instance how to act on rows being created/deleted
1744 //if the cursor is in a visible row, anchor to it
1746 if (topy < y && y < topy + bv()->workHeight())
1752 float LyXText::getCursorX(RowList::iterator rit,
1753 pos_type pos, pos_type last, bool boundary) const
1755 pos_type cursor_vpos = 0;
1757 float fill_separator;
1759 float fill_label_hfill;
1760 // This call HAS to be here because of the BidiTables!!!
1761 prepareToPrint(rit, x, fill_separator, fill_hfill,
1764 if (last < rit->pos())
1765 cursor_vpos = rit->pos();
1766 else if (pos > last && !boundary)
1767 cursor_vpos = (rit->par()->isRightToLeftPar(bv()->buffer()->params))
1768 ? rit->pos() : last + 1;
1769 else if (pos > rit->pos() &&
1770 (pos > last || boundary))
1771 /// Place cursor after char at (logical) position pos - 1
1772 cursor_vpos = (bidi_level(pos - 1) % 2 == 0)
1773 ? log2vis(pos - 1) + 1 : log2vis(pos - 1);
1775 /// Place cursor before char at (logical) position pos
1776 cursor_vpos = (bidi_level(pos) % 2 == 0)
1777 ? log2vis(pos) : log2vis(pos) + 1;
1779 pos_type body_pos = rit->par()->beginningOfBody();
1780 if ((body_pos > 0) &&
1781 ((body_pos - 1 > last) ||
1782 !rit->par()->isLineSeparator(body_pos - 1)))
1785 for (pos_type vpos = rit->pos(); vpos < cursor_vpos; ++vpos) {
1786 pos_type pos = vis2log(vpos);
1787 if (body_pos > 0 && pos == body_pos - 1) {
1788 x += fill_label_hfill +
1789 font_metrics::width(
1790 rit->par()->layout()->labelsep,
1791 getLabelFont(bv()->buffer(),
1793 if (rit->par()->isLineSeparator(body_pos - 1))
1794 x -= singleWidth(&*rit->par(), body_pos - 1);
1797 if (hfillExpansion(*this, rit, pos)) {
1798 x += singleWidth(&*rit->par(), pos);
1799 if (pos >= body_pos)
1802 x += fill_label_hfill;
1803 } else if (rit->par()->isSeparator(pos)) {
1804 x += singleWidth(&*rit->par(), pos);
1805 if (pos >= body_pos)
1806 x += fill_separator;
1808 x += singleWidth(&*rit->par(), pos);
1814 void LyXText::setCursorIntern(Paragraph * par,
1815 pos_type pos, bool setfont, bool boundary)
1817 InsetText * it = static_cast<InsetText *>(par->inInset());
1819 if (it != inset_owner) {
1820 lyxerr[Debug::INSETS] << "InsetText is " << it
1822 << "inset_owner is "
1823 << inset_owner << endl;
1824 #ifdef WITH_WARNINGS
1825 #warning I believe this code is wrong. (Lgb)
1826 #warning Jürgen, have a look at this. (Lgb)
1827 #warning Hmmm, I guess you are right but we
1828 #warning should verify when this is needed
1830 // Jürgen, would you like to have a look?
1831 // I guess we need to move the outer cursor
1832 // and open and lock the inset (bla bla bla)
1833 // stuff I don't know... so can you have a look?
1835 // I moved the lyxerr stuff in here so we can see if
1836 // this is actually really needed and where!
1838 // it->getLyXText(bv())->setCursorIntern(bv(), par, pos, setfont, boundary);
1843 setCursor(cursor, par, pos, boundary);
1849 void LyXText::setCurrentFont()
1851 pos_type pos = cursor.pos();
1852 if (cursor.boundary() && pos > 0)
1856 if (pos == cursor.par()->size())
1858 else // potentional bug... BUG (Lgb)
1859 if (cursor.par()->isSeparator(pos)) {
1860 if (pos > cursor.row()->pos() &&
1861 bidi_level(pos) % 2 ==
1862 bidi_level(pos - 1) % 2)
1864 else if (pos + 1 < cursor.par()->size())
1870 cursor.par()->getFontSettings(bv()->buffer()->params, pos);
1871 real_current_font = getFont(bv()->buffer(), cursor.par(), pos);
1873 if (cursor.pos() == cursor.par()->size() &&
1874 isBoundary(bv()->buffer(), cursor.par(), cursor.pos()) &&
1875 !cursor.boundary()) {
1876 Language const * lang =
1877 cursor.par()->getParLanguage(bv()->buffer()->params);
1878 current_font.setLanguage(lang);
1879 current_font.setNumber(LyXFont::OFF);
1880 real_current_font.setLanguage(lang);
1881 real_current_font.setNumber(LyXFont::OFF);
1886 // returns the column near the specified x-coordinate of the row
1887 // x is set to the real beginning of this column
1889 LyXText::getColumnNearX(RowList::iterator rit, int & x, bool & boundary) const
1892 float fill_separator;
1894 float fill_label_hfill;
1896 prepareToPrint(rit, tmpx, fill_separator,
1897 fill_hfill, fill_label_hfill);
1899 pos_type vc = rit->pos();
1900 pos_type last = lastPrintablePos(*this, rit);
1903 LyXLayout_ptr const & layout = rit->par()->layout();
1905 bool left_side = false;
1907 pos_type body_pos = rit->par()->beginningOfBody();
1908 float last_tmpx = tmpx;
1911 (body_pos - 1 > last ||
1912 !rit->par()->isLineSeparator(body_pos - 1)))
1915 // check for empty row
1916 if (!rit->par()->size()) {
1921 while (vc <= last && tmpx <= x) {
1924 if (body_pos > 0 && c == body_pos - 1) {
1925 tmpx += fill_label_hfill +
1926 font_metrics::width(layout->labelsep,
1927 getLabelFont(bv()->buffer(), &*rit->par()));
1928 if (rit->par()->isLineSeparator(body_pos - 1))
1929 tmpx -= singleWidth(&*rit->par(), body_pos - 1);
1932 if (hfillExpansion(*this, rit, c)) {
1933 tmpx += singleWidth(&*rit->par(), c);
1937 tmpx += fill_label_hfill;
1938 } else if (rit->par()->isSeparator(c)) {
1939 tmpx += singleWidth(&*rit->par(), c);
1941 tmpx+= fill_separator;
1943 tmpx += singleWidth(&*rit->par(), c);
1948 if ((tmpx + last_tmpx) / 2 > x) {
1953 if (vc > last + 1) // This shouldn't happen.
1957 // This (rtl_support test) is not needed, but gives
1958 // some speedup if rtl_support=false
1959 bool const lastrow = lyxrc.rtl_support &&
1960 (boost::next(rit) == rowlist_.end() ||
1961 boost::next(rit)->par() != rit->par());
1962 // If lastrow is false, we don't need to compute
1963 // the value of rtl.
1964 bool const rtl = (lastrow)
1965 ? rit->par()->isRightToLeftPar(bv()->buffer()->params)
1968 ((rtl && left_side && vc == rit->pos() && x < tmpx - 5) ||
1969 (!rtl && !left_side && vc == last + 1 && x > tmpx + 5)))
1971 else if (vc == rit->pos()) {
1973 if (bidi_level(c) % 2 == 1)
1976 c = vis2log(vc - 1);
1977 bool const rtl = (bidi_level(c) % 2 == 1);
1978 if (left_side == rtl) {
1980 boundary = isBoundary(bv()->buffer(), &*rit->par(), c);
1984 if (rit->pos() <= last && c > last
1985 && rit->par()->isNewline(last)) {
1986 if (bidi_level(last) % 2 == 0)
1987 tmpx -= singleWidth(&*rit->par(), last);
1989 tmpx += singleWidth(&*rit->par(), last);
1999 void LyXText::setCursorFromCoordinates(int x, int y)
2001 LyXCursor old_cursor = cursor;
2003 setCursorFromCoordinates(cursor, x, y);
2005 deleteEmptyParagraphMechanism(old_cursor);
2012 * return true if the cursor given is at the end of a row,
2013 * and the next row is filled by an inset that spans an entire
2016 bool beforeFullRowInset(LyXText & lt, RowList::iterator row,
2018 if (boost::next(row) == lt.rows().end())
2020 Row const & next = *boost::next(row);
2022 if (next.pos() != cur.pos() || next.par() != cur.par())
2024 if (!cur.par()->isInset(cur.pos()))
2026 Inset const * inset = cur.par()->getInset(cur.pos());
2027 if (inset->needFullRow() || inset->display())
2034 void LyXText::setCursorFromCoordinates(LyXCursor & cur, int x, int y)
2036 // Get the row first.
2038 RowList::iterator row = getRowNearY(y);
2040 pos_type const column = getColumnNearX(row, x, bound);
2041 cur.par(&*row->par());
2042 cur.pos(row->pos() + column);
2044 cur.y(y + row->baseline());
2047 if (beforeFullRowInset(*this, row, cur)) {
2048 pos_type last = lastPrintablePos(*this, row);
2049 float x = getCursorX(boost::next(row), cur.pos(), last, bound);
2051 cur.iy(y + row->height() + boost::next(row)->baseline());
2052 cur.irow(boost::next(row));
2058 cur.boundary(bound);
2062 void LyXText::cursorLeft(bool internal)
2064 if (cursor.pos() > 0) {
2065 bool boundary = cursor.boundary();
2066 setCursor(cursor.par(), cursor.pos() - 1, true, false);
2067 if (!internal && !boundary &&
2068 isBoundary(bv()->buffer(), cursor.par(), cursor.pos() + 1))
2069 setCursor(cursor.par(), cursor.pos() + 1, true, true);
2070 } else if (cursor.par()->previous()) { // steps into the above paragraph.
2071 Paragraph * par = cursor.par()->previous();
2072 setCursor(par, par->size());
2077 void LyXText::cursorRight(bool internal)
2079 if (!internal && cursor.boundary() &&
2080 !cursor.par()->isNewline(cursor.pos()))
2081 setCursor(cursor.par(), cursor.pos(), true, false);
2082 else if (cursor.pos() < cursor.par()->size()) {
2083 setCursor(cursor.par(), cursor.pos() + 1, true, false);
2085 isBoundary(bv()->buffer(), cursor.par(), cursor.pos()))
2086 setCursor(cursor.par(), cursor.pos(), true, true);
2087 } else if (cursor.par()->next())
2088 setCursor(cursor.par()->next(), 0);
2092 void LyXText::cursorUp(bool selecting)
2095 int x = cursor.x_fix();
2096 int y = cursor.y() - cursor.row()->baseline() - 1;
2097 setCursorFromCoordinates(x, y);
2100 int y1 = cursor.iy() - topy;
2103 Inset * inset_hit = checkInsetHit(x, y1);
2104 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2105 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2109 setCursorFromCoordinates(bv(), cursor.x_fix(),
2110 cursor.y() - cursor.row()->baseline() - 1);
2115 void LyXText::cursorDown(bool selecting)
2118 int x = cursor.x_fix();
2119 int y = cursor.y() - cursor.row()->baseline() +
2120 cursor.row()->height() + 1;
2121 setCursorFromCoordinates(x, y);
2122 if (!selecting && cursor.row() == cursor.irow()) {
2124 int y1 = cursor.iy() - topy;
2127 Inset * inset_hit = checkInsetHit(x, y1);
2128 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2129 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2133 setCursorFromCoordinates(bv(), cursor.x_fix(),
2134 cursor.y() - cursor.row()->baseline()
2135 + cursor.row()->height() + 1);
2140 void LyXText::cursorUpParagraph()
2142 if (cursor.pos() > 0) {
2143 setCursor(cursor.par(), 0);
2145 else if (cursor.par()->previous()) {
2146 setCursor(cursor.par()->previous(), 0);
2151 void LyXText::cursorDownParagraph()
2153 if (cursor.par()->next()) {
2154 setCursor(cursor.par()->next(), 0);
2156 setCursor(cursor.par(), cursor.par()->size());
2160 // fix the cursor `cur' after a characters has been deleted at `where'
2161 // position. Called by deleteEmptyParagraphMechanism
2162 void LyXText::fixCursorAfterDelete(LyXCursor & cur,
2163 LyXCursor const & where)
2165 // if cursor is not in the paragraph where the delete occured,
2167 if (cur.par() != where.par())
2170 // if cursor position is after the place where the delete occured,
2172 if (cur.pos() > where.pos())
2173 cur.pos(cur.pos()-1);
2175 // check also if we don't want to set the cursor on a spot behind the
2176 // pagragraph because we erased the last character.
2177 if (cur.pos() > cur.par()->size())
2178 cur.pos(cur.par()->size());
2180 // recompute row et al. for this cursor
2181 setCursor(cur, cur.par(), cur.pos(), cur.boundary());
2185 bool LyXText::deleteEmptyParagraphMechanism(LyXCursor const & old_cursor)
2187 // Would be wrong to delete anything if we have a selection.
2188 if (selection.set())
2191 // We allow all kinds of "mumbo-jumbo" when freespacing.
2192 if (old_cursor.par()->layout()->free_spacing
2193 || old_cursor.par()->isFreeSpacing()) {
2197 /* Ok I'll put some comments here about what is missing.
2198 I have fixed BackSpace (and thus Delete) to not delete
2199 double-spaces automagically. I have also changed Cut,
2200 Copy and Paste to hopefully do some sensible things.
2201 There are still some small problems that can lead to
2202 double spaces stored in the document file or space at
2203 the beginning of paragraphs. This happens if you have
2204 the cursor betwenn to spaces and then save. Or if you
2205 cut and paste and the selection have a space at the
2206 beginning and then save right after the paste. I am
2207 sure none of these are very hard to fix, but I will
2208 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
2209 that I can get some feedback. (Lgb)
2212 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
2213 // delete the LineSeparator.
2216 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
2217 // delete the LineSeparator.
2220 // If the pos around the old_cursor were spaces, delete one of them.
2221 if (old_cursor.par() != cursor.par()
2222 || old_cursor.pos() != cursor.pos()) {
2223 // Only if the cursor has really moved
2225 if (old_cursor.pos() > 0
2226 && old_cursor.pos() < old_cursor.par()->size()
2227 && old_cursor.par()->isLineSeparator(old_cursor.pos())
2228 && old_cursor.par()->isLineSeparator(old_cursor.pos() - 1)) {
2229 old_cursor.par()->erase(old_cursor.pos() - 1);
2230 redoParagraphs(old_cursor, old_cursor.par()->next());
2232 #ifdef WITH_WARNINGS
2233 #warning This will not work anymore when we have multiple views of the same buffer
2234 // In this case, we will have to correct also the cursors held by
2235 // other bufferviews. It will probably be easier to do that in a more
2236 // automated way in LyXCursor code. (JMarc 26/09/2001)
2238 // correct all cursors held by the LyXText
2239 fixCursorAfterDelete(cursor, old_cursor);
2240 fixCursorAfterDelete(selection.cursor,
2242 fixCursorAfterDelete(selection.start,
2244 fixCursorAfterDelete(selection.end, old_cursor);
2245 fixCursorAfterDelete(last_sel_cursor,
2247 fixCursorAfterDelete(toggle_cursor, old_cursor);
2248 fixCursorAfterDelete(toggle_end_cursor,
2254 // don't delete anything if this is the ONLY paragraph!
2255 if (!old_cursor.par()->next() && !old_cursor.par()->previous())
2258 // Do not delete empty paragraphs with keepempty set.
2259 if (old_cursor.par()->layout()->keepempty)
2262 // only do our magic if we changed paragraph
2263 if (old_cursor.par() == cursor.par())
2266 // record if we have deleted a paragraph
2267 // we can't possibly have deleted a paragraph before this point
2268 bool deleted = false;
2270 if ((old_cursor.par()->empty()
2271 || (old_cursor.par()->size() == 1
2272 && old_cursor.par()->isLineSeparator(0)))) {
2273 // ok, we will delete anything
2274 LyXCursor tmpcursor;
2278 if (old_cursor.row() != rows().begin()) {
2279 const_cast<LyXText *>(this)->postPaint(old_cursor.y() - old_cursor.row()->baseline()
2280 - boost::prior(old_cursor.row())->height());
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());
2294 if (ownerParagraphs().begin() == old_cursor.par()) {
2295 ownerParagraph(&*boost::next(ownerParagraphs().begin()));
2298 delete old_cursor.par();
2300 /* Breakagain the next par. Needed because of
2301 * the parindent that can occur or dissappear.
2302 * The next row can change its height, if
2303 * there is another layout before */
2304 if (refresh_row != rows().end()) {
2305 if (boost::next(refresh_row) != rows().end()) {
2306 breakAgain(boost::next(refresh_row));
2309 setHeightOfRow(refresh_row);
2312 RowList::iterator nextrow = boost::next(old_cursor.row());
2313 const_cast<LyXText *>(this)->postPaint(
2314 old_cursor.y() - old_cursor.row()->baseline());
2317 cursor = old_cursor; // that undo can restore the right cursor position
2318 Paragraph * endpar = old_cursor.par()->next();
2319 if (endpar && endpar->getDepth()) {
2320 while (endpar && endpar->getDepth()) {
2321 endpar = endpar->next();
2324 setUndo(bv(), Undo::DELETE, old_cursor.par(), endpar);
2328 removeRow(old_cursor.row());
2330 if (ownerParagraphs().begin() == old_cursor.par()) {
2331 ownerParagraph(&*boost::next(ownerParagraphs().begin()));
2334 delete old_cursor.par();
2336 /* Breakagain the next par. Needed because of
2337 the parindent that can occur or dissappear.
2338 The next row can change its height, if
2339 there is another layout before */
2340 if (nextrow != rows().end()) {
2341 breakAgain(nextrow);
2347 setCursorIntern(cursor.par(), cursor.pos());
2349 if (selection.cursor.par() == old_cursor.par()
2350 && selection.cursor.pos() == old_cursor.pos()) {
2351 // correct selection
2352 selection.cursor = cursor;
2356 if (old_cursor.par()->stripLeadingSpaces()) {
2357 redoParagraphs(old_cursor,
2358 old_cursor.par()->next());
2360 setCursorIntern(cursor.par(), cursor.pos());
2361 selection.cursor = cursor;
2368 ParagraphList & LyXText::ownerParagraphs() const
2371 return inset_owner->paragraphs;
2373 return bv_owner->buffer()->paragraphs;
2377 void LyXText::ownerParagraph(Paragraph * p) const
2380 inset_owner->paragraph(p);
2382 bv_owner->buffer()->paragraphs.set(p);
2387 void LyXText::ownerParagraph(int id, Paragraph * p) const
2389 Paragraph * op = bv_owner->buffer()->getParFromID(id);
2390 if (op && op->inInset()) {
2391 static_cast<InsetText *>(op->inInset())->paragraph(p);
2398 LyXText::refresh_status LyXText::refreshStatus() const
2400 return refresh_status_;
2404 void LyXText::clearPaint()
2406 refresh_status_ = REFRESH_NONE;
2407 refresh_row = rows().end();
2412 void LyXText::postPaint(int start_y)
2414 refresh_status old = refresh_status_;
2416 refresh_status_ = REFRESH_AREA;
2417 refresh_row = rows().end();
2419 if (old != REFRESH_NONE && refresh_y < start_y)
2422 refresh_y = start_y;
2427 // We are an inset's lyxtext. Tell the top-level lyxtext
2428 // it needs to update the row we're in.
2429 LyXText * t = bv()->text;
2430 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2434 // FIXME: we should probably remove this y parameter,
2435 // make refresh_y be 0, and use row->y etc.
2436 void LyXText::postRowPaint(RowList::iterator rit, int start_y)
2438 if (refresh_status_ != REFRESH_NONE && refresh_y < start_y) {
2439 refresh_status_ = REFRESH_AREA;
2442 refresh_y = start_y;
2445 if (refresh_status_ == REFRESH_AREA)
2448 refresh_status_ = REFRESH_ROW;
2454 // We are an inset's lyxtext. Tell the top-level lyxtext
2455 // it needs to update the row we're in.
2456 LyXText * t = bv()->text;
2457 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2461 bool LyXText::isInInset() const
2463 // Sub-level has non-null bv owner and
2464 // non-null inset owner.
2465 return inset_owner != 0 && bv_owner != 0;
2469 int defaultRowHeight()
2471 LyXFont const font(LyXFont::ALL_SANE);
2472 return int(font_metrics::maxAscent(font)
2473 + font_metrics::maxDescent(font) * 1.5);