1 /* This file is part of
2 * ======================================================
4 * LyX, The Document Processor
6 * Copyright 1995 Matthias Ettrich
7 * Copyright 1995-2001 The LyX Team.
9 * ====================================================== */
15 #include "paragraph.h"
16 #include "frontends/LyXView.h"
17 #include "undo_funcs.h"
19 #include "bufferparams.h"
21 #include "BufferView.h"
22 #include "CutAndPaste.h"
23 #include "frontends/Painter.h"
24 #include "frontends/font_metrics.h"
28 #include "FloatList.h"
30 #include "ParagraphParameters.h"
32 #include "lyxrow_funcs.h"
34 #include "insets/insetbibitem.h"
35 #include "insets/insetfloat.h"
37 #include "support/LAssert.h"
38 #include "support/textutils.h"
39 #include "support/lstrings.h"
41 #include "support/BoostFormat.h"
51 LyXText::LyXText(BufferView * bv)
52 : height(0), width(0), anchor_row_offset_(0),
53 inset_owner(0), the_locking_inset(0), bv_owner(bv)
55 anchor_row_ = rows().end();
56 need_break_row = rows().end();
57 refresh_row = rows().end();
63 LyXText::LyXText(BufferView * bv, InsetText * inset)
64 : height(0), width(0), anchor_row_offset_(0),
65 inset_owner(inset), the_locking_inset(0), bv_owner(bv)
67 anchor_row_ = rows().end();
68 need_break_row = rows().end();
69 refresh_row = rows().end();
75 void LyXText::init(BufferView * bview, bool reinit)
79 need_break_row = rows().end();
81 copylayouttype.erase();
84 } else if (!rowlist_.empty())
87 ParagraphList::iterator par = ownerParagraphs().begin();
88 ParagraphList::iterator end = ownerParagraphs().end();
90 current_font = getFont(bview->buffer(), &*par, 0);
92 for (; par != end; ++par) {
93 insertParagraph(&*par, rowlist_.end());
95 setCursorIntern(&*rowlist_.begin()->par(), 0);
96 selection.cursor = cursor;
104 LyXFont const realizeFont(LyXFont const & font,
108 LyXTextClass const & tclass = buf->params.getLyXTextClass();
109 LyXFont tmpfont(font);
110 Paragraph::depth_type par_depth = par->getDepth();
112 // Resolve against environment font information
113 while (par && par_depth && !tmpfont.resolved()) {
114 par = par->outerHook();
116 tmpfont.realize(par->layout()->font);
117 par_depth = par->getDepth();
121 tmpfont.realize(tclass.defaultfont());
129 // Gets the fully instantiated font at a given position in a paragraph
130 // Basically the same routine as Paragraph::getFont() in paragraph.C.
131 // The difference is that this one is used for displaying, and thus we
132 // are allowed to make cosmetic improvements. For instance make footnotes
134 // If position is -1, we get the layout font of the paragraph.
135 // If position is -2, we get the font of the manual label of the paragraph.
136 LyXFont const LyXText::getFont(Buffer const * buf, Paragraph * par,
139 lyx::Assert(pos >= 0);
141 LyXLayout_ptr const & layout = par->layout();
143 // We specialize the 95% common case:
144 if (!par->getDepth()) {
145 if (layout->labeltype == LABEL_MANUAL
146 && pos < par->beginningOfBody()) {
148 LyXFont f = par->getFontSettings(buf->params, pos);
150 par->inInset()->getDrawFont(f);
151 return f.realize(layout->reslabelfont);
153 LyXFont f = par->getFontSettings(buf->params, pos);
155 par->inInset()->getDrawFont(f);
156 return f.realize(layout->resfont);
160 // The uncommon case need not be optimized as much
164 if (pos < par->beginningOfBody()) {
166 layoutfont = layout->labelfont;
169 layoutfont = layout->font;
172 LyXFont tmpfont = par->getFontSettings(buf->params, pos);
173 tmpfont.realize(layoutfont);
176 par->inInset()->getDrawFont(tmpfont);
178 return realizeFont(tmpfont, buf, par);
182 LyXFont const LyXText::getLayoutFont(Buffer const * buf, Paragraph * par) const
184 LyXLayout_ptr const & layout = par->layout();
186 if (!par->getDepth()) {
187 return layout->resfont;
190 return realizeFont(layout->font, buf, par);
194 LyXFont const LyXText::getLabelFont(Buffer const * buf, Paragraph * par) const
196 LyXLayout_ptr const & layout = par->layout();
198 if (!par->getDepth()) {
199 return layout->reslabelfont;
202 return realizeFont(layout->labelfont, buf, par);
206 void LyXText::setCharFont(Paragraph * par,
207 pos_type pos, LyXFont const & fnt,
210 Buffer const * buf = bv()->buffer();
211 LyXFont font = getFont(buf, par, pos);
212 font.update(fnt, buf->params.language, toggleall);
213 // Let the insets convert their font
214 if (par->isInset(pos)) {
215 Inset * inset = par->getInset(pos);
216 if (isEditableInset(inset)) {
217 UpdatableInset * uinset =
218 static_cast<UpdatableInset *>(inset);
219 uinset->setFont(bv(), fnt, toggleall, true);
223 // Plug thru to version below:
224 setCharFont(buf, par, pos, font);
228 void LyXText::setCharFont(Buffer const * buf, Paragraph * par,
229 pos_type pos, LyXFont const & fnt)
233 LyXTextClass const & tclass = buf->params.getLyXTextClass();
234 LyXLayout_ptr const & layout = par->layout();
236 // Get concrete layout font to reduce against
239 if (pos < par->beginningOfBody())
240 layoutfont = layout->labelfont;
242 layoutfont = layout->font;
244 // Realize against environment font information
245 if (par->getDepth()) {
246 Paragraph * tp = par;
247 while (!layoutfont.resolved() && tp && tp->getDepth()) {
248 tp = tp->outerHook();
250 layoutfont.realize(tp->layout()->font);
254 layoutfont.realize(tclass.defaultfont());
256 // Now, reduce font against full layout font
257 font.reduce(layoutfont);
259 par->setFont(pos, font);
263 // removes the row and reset the touched counters
264 void LyXText::removeRow(RowList::iterator rit)
266 /* FIXME: when we cache the bview, this should just
267 * become a postPaint(), I think */
268 if (refresh_row == rit) {
269 if (rit == rows().begin())
270 refresh_row = boost::next(rit);
272 refresh_row = boost::prior(rit);
274 // what about refresh_y
277 if (anchor_row_ == rit) {
278 if (rit != rows().begin()) {
279 anchor_row_ = boost::prior(rit);
280 anchor_row_offset_ += boost::prior(rit)->height();
282 anchor_row_ = boost::next(rit);
283 anchor_row_offset_ -= rit->height();
287 // the text becomes smaller
288 height -= rit->height();
294 // remove all following rows of the paragraph of the specified row.
295 void LyXText::removeParagraph(RowList::iterator rit)
297 ParagraphList::iterator tmppit = rit->par();
300 while (rit != rows().end() && rit->par() == tmppit) {
301 RowList::iterator tmprit = boost::next(rit);
308 #warning FIXME Convert this to ParagraphList::iterator
309 void LyXText::insertParagraph(Paragraph * par, RowList::iterator rowit)
311 // insert a new row, starting at position 0
313 RowList::iterator rit = rowlist_.insert(rowit, newrow);
315 // and now append the whole paragraph before the new row
316 appendParagraph(rit);
320 Inset * LyXText::getInset() const
322 if (cursor.pos() < cursor.par()->size()
323 && cursor.par()->isInset(cursor.pos())) {
324 return cursor.par()->getInset(cursor.pos());
330 void LyXText::toggleInset()
332 Inset * inset = getInset();
333 // is there an editable inset at cursor position?
334 if (!isEditableInset(inset)) {
335 // No, try to see if we are inside a collapsable inset
336 if (inset_owner && inset_owner->owner()
337 && inset_owner->owner()->isOpen()) {
338 bv()->unlockInset(static_cast<UpdatableInset *>(inset_owner->owner()));
339 inset_owner->owner()->close(bv());
340 bv()->getLyXText()->cursorRight(bv());
344 //bv()->owner()->message(inset->editMessage());
346 // do we want to keep this?? (JMarc)
347 if (!isHighlyEditableInset(inset))
348 setCursorParUndo(bv());
350 if (inset->isOpen()) {
356 bv()->updateInset(inset);
360 /* used in setlayout */
361 // Asger is not sure we want to do this...
362 void LyXText::makeFontEntriesLayoutSpecific(Buffer const & buf,
365 LyXLayout_ptr const & layout = par.layout();
368 for (pos_type pos = 0; pos < par.size(); ++pos) {
369 if (pos < par.beginningOfBody())
370 layoutfont = layout->labelfont;
372 layoutfont = layout->font;
374 LyXFont tmpfont = par.getFontSettings(buf.params, pos);
375 tmpfont.reduce(layoutfont);
376 par.setFont(pos, tmpfont);
381 Paragraph * LyXText::setLayout(LyXCursor & cur, LyXCursor & sstart_cur,
382 LyXCursor & send_cur,
383 string const & layout)
385 Paragraph * endpar = send_cur.par()->next();
386 Paragraph * undoendpar = endpar;
388 if (endpar && endpar->getDepth()) {
389 while (endpar && endpar->getDepth()) {
390 endpar = endpar->next();
394 endpar = endpar->next(); // because of parindents etc.
397 setUndo(bv(), Undo::EDIT, sstart_cur.par(), undoendpar);
399 // ok we have a selection. This is always between sstart_cur
400 // and sel_end cursor
402 Paragraph * par = sstart_cur.par();
403 Paragraph * epar = send_cur.par()->next();
405 LyXLayout_ptr const & lyxlayout =
406 bv()->buffer()->params.getLyXTextClass()[layout];
409 par->applyLayout(lyxlayout);
410 makeFontEntriesLayoutSpecific(*bv()->buffer(), *par);
411 Paragraph * fppar = par;
412 fppar->params().spaceTop(lyxlayout->fill_top ?
413 VSpace(VSpace::VFILL)
414 : VSpace(VSpace::NONE));
415 fppar->params().spaceBottom(lyxlayout->fill_bottom ?
416 VSpace(VSpace::VFILL)
417 : VSpace(VSpace::NONE));
418 if (lyxlayout->margintype == MARGIN_MANUAL)
419 par->setLabelWidthString(lyxlayout->labelstring());
422 } while (par != epar);
428 // set layout over selection and make a total rebreak of those paragraphs
429 void LyXText::setLayout(string const & layout)
431 LyXCursor tmpcursor = cursor; /* store the current cursor */
433 // if there is no selection just set the layout
434 // of the current paragraph */
435 if (!selection.set()) {
436 selection.start = cursor; // dummy selection
437 selection.end = cursor;
439 Paragraph * endpar = setLayout(cursor, selection.start,
440 selection.end, layout);
441 redoParagraphs(selection.start, endpar);
443 // we have to reset the selection, because the
444 // geometry could have changed
445 setCursor(selection.start.par(),
446 selection.start.pos(), false);
447 selection.cursor = cursor;
448 setCursor(selection.end.par(), selection.end.pos(), false);
452 setCursor(tmpcursor.par(), tmpcursor.pos(), true);
456 bool LyXText::changeDepth(bv_funcs::DEPTH_CHANGE type, bool test_only)
458 ParagraphList::iterator pit(cursor.par());
459 ParagraphList::iterator end(cursor.par());
460 ParagraphList::iterator start = pit;
462 if (selection.set()) {
463 pit = selection.start.par();
464 end = selection.end.par();
468 ParagraphList::iterator pastend = end;
470 setUndo(bv(), Undo::EDIT, &(*start), &(*pastend));
472 bool changed = false;
474 int prev_after_depth = 0;
475 #warning parlist ... could be nicer ?
476 if (start != ownerParagraphs().begin())
477 prev_after_depth = boost::prior(start)->getMaxDepthAfter();
480 int const depth = pit->params().depth();
481 if (type == bv_funcs::INC_DEPTH) {
482 if (depth < prev_after_depth
483 && pit->layout()->labeltype != LABEL_BIBLIO) {
486 pit->params().depth(depth + 1);
491 pit->params().depth(depth - 1);
494 prev_after_depth = pit->getMaxDepthAfter();
505 // Wow, redoParagraphs is stupid.
507 setCursor(tmpcursor, &(*start), 0);
508 redoParagraphs(tmpcursor, &(*pastend));
510 // We need to actually move the text->cursor. I don't
511 // understand why ...
514 // we have to reset the visual selection because the
515 // geometry could have changed
516 if (selection.set()) {
517 setCursor(selection.start.par(), selection.start.pos());
518 selection.cursor = cursor;
519 setCursor(selection.end.par(), selection.end.pos());
522 // this handles the counter labels, and also fixes up
523 // depth values for follow-on (child) paragraphs
527 setCursor(tmpcursor.par(), tmpcursor.pos());
533 // set font over selection and make a total rebreak of those paragraphs
534 void LyXText::setFont(LyXFont const & font, bool toggleall)
536 // if there is no selection just set the current_font
537 if (!selection.set()) {
538 // Determine basis font
540 if (cursor.pos() < cursor.par()->beginningOfBody()) {
541 layoutfont = getLabelFont(bv()->buffer(),
544 layoutfont = getLayoutFont(bv()->buffer(),
547 // Update current font
548 real_current_font.update(font,
549 bv()->buffer()->params.language,
552 // Reduce to implicit settings
553 current_font = real_current_font;
554 current_font.reduce(layoutfont);
555 // And resolve it completely
556 real_current_font.realize(layoutfont);
561 LyXCursor tmpcursor = cursor; // store the current cursor
563 // ok we have a selection. This is always between sel_start_cursor
564 // and sel_end cursor
566 setUndo(bv(), Undo::EDIT,
567 selection.start.par(), selection.end.par()->next());
569 cursor = selection.start;
570 while (cursor.par() != selection.end.par() ||
571 cursor.pos() < selection.end.pos())
573 if (cursor.pos() < cursor.par()->size()) {
574 // an open footnote should behave like a closed one
575 setCharFont(cursor.par(), cursor.pos(),
577 cursor.pos(cursor.pos() + 1);
580 cursor.par(cursor.par()->next());
585 redoParagraphs(selection.start, selection.end.par()->next());
587 // we have to reset the selection, because the
588 // geometry could have changed, but we keep
589 // it for user convenience
590 setCursor(selection.start.par(), selection.start.pos());
591 selection.cursor = cursor;
592 setCursor(selection.end.par(), selection.end.pos());
594 setCursor(tmpcursor.par(), tmpcursor.pos(), true,
595 tmpcursor.boundary());
599 void LyXText::redoHeightOfParagraph()
601 RowList::iterator tmprow = cursor.row();
602 int y = cursor.y() - tmprow->baseline();
604 setHeightOfRow(tmprow);
606 while (tmprow != rows().begin()
607 && boost::prior(tmprow)->par() == tmprow->par()) {
609 y -= tmprow->height();
610 setHeightOfRow(tmprow);
615 setCursor(cursor.par(), cursor.pos(), false, cursor.boundary());
619 void LyXText::redoDrawingOfParagraph(LyXCursor const & cur)
621 RowList::iterator tmprow = cur.row();
623 int y = cur.y() - tmprow->baseline();
624 setHeightOfRow(tmprow);
626 while (tmprow != rows().begin()
627 && boost::prior(tmprow)->par() == tmprow->par()) {
629 y -= tmprow->height();
633 setCursor(cur.par(), cur.pos());
637 // deletes and inserts again all paragaphs between the cursor
638 // and the specified par
639 // This function is needed after SetLayout and SetFont etc.
640 void LyXText::redoParagraphs(LyXCursor const & cur,
641 Paragraph const * ep)
643 RowList::iterator tmprit = cur.row();
644 ParagraphList::iterator endpit(const_cast<Paragraph*>(ep));
645 int y = cur.y() - tmprit->baseline();
647 ParagraphList::iterator first_phys_pit;
648 if (tmprit == rows().begin()) {
649 // A trick/hack for UNDO.
650 // This is needed because in an UNDO/REDO we could have
651 // changed the ownerParagrah() so the paragraph inside
652 // the row is NOT my really first par anymore.
653 // Got it Lars ;) (Jug 20011206)
654 first_phys_pit = ownerParagraphs().begin();
656 // In here prevrit could be set to rows().end(). (Lgb)
658 first_phys_pit = tmprit->par();
659 while (tmprit != rows().begin()
660 && boost::prior(tmprit)->par() == first_phys_pit)
663 y -= tmprit->height();
666 // Is it possible to put the prevrit setting in here? (Lgb)
669 RowList::iterator prevrit;
670 bool good_prevrit = false;
672 // It seems to mee that good_prevrit is not needed if we let
673 // a bad prevrit have the value rows().end() (Lgb)
674 if (tmprit != rows().begin()) {
675 prevrit = boost::prior(tmprit);
680 while (tmprit != rows().end() && tmprit->par() != endpit) {
681 RowList::iterator tmprit2 = tmprit++;
685 // Reinsert the paragraphs.
686 ParagraphList::iterator tmppit = first_phys_pit;
688 // See if this loop can be rewritten as a while loop instead.
689 // That should also make the code a bit easier to read. (Lgb)
691 if (tmppit != ownerParagraphs().end()) {
692 insertParagraph(&*tmppit, tmprit);
693 while (tmprit != rows().end()
694 && tmprit->par() == tmppit) {
699 } while (tmppit != ownerParagraphs().end() && tmppit != endpit);
702 // If the above changes are done, then we can compare prevrit
703 // with rows().end() here. (Lgb)
705 setHeightOfRow(prevrit);
706 const_cast<LyXText *>(this)->postPaint(y - prevrit->height());
708 setHeightOfRow(rows().begin());
709 const_cast<LyXText *>(this)->postPaint(0);
711 if (tmprit != rows().end())
712 setHeightOfRow(tmprit);
717 void LyXText::fullRebreak()
719 if (rows().empty()) {
723 if (need_break_row != rows().end()) {
724 breakAgain(need_break_row);
725 need_break_row = rows().end();
731 // important for the screen
734 // the cursor set functions have a special mechanism. When they
735 // realize, that you left an empty paragraph, they will delete it.
736 // They also delete the corresponding row
738 // need the selection cursor:
739 void LyXText::setSelection()
741 bool const lsel = selection.set();
743 if (!selection.set()) {
744 last_sel_cursor = selection.cursor;
745 selection.start = selection.cursor;
746 selection.end = selection.cursor;
751 // first the toggling area
752 if (cursor.y() < last_sel_cursor.y()
753 || (cursor.y() == last_sel_cursor.y()
754 && cursor.x() < last_sel_cursor.x())) {
755 toggle_end_cursor = last_sel_cursor;
756 toggle_cursor = cursor;
758 toggle_end_cursor = cursor;
759 toggle_cursor = last_sel_cursor;
762 last_sel_cursor = cursor;
764 // and now the whole selection
766 if (selection.cursor.par() == cursor.par())
767 if (selection.cursor.pos() < cursor.pos()) {
768 selection.end = cursor;
769 selection.start = selection.cursor;
771 selection.end = selection.cursor;
772 selection.start = cursor;
774 else if (selection.cursor.y() < cursor.y() ||
775 (selection.cursor.y() == cursor.y()
776 && selection.cursor.x() < cursor.x())) {
777 selection.end = cursor;
778 selection.start = selection.cursor;
781 selection.end = selection.cursor;
782 selection.start = cursor;
785 // a selection with no contents is not a selection
786 if (selection.start.par() == selection.end.par() &&
787 selection.start.pos() == selection.end.pos())
788 selection.set(false);
790 if (inset_owner && (selection.set() || lsel))
791 inset_owner->setUpdateStatus(bv(), InsetText::SELECTION);
795 string const LyXText::selectionAsString(Buffer const * buffer,
798 if (!selection.set()) return string();
800 // should be const ...
801 Paragraph * startpar(selection.start.par());
802 Paragraph * endpar(selection.end.par());
803 pos_type const startpos(selection.start.pos());
804 pos_type const endpos(selection.end.pos());
806 if (startpar == endpar) {
807 return startpar->asString(buffer, startpos, endpos, label);
812 // First paragraph in selection
813 result += startpar->asString(buffer, startpos, startpar->size(), label) + "\n\n";
815 // The paragraphs in between (if any)
816 LyXCursor tmpcur(selection.start);
817 tmpcur.par(tmpcur.par()->next());
818 while (tmpcur.par() != endpar) {
819 result += tmpcur.par()->asString(buffer, 0,
820 tmpcur.par()->size(),
822 tmpcur.par(tmpcur.par()->next());
825 // Last paragraph in selection
826 result += endpar->asString(buffer, 0, endpos, label);
832 void LyXText::clearSelection()
834 selection.set(false);
835 selection.mark(false);
836 last_sel_cursor = selection.end = selection.start = selection.cursor = cursor;
837 // reset this in the bv_owner!
838 if (bv_owner && bv_owner->text)
839 bv_owner->text->xsel_cache.set(false);
843 void LyXText::cursorHome()
845 setCursor(cursor.par(), cursor.row()->pos());
849 void LyXText::cursorEnd()
851 if (cursor.par()->empty())
854 // There is a lot of unneeded recalculation going on here:
855 // - boost::next(curosr.row())
856 // - lastPost(*this, cursor.row())
858 if (boost::next(cursor.row()) == rows().end()
859 || boost::next(cursor.row())->par() != cursor.row()->par()) {
860 setCursor(cursor.par(), lastPos(*this, cursor.row()) + 1);
862 if (!cursor.par()->empty() &&
863 (cursor.par()->getChar(lastPos(*this, cursor.row())) == ' '
864 || cursor.par()->isNewline(lastPos(*this, cursor.row())))) {
865 setCursor(cursor.par(), lastPos(*this, cursor.row()));
867 setCursor(cursor.par(),
868 lastPos(*this, cursor.row()) + 1);
874 void LyXText::cursorTop()
876 while (cursor.par()->previous())
877 cursor.par(cursor.par()->previous());
878 setCursor(cursor.par(), 0);
882 void LyXText::cursorBottom()
884 while (cursor.par()->next())
885 cursor.par(cursor.par()->next());
886 setCursor(cursor.par(), cursor.par()->size());
890 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
892 // If the mask is completely neutral, tell user
893 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
894 // Could only happen with user style
895 bv()->owner()->message(_("No font change defined. Use Character under the Layout menu to define font change."));
899 // Try implicit word selection
900 // If there is a change in the language the implicit word selection
902 LyXCursor resetCursor = cursor;
903 bool implicitSelection = (font.language() == ignore_language
904 && font.number() == LyXFont::IGNORE)
905 ? selectWordWhenUnderCursor(WHOLE_WORD_STRICT) : false;
908 setFont(font, toggleall);
910 // Implicit selections are cleared afterwards
911 //and cursor is set to the original position.
912 if (implicitSelection) {
914 cursor = resetCursor;
915 setCursor(cursor.par(), cursor.pos());
916 selection.cursor = cursor;
919 inset_owner->setUpdateStatus(bv(), InsetText::CURSOR_PAR);
923 string LyXText::getStringToIndex()
925 // Try implicit word selection
926 // If there is a change in the language the implicit word selection
928 LyXCursor const reset_cursor = cursor;
929 bool const implicitSelection = selectWordWhenUnderCursor(PREVIOUS_WORD);
932 if (!selection.set())
933 bv()->owner()->message(_("Nothing to index!"));
934 else if (selection.start.par() != selection.end.par())
935 bv()->owner()->message(_("Cannot index more than one paragraph!"));
937 idxstring = selectionAsString(bv()->buffer(), false);
939 // Reset cursors to their original position.
940 cursor = reset_cursor;
941 setCursor(cursor.par(), cursor.pos());
942 selection.cursor = cursor;
944 // Clear the implicit selection.
945 if (implicitSelection)
952 // the DTP switches for paragraphs. LyX will store them in the first
953 // physicla paragraph. When a paragraph is broken, the top settings rest,
954 // the bottom settings are given to the new one. So I can make shure,
955 // they do not duplicate themself and you cannnot make dirty things with
958 void LyXText::setParagraph(bool line_top, bool line_bottom,
959 bool pagebreak_top, bool pagebreak_bottom,
960 VSpace const & space_top,
961 VSpace const & space_bottom,
962 Spacing const & spacing,
964 string const & labelwidthstring,
967 LyXCursor tmpcursor = cursor;
968 if (!selection.set()) {
969 selection.start = cursor;
970 selection.end = cursor;
973 // make sure that the depth behind the selection are restored, too
974 Paragraph * endpar = selection.end.par()->next();
975 Paragraph * undoendpar = endpar;
977 if (endpar && endpar->getDepth()) {
978 while (endpar && endpar->getDepth()) {
979 endpar = endpar->next();
984 // because of parindents etc.
985 endpar = endpar->next();
988 setUndo(bv(), Undo::EDIT, selection.start.par(), undoendpar);
991 Paragraph * tmppar = selection.end.par();
993 while (tmppar != selection.start.par()->previous()) {
994 setCursor(tmppar, 0);
995 postPaint(cursor.y() - cursor.row()->baseline());
996 cursor.par()->params().lineTop(line_top);
997 cursor.par()->params().lineBottom(line_bottom);
998 cursor.par()->params().pagebreakTop(pagebreak_top);
999 cursor.par()->params().pagebreakBottom(pagebreak_bottom);
1000 cursor.par()->params().spaceTop(space_top);
1001 cursor.par()->params().spaceBottom(space_bottom);
1002 cursor.par()->params().spacing(spacing);
1003 // does the layout allow the new alignment?
1004 LyXLayout_ptr const & layout = cursor.par()->layout();
1006 if (align == LYX_ALIGN_LAYOUT)
1007 align = layout->align;
1008 if (align & layout->alignpossible) {
1009 if (align == layout->align)
1010 cursor.par()->params().align(LYX_ALIGN_LAYOUT);
1012 cursor.par()->params().align(align);
1014 cursor.par()->setLabelWidthString(labelwidthstring);
1015 cursor.par()->params().noindent(noindent);
1016 tmppar = cursor.par()->previous();
1019 redoParagraphs(selection.start, endpar);
1022 setCursor(selection.start.par(), selection.start.pos());
1023 selection.cursor = cursor;
1024 setCursor(selection.end.par(), selection.end.pos());
1026 setCursor(tmpcursor.par(), tmpcursor.pos());
1028 bv()->updateInset(inset_owner);
1032 // set the counter of a paragraph. This includes the labels
1033 void LyXText::setCounter(Buffer const * buf, Paragraph * par)
1035 LyXTextClass const & textclass = buf->params.getLyXTextClass();
1036 LyXLayout_ptr const & layout = par->layout();
1038 if (par->previous()) {
1040 par->params().appendix(par->previous()->params().appendix());
1041 if (!par->params().appendix() && par->params().startOfAppendix()) {
1042 par->params().appendix(true);
1043 textclass.counters().reset();
1045 par->enumdepth = par->previous()->enumdepth;
1046 par->itemdepth = par->previous()->itemdepth;
1048 par->params().appendix(par->params().startOfAppendix());
1053 /* Maybe we have to increment the enumeration depth.
1054 * BUT, enumeration in a footnote is considered in isolation from its
1055 * surrounding paragraph so don't increment if this is the
1056 * first line of the footnote
1057 * AND, bibliographies can't have their depth changed ie. they
1058 * are always of depth 0
1061 && par->previous()->getDepth() < par->getDepth()
1062 && par->previous()->layout()->labeltype == LABEL_COUNTER_ENUMI
1063 && par->enumdepth < 3
1064 && layout->labeltype != LABEL_BIBLIO) {
1068 // Maybe we have to decrement the enumeration depth, see note above
1070 && par->previous()->getDepth() > par->getDepth()
1071 && layout->labeltype != LABEL_BIBLIO) {
1072 par->enumdepth = par->depthHook(par->getDepth())->enumdepth;
1075 if (!par->params().labelString().empty()) {
1076 par->params().labelString(string());
1079 if (layout->margintype == MARGIN_MANUAL) {
1080 if (par->params().labelWidthString().empty()) {
1081 par->setLabelWidthString(layout->labelstring());
1084 par->setLabelWidthString(string());
1087 // is it a layout that has an automatic label?
1088 if (layout->labeltype >= LABEL_COUNTER_CHAPTER) {
1089 int const i = layout->labeltype - LABEL_COUNTER_CHAPTER;
1093 if (i >= 0 && i <= buf->params.secnumdepth) {
1097 textclass.counters().step(layout->latexname());
1099 // Is there a label? Useful for Chapter layout
1100 if (!par->params().appendix()) {
1101 s << layout->labelstring();
1103 s << layout->labelstring_appendix();
1106 // Use of an integer is here less than elegant. For now.
1107 int head = textclass.maxcounter() - LABEL_COUNTER_CHAPTER;
1108 if (!par->params().appendix()) {
1109 numbertype = "sectioning";
1111 numbertype = "appendix";
1112 if (par->isRightToLeftPar(buf->params))
1113 langtype = "hebrew";
1118 s << textclass.counters()
1119 .numberLabel(layout->latexname(),
1120 numbertype, langtype, head);
1122 par->params().labelString(STRCONV(s.str()));
1124 // reset enum counters
1125 textclass.counters().reset("enum");
1126 } else if (layout->labeltype < LABEL_COUNTER_ENUMI) {
1127 textclass.counters().reset("enum");
1128 } else if (layout->labeltype == LABEL_COUNTER_ENUMI) {
1130 // Yes I know this is a really, really! bad solution
1132 string enumcounter("enum");
1134 switch (par->enumdepth) {
1143 enumcounter += "iv";
1146 // not a valid enumdepth...
1150 textclass.counters().step(enumcounter);
1152 s << textclass.counters()
1153 .numberLabel(enumcounter, "enumeration");
1154 par->params().labelString(STRCONV(s.str()));
1156 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
1157 textclass.counters().step("bibitem");
1158 int number = textclass.counters().value("bibitem");
1159 if (par->bibitem()) {
1160 par->bibitem()->setCounter(number);
1161 par->params().labelString(layout->labelstring());
1163 // In biblio should't be following counters but...
1165 string s = layout->labelstring();
1167 // the caption hack:
1168 if (layout->labeltype == LABEL_SENSITIVE) {
1169 Paragraph * tmppar = par;
1172 while (tmppar && tmppar->inInset()
1173 // the single '=' is intended below
1174 && (in = tmppar->inInset()->owner())) {
1175 if (in->lyxCode() == Inset::FLOAT_CODE ||
1176 in->lyxCode() == Inset::WRAP_CODE) {
1180 tmppar = in->parOwner();
1186 = textclass.floats().getType(static_cast<InsetFloat*>(in)->type());
1188 textclass.counters().step(fl.type());
1190 // Doesn't work... yet.
1191 #if USE_BOOST_FORMAT
1192 s = boost::io::str(boost::format(_("%1$s #:")) % fl.name());
1193 // s << boost::format(_("%1$s %1$d:")
1195 // % buf->counters().value(fl.name());
1198 //o << fl.name() << ' ' << buf->counters().value(fl.name()) << ":";
1199 o << fl.name() << " #:";
1200 s = STRCONV(o.str());
1203 // par->SetLayout(0);
1204 // s = layout->labelstring;
1205 s = _("Senseless: ");
1208 par->params().labelString(s);
1210 // reset the enumeration counter. They are always reset
1211 // when there is any other layout between
1212 // Just fall-through between the cases so that all
1213 // enum counters deeper than enumdepth is also reset.
1214 switch (par->enumdepth) {
1216 textclass.counters().reset("enumi");
1218 textclass.counters().reset("enumii");
1220 textclass.counters().reset("enumiii");
1222 textclass.counters().reset("enumiv");
1228 // Updates all counters. Paragraphs with changed label string will be rebroken
1229 void LyXText::updateCounters()
1231 RowList::iterator rowit = rows().begin();
1232 ParagraphList::iterator pit = rowit->par();
1234 // CHECK if this is really needed. (Lgb)
1235 bv()->buffer()->params.getLyXTextClass().counters().reset();
1237 while (pit != ownerParagraphs().end()) {
1238 while (rowit->par() != pit)
1241 string const oldLabel = pit->params().labelString();
1244 if (pit != ownerParagraphs().begin())
1245 maxdepth = boost::prior(pit)->getMaxDepthAfter();
1247 if (pit->params().depth() > maxdepth)
1248 pit->params().depth(maxdepth);
1250 // setCounter can potentially change the labelString.
1251 setCounter(bv()->buffer(), &*pit);
1253 string const & newLabel = pit->params().labelString();
1255 if (oldLabel.empty() && !newLabel.empty()) {
1256 removeParagraph(rowit);
1257 appendParagraph(rowit);
1265 void LyXText::insertInset(Inset * inset)
1267 if (!cursor.par()->insetAllowed(inset->lyxCode()))
1269 setUndo(bv(), Undo::FINISH, cursor.par(), cursor.par()->next());
1271 cursor.par()->insertInset(cursor.pos(), inset);
1272 // Just to rebreak and refresh correctly.
1273 // The character will not be inserted a second time
1274 insertChar(Paragraph::META_INSET);
1275 // If we enter a highly editable inset the cursor should be to before
1276 // the inset. This couldn't happen before as Undo was not handled inside
1277 // inset now after the Undo LyX tries to call inset->Edit(...) again
1278 // and cannot do this as the cursor is behind the inset and GetInset
1279 // does not return the inset!
1280 if (isHighlyEditableInset(inset)) {
1287 void LyXText::copyEnvironmentType()
1289 copylayouttype = cursor.par()->layout()->name();
1293 void LyXText::pasteEnvironmentType()
1295 // do nothing if there has been no previous copyEnvironmentType()
1296 if (!copylayouttype.empty())
1297 setLayout(copylayouttype);
1301 void LyXText::cutSelection(bool doclear, bool realcut)
1303 // Stuff what we got on the clipboard. Even if there is no selection.
1305 // There is a problem with having the stuffing here in that the
1306 // larger the selection the slower LyX will get. This can be
1307 // solved by running the line below only when the selection has
1308 // finished. The solution used currently just works, to make it
1309 // faster we need to be more clever and probably also have more
1310 // calls to stuffClipboard. (Lgb)
1311 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1313 // This doesn't make sense, if there is no selection
1314 if (!selection.set())
1317 // OK, we have a selection. This is always between selection.start
1318 // and selection.end
1320 // make sure that the depth behind the selection are restored, too
1321 Paragraph * endpar = selection.end.par()->next();
1322 Paragraph * undoendpar = endpar;
1324 if (endpar && endpar->getDepth()) {
1325 while (endpar && endpar->getDepth()) {
1326 endpar = endpar->next();
1327 undoendpar = endpar;
1329 } else if (endpar) {
1330 endpar = endpar->next(); // because of parindents etc.
1333 setUndo(bv(), Undo::DELETE,
1334 selection.start.par(), undoendpar);
1336 // there are two cases: cut only within one paragraph or
1337 // more than one paragraph
1338 if (selection.start.par() == selection.end.par()) {
1339 // only within one paragraph
1340 endpar = selection.end.par();
1341 int pos = selection.end.pos();
1342 CutAndPaste::cutSelection(selection.start.par(), &endpar,
1343 selection.start.pos(), pos,
1344 bv()->buffer()->params.textclass,
1346 selection.end.pos(pos);
1348 endpar = selection.end.par();
1349 int pos = selection.end.pos();
1350 CutAndPaste::cutSelection(selection.start.par(), &endpar,
1351 selection.start.pos(), pos,
1352 bv()->buffer()->params.textclass,
1355 selection.end.par(endpar);
1356 selection.end.pos(pos);
1357 cursor.pos(selection.end.pos());
1359 endpar = endpar->next();
1361 // sometimes necessary
1363 selection.start.par()->stripLeadingSpaces();
1365 redoParagraphs(selection.start, endpar);
1367 // cutSelection can invalidate the cursor so we need to set
1369 // we prefer the end for when tracking changes
1370 cursor = selection.end;
1372 // need a valid cursor. (Lgb)
1375 setCursor(cursor.par(), cursor.pos());
1376 selection.cursor = cursor;
1381 void LyXText::copySelection()
1383 // stuff the selection onto the X clipboard, from an explicit copy request
1384 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1386 // this doesnt make sense, if there is no selection
1387 if (!selection.set())
1390 // ok we have a selection. This is always between selection.start
1391 // and sel_end cursor
1393 // copy behind a space if there is one
1394 while (selection.start.par()->size() > selection.start.pos()
1395 && selection.start.par()->isLineSeparator(selection.start.pos())
1396 && (selection.start.par() != selection.end.par()
1397 || selection.start.pos() < selection.end.pos()))
1398 selection.start.pos(selection.start.pos() + 1);
1400 CutAndPaste::copySelection(selection.start.par(), selection.end.par(),
1401 selection.start.pos(), selection.end.pos(),
1402 bv()->buffer()->params.textclass);
1406 void LyXText::pasteSelection()
1408 // this does not make sense, if there is nothing to paste
1409 if (!CutAndPaste::checkPastePossible())
1412 setUndo(bv(), Undo::INSERT,
1413 cursor.par(), cursor.par()->next());
1416 Paragraph * actpar = cursor.par();
1417 int pos = cursor.pos();
1419 CutAndPaste::pasteSelection(&actpar, &endpar, pos,
1420 bv()->buffer()->params.textclass);
1422 redoParagraphs(cursor, endpar);
1424 setCursor(cursor.par(), cursor.pos());
1427 selection.cursor = cursor;
1428 setCursor(actpar, pos);
1434 void LyXText::setSelectionRange(lyx::pos_type length)
1439 selection.cursor = cursor;
1446 // simple replacing. The font of the first selected character is used
1447 void LyXText::replaceSelectionWithString(string const & str)
1449 setCursorParUndo(bv());
1452 if (!selection.set()) { // create a dummy selection
1453 selection.end = cursor;
1454 selection.start = cursor;
1457 // Get font setting before we cut
1458 pos_type pos = selection.end.pos();
1459 LyXFont const font = selection.start.par()
1460 ->getFontSettings(bv()->buffer()->params,
1461 selection.start.pos());
1463 // Insert the new string
1464 for (string::const_iterator cit = str.begin(); cit != str.end(); ++cit) {
1465 selection.end.par()->insertChar(pos, (*cit), font);
1469 // Cut the selection
1470 cutSelection(true, false);
1476 // needed to insert the selection
1477 void LyXText::insertStringAsLines(string const & str)
1479 Paragraph * par = cursor.par();
1480 pos_type pos = cursor.pos();
1481 Paragraph * endpar = cursor.par()->next();
1483 setCursorParUndo(bv());
1485 // only to be sure, should not be neccessary
1488 bv()->buffer()->insertStringAsLines(par, pos, current_font, str);
1490 redoParagraphs(cursor, endpar);
1491 setCursor(cursor.par(), cursor.pos());
1492 selection.cursor = cursor;
1493 setCursor(par, pos);
1498 // turns double-CR to single CR, others where converted into one
1499 // blank. Then InsertStringAsLines is called
1500 void LyXText::insertStringAsParagraphs(string const & str)
1502 string linestr(str);
1503 bool newline_inserted = false;
1504 for (string::size_type i = 0; i < linestr.length(); ++i) {
1505 if (linestr[i] == '\n') {
1506 if (newline_inserted) {
1507 // we know that \r will be ignored by
1508 // InsertStringA. Of course, it is a dirty
1509 // trick, but it works...
1510 linestr[i - 1] = '\r';
1514 newline_inserted = true;
1516 } else if (IsPrintable(linestr[i])) {
1517 newline_inserted = false;
1520 insertStringAsLines(linestr);
1524 void LyXText::checkParagraph(Paragraph * par, pos_type pos)
1526 LyXCursor tmpcursor;
1530 RowList::iterator row = getRow(par, pos, y);
1531 RowList::iterator beg = rows().begin();
1533 // is there a break one row above
1535 && boost::prior(row)->par() == row->par()) {
1536 z = rowBreakPoint(*boost::prior(row));
1537 if (z >= row->pos()) {
1538 // set the dimensions of the row above
1539 y -= boost::prior(row)->height();
1542 breakAgain(boost::prior(row));
1544 // set the cursor again. Otherwise
1545 // dangling pointers are possible
1546 setCursor(cursor.par(), cursor.pos(),
1547 false, cursor.boundary());
1548 selection.cursor = cursor;
1553 int const tmpheight = row->height();
1554 pos_type const tmplast = lastPos(*this, row);
1557 if (row->height() == tmpheight && lastPos(*this, row) == tmplast) {
1558 postRowPaint(row, y);
1563 // check the special right address boxes
1564 if (par->layout()->margintype == MARGIN_RIGHT_ADDRESS_BOX) {
1571 redoDrawingOfParagraph(tmpcursor);
1574 // set the cursor again. Otherwise dangling pointers are possible
1575 // also set the selection
1577 if (selection.set()) {
1579 setCursorIntern(selection.cursor.par(), selection.cursor.pos(),
1580 false, selection.cursor.boundary());
1581 selection.cursor = cursor;
1582 setCursorIntern(selection.start.par(),
1583 selection.start.pos(),
1584 false, selection.start.boundary());
1585 selection.start = cursor;
1586 setCursorIntern(selection.end.par(),
1587 selection.end.pos(),
1588 false, selection.end.boundary());
1589 selection.end = cursor;
1590 setCursorIntern(last_sel_cursor.par(),
1591 last_sel_cursor.pos(),
1592 false, last_sel_cursor.boundary());
1593 last_sel_cursor = cursor;
1596 setCursorIntern(cursor.par(), cursor.pos(),
1597 false, cursor.boundary());
1601 // returns false if inset wasn't found
1602 bool LyXText::updateInset(Inset * inset)
1604 // first check the current paragraph
1605 int pos = cursor.par()->getPositionOfInset(inset);
1607 checkParagraph(cursor.par(), pos);
1611 // check every paragraph
1613 ParagraphList::iterator par = ownerParagraphs().begin();
1614 ParagraphList::iterator end = ownerParagraphs().end();
1617 pos = par->getPositionOfInset(inset);
1619 checkParagraph(&*par, pos);
1623 } while (par != end);
1629 bool LyXText::setCursor(Paragraph * par,
1631 bool setfont, bool boundary)
1633 LyXCursor old_cursor = cursor;
1634 setCursorIntern(par, pos, setfont, boundary);
1635 return deleteEmptyParagraphMechanism(old_cursor);
1639 void LyXText::setCursor(LyXCursor & cur, Paragraph * par,
1640 pos_type pos, bool boundary)
1646 cur.boundary(boundary);
1648 // get the cursor y position in text
1650 RowList::iterator row = getRow(par, pos, y);
1651 RowList::iterator beg = rows().begin();
1653 RowList::iterator old_row = row;
1655 // if we are before the first char of this row and are still in the
1656 // same paragraph and there is a previous row then put the cursor on
1657 // the end of the previous row
1658 cur.iy(y + row->baseline());
1660 if (row != beg && pos &&
1661 boost::prior(row)->par() == row->par() &&
1662 pos < par->size() &&
1663 par->getChar(pos) == Paragraph::META_INSET &&
1664 (ins = par->getInset(pos)) && (ins->needFullRow() || ins->display()))
1671 // y is now the beginning of the cursor row
1672 y += row->baseline();
1673 // y is now the cursor baseline
1676 pos_type last = lastPrintablePos(*this, old_row);
1678 // None of these should happen, but we're scaredy-cats
1679 if (pos > par->size()) {
1680 lyxerr << "dont like 1 please report" << endl;
1683 } else if (pos > last + 1) {
1684 lyxerr << "dont like 2 please report" << endl;
1685 // This shouldn't happen.
1688 } else if (pos < row->pos()) {
1689 lyxerr << "dont like 3 please report" << endl;
1694 // now get the cursors x position
1695 float x = getCursorX(row, pos, last, boundary);
1698 if (old_row != row) {
1699 x = getCursorX(old_row, pos, last, boundary);
1703 /* We take out this for the time being because 1) the redraw code is not
1704 prepared to this yet and 2) because some good policy has yet to be decided
1705 while editting: for instance how to act on rows being created/deleted
1709 //if the cursor is in a visible row, anchor to it
1711 if (topy < y && y < topy + bv()->workHeight())
1717 float LyXText::getCursorX(RowList::iterator rit,
1718 pos_type pos, pos_type last, bool boundary) const
1720 pos_type cursor_vpos = 0;
1722 float fill_separator;
1724 float fill_label_hfill;
1725 // This call HAS to be here because of the BidiTables!!!
1726 prepareToPrint(rit, x, fill_separator, fill_hfill,
1729 if (last < rit->pos())
1730 cursor_vpos = rit->pos();
1731 else if (pos > last && !boundary)
1732 cursor_vpos = (rit->par()->isRightToLeftPar(bv()->buffer()->params))
1733 ? rit->pos() : last + 1;
1734 else if (pos > rit->pos() &&
1735 (pos > last || boundary))
1736 /// Place cursor after char at (logical) position pos - 1
1737 cursor_vpos = (bidi_level(pos - 1) % 2 == 0)
1738 ? log2vis(pos - 1) + 1 : log2vis(pos - 1);
1740 /// Place cursor before char at (logical) position pos
1741 cursor_vpos = (bidi_level(pos) % 2 == 0)
1742 ? log2vis(pos) : log2vis(pos) + 1;
1744 pos_type body_pos = rit->par()->beginningOfBody();
1745 if ((body_pos > 0) &&
1746 ((body_pos - 1 > last) ||
1747 !rit->par()->isLineSeparator(body_pos - 1)))
1750 for (pos_type vpos = rit->pos(); vpos < cursor_vpos; ++vpos) {
1751 pos_type pos = vis2log(vpos);
1752 if (body_pos > 0 && pos == body_pos - 1) {
1753 x += fill_label_hfill +
1754 font_metrics::width(
1755 rit->par()->layout()->labelsep,
1756 getLabelFont(bv()->buffer(),
1758 if (rit->par()->isLineSeparator(body_pos - 1))
1759 x -= singleWidth(&*rit->par(), body_pos - 1);
1762 if (hfillExpansion(*this, rit, pos)) {
1763 x += singleWidth(&*rit->par(), pos);
1764 if (pos >= body_pos)
1767 x += fill_label_hfill;
1768 } else if (rit->par()->isSeparator(pos)) {
1769 x += singleWidth(&*rit->par(), pos);
1770 if (pos >= body_pos)
1771 x += fill_separator;
1773 x += singleWidth(&*rit->par(), pos);
1779 void LyXText::setCursorIntern(Paragraph * par,
1780 pos_type pos, bool setfont, bool boundary)
1782 InsetText * it = static_cast<InsetText *>(par->inInset());
1784 if (it != inset_owner) {
1785 lyxerr[Debug::INSETS] << "InsetText is " << it
1787 << "inset_owner is "
1788 << inset_owner << endl;
1789 #ifdef WITH_WARNINGS
1790 #warning I believe this code is wrong. (Lgb)
1791 #warning Jürgen, have a look at this. (Lgb)
1792 #warning Hmmm, I guess you are right but we
1793 #warning should verify when this is needed
1795 // Jürgen, would you like to have a look?
1796 // I guess we need to move the outer cursor
1797 // and open and lock the inset (bla bla bla)
1798 // stuff I don't know... so can you have a look?
1800 // I moved the lyxerr stuff in here so we can see if
1801 // this is actually really needed and where!
1803 // it->getLyXText(bv())->setCursorIntern(bv(), par, pos, setfont, boundary);
1808 setCursor(cursor, par, pos, boundary);
1814 void LyXText::setCurrentFont()
1816 pos_type pos = cursor.pos();
1817 if (cursor.boundary() && pos > 0)
1821 if (pos == cursor.par()->size())
1823 else // potentional bug... BUG (Lgb)
1824 if (cursor.par()->isSeparator(pos)) {
1825 if (pos > cursor.row()->pos() &&
1826 bidi_level(pos) % 2 ==
1827 bidi_level(pos - 1) % 2)
1829 else if (pos + 1 < cursor.par()->size())
1835 cursor.par()->getFontSettings(bv()->buffer()->params, pos);
1836 real_current_font = getFont(bv()->buffer(), cursor.par(), pos);
1838 if (cursor.pos() == cursor.par()->size() &&
1839 isBoundary(bv()->buffer(), cursor.par(), cursor.pos()) &&
1840 !cursor.boundary()) {
1841 Language const * lang =
1842 cursor.par()->getParLanguage(bv()->buffer()->params);
1843 current_font.setLanguage(lang);
1844 current_font.setNumber(LyXFont::OFF);
1845 real_current_font.setLanguage(lang);
1846 real_current_font.setNumber(LyXFont::OFF);
1851 // returns the column near the specified x-coordinate of the row
1852 // x is set to the real beginning of this column
1854 LyXText::getColumnNearX(RowList::iterator rit, int & x, bool & boundary) const
1857 float fill_separator;
1859 float fill_label_hfill;
1861 prepareToPrint(rit, tmpx, fill_separator,
1862 fill_hfill, fill_label_hfill);
1864 pos_type vc = rit->pos();
1865 pos_type last = lastPrintablePos(*this, rit);
1868 LyXLayout_ptr const & layout = rit->par()->layout();
1870 bool left_side = false;
1872 pos_type body_pos = rit->par()->beginningOfBody();
1873 float last_tmpx = tmpx;
1876 (body_pos - 1 > last ||
1877 !rit->par()->isLineSeparator(body_pos - 1)))
1880 // check for empty row
1881 if (!rit->par()->size()) {
1886 while (vc <= last && tmpx <= x) {
1889 if (body_pos > 0 && c == body_pos - 1) {
1890 tmpx += fill_label_hfill +
1891 font_metrics::width(layout->labelsep,
1892 getLabelFont(bv()->buffer(), &*rit->par()));
1893 if (rit->par()->isLineSeparator(body_pos - 1))
1894 tmpx -= singleWidth(&*rit->par(), body_pos - 1);
1897 if (hfillExpansion(*this, rit, c)) {
1898 tmpx += singleWidth(&*rit->par(), c);
1902 tmpx += fill_label_hfill;
1903 } else if (rit->par()->isSeparator(c)) {
1904 tmpx += singleWidth(&*rit->par(), c);
1906 tmpx+= fill_separator;
1908 tmpx += singleWidth(&*rit->par(), c);
1913 if ((tmpx + last_tmpx) / 2 > x) {
1918 if (vc > last + 1) // This shouldn't happen.
1922 // This (rtl_support test) is not needed, but gives
1923 // some speedup if rtl_support=false
1924 bool const lastrow = lyxrc.rtl_support &&
1925 (boost::next(rit) == rowlist_.end() ||
1926 boost::next(rit)->par() != rit->par());
1927 // If lastrow is false, we don't need to compute
1928 // the value of rtl.
1929 bool const rtl = (lastrow)
1930 ? rit->par()->isRightToLeftPar(bv()->buffer()->params)
1933 ((rtl && left_side && vc == rit->pos() && x < tmpx - 5) ||
1934 (!rtl && !left_side && vc == last + 1 && x > tmpx + 5)))
1936 else if (vc == rit->pos()) {
1938 if (bidi_level(c) % 2 == 1)
1941 c = vis2log(vc - 1);
1942 bool const rtl = (bidi_level(c) % 2 == 1);
1943 if (left_side == rtl) {
1945 boundary = isBoundary(bv()->buffer(), &*rit->par(), c);
1949 if (rit->pos() <= last && c > last
1950 && rit->par()->isNewline(last)) {
1951 if (bidi_level(last) % 2 == 0)
1952 tmpx -= singleWidth(&*rit->par(), last);
1954 tmpx += singleWidth(&*rit->par(), last);
1964 void LyXText::setCursorFromCoordinates(int x, int y)
1966 LyXCursor old_cursor = cursor;
1968 setCursorFromCoordinates(cursor, x, y);
1970 deleteEmptyParagraphMechanism(old_cursor);
1977 * return true if the cursor given is at the end of a row,
1978 * and the next row is filled by an inset that spans an entire
1981 bool beforeFullRowInset(LyXText & lt, RowList::iterator row,
1983 if (boost::next(row) == lt.rows().end())
1985 Row const & next = *boost::next(row);
1987 if (next.pos() != cur.pos() || next.par() != cur.par())
1989 if (!cur.par()->isInset(cur.pos()))
1991 Inset const * inset = cur.par()->getInset(cur.pos());
1992 if (inset->needFullRow() || inset->display())
1999 void LyXText::setCursorFromCoordinates(LyXCursor & cur, int x, int y)
2001 // Get the row first.
2003 RowList::iterator row = getRowNearY(y);
2005 pos_type const column = getColumnNearX(row, x, bound);
2006 cur.par(&*row->par());
2007 cur.pos(row->pos() + column);
2009 cur.y(y + row->baseline());
2012 if (beforeFullRowInset(*this, row, cur)) {
2013 pos_type last = lastPrintablePos(*this, row);
2014 float x = getCursorX(boost::next(row), cur.pos(), last, bound);
2016 cur.iy(y + row->height() + boost::next(row)->baseline());
2017 cur.irow(boost::next(row));
2023 cur.boundary(bound);
2027 void LyXText::cursorLeft(bool internal)
2029 if (cursor.pos() > 0) {
2030 bool boundary = cursor.boundary();
2031 setCursor(cursor.par(), cursor.pos() - 1, true, false);
2032 if (!internal && !boundary &&
2033 isBoundary(bv()->buffer(), cursor.par(), cursor.pos() + 1))
2034 setCursor(cursor.par(), cursor.pos() + 1, true, true);
2035 } else if (cursor.par()->previous()) { // steps into the above paragraph.
2036 Paragraph * par = cursor.par()->previous();
2037 setCursor(par, par->size());
2042 void LyXText::cursorRight(bool internal)
2044 if (!internal && cursor.boundary() &&
2045 !cursor.par()->isNewline(cursor.pos()))
2046 setCursor(cursor.par(), cursor.pos(), true, false);
2047 else if (cursor.pos() < cursor.par()->size()) {
2048 setCursor(cursor.par(), cursor.pos() + 1, true, false);
2050 isBoundary(bv()->buffer(), cursor.par(), cursor.pos()))
2051 setCursor(cursor.par(), cursor.pos(), true, true);
2052 } else if (cursor.par()->next())
2053 setCursor(cursor.par()->next(), 0);
2057 void LyXText::cursorUp(bool selecting)
2060 int x = cursor.x_fix();
2061 int y = cursor.y() - cursor.row()->baseline() - 1;
2062 setCursorFromCoordinates(x, y);
2065 int y1 = cursor.iy() - topy;
2068 Inset * inset_hit = checkInsetHit(x, y1);
2069 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2070 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2074 setCursorFromCoordinates(bv(), cursor.x_fix(),
2075 cursor.y() - cursor.row()->baseline() - 1);
2080 void LyXText::cursorDown(bool selecting)
2083 int x = cursor.x_fix();
2084 int y = cursor.y() - cursor.row()->baseline() +
2085 cursor.row()->height() + 1;
2086 setCursorFromCoordinates(x, y);
2087 if (!selecting && cursor.row() == cursor.irow()) {
2089 int y1 = cursor.iy() - topy;
2092 Inset * inset_hit = checkInsetHit(x, y1);
2093 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2094 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2098 setCursorFromCoordinates(bv(), cursor.x_fix(),
2099 cursor.y() - cursor.row()->baseline()
2100 + cursor.row()->height() + 1);
2105 void LyXText::cursorUpParagraph()
2107 if (cursor.pos() > 0) {
2108 setCursor(cursor.par(), 0);
2110 else if (cursor.par()->previous()) {
2111 setCursor(cursor.par()->previous(), 0);
2116 void LyXText::cursorDownParagraph()
2118 if (cursor.par()->next()) {
2119 setCursor(cursor.par()->next(), 0);
2121 setCursor(cursor.par(), cursor.par()->size());
2125 // fix the cursor `cur' after a characters has been deleted at `where'
2126 // position. Called by deleteEmptyParagraphMechanism
2127 void LyXText::fixCursorAfterDelete(LyXCursor & cur,
2128 LyXCursor const & where)
2130 // if cursor is not in the paragraph where the delete occured,
2132 if (cur.par() != where.par())
2135 // if cursor position is after the place where the delete occured,
2137 if (cur.pos() > where.pos())
2138 cur.pos(cur.pos()-1);
2140 // check also if we don't want to set the cursor on a spot behind the
2141 // pagragraph because we erased the last character.
2142 if (cur.pos() > cur.par()->size())
2143 cur.pos(cur.par()->size());
2145 // recompute row et al. for this cursor
2146 setCursor(cur, cur.par(), cur.pos(), cur.boundary());
2150 bool LyXText::deleteEmptyParagraphMechanism(LyXCursor const & old_cursor)
2152 // Would be wrong to delete anything if we have a selection.
2153 if (selection.set())
2156 // We allow all kinds of "mumbo-jumbo" when freespacing.
2157 if (old_cursor.par()->layout()->free_spacing
2158 || old_cursor.par()->isFreeSpacing()) {
2162 /* Ok I'll put some comments here about what is missing.
2163 I have fixed BackSpace (and thus Delete) to not delete
2164 double-spaces automagically. I have also changed Cut,
2165 Copy and Paste to hopefully do some sensible things.
2166 There are still some small problems that can lead to
2167 double spaces stored in the document file or space at
2168 the beginning of paragraphs. This happens if you have
2169 the cursor betwenn to spaces and then save. Or if you
2170 cut and paste and the selection have a space at the
2171 beginning and then save right after the paste. I am
2172 sure none of these are very hard to fix, but I will
2173 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
2174 that I can get some feedback. (Lgb)
2177 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
2178 // delete the LineSeparator.
2181 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
2182 // delete the LineSeparator.
2185 // If the pos around the old_cursor were spaces, delete one of them.
2186 if (old_cursor.par() != cursor.par()
2187 || old_cursor.pos() != cursor.pos()) {
2188 // Only if the cursor has really moved
2190 if (old_cursor.pos() > 0
2191 && old_cursor.pos() < old_cursor.par()->size()
2192 && old_cursor.par()->isLineSeparator(old_cursor.pos())
2193 && old_cursor.par()->isLineSeparator(old_cursor.pos() - 1)) {
2194 old_cursor.par()->erase(old_cursor.pos() - 1);
2195 redoParagraphs(old_cursor, old_cursor.par()->next());
2197 #ifdef WITH_WARNINGS
2198 #warning This will not work anymore when we have multiple views of the same buffer
2199 // In this case, we will have to correct also the cursors held by
2200 // other bufferviews. It will probably be easier to do that in a more
2201 // automated way in LyXCursor code. (JMarc 26/09/2001)
2203 // correct all cursors held by the LyXText
2204 fixCursorAfterDelete(cursor, old_cursor);
2205 fixCursorAfterDelete(selection.cursor,
2207 fixCursorAfterDelete(selection.start,
2209 fixCursorAfterDelete(selection.end, old_cursor);
2210 fixCursorAfterDelete(last_sel_cursor,
2212 fixCursorAfterDelete(toggle_cursor, old_cursor);
2213 fixCursorAfterDelete(toggle_end_cursor,
2219 // don't delete anything if this is the ONLY paragraph!
2220 if (!old_cursor.par()->next() && !old_cursor.par()->previous())
2223 // Do not delete empty paragraphs with keepempty set.
2224 if (old_cursor.par()->layout()->keepempty)
2227 // only do our magic if we changed paragraph
2228 if (old_cursor.par() == cursor.par())
2231 // record if we have deleted a paragraph
2232 // we can't possibly have deleted a paragraph before this point
2233 bool deleted = false;
2235 if ((old_cursor.par()->empty()
2236 || (old_cursor.par()->size() == 1
2237 && old_cursor.par()->isLineSeparator(0)))) {
2238 // ok, we will delete anything
2239 LyXCursor tmpcursor;
2243 if (old_cursor.row() != rows().begin()) {
2245 prevrow = boost::prior(old_cursor.row());
2246 const_cast<LyXText *>(this)->postPaint(old_cursor.y() - old_cursor.row()->baseline() - prevrow->height());
2248 cursor = old_cursor; // that undo can restore the right cursor position
2249 Paragraph * endpar = old_cursor.par()->next();
2250 if (endpar && endpar->getDepth()) {
2251 while (endpar && endpar->getDepth()) {
2252 endpar = endpar->next();
2255 setUndo(bv(), Undo::DELETE, old_cursor.par(), endpar);
2259 removeRow(old_cursor.row());
2260 if (ownerParagraphs().begin() == old_cursor.par()) {
2261 ownerParagraph(&*boost::next(ownerParagraphs().begin()));
2264 delete old_cursor.par();
2266 /* Breakagain the next par. Needed because of
2267 * the parindent that can occur or dissappear.
2268 * The next row can change its height, if
2269 * there is another layout before */
2270 if (boost::next(prevrow) != rows().end()) {
2271 breakAgain(boost::next(prevrow));
2274 setHeightOfRow(prevrow);
2276 RowList::iterator nextrow = boost::next(old_cursor.row());
2277 const_cast<LyXText *>(this)->postPaint(
2278 old_cursor.y() - old_cursor.row()->baseline());
2281 cursor = old_cursor; // that undo can restore the right cursor position
2282 Paragraph * endpar = old_cursor.par()->next();
2283 if (endpar && endpar->getDepth()) {
2284 while (endpar && endpar->getDepth()) {
2285 endpar = endpar->next();
2288 setUndo(bv(), Undo::DELETE, old_cursor.par(), endpar);
2292 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 (nextrow != rows().end()) {
2305 breakAgain(nextrow);
2311 setCursorIntern(cursor.par(), cursor.pos());
2313 if (selection.cursor.par() == old_cursor.par()
2314 && selection.cursor.pos() == old_cursor.pos()) {
2315 // correct selection
2316 selection.cursor = cursor;
2320 if (old_cursor.par()->stripLeadingSpaces()) {
2321 redoParagraphs(old_cursor,
2322 old_cursor.par()->next());
2324 setCursorIntern(cursor.par(), cursor.pos());
2325 selection.cursor = cursor;
2332 ParagraphList & LyXText::ownerParagraphs() const
2335 return inset_owner->paragraphs;
2337 return bv_owner->buffer()->paragraphs;
2341 void LyXText::ownerParagraph(Paragraph * p) const
2344 inset_owner->paragraph(p);
2346 bv_owner->buffer()->paragraphs.set(p);
2351 void LyXText::ownerParagraph(int id, Paragraph * p) const
2353 Paragraph * op = bv_owner->buffer()->getParFromID(id);
2354 if (op && op->inInset()) {
2355 static_cast<InsetText *>(op->inInset())->paragraph(p);
2362 LyXText::refresh_status LyXText::refreshStatus() const
2364 return refresh_status_;
2368 void LyXText::clearPaint()
2370 refresh_status_ = REFRESH_NONE;
2371 refresh_row = rows().end();
2376 void LyXText::postPaint(int start_y)
2378 refresh_status old = refresh_status_;
2380 refresh_status_ = REFRESH_AREA;
2381 refresh_row = rows().end();
2383 if (old != REFRESH_NONE && refresh_y < start_y)
2386 refresh_y = start_y;
2391 // We are an inset's lyxtext. Tell the top-level lyxtext
2392 // it needs to update the row we're in.
2393 LyXText * t = bv()->text;
2394 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2398 // FIXME: we should probably remove this y parameter,
2399 // make refresh_y be 0, and use row->y etc.
2400 void LyXText::postRowPaint(RowList::iterator rit, int start_y)
2402 if (refresh_status_ != REFRESH_NONE && refresh_y < start_y) {
2403 refresh_status_ = REFRESH_AREA;
2406 refresh_y = start_y;
2409 if (refresh_status_ == REFRESH_AREA)
2412 refresh_status_ = REFRESH_ROW;
2418 // We are an inset's lyxtext. Tell the top-level lyxtext
2419 // it needs to update the row we're in.
2420 LyXText * t = bv()->text;
2421 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2425 bool LyXText::isInInset() const
2427 // Sub-level has non-null bv owner and
2428 // non-null inset owner.
2429 return inset_owner != 0 && bv_owner != 0;
2433 int defaultRowHeight()
2435 LyXFont const font(LyXFont::ALL_SANE);
2436 return int(font_metrics::maxAscent(font)
2437 + font_metrics::maxDescent(font) * 1.5);