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"
33 #include "paragraph_funcs.h"
35 #include "insets/insetbibitem.h"
36 #include "insets/insetfloat.h"
37 #include "insets/insetwrap.h"
39 #include "support/LAssert.h"
40 #include "support/textutils.h"
41 #include "support/lstrings.h"
43 #include "support/BoostFormat.h"
44 #include <boost/tuple/tuple.hpp>
54 LyXText::LyXText(BufferView * bv)
55 : height(0), width(0), anchor_row_offset_(0),
56 inset_owner(0), the_locking_inset(0), bv_owner(bv)
58 anchor_row_ = rows().end();
59 need_break_row = rows().end();
60 refresh_row = rows().end();
66 LyXText::LyXText(BufferView * bv, InsetText * inset)
67 : height(0), width(0), anchor_row_offset_(0),
68 inset_owner(inset), the_locking_inset(0), bv_owner(bv)
70 anchor_row_ = rows().end();
71 need_break_row = rows().end();
72 refresh_row = rows().end();
78 void LyXText::init(BufferView * bview, bool reinit)
82 need_break_row = rows().end();
86 } else if (!rowlist_.empty())
89 ParagraphList::iterator pit = ownerParagraphs().begin();
90 ParagraphList::iterator end = ownerParagraphs().end();
92 current_font = getFont(bview->buffer(), pit, 0);
94 for (; pit != end; ++pit) {
95 insertParagraph(pit, rowlist_.end());
97 setCursorIntern(rowlist_.begin()->par(), 0);
98 selection.cursor = cursor;
104 // Gets the fully instantiated font at a given position in a paragraph
105 // Basically the same routine as Paragraph::getFont() in paragraph.C.
106 // The difference is that this one is used for displaying, and thus we
107 // are allowed to make cosmetic improvements. For instance make footnotes
109 // If position is -1, we get the layout font of the paragraph.
110 // If position is -2, we get the font of the manual label of the paragraph.
111 LyXFont const LyXText::getFont(Buffer const * buf, ParagraphList::iterator pit,
114 lyx::Assert(pos >= 0);
116 LyXLayout_ptr const & layout = pit->layout();
118 // We specialize the 95% common case:
119 if (!pit->getDepth()) {
120 if (layout->labeltype == LABEL_MANUAL
121 && pos < pit->beginningOfBody()) {
123 LyXFont f = pit->getFontSettings(buf->params, pos);
125 pit->inInset()->getDrawFont(f);
126 return f.realize(layout->reslabelfont);
128 LyXFont f = pit->getFontSettings(buf->params, pos);
130 pit->inInset()->getDrawFont(f);
131 return f.realize(layout->resfont);
135 // The uncommon case need not be optimized as much
139 if (pos < pit->beginningOfBody()) {
141 layoutfont = layout->labelfont;
144 layoutfont = layout->font;
147 LyXFont tmpfont = pit->getFontSettings(buf->params, pos);
148 tmpfont.realize(layoutfont);
151 pit->inInset()->getDrawFont(tmpfont);
153 // Realize with the fonts of lesser depth.
154 tmpfont.realize(outerFont(pit, ownerParagraphs()));
156 return realizeFont(tmpfont, buf->params);
160 LyXFont const LyXText::getLayoutFont(Buffer const * buf,
161 ParagraphList::iterator pit) const
163 LyXLayout_ptr const & layout = pit->layout();
165 if (!pit->getDepth()) {
166 return layout->resfont;
169 LyXFont font(layout->font);
170 // Realize with the fonts of lesser depth.
171 font.realize(outerFont(pit, ownerParagraphs()));
173 return realizeFont(font, buf->params);
177 LyXFont const LyXText::getLabelFont(Buffer const * buf,
178 ParagraphList::iterator pit) const
180 LyXLayout_ptr const & layout = pit->layout();
182 if (!pit->getDepth()) {
183 return layout->reslabelfont;
186 LyXFont font(layout->labelfont);
187 // Realize with the fonts of lesser depth.
188 font.realize(outerFont(pit, ownerParagraphs()));
190 return realizeFont(layout->labelfont, buf->params);
194 void LyXText::setCharFont(ParagraphList::iterator pit,
195 pos_type pos, LyXFont const & fnt,
198 Buffer const * buf = bv()->buffer();
199 LyXFont font = getFont(buf, pit, pos);
200 font.update(fnt, buf->params.language, toggleall);
201 // Let the insets convert their font
202 if (pit->isInset(pos)) {
203 Inset * inset = pit->getInset(pos);
204 if (isEditableInset(inset)) {
205 UpdatableInset * uinset =
206 static_cast<UpdatableInset *>(inset);
207 uinset->setFont(bv(), fnt, toggleall, true);
211 // Plug thru to version below:
212 setCharFont(buf, pit, pos, font);
216 void LyXText::setCharFont(Buffer const * buf, ParagraphList::iterator pit,
217 pos_type pos, LyXFont const & fnt)
221 LyXTextClass const & tclass = buf->params.getLyXTextClass();
222 LyXLayout_ptr const & layout = pit->layout();
224 // Get concrete layout font to reduce against
227 if (pos < pit->beginningOfBody())
228 layoutfont = layout->labelfont;
230 layoutfont = layout->font;
232 // Realize against environment font information
233 if (pit->getDepth()) {
234 ParagraphList::iterator tp = pit;
235 while (!layoutfont.resolved() &&
236 tp != ownerParagraphs().end() &&
238 tp = outerHook(tp, ownerParagraphs());
239 if (tp != ownerParagraphs().end())
240 layoutfont.realize(tp->layout()->font);
244 layoutfont.realize(tclass.defaultfont());
246 // Now, reduce font against full layout font
247 font.reduce(layoutfont);
249 pit->setFont(pos, font);
253 // removes the row and reset the touched counters
254 void LyXText::removeRow(RowList::iterator rit)
256 /* FIXME: when we cache the bview, this should just
257 * become a postPaint(), I think */
258 if (refresh_row == rit) {
259 if (rit == rows().begin())
260 refresh_row = boost::next(rit);
262 refresh_row = boost::prior(rit);
264 // what about refresh_y
267 if (anchor_row_ == rit) {
268 if (rit != rows().begin()) {
269 anchor_row_ = boost::prior(rit);
270 anchor_row_offset_ += anchor_row_->height();
272 anchor_row_ = boost::next(rit);
273 anchor_row_offset_ -= rit->height();
277 // the text becomes smaller
278 height -= rit->height();
284 // remove all following rows of the paragraph of the specified row.
285 void LyXText::removeParagraph(RowList::iterator rit)
287 ParagraphList::iterator tmppit = rit->par();
290 while (rit != rows().end() && rit->par() == tmppit) {
291 RowList::iterator tmprit = boost::next(rit);
298 void LyXText::insertParagraph(ParagraphList::iterator pit,
299 RowList::iterator rowit)
301 // insert a new row, starting at position 0
303 RowList::iterator rit = rowlist_.insert(rowit, newrow);
305 // and now append the whole paragraph before the new row
306 appendParagraph(rit);
310 Inset * LyXText::getInset() const
312 ParagraphList::iterator pit = cursor.par();
313 pos_type const pos = cursor.pos();
315 if (pos < pit->size() && pit->isInset(pos)) {
316 return pit->getInset(pos);
322 void LyXText::toggleInset()
324 Inset * inset = getInset();
325 // is there an editable inset at cursor position?
326 if (!isEditableInset(inset)) {
327 // No, try to see if we are inside a collapsable inset
328 if (inset_owner && inset_owner->owner()
329 && inset_owner->owner()->isOpen()) {
330 bv()->unlockInset(static_cast<UpdatableInset *>(inset_owner->owner()));
331 inset_owner->owner()->close(bv());
332 bv()->getLyXText()->cursorRight(bv());
336 //bv()->owner()->message(inset->editMessage());
338 // do we want to keep this?? (JMarc)
339 if (!isHighlyEditableInset(inset))
340 setCursorParUndo(bv());
342 if (inset->isOpen()) {
348 bv()->updateInset(inset);
352 /* used in setlayout */
353 // Asger is not sure we want to do this...
354 void LyXText::makeFontEntriesLayoutSpecific(Buffer const & buf,
357 LyXLayout_ptr const & layout = par.layout();
358 pos_type const psize = par.size();
361 for (pos_type pos = 0; pos < psize; ++pos) {
362 if (pos < par.beginningOfBody())
363 layoutfont = layout->labelfont;
365 layoutfont = layout->font;
367 LyXFont tmpfont = par.getFontSettings(buf.params, pos);
368 tmpfont.reduce(layoutfont);
369 par.setFont(pos, tmpfont);
374 ParagraphList::iterator
375 LyXText::setLayout(LyXCursor & cur, LyXCursor & sstart_cur,
376 LyXCursor & send_cur,
377 string const & layout)
379 ParagraphList::iterator endpit = boost::next(send_cur.par());
380 ParagraphList::iterator undoendpit = endpit;
381 ParagraphList::iterator pars_end = ownerParagraphs().end();
383 if (endpit != pars_end && endpit->getDepth()) {
384 while (endpit != pars_end && endpit->getDepth()) {
388 } else if (endpit != pars_end) {
389 // because of parindents etc.
393 setUndo(bv(), Undo::EDIT, sstart_cur.par(), undoendpit);
395 // ok we have a selection. This is always between sstart_cur
396 // and sel_end cursor
398 ParagraphList::iterator pit = sstart_cur.par();
399 ParagraphList::iterator epit = boost::next(send_cur.par());
401 LyXLayout_ptr const & lyxlayout =
402 bv()->buffer()->params.getLyXTextClass()[layout];
405 pit->applyLayout(lyxlayout);
406 makeFontEntriesLayoutSpecific(*bv()->buffer(), *pit);
407 ParagraphList::iterator fppit = pit;
408 fppit->params().spaceTop(lyxlayout->fill_top ?
409 VSpace(VSpace::VFILL)
410 : VSpace(VSpace::NONE));
411 fppit->params().spaceBottom(lyxlayout->fill_bottom ?
412 VSpace(VSpace::VFILL)
413 : VSpace(VSpace::NONE));
414 if (lyxlayout->margintype == MARGIN_MANUAL)
415 pit->setLabelWidthString(lyxlayout->labelstring());
418 } while (pit != epit);
424 // set layout over selection and make a total rebreak of those paragraphs
425 void LyXText::setLayout(string const & layout)
427 LyXCursor tmpcursor = cursor; /* store the current cursor */
429 // if there is no selection just set the layout
430 // of the current paragraph */
431 if (!selection.set()) {
432 selection.start = cursor; // dummy selection
433 selection.end = cursor;
435 ParagraphList::iterator endpit = setLayout(cursor, selection.start,
436 selection.end, layout);
437 redoParagraphs(selection.start, endpit);
439 // we have to reset the selection, because the
440 // geometry could have changed
441 setCursor(selection.start.par(),
442 selection.start.pos(), false);
443 selection.cursor = cursor;
444 setCursor(selection.end.par(), selection.end.pos(), false);
448 setCursor(tmpcursor.par(), tmpcursor.pos(), true);
452 bool LyXText::changeDepth(bv_funcs::DEPTH_CHANGE type, bool test_only)
454 ParagraphList::iterator pit(cursor.par());
455 ParagraphList::iterator end(cursor.par());
456 ParagraphList::iterator start = pit;
458 if (selection.set()) {
459 pit = selection.start.par();
460 end = selection.end.par();
464 ParagraphList::iterator pastend = boost::next(end);
467 setUndo(bv(), Undo::EDIT, start, pastend);
469 bool changed = false;
471 int prev_after_depth = 0;
472 #warning parlist ... could be nicer ?
473 if (start != ownerParagraphs().begin()) {
474 prev_after_depth = boost::prior(start)->getMaxDepthAfter();
478 int const depth = pit->params().depth();
479 if (type == bv_funcs::INC_DEPTH) {
480 if (depth < prev_after_depth
481 && pit->layout()->labeltype != LABEL_BIBLIO) {
484 pit->params().depth(depth + 1);
491 pit->params().depth(depth - 1);
494 prev_after_depth = pit->getMaxDepthAfter();
506 // Wow, redoParagraphs is stupid.
508 setCursor(tmpcursor, &(*start), 0);
510 //redoParagraphs(tmpcursor, &(*pastend));
511 redoParagraphs(tmpcursor, &(*pastend));
513 // We need to actually move the text->cursor. I don't
514 // understand why ...
517 // we have to reset the visual selection because the
518 // geometry could have changed
519 if (selection.set()) {
520 setCursor(selection.start.par(), selection.start.pos());
521 selection.cursor = cursor;
522 setCursor(selection.end.par(), selection.end.pos());
525 // this handles the counter labels, and also fixes up
526 // depth values for follow-on (child) paragraphs
530 setCursor(tmpcursor.par(), tmpcursor.pos());
536 // set font over selection and make a total rebreak of those paragraphs
537 void LyXText::setFont(LyXFont const & font, bool toggleall)
539 // if there is no selection just set the current_font
540 if (!selection.set()) {
541 // Determine basis font
543 if (cursor.pos() < cursor.par()->beginningOfBody()) {
544 layoutfont = getLabelFont(bv()->buffer(),
547 layoutfont = getLayoutFont(bv()->buffer(),
550 // Update current font
551 real_current_font.update(font,
552 bv()->buffer()->params.language,
555 // Reduce to implicit settings
556 current_font = real_current_font;
557 current_font.reduce(layoutfont);
558 // And resolve it completely
559 real_current_font.realize(layoutfont);
564 LyXCursor tmpcursor = cursor; // store the current cursor
566 // ok we have a selection. This is always between sel_start_cursor
567 // and sel_end cursor
569 setUndo(bv(), Undo::EDIT,
570 selection.start.par(), boost::next(selection.end.par()));
572 cursor = selection.start;
573 while (cursor.par() != selection.end.par() ||
574 cursor.pos() < selection.end.pos())
576 if (cursor.pos() < cursor.par()->size()) {
577 // an open footnote should behave like a closed one
578 setCharFont(cursor.par(), cursor.pos(),
580 cursor.pos(cursor.pos() + 1);
583 cursor.par(boost::next(cursor.par()));
588 redoParagraphs(selection.start, boost::next(selection.end.par()));
590 // we have to reset the selection, because the
591 // geometry could have changed, but we keep
592 // it for user convenience
593 setCursor(selection.start.par(), selection.start.pos());
594 selection.cursor = cursor;
595 setCursor(selection.end.par(), selection.end.pos());
597 setCursor(tmpcursor.par(), tmpcursor.pos(), true,
598 tmpcursor.boundary());
602 void LyXText::redoHeightOfParagraph()
604 RowList::iterator tmprow = cursor.row();
605 int y = cursor.y() - tmprow->baseline();
607 setHeightOfRow(tmprow);
609 while (tmprow != rows().begin()
610 && boost::prior(tmprow)->par() == tmprow->par()) {
612 y -= tmprow->height();
613 setHeightOfRow(tmprow);
618 setCursor(cursor.par(), cursor.pos(), false, cursor.boundary());
622 void LyXText::redoDrawingOfParagraph(LyXCursor const & cur)
624 RowList::iterator tmprow = cur.row();
626 int y = cur.y() - tmprow->baseline();
627 setHeightOfRow(tmprow);
629 while (tmprow != rows().begin()
630 && boost::prior(tmprow)->par() == tmprow->par()) {
632 y -= tmprow->height();
636 setCursor(cur.par(), cur.pos());
640 // deletes and inserts again all paragaphs between the cursor
641 // and the specified par
642 // This function is needed after SetLayout and SetFont etc.
643 void LyXText::redoParagraphs(LyXCursor const & cur,
644 ParagraphList::iterator endpit)
646 RowList::iterator tmprit = cur.row();
647 int y = cur.y() - tmprit->baseline();
649 ParagraphList::iterator first_phys_pit;
650 RowList::iterator prevrit;
651 if (tmprit == rows().begin()) {
652 // A trick/hack for UNDO.
653 // This is needed because in an UNDO/REDO we could have
654 // changed the ownerParagrah() so the paragraph inside
655 // the row is NOT my really first par anymore.
656 // Got it Lars ;) (Jug 20011206)
657 first_phys_pit = ownerParagraphs().begin();
658 prevrit = rows().end();
660 first_phys_pit = tmprit->par();
661 while (tmprit != rows().begin()
662 && boost::prior(tmprit)->par() == first_phys_pit)
665 y -= tmprit->height();
667 prevrit = boost::prior(tmprit);
671 while (tmprit != rows().end() && tmprit->par() != endpit) {
672 RowList::iterator tmprit2 = tmprit++;
676 // Reinsert the paragraphs.
677 ParagraphList::iterator tmppit = first_phys_pit;
679 while (tmppit != ownerParagraphs().end()) {
680 insertParagraph(tmppit, tmprit);
681 while (tmprit != rows().end()
682 && tmprit->par() == tmppit) {
686 if (tmppit == endpit)
689 if (prevrit != rows().end()) {
690 setHeightOfRow(prevrit);
691 const_cast<LyXText *>(this)->postPaint(y - prevrit->height());
693 setHeightOfRow(rows().begin());
694 const_cast<LyXText *>(this)->postPaint(0);
696 if (tmprit != rows().end())
697 setHeightOfRow(tmprit);
703 void LyXText::fullRebreak()
705 if (rows().empty()) {
710 RowList::iterator rows_end = rows().end();
712 if (need_break_row != rows_end) {
713 breakAgain(need_break_row);
714 need_break_row = rows_end;
720 // important for the screen
723 // the cursor set functions have a special mechanism. When they
724 // realize, that you left an empty paragraph, they will delete it.
725 // They also delete the corresponding row
727 // need the selection cursor:
728 void LyXText::setSelection()
730 bool const lsel = selection.set();
732 if (!selection.set()) {
733 last_sel_cursor = selection.cursor;
734 selection.start = selection.cursor;
735 selection.end = selection.cursor;
740 // first the toggling area
741 if (cursor.y() < last_sel_cursor.y()
742 || (cursor.y() == last_sel_cursor.y()
743 && cursor.x() < last_sel_cursor.x())) {
744 toggle_end_cursor = last_sel_cursor;
745 toggle_cursor = cursor;
747 toggle_end_cursor = cursor;
748 toggle_cursor = last_sel_cursor;
751 last_sel_cursor = cursor;
753 // and now the whole selection
755 if (selection.cursor.par() == cursor.par())
756 if (selection.cursor.pos() < cursor.pos()) {
757 selection.end = cursor;
758 selection.start = selection.cursor;
760 selection.end = selection.cursor;
761 selection.start = cursor;
763 else if (selection.cursor.y() < cursor.y() ||
764 (selection.cursor.y() == cursor.y()
765 && selection.cursor.x() < cursor.x())) {
766 selection.end = cursor;
767 selection.start = selection.cursor;
770 selection.end = selection.cursor;
771 selection.start = cursor;
774 // a selection with no contents is not a selection
775 if (selection.start.par() == selection.end.par() &&
776 selection.start.pos() == selection.end.pos())
777 selection.set(false);
779 if (inset_owner && (selection.set() || lsel))
780 inset_owner->setUpdateStatus(bv(), InsetText::SELECTION);
784 string const LyXText::selectionAsString(Buffer const * buffer,
787 if (!selection.set()) return string();
789 // should be const ...
790 ParagraphList::iterator startpit = selection.start.par();
791 ParagraphList::iterator endpit = selection.end.par();
792 pos_type const startpos(selection.start.pos());
793 pos_type const endpos(selection.end.pos());
795 if (startpit == endpit) {
796 return startpit->asString(buffer, startpos, endpos, label);
801 // First paragraph in selection
802 result += startpit->asString(buffer, startpos, startpit->size(), label) + "\n\n";
804 // The paragraphs in between (if any)
805 ParagraphList::iterator pit = boost::next(startpit);
806 for (; pit != endpit; ++pit) {
807 result += pit->asString(buffer, 0, pit->size(), label) + "\n\n";
810 // Last paragraph in selection
811 result += endpit->asString(buffer, 0, endpos, label);
817 void LyXText::clearSelection()
819 selection.set(false);
820 selection.mark(false);
821 last_sel_cursor = selection.end = selection.start = selection.cursor = cursor;
822 // reset this in the bv_owner!
823 if (bv_owner && bv_owner->text)
824 bv_owner->text->xsel_cache.set(false);
828 void LyXText::cursorHome()
830 setCursor(cursor.par(), cursor.row()->pos());
834 void LyXText::cursorEnd()
836 if (cursor.par()->empty())
839 RowList::iterator rit = cursor.row();
840 RowList::iterator next_rit = boost::next(rit);
841 ParagraphList::iterator pit = rit->par();
842 pos_type last_pos = lastPos(*this, rit);
844 if (next_rit == rows().end() || next_rit->par() != pit) {
848 (pit->getChar(last_pos) != ' ' && !pit->isNewline(last_pos))) {
853 setCursor(pit, last_pos);
857 void LyXText::cursorTop()
859 setCursor(ownerParagraphs().begin(), 0);
863 void LyXText::cursorBottom()
866 // This is how it should be:
867 // ParagraphList::iterator lastpit = boost::prior(ownerParagraphs().end());
868 ParagraphList::iterator lastpit = &ownerParagraphs().back();
869 int pos = lastpit->size();
870 setCursor(lastpit, pos);
874 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
876 // If the mask is completely neutral, tell user
877 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
878 // Could only happen with user style
879 bv()->owner()->message(_("No font change defined. Use Character under the Layout menu to define font change."));
883 // Try implicit word selection
884 // If there is a change in the language the implicit word selection
886 LyXCursor resetCursor = cursor;
887 bool implicitSelection = (font.language() == ignore_language
888 && font.number() == LyXFont::IGNORE)
889 ? selectWordWhenUnderCursor(WHOLE_WORD_STRICT) : false;
892 setFont(font, toggleall);
894 // Implicit selections are cleared afterwards
895 //and cursor is set to the original position.
896 if (implicitSelection) {
898 cursor = resetCursor;
899 setCursor(cursor.par(), cursor.pos());
900 selection.cursor = cursor;
903 inset_owner->setUpdateStatus(bv(), InsetText::CURSOR_PAR);
907 string LyXText::getStringToIndex()
909 // Try implicit word selection
910 // If there is a change in the language the implicit word selection
912 LyXCursor const reset_cursor = cursor;
913 bool const implicitSelection = selectWordWhenUnderCursor(PREVIOUS_WORD);
916 if (!selection.set())
917 bv()->owner()->message(_("Nothing to index!"));
918 else if (selection.start.par() != selection.end.par())
919 bv()->owner()->message(_("Cannot index more than one paragraph!"));
921 idxstring = selectionAsString(bv()->buffer(), false);
923 // Reset cursors to their original position.
924 cursor = reset_cursor;
925 setCursor(cursor.par(), cursor.pos());
926 selection.cursor = cursor;
928 // Clear the implicit selection.
929 if (implicitSelection)
936 // the DTP switches for paragraphs. LyX will store them in the first
937 // physicla paragraph. When a paragraph is broken, the top settings rest,
938 // the bottom settings are given to the new one. So I can make shure,
939 // they do not duplicate themself and you cannnot make dirty things with
942 void LyXText::setParagraph(bool line_top, bool line_bottom,
943 bool pagebreak_top, bool pagebreak_bottom,
944 VSpace const & space_top,
945 VSpace const & space_bottom,
946 Spacing const & spacing,
948 string const & labelwidthstring,
951 LyXCursor tmpcursor = cursor;
952 if (!selection.set()) {
953 selection.start = cursor;
954 selection.end = cursor;
957 // make sure that the depth behind the selection are restored, too
958 ParagraphList::iterator endpit = boost::next(selection.end.par());
959 ParagraphList::iterator undoendpit = endpit;
960 ParagraphList::iterator pars_end = ownerParagraphs().end();
962 if (endpit != pars_end && endpit->getDepth()) {
963 while (endpit != pars_end && endpit->getDepth()) {
967 } else if (endpit != pars_end) {
968 // because of parindents etc.
972 setUndo(bv(), Undo::EDIT, selection.start.par(), undoendpit);
975 ParagraphList::iterator tmppit = selection.end.par();
977 while (tmppit != boost::prior(selection.start.par())) {
978 setCursor(tmppit, 0);
979 postPaint(cursor.y() - cursor.row()->baseline());
981 ParagraphList::iterator pit = cursor.par();
982 ParagraphParameters & params = pit->params();
984 params.lineTop(line_top);
985 params.lineBottom(line_bottom);
986 params.pagebreakTop(pagebreak_top);
987 params.pagebreakBottom(pagebreak_bottom);
988 params.spaceTop(space_top);
989 params.spaceBottom(space_bottom);
990 params.spacing(spacing);
991 // does the layout allow the new alignment?
992 LyXLayout_ptr const & layout = pit->layout();
994 if (align == LYX_ALIGN_LAYOUT)
995 align = layout->align;
996 if (align & layout->alignpossible) {
997 if (align == layout->align)
998 params.align(LYX_ALIGN_LAYOUT);
1000 params.align(align);
1002 pit->setLabelWidthString(labelwidthstring);
1003 params.noindent(noindent);
1004 tmppit = boost::prior(pit);
1007 redoParagraphs(selection.start, endpit);
1010 setCursor(selection.start.par(), selection.start.pos());
1011 selection.cursor = cursor;
1012 setCursor(selection.end.par(), selection.end.pos());
1014 setCursor(tmpcursor.par(), tmpcursor.pos());
1016 bv()->updateInset(inset_owner);
1020 // set the counter of a paragraph. This includes the labels
1021 void LyXText::setCounter(Buffer const * buf, ParagraphList::iterator pit)
1023 LyXTextClass const & textclass = buf->params.getLyXTextClass();
1024 LyXLayout_ptr const & layout = pit->layout();
1026 if (pit != ownerParagraphs().begin()) {
1028 pit->params().appendix(boost::prior(pit)->params().appendix());
1029 if (!pit->params().appendix() &&
1030 pit->params().startOfAppendix()) {
1031 pit->params().appendix(true);
1032 textclass.counters().reset();
1034 pit->enumdepth = boost::prior(pit)->enumdepth;
1035 pit->itemdepth = boost::prior(pit)->itemdepth;
1037 pit->params().appendix(pit->params().startOfAppendix());
1042 /* Maybe we have to increment the enumeration depth.
1043 * BUT, enumeration in a footnote is considered in isolation from its
1044 * surrounding paragraph so don't increment if this is the
1045 * first line of the footnote
1046 * AND, bibliographies can't have their depth changed ie. they
1047 * are always of depth 0
1049 if (pit != ownerParagraphs().begin()
1050 && boost::prior(pit)->getDepth() < pit->getDepth()
1051 && boost::prior(pit)->layout()->labeltype == LABEL_COUNTER_ENUMI
1052 && pit->enumdepth < 3
1053 && layout->labeltype != LABEL_BIBLIO) {
1057 // Maybe we have to decrement the enumeration depth, see note above
1058 if (pit != ownerParagraphs().begin()
1059 && boost::prior(pit)->getDepth() > pit->getDepth()
1060 && layout->labeltype != LABEL_BIBLIO) {
1061 pit->enumdepth = depthHook(pit, ownerParagraphs(),
1062 pit->getDepth())->enumdepth;
1065 if (!pit->params().labelString().empty()) {
1066 pit->params().labelString(string());
1069 if (layout->margintype == MARGIN_MANUAL) {
1070 if (pit->params().labelWidthString().empty()) {
1071 pit->setLabelWidthString(layout->labelstring());
1074 pit->setLabelWidthString(string());
1077 // is it a layout that has an automatic label?
1078 if (layout->labeltype >= LABEL_COUNTER_CHAPTER) {
1079 int const i = layout->labeltype - LABEL_COUNTER_CHAPTER;
1083 if (i >= 0 && i <= buf->params.secnumdepth) {
1087 textclass.counters().step(layout->latexname());
1089 // Is there a label? Useful for Chapter layout
1090 if (!pit->params().appendix()) {
1091 s << buf->B_(layout->labelstring());
1093 s << buf->B_(layout->labelstring_appendix());
1096 // Use of an integer is here less than elegant. For now.
1097 int head = textclass.maxcounter() - LABEL_COUNTER_CHAPTER;
1098 if (!pit->params().appendix()) {
1099 numbertype = "sectioning";
1101 numbertype = "appendix";
1102 if (pit->isRightToLeftPar(buf->params))
1103 langtype = "hebrew";
1109 << textclass.counters()
1110 .numberLabel(layout->latexname(),
1111 numbertype, langtype, head);
1113 pit->params().labelString(STRCONV(s.str()));
1115 // reset enum counters
1116 textclass.counters().reset("enum");
1117 } else if (layout->labeltype < LABEL_COUNTER_ENUMI) {
1118 textclass.counters().reset("enum");
1119 } else if (layout->labeltype == LABEL_COUNTER_ENUMI) {
1121 // Yes I know this is a really, really! bad solution
1123 string enumcounter("enum");
1125 switch (pit->enumdepth) {
1134 enumcounter += "iv";
1137 // not a valid enumdepth...
1141 textclass.counters().step(enumcounter);
1143 s << textclass.counters()
1144 .numberLabel(enumcounter, "enumeration");
1145 pit->params().labelString(STRCONV(s.str()));
1147 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
1148 textclass.counters().step("bibitem");
1149 int number = textclass.counters().value("bibitem");
1150 if (pit->bibitem()) {
1151 pit->bibitem()->setCounter(number);
1152 pit->params().labelString(layout->labelstring());
1154 // In biblio should't be following counters but...
1156 string s = buf->B_(layout->labelstring());
1158 // the caption hack:
1159 if (layout->labeltype == LABEL_SENSITIVE) {
1160 ParagraphList::iterator tmppit = pit;
1163 while (tmppit != ownerParagraphs().end() &&
1165 // the single '=' is intended below
1166 && (in = tmppit->inInset()->owner())) {
1167 if (in->lyxCode() == Inset::FLOAT_CODE ||
1168 in->lyxCode() == Inset::WRAP_CODE) {
1172 tmppit = in->parOwner();
1179 if (in->lyxCode() == Inset::FLOAT_CODE)
1180 type = static_cast<InsetFloat*>(in)->params().type;
1181 else if (in->lyxCode() == Inset::WRAP_CODE)
1182 type = static_cast<InsetWrap*>(in)->params().type;
1186 Floating const & fl = textclass.floats().getType(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 #:")) % buf->B_(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 << buf->B_(fl.name()) << " #:";
1200 s = STRCONV(o.str());
1203 // par->SetLayout(0);
1204 // s = layout->labelstring;
1205 s = _("Senseless: ");
1208 pit->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 (pit->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 for (; pit != ownerParagraphs().end(); ++pit) {
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);
1263 void LyXText::insertInset(Inset * inset)
1265 if (!cursor.par()->insetAllowed(inset->lyxCode()))
1267 setUndo(bv(), Undo::FINISH, cursor.par(),
1268 boost::next(cursor.par()));
1270 cursor.par()->insertInset(cursor.pos(), inset);
1271 // Just to rebreak and refresh correctly.
1272 // The character will not be inserted a second time
1273 insertChar(Paragraph::META_INSET);
1274 // If we enter a highly editable inset the cursor should be to before
1275 // the inset. This couldn't happen before as Undo was not handled inside
1276 // inset now after the Undo LyX tries to call inset->Edit(...) again
1277 // and cannot do this as the cursor is behind the inset and GetInset
1278 // does not return the inset!
1279 if (isHighlyEditableInset(inset)) {
1286 void LyXText::cutSelection(bool doclear, bool realcut)
1288 // Stuff what we got on the clipboard. Even if there is no selection.
1290 // There is a problem with having the stuffing here in that the
1291 // larger the selection the slower LyX will get. This can be
1292 // solved by running the line below only when the selection has
1293 // finished. The solution used currently just works, to make it
1294 // faster we need to be more clever and probably also have more
1295 // calls to stuffClipboard. (Lgb)
1296 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1298 // This doesn't make sense, if there is no selection
1299 if (!selection.set())
1302 // OK, we have a selection. This is always between selection.start
1303 // and selection.end
1305 // make sure that the depth behind the selection are restored, too
1306 ParagraphList::iterator endpit = boost::next(selection.end.par());
1307 ParagraphList::iterator undoendpit = endpit;
1308 ParagraphList::iterator pars_end = ownerParagraphs().end();
1310 if (endpit != pars_end && endpit->getDepth()) {
1311 while (endpit != pars_end && endpit->getDepth()) {
1313 undoendpit = endpit;
1315 } else if (endpit != pars_end) {
1316 // because of parindents etc.
1320 setUndo(bv(), Undo::DELETE, selection.start.par(), undoendpit);
1323 endpit = selection.end.par();
1324 int endpos = selection.end.pos();
1326 boost::tie(endpit, endpos) = realcut ?
1327 CutAndPaste::cutSelection(ownerParagraphs(),
1328 selection.start.par(), endpit,
1329 selection.start.pos(), endpos,
1330 bv()->buffer()->params.textclass,
1332 : CutAndPaste::eraseSelection(ownerParagraphs(),
1333 selection.start.par(), endpit,
1334 selection.start.pos(), endpos,
1336 // sometimes necessary
1338 selection.start.par()->stripLeadingSpaces();
1340 redoParagraphs(selection.start, boost::next(endpit));
1341 #warning FIXME latent bug
1342 // endpit will be invalidated on redoParagraphs once ParagraphList
1343 // becomes a std::list? There are maybe other places on which this
1344 // can happend? (Ab)
1345 // cutSelection can invalidate the cursor so we need to set
1347 // we prefer the end for when tracking changes
1351 // need a valid cursor. (Lgb)
1354 setCursor(cursor.par(), cursor.pos());
1355 selection.cursor = cursor;
1360 void LyXText::copySelection()
1362 // stuff the selection onto the X clipboard, from an explicit copy request
1363 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1365 // this doesnt make sense, if there is no selection
1366 if (!selection.set())
1369 // ok we have a selection. This is always between selection.start
1370 // and sel_end cursor
1372 // copy behind a space if there is one
1373 while (selection.start.par()->size() > selection.start.pos()
1374 && selection.start.par()->isLineSeparator(selection.start.pos())
1375 && (selection.start.par() != selection.end.par()
1376 || selection.start.pos() < selection.end.pos()))
1377 selection.start.pos(selection.start.pos() + 1);
1379 CutAndPaste::copySelection(selection.start.par(),
1380 selection.end.par(),
1381 selection.start.pos(), selection.end.pos(),
1382 bv()->buffer()->params.textclass);
1386 void LyXText::pasteSelection()
1388 // this does not make sense, if there is nothing to paste
1389 if (!CutAndPaste::checkPastePossible())
1392 setUndo(bv(), Undo::INSERT,
1393 cursor.par(), boost::next(cursor.par()));
1395 ParagraphList::iterator endpit;
1398 boost::tie(ppp, endpit) =
1399 CutAndPaste::pasteSelection(ownerParagraphs(),
1400 cursor.par(), cursor.pos(),
1401 bv()->buffer()->params.textclass);
1403 redoParagraphs(cursor, endpit);
1405 setCursor(cursor.par(), cursor.pos());
1408 selection.cursor = cursor;
1409 setCursor(ppp.first, ppp.second);
1415 void LyXText::setSelectionRange(lyx::pos_type length)
1420 selection.cursor = cursor;
1427 // simple replacing. The font of the first selected character is used
1428 void LyXText::replaceSelectionWithString(string const & str)
1430 setCursorParUndo(bv());
1433 if (!selection.set()) { // create a dummy selection
1434 selection.end = cursor;
1435 selection.start = cursor;
1438 // Get font setting before we cut
1439 pos_type pos = selection.end.pos();
1440 LyXFont const font = selection.start.par()
1441 ->getFontSettings(bv()->buffer()->params,
1442 selection.start.pos());
1444 // Insert the new string
1445 for (string::const_iterator cit = str.begin(); cit != str.end(); ++cit) {
1446 selection.end.par()->insertChar(pos, (*cit), font);
1450 // Cut the selection
1451 cutSelection(true, false);
1457 // needed to insert the selection
1458 void LyXText::insertStringAsLines(string const & str)
1460 ParagraphList::iterator pit = cursor.par();
1461 pos_type pos = cursor.pos();
1462 ParagraphList::iterator endpit = boost::next(cursor.par());
1464 setCursorParUndo(bv());
1466 // only to be sure, should not be neccessary
1469 bv()->buffer()->insertStringAsLines(pit, pos, current_font, str);
1471 redoParagraphs(cursor, endpit);
1472 setCursor(cursor.par(), cursor.pos());
1473 selection.cursor = cursor;
1474 setCursor(pit, pos);
1479 // turns double-CR to single CR, others where converted into one
1480 // blank. Then InsertStringAsLines is called
1481 void LyXText::insertStringAsParagraphs(string const & str)
1483 string linestr(str);
1484 bool newline_inserted = false;
1485 for (string::size_type i = 0; i < linestr.length(); ++i) {
1486 if (linestr[i] == '\n') {
1487 if (newline_inserted) {
1488 // we know that \r will be ignored by
1489 // InsertStringA. Of course, it is a dirty
1490 // trick, but it works...
1491 linestr[i - 1] = '\r';
1495 newline_inserted = true;
1497 } else if (IsPrintable(linestr[i])) {
1498 newline_inserted = false;
1501 insertStringAsLines(linestr);
1505 void LyXText::checkParagraph(ParagraphList::iterator pit, pos_type pos)
1507 LyXCursor tmpcursor;
1511 RowList::iterator row = getRow(pit, pos, y);
1512 RowList::iterator beg = rows().begin();
1514 // is there a break one row above
1516 && boost::prior(row)->par() == row->par()) {
1517 z = rowBreakPoint(*boost::prior(row));
1518 if (z >= row->pos()) {
1519 // set the dimensions of the row above
1520 y -= boost::prior(row)->height();
1523 breakAgain(boost::prior(row));
1525 // set the cursor again. Otherwise
1526 // dangling pointers are possible
1527 setCursor(cursor.par(), cursor.pos(),
1528 false, cursor.boundary());
1529 selection.cursor = cursor;
1534 int const tmpheight = row->height();
1535 pos_type const tmplast = lastPos(*this, row);
1538 if (row->height() == tmpheight && lastPos(*this, row) == tmplast) {
1539 postRowPaint(row, y);
1544 // check the special right address boxes
1545 if (pit->layout()->margintype == MARGIN_RIGHT_ADDRESS_BOX) {
1552 redoDrawingOfParagraph(tmpcursor);
1555 // set the cursor again. Otherwise dangling pointers are possible
1556 // also set the selection
1558 if (selection.set()) {
1560 setCursorIntern(selection.cursor.par(), selection.cursor.pos(),
1561 false, selection.cursor.boundary());
1562 selection.cursor = cursor;
1563 setCursorIntern(selection.start.par(),
1564 selection.start.pos(),
1565 false, selection.start.boundary());
1566 selection.start = cursor;
1567 setCursorIntern(selection.end.par(),
1568 selection.end.pos(),
1569 false, selection.end.boundary());
1570 selection.end = cursor;
1571 setCursorIntern(last_sel_cursor.par(),
1572 last_sel_cursor.pos(),
1573 false, last_sel_cursor.boundary());
1574 last_sel_cursor = cursor;
1577 setCursorIntern(cursor.par(), cursor.pos(),
1578 false, cursor.boundary());
1582 // returns false if inset wasn't found
1583 bool LyXText::updateInset(Inset * inset)
1585 // first check the current paragraph
1586 int pos = cursor.par()->getPositionOfInset(inset);
1588 checkParagraph(cursor.par(), pos);
1592 // check every paragraph
1594 ParagraphList::iterator par = ownerParagraphs().begin();
1595 ParagraphList::iterator end = ownerParagraphs().end();
1596 for (; par != end; ++par) {
1597 pos = par->getPositionOfInset(inset);
1599 checkParagraph(par, pos);
1608 bool LyXText::setCursor(ParagraphList::iterator pit,
1610 bool setfont, bool boundary)
1612 LyXCursor old_cursor = cursor;
1613 setCursorIntern(pit, pos, setfont, boundary);
1614 return deleteEmptyParagraphMechanism(old_cursor);
1618 void LyXText::setCursor(LyXCursor & cur, ParagraphList::iterator pit,
1619 pos_type pos, bool boundary)
1621 lyx::Assert(pit != ownerParagraphs().end());
1625 cur.boundary(boundary);
1627 // get the cursor y position in text
1629 RowList::iterator row = getRow(pit, pos, y);
1630 RowList::iterator beg = rows().begin();
1632 RowList::iterator old_row = row;
1634 // if we are before the first char of this row and are still in the
1635 // same paragraph and there is a previous row then put the cursor on
1636 // the end of the previous row
1637 cur.iy(y + row->baseline());
1640 boost::prior(row)->par() == row->par() &&
1641 pos < pit->size() &&
1642 pit->getChar(pos) == Paragraph::META_INSET) {
1643 Inset * ins = pit->getInset(pos);
1644 if (ins && (ins->needFullRow() || ins->display())) {
1651 // y is now the beginning of the cursor row
1652 y += row->baseline();
1653 // y is now the cursor baseline
1656 pos_type last = lastPrintablePos(*this, old_row);
1658 // None of these should happen, but we're scaredy-cats
1659 if (pos > pit->size()) {
1660 lyxerr << "dont like 1 please report" << endl;
1663 } else if (pos > last + 1) {
1664 lyxerr << "dont like 2 please report" << endl;
1665 // This shouldn't happen.
1668 } else if (pos < row->pos()) {
1669 lyxerr << "dont like 3 please report" << endl;
1674 // now get the cursors x position
1675 float x = getCursorX(row, pos, last, boundary);
1678 if (old_row != row) {
1679 x = getCursorX(old_row, pos, last, boundary);
1683 /* We take out this for the time being because 1) the redraw code is not
1684 prepared to this yet and 2) because some good policy has yet to be decided
1685 while editting: for instance how to act on rows being created/deleted
1689 //if the cursor is in a visible row, anchor to it
1691 if (topy < y && y < topy + bv()->workHeight())
1697 float LyXText::getCursorX(RowList::iterator rit,
1698 pos_type pos, pos_type last, bool boundary) const
1700 pos_type cursor_vpos = 0;
1702 float fill_separator;
1704 float fill_label_hfill;
1705 // This call HAS to be here because of the BidiTables!!!
1706 prepareToPrint(rit, x, fill_separator, fill_hfill,
1709 ParagraphList::iterator rit_par = rit->par();
1710 pos_type const rit_pos = rit->pos();
1713 cursor_vpos = rit_pos;
1714 else if (pos > last && !boundary)
1715 cursor_vpos = (rit_par->isRightToLeftPar(bv()->buffer()->params))
1716 ? rit_pos : last + 1;
1717 else if (pos > rit_pos && (pos > last || boundary))
1718 /// Place cursor after char at (logical) position pos - 1
1719 cursor_vpos = (bidi_level(pos - 1) % 2 == 0)
1720 ? log2vis(pos - 1) + 1 : log2vis(pos - 1);
1722 /// Place cursor before char at (logical) position pos
1723 cursor_vpos = (bidi_level(pos) % 2 == 0)
1724 ? log2vis(pos) : log2vis(pos) + 1;
1726 pos_type body_pos = rit_par->beginningOfBody();
1727 if ((body_pos > 0) &&
1728 ((body_pos - 1 > last) || !rit_par->isLineSeparator(body_pos - 1)))
1731 for (pos_type vpos = rit_pos; vpos < cursor_vpos; ++vpos) {
1732 pos_type pos = vis2log(vpos);
1733 if (body_pos > 0 && pos == body_pos - 1) {
1734 x += fill_label_hfill +
1735 font_metrics::width(
1736 rit_par->layout()->labelsep,
1737 getLabelFont(bv()->buffer(), rit_par));
1738 if (rit_par->isLineSeparator(body_pos - 1))
1739 x -= singleWidth(rit_par, body_pos - 1);
1742 if (hfillExpansion(*this, rit, pos)) {
1743 x += singleWidth(rit_par, pos);
1744 if (pos >= body_pos)
1747 x += fill_label_hfill;
1748 } else if (rit_par->isSeparator(pos)) {
1749 x += singleWidth(rit_par, pos);
1750 if (pos >= body_pos)
1751 x += fill_separator;
1753 x += singleWidth(rit_par, pos);
1759 void LyXText::setCursorIntern(ParagraphList::iterator pit,
1760 pos_type pos, bool setfont, bool boundary)
1762 InsetText * it = static_cast<InsetText *>(pit->inInset());
1764 if (it != inset_owner) {
1765 lyxerr[Debug::INSETS] << "InsetText is " << it
1767 << "inset_owner is "
1768 << inset_owner << endl;
1769 #ifdef WITH_WARNINGS
1770 #warning I believe this code is wrong. (Lgb)
1771 #warning Jürgen, have a look at this. (Lgb)
1772 #warning Hmmm, I guess you are right but we
1773 #warning should verify when this is needed
1775 // Jürgen, would you like to have a look?
1776 // I guess we need to move the outer cursor
1777 // and open and lock the inset (bla bla bla)
1778 // stuff I don't know... so can you have a look?
1780 // I moved the lyxerr stuff in here so we can see if
1781 // this is actually really needed and where!
1783 // it->getLyXText(bv())->setCursorIntern(bv(), par, pos, setfont, boundary);
1788 setCursor(cursor, pit, pos, boundary);
1794 void LyXText::setCurrentFont()
1796 pos_type pos = cursor.pos();
1797 ParagraphList::iterator pit = cursor.par();
1799 if (cursor.boundary() && pos > 0)
1803 if (pos == pit->size())
1805 else // potentional bug... BUG (Lgb)
1806 if (pit->isSeparator(pos)) {
1807 if (pos > cursor.row()->pos() &&
1808 bidi_level(pos) % 2 ==
1809 bidi_level(pos - 1) % 2)
1811 else if (pos + 1 < pit->size())
1817 pit->getFontSettings(bv()->buffer()->params, pos);
1818 real_current_font = getFont(bv()->buffer(), pit, pos);
1820 if (cursor.pos() == pit->size() &&
1821 isBoundary(bv()->buffer(), *pit, cursor.pos()) &&
1822 !cursor.boundary()) {
1823 Language const * lang =
1824 pit->getParLanguage(bv()->buffer()->params);
1825 current_font.setLanguage(lang);
1826 current_font.setNumber(LyXFont::OFF);
1827 real_current_font.setLanguage(lang);
1828 real_current_font.setNumber(LyXFont::OFF);
1833 // returns the column near the specified x-coordinate of the row
1834 // x is set to the real beginning of this column
1836 LyXText::getColumnNearX(RowList::iterator rit, int & x, bool & boundary) const
1839 float fill_separator;
1841 float fill_label_hfill;
1843 prepareToPrint(rit, tmpx, fill_separator,
1844 fill_hfill, fill_label_hfill);
1846 pos_type vc = rit->pos();
1847 pos_type last = lastPrintablePos(*this, rit);
1850 ParagraphList::iterator rit_par = rit->par();
1851 LyXLayout_ptr const & layout = rit->par()->layout();
1853 bool left_side = false;
1855 pos_type body_pos = rit_par->beginningOfBody();
1856 float last_tmpx = tmpx;
1859 (body_pos - 1 > last ||
1860 !rit_par->isLineSeparator(body_pos - 1)))
1863 // check for empty row
1864 if (!rit_par->size()) {
1869 while (vc <= last && tmpx <= x) {
1872 if (body_pos > 0 && c == body_pos - 1) {
1873 tmpx += fill_label_hfill +
1874 font_metrics::width(layout->labelsep,
1875 getLabelFont(bv()->buffer(), &*rit_par));
1876 if (rit_par->isLineSeparator(body_pos - 1))
1877 tmpx -= singleWidth(rit_par, body_pos - 1);
1880 if (hfillExpansion(*this, rit, c)) {
1881 tmpx += singleWidth(rit_par, c);
1885 tmpx += fill_label_hfill;
1886 } else if (rit_par->isSeparator(c)) {
1887 tmpx += singleWidth(rit_par, c);
1889 tmpx+= fill_separator;
1891 tmpx += singleWidth(rit_par, c);
1896 if ((tmpx + last_tmpx) / 2 > x) {
1901 if (vc > last + 1) // This shouldn't happen.
1905 // This (rtl_support test) is not needed, but gives
1906 // some speedup if rtl_support=false
1907 bool const lastrow = lyxrc.rtl_support &&
1908 (boost::next(rit) == rowlist_.end() ||
1909 boost::next(rit)->par() != rit_par);
1910 // If lastrow is false, we don't need to compute
1911 // the value of rtl.
1912 bool const rtl = (lastrow)
1913 ? rit_par->isRightToLeftPar(bv()->buffer()->params)
1916 ((rtl && left_side && vc == rit->pos() && x < tmpx - 5) ||
1917 (!rtl && !left_side && vc == last + 1 && x > tmpx + 5)))
1919 else if (vc == rit->pos()) {
1921 if (bidi_level(c) % 2 == 1)
1924 c = vis2log(vc - 1);
1925 bool const rtl = (bidi_level(c) % 2 == 1);
1926 if (left_side == rtl) {
1928 boundary = isBoundary(bv()->buffer(), *rit_par, c);
1932 if (rit->pos() <= last && c > last
1933 && rit_par->isNewline(last)) {
1934 if (bidi_level(last) % 2 == 0)
1935 tmpx -= singleWidth(rit_par, last);
1937 tmpx += singleWidth(rit_par, last);
1947 void LyXText::setCursorFromCoordinates(int x, int y)
1949 LyXCursor old_cursor = cursor;
1951 setCursorFromCoordinates(cursor, x, y);
1953 deleteEmptyParagraphMechanism(old_cursor);
1960 * return true if the cursor given is at the end of a row,
1961 * and the next row is filled by an inset that spans an entire
1964 bool beforeFullRowInset(LyXText & lt, LyXCursor const & cur) {
1965 RowList::iterator row = cur.row();
1966 if (boost::next(row) == lt.rows().end())
1968 Row const & next = *boost::next(row);
1970 if (next.pos() != cur.pos() || next.par() != cur.par())
1973 if (cur.pos() == cur.par()->size()
1974 || !cur.par()->isInset(cur.pos()))
1976 Inset const * inset = cur.par()->getInset(cur.pos());
1977 if (inset->needFullRow() || inset->display())
1984 void LyXText::setCursorFromCoordinates(LyXCursor & cur, int x, int y)
1986 // Get the row first.
1988 RowList::iterator row = getRowNearY(y);
1990 pos_type const column = getColumnNearX(row, x, bound);
1991 cur.par(row->par());
1992 cur.pos(row->pos() + column);
1994 cur.y(y + row->baseline());
1997 if (beforeFullRowInset(*this, cur)) {
1998 pos_type last = lastPrintablePos(*this, row);
1999 float x = getCursorX(boost::next(row), cur.pos(), last, bound);
2001 cur.iy(y + row->height() + boost::next(row)->baseline());
2002 cur.irow(boost::next(row));
2008 cur.boundary(bound);
2012 void LyXText::cursorLeft(bool internal)
2014 if (cursor.pos() > 0) {
2015 bool boundary = cursor.boundary();
2016 setCursor(cursor.par(), cursor.pos() - 1, true, false);
2017 if (!internal && !boundary &&
2018 isBoundary(bv()->buffer(), *cursor.par(), cursor.pos() + 1))
2019 setCursor(cursor.par(), cursor.pos() + 1, true, true);
2020 } else if (cursor.par() != ownerParagraphs().begin()) { // steps into the above paragraph.
2021 ParagraphList::iterator pit = boost::prior(cursor.par());
2022 setCursor(pit, pit->size());
2027 void LyXText::cursorRight(bool internal)
2029 bool const at_end = (cursor.pos() == cursor.par()->size());
2030 bool const at_newline = !at_end &&
2031 cursor.par()->isNewline(cursor.pos());
2033 if (!internal && cursor.boundary() && !at_newline)
2034 setCursor(cursor.par(), cursor.pos(), true, false);
2036 setCursor(cursor.par(), cursor.pos() + 1, true, false);
2038 isBoundary(bv()->buffer(), *cursor.par(), cursor.pos()))
2039 setCursor(cursor.par(), cursor.pos(), true, true);
2040 } else if (boost::next(cursor.par()) != ownerParagraphs().end())
2041 setCursor(boost::next(cursor.par()), 0);
2045 void LyXText::cursorUp(bool selecting)
2048 int x = cursor.x_fix();
2049 int y = cursor.y() - cursor.row()->baseline() - 1;
2050 setCursorFromCoordinates(x, y);
2053 int y1 = cursor.iy() - topy;
2056 Inset * inset_hit = checkInsetHit(x, y1);
2057 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2058 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2062 setCursorFromCoordinates(bv(), cursor.x_fix(),
2063 cursor.y() - cursor.row()->baseline() - 1);
2068 void LyXText::cursorDown(bool selecting)
2071 int x = cursor.x_fix();
2072 int y = cursor.y() - cursor.row()->baseline() +
2073 cursor.row()->height() + 1;
2074 setCursorFromCoordinates(x, y);
2075 if (!selecting && cursor.row() == cursor.irow()) {
2077 int y1 = cursor.iy() - topy;
2080 Inset * inset_hit = checkInsetHit(x, y1);
2081 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2082 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2086 setCursorFromCoordinates(bv(), cursor.x_fix(),
2087 cursor.y() - cursor.row()->baseline()
2088 + cursor.row()->height() + 1);
2093 void LyXText::cursorUpParagraph()
2095 if (cursor.pos() > 0) {
2096 setCursor(cursor.par(), 0);
2098 else if (cursor.par() != ownerParagraphs().begin()) {
2099 setCursor(boost::prior(cursor.par()), 0);
2104 void LyXText::cursorDownParagraph()
2106 if (boost::next(cursor.par()) != ownerParagraphs().end()) {
2107 setCursor(boost::next(cursor.par()), 0);
2109 setCursor(cursor.par(), cursor.par()->size());
2113 // fix the cursor `cur' after a characters has been deleted at `where'
2114 // position. Called by deleteEmptyParagraphMechanism
2115 void LyXText::fixCursorAfterDelete(LyXCursor & cur,
2116 LyXCursor const & where)
2118 // if cursor is not in the paragraph where the delete occured,
2120 if (cur.par() != where.par())
2123 // if cursor position is after the place where the delete occured,
2125 if (cur.pos() > where.pos())
2126 cur.pos(cur.pos()-1);
2128 // check also if we don't want to set the cursor on a spot behind the
2129 // pagragraph because we erased the last character.
2130 if (cur.pos() > cur.par()->size())
2131 cur.pos(cur.par()->size());
2133 // recompute row et al. for this cursor
2134 setCursor(cur, cur.par(), cur.pos(), cur.boundary());
2138 bool LyXText::deleteEmptyParagraphMechanism(LyXCursor const & old_cursor)
2140 // Would be wrong to delete anything if we have a selection.
2141 if (selection.set())
2144 // We allow all kinds of "mumbo-jumbo" when freespacing.
2145 if (old_cursor.par()->layout()->free_spacing
2146 || old_cursor.par()->isFreeSpacing()) {
2150 /* Ok I'll put some comments here about what is missing.
2151 I have fixed BackSpace (and thus Delete) to not delete
2152 double-spaces automagically. I have also changed Cut,
2153 Copy and Paste to hopefully do some sensible things.
2154 There are still some small problems that can lead to
2155 double spaces stored in the document file or space at
2156 the beginning of paragraphs. This happens if you have
2157 the cursor betwenn to spaces and then save. Or if you
2158 cut and paste and the selection have a space at the
2159 beginning and then save right after the paste. I am
2160 sure none of these are very hard to fix, but I will
2161 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
2162 that I can get some feedback. (Lgb)
2165 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
2166 // delete the LineSeparator.
2169 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
2170 // delete the LineSeparator.
2173 // If the pos around the old_cursor were spaces, delete one of them.
2174 if (old_cursor.par() != cursor.par()
2175 || old_cursor.pos() != cursor.pos()) {
2176 // Only if the cursor has really moved
2178 if (old_cursor.pos() > 0
2179 && old_cursor.pos() < old_cursor.par()->size()
2180 && old_cursor.par()->isLineSeparator(old_cursor.pos())
2181 && old_cursor.par()->isLineSeparator(old_cursor.pos() - 1)) {
2182 old_cursor.par()->erase(old_cursor.pos() - 1);
2183 redoParagraphs(old_cursor, boost::next(old_cursor.par()));
2185 #ifdef WITH_WARNINGS
2186 #warning This will not work anymore when we have multiple views of the same buffer
2187 // In this case, we will have to correct also the cursors held by
2188 // other bufferviews. It will probably be easier to do that in a more
2189 // automated way in LyXCursor code. (JMarc 26/09/2001)
2191 // correct all cursors held by the LyXText
2192 fixCursorAfterDelete(cursor, old_cursor);
2193 fixCursorAfterDelete(selection.cursor,
2195 fixCursorAfterDelete(selection.start,
2197 fixCursorAfterDelete(selection.end, old_cursor);
2198 fixCursorAfterDelete(last_sel_cursor,
2200 fixCursorAfterDelete(toggle_cursor, old_cursor);
2201 fixCursorAfterDelete(toggle_end_cursor,
2207 // don't delete anything if this is the ONLY paragraph!
2208 if (ownerParagraphs().size() == 1)
2211 // Do not delete empty paragraphs with keepempty set.
2212 if (old_cursor.par()->layout()->keepempty)
2215 // only do our magic if we changed paragraph
2216 if (old_cursor.par() == cursor.par())
2219 // record if we have deleted a paragraph
2220 // we can't possibly have deleted a paragraph before this point
2221 bool deleted = false;
2223 if (old_cursor.par()->empty() ||
2224 (old_cursor.par()->size() == 1 &&
2225 old_cursor.par()->isLineSeparator(0))) {
2226 // ok, we will delete anything
2227 LyXCursor tmpcursor;
2231 bool selection_position_was_oldcursor_position = (
2232 selection.cursor.par() == old_cursor.par()
2233 && selection.cursor.pos() == old_cursor.pos());
2235 if (old_cursor.row() != rows().begin()) {
2237 prevrow = boost::prior(old_cursor.row());
2238 const_cast<LyXText *>(this)->postPaint(old_cursor.y() - old_cursor.row()->baseline() - prevrow->height());
2240 cursor = old_cursor; // that undo can restore the right cursor position
2241 #warning FIXME. --end() iterator is usable here
2242 ParagraphList::iterator endpit = boost::next(old_cursor.par());
2243 while (endpit != ownerParagraphs().end() &&
2244 endpit->getDepth()) {
2248 setUndo(bv(), Undo::DELETE, old_cursor.par(), endpit);
2252 removeRow(old_cursor.row());
2254 ownerParagraphs().erase(old_cursor.par());
2256 /* Breakagain the next par. Needed because of
2257 * the parindent that can occur or dissappear.
2258 * The next row can change its height, if
2259 * there is another layout before */
2260 if (boost::next(prevrow) != rows().end()) {
2261 breakAgain(boost::next(prevrow));
2264 setHeightOfRow(prevrow);
2266 RowList::iterator nextrow = boost::next(old_cursor.row());
2267 const_cast<LyXText *>(this)->postPaint(
2268 old_cursor.y() - old_cursor.row()->baseline());
2271 cursor = old_cursor; // that undo can restore the right cursor position
2272 #warning FIXME. --end() iterator is usable here
2273 ParagraphList::iterator endpit = boost::next(old_cursor.par());
2274 while (endpit != ownerParagraphs().end() &&
2275 endpit->getDepth()) {
2279 setUndo(bv(), Undo::DELETE, old_cursor.par(), endpit);
2283 removeRow(old_cursor.row());
2285 ownerParagraphs().erase(old_cursor.par());
2287 /* Breakagain the next par. Needed because of
2288 the parindent that can occur or dissappear.
2289 The next row can change its height, if
2290 there is another layout before */
2291 if (nextrow != rows().end()) {
2292 breakAgain(nextrow);
2298 setCursorIntern(cursor.par(), cursor.pos());
2300 if (selection_position_was_oldcursor_position) {
2301 // correct selection
2302 selection.cursor = cursor;
2306 if (old_cursor.par()->stripLeadingSpaces()) {
2307 redoParagraphs(old_cursor, boost::next(old_cursor.par()));
2309 setCursorIntern(cursor.par(), cursor.pos());
2310 selection.cursor = cursor;
2317 ParagraphList & LyXText::ownerParagraphs() const
2320 return inset_owner->paragraphs;
2322 return bv_owner->buffer()->paragraphs;
2326 LyXText::refresh_status LyXText::refreshStatus() const
2328 return refresh_status_;
2332 void LyXText::clearPaint()
2334 refresh_status_ = REFRESH_NONE;
2335 refresh_row = rows().end();
2340 void LyXText::postPaint(int start_y)
2342 refresh_status old = refresh_status_;
2344 refresh_status_ = REFRESH_AREA;
2345 refresh_row = rows().end();
2347 if (old != REFRESH_NONE && refresh_y < start_y)
2350 refresh_y = start_y;
2355 // We are an inset's lyxtext. Tell the top-level lyxtext
2356 // it needs to update the row we're in.
2357 LyXText * t = bv()->text;
2358 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2362 // FIXME: we should probably remove this y parameter,
2363 // make refresh_y be 0, and use row->y etc.
2364 void LyXText::postRowPaint(RowList::iterator rit, int start_y)
2366 if (refresh_status_ != REFRESH_NONE && refresh_y < start_y) {
2367 refresh_status_ = REFRESH_AREA;
2370 refresh_y = start_y;
2373 if (refresh_status_ == REFRESH_AREA)
2376 refresh_status_ = REFRESH_ROW;
2382 // We are an inset's lyxtext. Tell the top-level lyxtext
2383 // it needs to update the row we're in.
2384 LyXText * t = bv()->text;
2385 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2389 bool LyXText::isInInset() const
2391 // Sub-level has non-null bv owner and
2392 // non-null inset owner.
2393 return inset_owner != 0 && bv_owner != 0;
2397 int defaultRowHeight()
2399 LyXFont const font(LyXFont::ALL_SANE);
2400 return int(font_metrics::maxAscent(font)
2401 + font_metrics::maxDescent(font) * 1.5);