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"
38 #include "support/LAssert.h"
39 #include "support/textutils.h"
40 #include "support/lstrings.h"
42 #include "support/BoostFormat.h"
52 LyXText::LyXText(BufferView * bv)
53 : height(0), width(0), anchor_row_offset_(0),
54 inset_owner(0), the_locking_inset(0), bv_owner(bv)
56 anchor_row_ = rows().end();
57 need_break_row = rows().end();
58 refresh_row = rows().end();
64 LyXText::LyXText(BufferView * bv, InsetText * inset)
65 : height(0), width(0), anchor_row_offset_(0),
66 inset_owner(inset), the_locking_inset(0), bv_owner(bv)
68 anchor_row_ = rows().end();
69 need_break_row = rows().end();
70 refresh_row = rows().end();
76 void LyXText::init(BufferView * bview, bool reinit)
80 need_break_row = rows().end();
82 copylayouttype.erase();
85 } else if (!rowlist_.empty())
88 ParagraphList::iterator pit = ownerParagraphs().begin();
89 ParagraphList::iterator end = ownerParagraphs().end();
91 current_font = getFont(bview->buffer(), pit, 0);
93 for (; pit != end; ++pit) {
94 insertParagraph(pit, rowlist_.end());
96 setCursorIntern(rowlist_.begin()->par(), 0);
97 selection.cursor = cursor;
103 // Gets the fully instantiated font at a given position in a paragraph
104 // Basically the same routine as Paragraph::getFont() in paragraph.C.
105 // The difference is that this one is used for displaying, and thus we
106 // are allowed to make cosmetic improvements. For instance make footnotes
108 // If position is -1, we get the layout font of the paragraph.
109 // If position is -2, we get the font of the manual label of the paragraph.
110 LyXFont const LyXText::getFont(Buffer const * buf, ParagraphList::iterator pit,
113 lyx::Assert(pos >= 0);
115 LyXLayout_ptr const & layout = pit->layout();
117 // We specialize the 95% common case:
118 if (!pit->getDepth()) {
119 if (layout->labeltype == LABEL_MANUAL
120 && pos < pit->beginningOfBody()) {
122 LyXFont f = pit->getFontSettings(buf->params, pos);
124 pit->inInset()->getDrawFont(f);
125 return f.realize(layout->reslabelfont);
127 LyXFont f = pit->getFontSettings(buf->params, pos);
129 pit->inInset()->getDrawFont(f);
130 return f.realize(layout->resfont);
134 // The uncommon case need not be optimized as much
138 if (pos < pit->beginningOfBody()) {
140 layoutfont = layout->labelfont;
143 layoutfont = layout->font;
146 LyXFont tmpfont = pit->getFontSettings(buf->params, pos);
147 tmpfont.realize(layoutfont);
150 pit->inInset()->getDrawFont(tmpfont);
152 return realizeFont(tmpfont, buf, ownerParagraphs(), pit);
156 LyXFont const LyXText::getLayoutFont(Buffer const * buf,
157 ParagraphList::iterator pit) const
159 LyXLayout_ptr const & layout = pit->layout();
161 if (!pit->getDepth()) {
162 return layout->resfont;
165 return realizeFont(layout->font, buf, ownerParagraphs(), pit);
169 LyXFont const LyXText::getLabelFont(Buffer const * buf,
170 ParagraphList::iterator pit) const
172 LyXLayout_ptr const & layout = pit->layout();
174 if (!pit->getDepth()) {
175 return layout->reslabelfont;
178 return realizeFont(layout->labelfont, buf, ownerParagraphs(), pit);
182 void LyXText::setCharFont(ParagraphList::iterator pit,
183 pos_type pos, LyXFont const & fnt,
186 Buffer const * buf = bv()->buffer();
187 LyXFont font = getFont(buf, pit, pos);
188 font.update(fnt, buf->params.language, toggleall);
189 // Let the insets convert their font
190 if (pit->isInset(pos)) {
191 Inset * inset = pit->getInset(pos);
192 if (isEditableInset(inset)) {
193 UpdatableInset * uinset =
194 static_cast<UpdatableInset *>(inset);
195 uinset->setFont(bv(), fnt, toggleall, true);
199 // Plug thru to version below:
200 setCharFont(buf, pit, pos, font);
204 void LyXText::setCharFont(Buffer const * buf, ParagraphList::iterator pit,
205 pos_type pos, LyXFont const & fnt)
209 LyXTextClass const & tclass = buf->params.getLyXTextClass();
210 LyXLayout_ptr const & layout = pit->layout();
212 // Get concrete layout font to reduce against
215 if (pos < pit->beginningOfBody())
216 layoutfont = layout->labelfont;
218 layoutfont = layout->font;
220 // Realize against environment font information
221 if (pit->getDepth()) {
222 #warning FIXME I think I hate this outerHood stuff.
223 Paragraph * tp = &*pit;
224 while (!layoutfont.resolved() && tp && tp->getDepth()) {
227 layoutfont.realize(tp->layout()->font);
231 layoutfont.realize(tclass.defaultfont());
233 // Now, reduce font against full layout font
234 font.reduce(layoutfont);
236 pit->setFont(pos, font);
240 // removes the row and reset the touched counters
241 void LyXText::removeRow(RowList::iterator rit)
243 /* FIXME: when we cache the bview, this should just
244 * become a postPaint(), I think */
245 if (refresh_row == rit) {
246 if (rit == rows().begin())
247 refresh_row = boost::next(rit);
249 refresh_row = boost::prior(rit);
251 // what about refresh_y
254 if (anchor_row_ == rit) {
255 if (rit != rows().begin()) {
256 anchor_row_ = boost::prior(rit);
257 anchor_row_offset_ += anchor_row_->height();
259 anchor_row_ = boost::next(rit);
260 anchor_row_offset_ -= rit->height();
264 // the text becomes smaller
265 height -= rit->height();
271 // remove all following rows of the paragraph of the specified row.
272 void LyXText::removeParagraph(RowList::iterator rit)
274 ParagraphList::iterator tmppit = rit->par();
277 while (rit != rows().end() && rit->par() == tmppit) {
278 RowList::iterator tmprit = boost::next(rit);
285 void LyXText::insertParagraph(ParagraphList::iterator pit,
286 RowList::iterator rowit)
288 // insert a new row, starting at position 0
290 RowList::iterator rit = rowlist_.insert(rowit, newrow);
292 // and now append the whole paragraph before the new row
293 appendParagraph(rit);
297 Inset * LyXText::getInset() const
299 ParagraphList::iterator pit = cursor.par();
300 pos_type const pos = cursor.pos();
302 if (pos < pit->size() && pit->isInset(pos)) {
303 return pit->getInset(pos);
309 void LyXText::toggleInset()
311 Inset * inset = getInset();
312 // is there an editable inset at cursor position?
313 if (!isEditableInset(inset)) {
314 // No, try to see if we are inside a collapsable inset
315 if (inset_owner && inset_owner->owner()
316 && inset_owner->owner()->isOpen()) {
317 bv()->unlockInset(static_cast<UpdatableInset *>(inset_owner->owner()));
318 inset_owner->owner()->close(bv());
319 bv()->getLyXText()->cursorRight(bv());
323 //bv()->owner()->message(inset->editMessage());
325 // do we want to keep this?? (JMarc)
326 if (!isHighlyEditableInset(inset))
327 setCursorParUndo(bv());
329 if (inset->isOpen()) {
335 bv()->updateInset(inset);
339 /* used in setlayout */
340 // Asger is not sure we want to do this...
341 void LyXText::makeFontEntriesLayoutSpecific(Buffer const & buf,
344 LyXLayout_ptr const & layout = par.layout();
345 pos_type const psize = par.size();
348 for (pos_type pos = 0; pos < psize; ++pos) {
349 if (pos < par.beginningOfBody())
350 layoutfont = layout->labelfont;
352 layoutfont = layout->font;
354 LyXFont tmpfont = par.getFontSettings(buf.params, pos);
355 tmpfont.reduce(layoutfont);
356 par.setFont(pos, tmpfont);
361 ParagraphList::iterator
362 LyXText::setLayout(LyXCursor & cur, LyXCursor & sstart_cur,
363 LyXCursor & send_cur,
364 string const & layout)
366 ParagraphList::iterator endpit = boost::next(send_cur.par());
367 ParagraphList::iterator undoendpit = endpit;
368 ParagraphList::iterator pars_end = ownerParagraphs().end();
370 if (endpit != pars_end && endpit->getDepth()) {
371 while (endpit != pars_end && endpit->getDepth()) {
375 } else if (endpit != pars_end) {
376 // because of parindents etc.
380 setUndo(bv(), Undo::EDIT, &*sstart_cur.par(), &*undoendpit);
382 // ok we have a selection. This is always between sstart_cur
383 // and sel_end cursor
385 ParagraphList::iterator pit = sstart_cur.par();
386 ParagraphList::iterator epit = boost::next(send_cur.par());
388 LyXLayout_ptr const & lyxlayout =
389 bv()->buffer()->params.getLyXTextClass()[layout];
392 pit->applyLayout(lyxlayout);
393 makeFontEntriesLayoutSpecific(*bv()->buffer(), *pit);
394 ParagraphList::iterator fppit = pit;
395 fppit->params().spaceTop(lyxlayout->fill_top ?
396 VSpace(VSpace::VFILL)
397 : VSpace(VSpace::NONE));
398 fppit->params().spaceBottom(lyxlayout->fill_bottom ?
399 VSpace(VSpace::VFILL)
400 : VSpace(VSpace::NONE));
401 if (lyxlayout->margintype == MARGIN_MANUAL)
402 pit->setLabelWidthString(lyxlayout->labelstring());
405 } while (pit != epit);
411 // set layout over selection and make a total rebreak of those paragraphs
412 void LyXText::setLayout(string const & layout)
414 LyXCursor tmpcursor = cursor; /* store the current cursor */
416 // if there is no selection just set the layout
417 // of the current paragraph */
418 if (!selection.set()) {
419 selection.start = cursor; // dummy selection
420 selection.end = cursor;
422 ParagraphList::iterator endpit = setLayout(cursor, selection.start,
423 selection.end, layout);
424 redoParagraphs(selection.start, endpit);
426 // we have to reset the selection, because the
427 // geometry could have changed
428 setCursor(selection.start.par(),
429 selection.start.pos(), false);
430 selection.cursor = cursor;
431 setCursor(selection.end.par(), selection.end.pos(), false);
435 setCursor(tmpcursor.par(), tmpcursor.pos(), true);
439 bool LyXText::changeDepth(bv_funcs::DEPTH_CHANGE type, bool test_only)
441 ParagraphList::iterator pit(cursor.par());
442 ParagraphList::iterator end(cursor.par());
443 ParagraphList::iterator start = pit;
445 if (selection.set()) {
446 pit = selection.start.par();
447 end = selection.end.par();
451 ParagraphList::iterator pastend = boost::next(end);
454 setUndo(bv(), Undo::EDIT, &(*start), &(*pastend));
456 bool changed = false;
458 int prev_after_depth = 0;
459 #warning parlist ... could be nicer ?
460 if (start != ownerParagraphs().begin()) {
461 prev_after_depth = boost::prior(start)->getMaxDepthAfter();
465 int const depth = pit->params().depth();
466 if (type == bv_funcs::INC_DEPTH) {
467 if (depth < prev_after_depth
468 && pit->layout()->labeltype != LABEL_BIBLIO) {
471 pit->params().depth(depth + 1);
478 pit->params().depth(depth - 1);
481 prev_after_depth = pit->getMaxDepthAfter();
493 // Wow, redoParagraphs is stupid.
495 setCursor(tmpcursor, &(*start), 0);
497 //redoParagraphs(tmpcursor, &(*pastend));
498 redoParagraphs(tmpcursor, &(*pastend));
500 // We need to actually move the text->cursor. I don't
501 // understand why ...
504 // we have to reset the visual selection because the
505 // geometry could have changed
506 if (selection.set()) {
507 setCursor(selection.start.par(), selection.start.pos());
508 selection.cursor = cursor;
509 setCursor(selection.end.par(), selection.end.pos());
512 // this handles the counter labels, and also fixes up
513 // depth values for follow-on (child) paragraphs
517 setCursor(tmpcursor.par(), tmpcursor.pos());
523 // set font over selection and make a total rebreak of those paragraphs
524 void LyXText::setFont(LyXFont const & font, bool toggleall)
526 // if there is no selection just set the current_font
527 if (!selection.set()) {
528 // Determine basis font
530 if (cursor.pos() < cursor.par()->beginningOfBody()) {
531 layoutfont = getLabelFont(bv()->buffer(),
534 layoutfont = getLayoutFont(bv()->buffer(),
537 // Update current font
538 real_current_font.update(font,
539 bv()->buffer()->params.language,
542 // Reduce to implicit settings
543 current_font = real_current_font;
544 current_font.reduce(layoutfont);
545 // And resolve it completely
546 real_current_font.realize(layoutfont);
551 LyXCursor tmpcursor = cursor; // store the current cursor
553 // ok we have a selection. This is always between sel_start_cursor
554 // and sel_end cursor
556 setUndo(bv(), Undo::EDIT,
557 &*selection.start.par(), &*boost::next(selection.end.par()));
559 cursor = selection.start;
560 while (cursor.par() != selection.end.par() ||
561 cursor.pos() < selection.end.pos())
563 if (cursor.pos() < cursor.par()->size()) {
564 // an open footnote should behave like a closed one
565 setCharFont(cursor.par(), cursor.pos(),
567 cursor.pos(cursor.pos() + 1);
570 cursor.par(boost::next(cursor.par()));
575 redoParagraphs(selection.start, boost::next(selection.end.par()));
577 // we have to reset the selection, because the
578 // geometry could have changed, but we keep
579 // it for user convenience
580 setCursor(selection.start.par(), selection.start.pos());
581 selection.cursor = cursor;
582 setCursor(selection.end.par(), selection.end.pos());
584 setCursor(tmpcursor.par(), tmpcursor.pos(), true,
585 tmpcursor.boundary());
589 void LyXText::redoHeightOfParagraph()
591 RowList::iterator tmprow = cursor.row();
592 int y = cursor.y() - tmprow->baseline();
594 setHeightOfRow(tmprow);
596 while (tmprow != rows().begin()
597 && boost::prior(tmprow)->par() == tmprow->par()) {
599 y -= tmprow->height();
600 setHeightOfRow(tmprow);
605 setCursor(cursor.par(), cursor.pos(), false, cursor.boundary());
609 void LyXText::redoDrawingOfParagraph(LyXCursor const & cur)
611 RowList::iterator tmprow = cur.row();
613 int y = cur.y() - tmprow->baseline();
614 setHeightOfRow(tmprow);
616 while (tmprow != rows().begin()
617 && boost::prior(tmprow)->par() == tmprow->par()) {
619 y -= tmprow->height();
623 setCursor(cur.par(), cur.pos());
627 // deletes and inserts again all paragaphs between the cursor
628 // and the specified par
629 // This function is needed after SetLayout and SetFont etc.
630 void LyXText::redoParagraphs(LyXCursor const & cur,
631 ParagraphList::iterator endpit)
633 RowList::iterator tmprit = cur.row();
634 int y = cur.y() - tmprit->baseline();
636 ParagraphList::iterator first_phys_pit;
637 if (tmprit == rows().begin()) {
638 // A trick/hack for UNDO.
639 // This is needed because in an UNDO/REDO we could have
640 // changed the ownerParagrah() so the paragraph inside
641 // the row is NOT my really first par anymore.
642 // Got it Lars ;) (Jug 20011206)
643 first_phys_pit = ownerParagraphs().begin();
645 // In here prevrit could be set to rows().end(). (Lgb)
647 first_phys_pit = tmprit->par();
648 while (tmprit != rows().begin()
649 && boost::prior(tmprit)->par() == first_phys_pit)
652 y -= tmprit->height();
655 // Is it possible to put the prevrit setting in here? (Lgb)
658 RowList::iterator prevrit;
659 bool good_prevrit = false;
661 // It seems to mee that good_prevrit is not needed if we let
662 // a bad prevrit have the value rows().end() (Lgb)
663 if (tmprit != rows().begin()) {
664 prevrit = boost::prior(tmprit);
669 while (tmprit != rows().end() && tmprit->par() != endpit) {
670 RowList::iterator tmprit2 = tmprit++;
674 // Reinsert the paragraphs.
675 ParagraphList::iterator tmppit = first_phys_pit;
677 // See if this loop can be rewritten as a while loop instead.
678 // That should also make the code a bit easier to read. (Lgb)
680 if (tmppit != ownerParagraphs().end()) {
681 insertParagraph(tmppit, tmprit);
682 while (tmprit != rows().end()
683 && tmprit->par() == tmppit) {
688 } while (tmppit != ownerParagraphs().end() && tmppit != endpit);
691 // If the above changes are done, then we can compare prevrit
692 // with rows().end() here. (Lgb)
694 setHeightOfRow(prevrit);
695 const_cast<LyXText *>(this)->postPaint(y - prevrit->height());
697 setHeightOfRow(rows().begin());
698 const_cast<LyXText *>(this)->postPaint(0);
700 if (tmprit != rows().end())
701 setHeightOfRow(tmprit);
707 void LyXText::fullRebreak()
709 if (rows().empty()) {
714 RowList::iterator rows_end = rows().end();
716 if (need_break_row != rows_end) {
717 breakAgain(need_break_row);
718 need_break_row = rows_end;
724 // important for the screen
727 // the cursor set functions have a special mechanism. When they
728 // realize, that you left an empty paragraph, they will delete it.
729 // They also delete the corresponding row
731 // need the selection cursor:
732 void LyXText::setSelection()
734 bool const lsel = selection.set();
736 if (!selection.set()) {
737 last_sel_cursor = selection.cursor;
738 selection.start = selection.cursor;
739 selection.end = selection.cursor;
744 // first the toggling area
745 if (cursor.y() < last_sel_cursor.y()
746 || (cursor.y() == last_sel_cursor.y()
747 && cursor.x() < last_sel_cursor.x())) {
748 toggle_end_cursor = last_sel_cursor;
749 toggle_cursor = cursor;
751 toggle_end_cursor = cursor;
752 toggle_cursor = last_sel_cursor;
755 last_sel_cursor = cursor;
757 // and now the whole selection
759 if (selection.cursor.par() == cursor.par())
760 if (selection.cursor.pos() < cursor.pos()) {
761 selection.end = cursor;
762 selection.start = selection.cursor;
764 selection.end = selection.cursor;
765 selection.start = cursor;
767 else if (selection.cursor.y() < cursor.y() ||
768 (selection.cursor.y() == cursor.y()
769 && selection.cursor.x() < cursor.x())) {
770 selection.end = cursor;
771 selection.start = selection.cursor;
774 selection.end = selection.cursor;
775 selection.start = cursor;
778 // a selection with no contents is not a selection
779 if (selection.start.par() == selection.end.par() &&
780 selection.start.pos() == selection.end.pos())
781 selection.set(false);
783 if (inset_owner && (selection.set() || lsel))
784 inset_owner->setUpdateStatus(bv(), InsetText::SELECTION);
788 string const LyXText::selectionAsString(Buffer const * buffer,
791 if (!selection.set()) return string();
793 // should be const ...
794 ParagraphList::iterator startpit = selection.start.par();
795 ParagraphList::iterator endpit = selection.end.par();
796 pos_type const startpos(selection.start.pos());
797 pos_type const endpos(selection.end.pos());
799 if (startpit == endpit) {
800 return startpit->asString(buffer, startpos, endpos, label);
805 // First paragraph in selection
806 result += startpit->asString(buffer, startpos, startpit->size(), label) + "\n\n";
808 // The paragraphs in between (if any)
809 ParagraphList::iterator pit = boost::next(startpit);
810 for (; pit != endpit; ++pit) {
811 result += pit->asString(buffer, 0, pit->size(), label) + "\n\n";
814 // Last paragraph in selection
815 result += endpit->asString(buffer, 0, endpos, label);
821 void LyXText::clearSelection()
823 selection.set(false);
824 selection.mark(false);
825 last_sel_cursor = selection.end = selection.start = selection.cursor = cursor;
826 // reset this in the bv_owner!
827 if (bv_owner && bv_owner->text)
828 bv_owner->text->xsel_cache.set(false);
832 void LyXText::cursorHome()
834 setCursor(cursor.par(), cursor.row()->pos());
838 void LyXText::cursorEnd()
840 if (cursor.par()->empty())
843 RowList::iterator rit = cursor.row();
844 RowList::iterator next_rit = boost::next(rit);
845 ParagraphList::iterator pit = rit->par();
846 pos_type last_pos = lastPos(*this, rit);
848 if (next_rit == rows().end() || next_rit->par() != pit) {
852 (pit->getChar(last_pos) != ' ' && !pit->isNewline(last_pos))) {
857 setCursor(pit, last_pos);
861 void LyXText::cursorTop()
863 setCursor(ownerParagraphs().begin(), 0);
867 void LyXText::cursorBottom()
870 // This is how it should be:
871 // ParagraphList::iterator lastpit = boost::prior(ownerParagraphs().end());
872 ParagraphList::iterator lastpit = &ownerParagraphs().back();
873 int pos = lastpit->size();
874 setCursor(lastpit, pos);
878 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
880 // If the mask is completely neutral, tell user
881 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
882 // Could only happen with user style
883 bv()->owner()->message(_("No font change defined. Use Character under the Layout menu to define font change."));
887 // Try implicit word selection
888 // If there is a change in the language the implicit word selection
890 LyXCursor resetCursor = cursor;
891 bool implicitSelection = (font.language() == ignore_language
892 && font.number() == LyXFont::IGNORE)
893 ? selectWordWhenUnderCursor(WHOLE_WORD_STRICT) : false;
896 setFont(font, toggleall);
898 // Implicit selections are cleared afterwards
899 //and cursor is set to the original position.
900 if (implicitSelection) {
902 cursor = resetCursor;
903 setCursor(cursor.par(), cursor.pos());
904 selection.cursor = cursor;
907 inset_owner->setUpdateStatus(bv(), InsetText::CURSOR_PAR);
911 string LyXText::getStringToIndex()
913 // Try implicit word selection
914 // If there is a change in the language the implicit word selection
916 LyXCursor const reset_cursor = cursor;
917 bool const implicitSelection = selectWordWhenUnderCursor(PREVIOUS_WORD);
920 if (!selection.set())
921 bv()->owner()->message(_("Nothing to index!"));
922 else if (selection.start.par() != selection.end.par())
923 bv()->owner()->message(_("Cannot index more than one paragraph!"));
925 idxstring = selectionAsString(bv()->buffer(), false);
927 // Reset cursors to their original position.
928 cursor = reset_cursor;
929 setCursor(cursor.par(), cursor.pos());
930 selection.cursor = cursor;
932 // Clear the implicit selection.
933 if (implicitSelection)
940 // the DTP switches for paragraphs. LyX will store them in the first
941 // physicla paragraph. When a paragraph is broken, the top settings rest,
942 // the bottom settings are given to the new one. So I can make shure,
943 // they do not duplicate themself and you cannnot make dirty things with
946 void LyXText::setParagraph(bool line_top, bool line_bottom,
947 bool pagebreak_top, bool pagebreak_bottom,
948 VSpace const & space_top,
949 VSpace const & space_bottom,
950 Spacing const & spacing,
952 string const & labelwidthstring,
955 LyXCursor tmpcursor = cursor;
956 if (!selection.set()) {
957 selection.start = cursor;
958 selection.end = cursor;
961 // make sure that the depth behind the selection are restored, too
962 ParagraphList::iterator endpit = boost::next(selection.end.par());
963 ParagraphList::iterator undoendpit = endpit;
964 ParagraphList::iterator pars_end = ownerParagraphs().end();
966 if (endpit != pars_end && endpit->getDepth()) {
967 while (endpit != pars_end && endpit->getDepth()) {
971 } else if (endpit!= pars_end) {
972 // because of parindents etc.
976 setUndo(bv(), Undo::EDIT, &*selection.start.par(), &*undoendpit);
979 ParagraphList::iterator tmppit = selection.end.par();
981 while (tmppit != boost::prior(selection.start.par())) {
982 setCursor(tmppit, 0);
983 postPaint(cursor.y() - cursor.row()->baseline());
985 ParagraphList::iterator pit = cursor.par();
986 ParagraphParameters & params = pit->params();
988 params.lineTop(line_top);
989 params.lineBottom(line_bottom);
990 params.pagebreakTop(pagebreak_top);
991 params.pagebreakBottom(pagebreak_bottom);
992 params.spaceTop(space_top);
993 params.spaceBottom(space_bottom);
994 params.spacing(spacing);
995 // does the layout allow the new alignment?
996 LyXLayout_ptr const & layout = pit->layout();
998 if (align == LYX_ALIGN_LAYOUT)
999 align = layout->align;
1000 if (align & layout->alignpossible) {
1001 if (align == layout->align)
1002 params.align(LYX_ALIGN_LAYOUT);
1004 params.align(align);
1006 pit->setLabelWidthString(labelwidthstring);
1007 params.noindent(noindent);
1008 tmppit = boost::prior(pit);
1011 redoParagraphs(selection.start, endpit);
1014 setCursor(selection.start.par(), selection.start.pos());
1015 selection.cursor = cursor;
1016 setCursor(selection.end.par(), selection.end.pos());
1018 setCursor(tmpcursor.par(), tmpcursor.pos());
1020 bv()->updateInset(inset_owner);
1024 // set the counter of a paragraph. This includes the labels
1025 void LyXText::setCounter(Buffer const * buf, ParagraphList::iterator pit)
1027 LyXTextClass const & textclass = buf->params.getLyXTextClass();
1028 LyXLayout_ptr const & layout = pit->layout();
1030 if (pit != ownerParagraphs().begin()) {
1032 pit->params().appendix(boost::prior(pit)->params().appendix());
1033 if (!pit->params().appendix() &&
1034 pit->params().startOfAppendix()) {
1035 pit->params().appendix(true);
1036 textclass.counters().reset();
1038 pit->enumdepth = boost::prior(pit)->enumdepth;
1039 pit->itemdepth = boost::prior(pit)->itemdepth;
1041 pit->params().appendix(pit->params().startOfAppendix());
1046 /* Maybe we have to increment the enumeration depth.
1047 * BUT, enumeration in a footnote is considered in isolation from its
1048 * surrounding paragraph so don't increment if this is the
1049 * first line of the footnote
1050 * AND, bibliographies can't have their depth changed ie. they
1051 * are always of depth 0
1053 if (pit != ownerParagraphs().begin()
1054 && boost::prior(pit)->getDepth() < pit->getDepth()
1055 && boost::prior(pit)->layout()->labeltype == LABEL_COUNTER_ENUMI
1056 && pit->enumdepth < 3
1057 && layout->labeltype != LABEL_BIBLIO) {
1061 // Maybe we have to decrement the enumeration depth, see note above
1062 if (pit != ownerParagraphs().begin()
1063 && boost::prior(pit)->getDepth() > pit->getDepth()
1064 && layout->labeltype != LABEL_BIBLIO) {
1065 pit->enumdepth = depthHook(&*pit, pit->getDepth())->enumdepth;
1068 if (!pit->params().labelString().empty()) {
1069 pit->params().labelString(string());
1072 if (layout->margintype == MARGIN_MANUAL) {
1073 if (pit->params().labelWidthString().empty()) {
1074 pit->setLabelWidthString(layout->labelstring());
1077 pit->setLabelWidthString(string());
1080 // is it a layout that has an automatic label?
1081 if (layout->labeltype >= LABEL_COUNTER_CHAPTER) {
1082 int const i = layout->labeltype - LABEL_COUNTER_CHAPTER;
1086 if (i >= 0 && i <= buf->params.secnumdepth) {
1090 textclass.counters().step(layout->latexname());
1092 // Is there a label? Useful for Chapter layout
1093 if (!pit->params().appendix()) {
1094 s << layout->labelstring();
1096 s << layout->labelstring_appendix();
1099 // Use of an integer is here less than elegant. For now.
1100 int head = textclass.maxcounter() - LABEL_COUNTER_CHAPTER;
1101 if (!pit->params().appendix()) {
1102 numbertype = "sectioning";
1104 numbertype = "appendix";
1105 if (pit->isRightToLeftPar(buf->params))
1106 langtype = "hebrew";
1111 s << textclass.counters()
1112 .numberLabel(layout->latexname(),
1113 numbertype, langtype, head);
1115 pit->params().labelString(STRCONV(s.str()));
1117 // reset enum counters
1118 textclass.counters().reset("enum");
1119 } else if (layout->labeltype < LABEL_COUNTER_ENUMI) {
1120 textclass.counters().reset("enum");
1121 } else if (layout->labeltype == LABEL_COUNTER_ENUMI) {
1123 // Yes I know this is a really, really! bad solution
1125 string enumcounter("enum");
1127 switch (pit->enumdepth) {
1136 enumcounter += "iv";
1139 // not a valid enumdepth...
1143 textclass.counters().step(enumcounter);
1145 s << textclass.counters()
1146 .numberLabel(enumcounter, "enumeration");
1147 pit->params().labelString(STRCONV(s.str()));
1149 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
1150 textclass.counters().step("bibitem");
1151 int number = textclass.counters().value("bibitem");
1152 if (pit->bibitem()) {
1153 pit->bibitem()->setCounter(number);
1154 pit->params().labelString(layout->labelstring());
1156 // In biblio should't be following counters but...
1158 string s = layout->labelstring();
1160 // the caption hack:
1161 if (layout->labeltype == LABEL_SENSITIVE) {
1162 ParagraphList::iterator tmppit = pit;
1165 while (tmppit != ownerParagraphs().end() &&
1167 // the single '=' is intended below
1168 && (in = tmppit->inInset()->owner())) {
1169 if (in->lyxCode() == Inset::FLOAT_CODE ||
1170 in->lyxCode() == Inset::WRAP_CODE) {
1174 tmppit = in->parOwner();
1180 = textclass.floats().getType(static_cast<InsetFloat*>(in)->type());
1182 textclass.counters().step(fl.type());
1184 // Doesn't work... yet.
1185 #if USE_BOOST_FORMAT
1186 s = boost::io::str(boost::format(_("%1$s #:")) % fl.name());
1187 // s << boost::format(_("%1$s %1$d:")
1189 // % buf->counters().value(fl.name());
1192 //o << fl.name() << ' ' << buf->counters().value(fl.name()) << ":";
1193 o << fl.name() << " #:";
1194 s = STRCONV(o.str());
1197 // par->SetLayout(0);
1198 // s = layout->labelstring;
1199 s = _("Senseless: ");
1202 pit->params().labelString(s);
1204 // reset the enumeration counter. They are always reset
1205 // when there is any other layout between
1206 // Just fall-through between the cases so that all
1207 // enum counters deeper than enumdepth is also reset.
1208 switch (pit->enumdepth) {
1210 textclass.counters().reset("enumi");
1212 textclass.counters().reset("enumii");
1214 textclass.counters().reset("enumiii");
1216 textclass.counters().reset("enumiv");
1222 // Updates all counters. Paragraphs with changed label string will be rebroken
1223 void LyXText::updateCounters()
1225 RowList::iterator rowit = rows().begin();
1226 ParagraphList::iterator pit = rowit->par();
1228 // CHECK if this is really needed. (Lgb)
1229 bv()->buffer()->params.getLyXTextClass().counters().reset();
1231 for (; pit != ownerParagraphs().end(); ++pit) {
1232 while (rowit->par() != pit)
1235 string const oldLabel = pit->params().labelString();
1238 if (pit != ownerParagraphs().begin())
1239 maxdepth = boost::prior(pit)->getMaxDepthAfter();
1241 if (pit->params().depth() > maxdepth)
1242 pit->params().depth(maxdepth);
1244 // setCounter can potentially change the labelString.
1245 setCounter(bv()->buffer(), pit);
1247 string const & newLabel = pit->params().labelString();
1249 if (oldLabel.empty() && !newLabel.empty()) {
1250 removeParagraph(rowit);
1251 appendParagraph(rowit);
1257 void LyXText::insertInset(Inset * inset)
1259 if (!cursor.par()->insetAllowed(inset->lyxCode()))
1261 setUndo(bv(), Undo::FINISH, &*cursor.par(),
1262 &*boost::next(cursor.par()));
1264 cursor.par()->insertInset(cursor.pos(), inset);
1265 // Just to rebreak and refresh correctly.
1266 // The character will not be inserted a second time
1267 insertChar(Paragraph::META_INSET);
1268 // If we enter a highly editable inset the cursor should be to before
1269 // the inset. This couldn't happen before as Undo was not handled inside
1270 // inset now after the Undo LyX tries to call inset->Edit(...) again
1271 // and cannot do this as the cursor is behind the inset and GetInset
1272 // does not return the inset!
1273 if (isHighlyEditableInset(inset)) {
1280 void LyXText::copyEnvironmentType()
1282 copylayouttype = cursor.par()->layout()->name();
1286 void LyXText::pasteEnvironmentType()
1288 // do nothing if there has been no previous copyEnvironmentType()
1289 if (!copylayouttype.empty())
1290 setLayout(copylayouttype);
1294 void LyXText::cutSelection(bool doclear, bool realcut)
1296 // Stuff what we got on the clipboard. Even if there is no selection.
1298 // There is a problem with having the stuffing here in that the
1299 // larger the selection the slower LyX will get. This can be
1300 // solved by running the line below only when the selection has
1301 // finished. The solution used currently just works, to make it
1302 // faster we need to be more clever and probably also have more
1303 // calls to stuffClipboard. (Lgb)
1304 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1306 // This doesn't make sense, if there is no selection
1307 if (!selection.set())
1310 // OK, we have a selection. This is always between selection.start
1311 // and selection.end
1313 // make sure that the depth behind the selection are restored, too
1314 ParagraphList::iterator endpit = boost::next(selection.end.par());
1315 ParagraphList::iterator undoendpit = endpit;
1316 ParagraphList::iterator pars_end = ownerParagraphs().end();
1318 if (endpit != pars_end && endpit->getDepth()) {
1319 while (endpit != pars_end && endpit->getDepth()) {
1321 undoendpit = endpit;
1323 } else if (endpit != pars_end) {
1324 // because of parindents etc.
1328 setUndo(bv(), Undo::DELETE, &*selection.start.par(), &*undoendpit);
1330 // there are two cases: cut only within one paragraph or
1331 // more than one paragraph
1332 if (selection.start.par() == selection.end.par()) {
1333 // only within one paragraph
1334 endpit = selection.end.par();
1335 int pos = selection.end.pos();
1336 CutAndPaste::cutSelection(&*selection.start.par(), &*endpit,
1337 selection.start.pos(), pos,
1338 bv()->buffer()->params.textclass,
1340 selection.end.pos(pos);
1342 endpit = selection.end.par();
1343 int pos = selection.end.pos();
1344 CutAndPaste::cutSelection(&*selection.start.par(), &*endpit,
1345 selection.start.pos(), pos,
1346 bv()->buffer()->params.textclass,
1349 selection.end.par(endpit);
1350 selection.end.pos(pos);
1351 cursor.pos(selection.end.pos());
1355 // sometimes necessary
1357 selection.start.par()->stripLeadingSpaces();
1359 redoParagraphs(selection.start, endpit);
1361 // cutSelection can invalidate the cursor so we need to set
1363 // we prefer the end for when tracking changes
1364 cursor = selection.end;
1366 // need a valid cursor. (Lgb)
1369 setCursor(cursor.par(), cursor.pos());
1370 selection.cursor = cursor;
1375 void LyXText::copySelection()
1377 // stuff the selection onto the X clipboard, from an explicit copy request
1378 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1380 // this doesnt make sense, if there is no selection
1381 if (!selection.set())
1384 // ok we have a selection. This is always between selection.start
1385 // and sel_end cursor
1387 // copy behind a space if there is one
1388 while (selection.start.par()->size() > selection.start.pos()
1389 && selection.start.par()->isLineSeparator(selection.start.pos())
1390 && (selection.start.par() != selection.end.par()
1391 || selection.start.pos() < selection.end.pos()))
1392 selection.start.pos(selection.start.pos() + 1);
1394 CutAndPaste::copySelection(&*selection.start.par(),
1395 &*selection.end.par(),
1396 selection.start.pos(), selection.end.pos(),
1397 bv()->buffer()->params.textclass);
1401 void LyXText::pasteSelection()
1403 // this does not make sense, if there is nothing to paste
1404 if (!CutAndPaste::checkPastePossible())
1407 setUndo(bv(), Undo::INSERT,
1408 &*cursor.par(), &*boost::next(cursor.par()));
1411 ParagraphList::iterator actpit = cursor.par();
1412 int pos = cursor.pos();
1414 Paragraph * actpar = &*actpit;
1415 CutAndPaste::pasteSelection(&actpar, &endpar, pos,
1416 bv()->buffer()->params.textclass);
1418 redoParagraphs(cursor, endpar);
1420 setCursor(cursor.par(), cursor.pos());
1423 selection.cursor = cursor;
1424 setCursor(actpit, pos);
1430 void LyXText::setSelectionRange(lyx::pos_type length)
1435 selection.cursor = cursor;
1442 // simple replacing. The font of the first selected character is used
1443 void LyXText::replaceSelectionWithString(string const & str)
1445 setCursorParUndo(bv());
1448 if (!selection.set()) { // create a dummy selection
1449 selection.end = cursor;
1450 selection.start = cursor;
1453 // Get font setting before we cut
1454 pos_type pos = selection.end.pos();
1455 LyXFont const font = selection.start.par()
1456 ->getFontSettings(bv()->buffer()->params,
1457 selection.start.pos());
1459 // Insert the new string
1460 for (string::const_iterator cit = str.begin(); cit != str.end(); ++cit) {
1461 selection.end.par()->insertChar(pos, (*cit), font);
1465 // Cut the selection
1466 cutSelection(true, false);
1472 // needed to insert the selection
1473 void LyXText::insertStringAsLines(string const & str)
1475 ParagraphList::iterator pit = cursor.par();
1476 pos_type pos = cursor.pos();
1477 ParagraphList::iterator endpit = boost::next(cursor.par());
1479 setCursorParUndo(bv());
1481 // only to be sure, should not be neccessary
1484 Paragraph * par = &*pit;
1485 bv()->buffer()->insertStringAsLines(par, pos, current_font, str);
1487 redoParagraphs(cursor, endpit);
1488 setCursor(cursor.par(), cursor.pos());
1489 selection.cursor = cursor;
1490 setCursor(pit, pos);
1495 // turns double-CR to single CR, others where converted into one
1496 // blank. Then InsertStringAsLines is called
1497 void LyXText::insertStringAsParagraphs(string const & str)
1499 string linestr(str);
1500 bool newline_inserted = false;
1501 for (string::size_type i = 0; i < linestr.length(); ++i) {
1502 if (linestr[i] == '\n') {
1503 if (newline_inserted) {
1504 // we know that \r will be ignored by
1505 // InsertStringA. Of course, it is a dirty
1506 // trick, but it works...
1507 linestr[i - 1] = '\r';
1511 newline_inserted = true;
1513 } else if (IsPrintable(linestr[i])) {
1514 newline_inserted = false;
1517 insertStringAsLines(linestr);
1521 void LyXText::checkParagraph(ParagraphList::iterator pit, pos_type pos)
1523 LyXCursor tmpcursor;
1527 RowList::iterator row = getRow(pit, pos, y);
1528 RowList::iterator beg = rows().begin();
1530 // is there a break one row above
1532 && boost::prior(row)->par() == row->par()) {
1533 z = rowBreakPoint(*boost::prior(row));
1534 if (z >= row->pos()) {
1535 // set the dimensions of the row above
1536 y -= boost::prior(row)->height();
1539 breakAgain(boost::prior(row));
1541 // set the cursor again. Otherwise
1542 // dangling pointers are possible
1543 setCursor(cursor.par(), cursor.pos(),
1544 false, cursor.boundary());
1545 selection.cursor = cursor;
1550 int const tmpheight = row->height();
1551 pos_type const tmplast = lastPos(*this, row);
1554 if (row->height() == tmpheight && lastPos(*this, row) == tmplast) {
1555 postRowPaint(row, y);
1560 // check the special right address boxes
1561 if (pit->layout()->margintype == MARGIN_RIGHT_ADDRESS_BOX) {
1568 redoDrawingOfParagraph(tmpcursor);
1571 // set the cursor again. Otherwise dangling pointers are possible
1572 // also set the selection
1574 if (selection.set()) {
1576 setCursorIntern(selection.cursor.par(), selection.cursor.pos(),
1577 false, selection.cursor.boundary());
1578 selection.cursor = cursor;
1579 setCursorIntern(selection.start.par(),
1580 selection.start.pos(),
1581 false, selection.start.boundary());
1582 selection.start = cursor;
1583 setCursorIntern(selection.end.par(),
1584 selection.end.pos(),
1585 false, selection.end.boundary());
1586 selection.end = cursor;
1587 setCursorIntern(last_sel_cursor.par(),
1588 last_sel_cursor.pos(),
1589 false, last_sel_cursor.boundary());
1590 last_sel_cursor = cursor;
1593 setCursorIntern(cursor.par(), cursor.pos(),
1594 false, cursor.boundary());
1598 // returns false if inset wasn't found
1599 bool LyXText::updateInset(Inset * inset)
1601 // first check the current paragraph
1602 int pos = cursor.par()->getPositionOfInset(inset);
1604 checkParagraph(cursor.par(), pos);
1608 // check every paragraph
1610 ParagraphList::iterator par = ownerParagraphs().begin();
1611 ParagraphList::iterator end = ownerParagraphs().end();
1612 for (; par != end; ++par) {
1613 pos = par->getPositionOfInset(inset);
1615 checkParagraph(par, pos);
1624 bool LyXText::setCursor(ParagraphList::iterator pit,
1626 bool setfont, bool boundary)
1628 LyXCursor old_cursor = cursor;
1629 setCursorIntern(pit, pos, setfont, boundary);
1630 return deleteEmptyParagraphMechanism(old_cursor);
1634 void LyXText::setCursor(LyXCursor & cur, ParagraphList::iterator pit,
1635 pos_type pos, bool boundary)
1637 lyx::Assert(pit != ownerParagraphs().end());
1641 cur.boundary(boundary);
1643 // get the cursor y position in text
1645 RowList::iterator row = getRow(pit, pos, y);
1646 RowList::iterator beg = rows().begin();
1648 RowList::iterator old_row = row;
1650 // if we are before the first char of this row and are still in the
1651 // same paragraph and there is a previous row then put the cursor on
1652 // the end of the previous row
1653 cur.iy(y + row->baseline());
1655 if (row != beg && pos &&
1656 boost::prior(row)->par() == row->par() &&
1657 pos < pit->size() &&
1658 pit->getChar(pos) == Paragraph::META_INSET &&
1659 (ins = pit->getInset(pos)) && (ins->needFullRow() || ins->display()))
1666 // y is now the beginning of the cursor row
1667 y += row->baseline();
1668 // y is now the cursor baseline
1671 pos_type last = lastPrintablePos(*this, old_row);
1673 // None of these should happen, but we're scaredy-cats
1674 if (pos > pit->size()) {
1675 lyxerr << "dont like 1 please report" << endl;
1678 } else if (pos > last + 1) {
1679 lyxerr << "dont like 2 please report" << endl;
1680 // This shouldn't happen.
1683 } else if (pos < row->pos()) {
1684 lyxerr << "dont like 3 please report" << endl;
1689 // now get the cursors x position
1690 float x = getCursorX(row, pos, last, boundary);
1693 if (old_row != row) {
1694 x = getCursorX(old_row, pos, last, boundary);
1698 /* We take out this for the time being because 1) the redraw code is not
1699 prepared to this yet and 2) because some good policy has yet to be decided
1700 while editting: for instance how to act on rows being created/deleted
1704 //if the cursor is in a visible row, anchor to it
1706 if (topy < y && y < topy + bv()->workHeight())
1712 float LyXText::getCursorX(RowList::iterator rit,
1713 pos_type pos, pos_type last, bool boundary) const
1715 pos_type cursor_vpos = 0;
1717 float fill_separator;
1719 float fill_label_hfill;
1720 // This call HAS to be here because of the BidiTables!!!
1721 prepareToPrint(rit, x, fill_separator, fill_hfill,
1724 ParagraphList::iterator rit_par = rit->par();
1725 pos_type const rit_pos = rit->pos();
1728 cursor_vpos = rit_pos;
1729 else if (pos > last && !boundary)
1730 cursor_vpos = (rit_par->isRightToLeftPar(bv()->buffer()->params))
1731 ? rit_pos : last + 1;
1732 else if (pos > rit_pos && (pos > last || boundary))
1733 /// Place cursor after char at (logical) position pos - 1
1734 cursor_vpos = (bidi_level(pos - 1) % 2 == 0)
1735 ? log2vis(pos - 1) + 1 : log2vis(pos - 1);
1737 /// Place cursor before char at (logical) position pos
1738 cursor_vpos = (bidi_level(pos) % 2 == 0)
1739 ? log2vis(pos) : log2vis(pos) + 1;
1741 pos_type body_pos = rit_par->beginningOfBody();
1742 if ((body_pos > 0) &&
1743 ((body_pos - 1 > last) || !rit_par->isLineSeparator(body_pos - 1)))
1746 for (pos_type vpos = rit_pos; vpos < cursor_vpos; ++vpos) {
1747 pos_type pos = vis2log(vpos);
1748 if (body_pos > 0 && pos == body_pos - 1) {
1749 x += fill_label_hfill +
1750 font_metrics::width(
1751 rit_par->layout()->labelsep,
1752 getLabelFont(bv()->buffer(), rit_par));
1753 if (rit_par->isLineSeparator(body_pos - 1))
1754 x -= singleWidth(rit_par, body_pos - 1);
1757 if (hfillExpansion(*this, rit, pos)) {
1758 x += singleWidth(rit_par, pos);
1759 if (pos >= body_pos)
1762 x += fill_label_hfill;
1763 } else if (rit_par->isSeparator(pos)) {
1764 x += singleWidth(rit_par, pos);
1765 if (pos >= body_pos)
1766 x += fill_separator;
1768 x += singleWidth(rit_par, pos);
1774 void LyXText::setCursorIntern(ParagraphList::iterator pit,
1775 pos_type pos, bool setfont, bool boundary)
1777 InsetText * it = static_cast<InsetText *>(pit->inInset());
1779 if (it != inset_owner) {
1780 lyxerr[Debug::INSETS] << "InsetText is " << it
1782 << "inset_owner is "
1783 << inset_owner << endl;
1784 #ifdef WITH_WARNINGS
1785 #warning I believe this code is wrong. (Lgb)
1786 #warning Jürgen, have a look at this. (Lgb)
1787 #warning Hmmm, I guess you are right but we
1788 #warning should verify when this is needed
1790 // Jürgen, would you like to have a look?
1791 // I guess we need to move the outer cursor
1792 // and open and lock the inset (bla bla bla)
1793 // stuff I don't know... so can you have a look?
1795 // I moved the lyxerr stuff in here so we can see if
1796 // this is actually really needed and where!
1798 // it->getLyXText(bv())->setCursorIntern(bv(), par, pos, setfont, boundary);
1803 setCursor(cursor, pit, pos, boundary);
1809 void LyXText::setCurrentFont()
1811 pos_type pos = cursor.pos();
1812 ParagraphList::iterator pit = cursor.par();
1814 if (cursor.boundary() && pos > 0)
1818 if (pos == pit->size())
1820 else // potentional bug... BUG (Lgb)
1821 if (pit->isSeparator(pos)) {
1822 if (pos > cursor.row()->pos() &&
1823 bidi_level(pos) % 2 ==
1824 bidi_level(pos - 1) % 2)
1826 else if (pos + 1 < pit->size())
1832 pit->getFontSettings(bv()->buffer()->params, pos);
1833 real_current_font = getFont(bv()->buffer(), pit, pos);
1835 if (cursor.pos() == pit->size() &&
1836 isBoundary(bv()->buffer(), *pit, cursor.pos()) &&
1837 !cursor.boundary()) {
1838 Language const * lang =
1839 pit->getParLanguage(bv()->buffer()->params);
1840 current_font.setLanguage(lang);
1841 current_font.setNumber(LyXFont::OFF);
1842 real_current_font.setLanguage(lang);
1843 real_current_font.setNumber(LyXFont::OFF);
1848 // returns the column near the specified x-coordinate of the row
1849 // x is set to the real beginning of this column
1851 LyXText::getColumnNearX(RowList::iterator rit, int & x, bool & boundary) const
1854 float fill_separator;
1856 float fill_label_hfill;
1858 prepareToPrint(rit, tmpx, fill_separator,
1859 fill_hfill, fill_label_hfill);
1861 pos_type vc = rit->pos();
1862 pos_type last = lastPrintablePos(*this, rit);
1865 ParagraphList::iterator rit_par = rit->par();
1866 LyXLayout_ptr const & layout = rit->par()->layout();
1868 bool left_side = false;
1870 pos_type body_pos = rit_par->beginningOfBody();
1871 float last_tmpx = tmpx;
1874 (body_pos - 1 > last ||
1875 !rit_par->isLineSeparator(body_pos - 1)))
1878 // check for empty row
1879 if (!rit_par->size()) {
1884 while (vc <= last && tmpx <= x) {
1887 if (body_pos > 0 && c == body_pos - 1) {
1888 tmpx += fill_label_hfill +
1889 font_metrics::width(layout->labelsep,
1890 getLabelFont(bv()->buffer(), &*rit_par));
1891 if (rit_par->isLineSeparator(body_pos - 1))
1892 tmpx -= singleWidth(rit_par, body_pos - 1);
1895 if (hfillExpansion(*this, rit, c)) {
1896 tmpx += singleWidth(rit_par, c);
1900 tmpx += fill_label_hfill;
1901 } else if (rit_par->isSeparator(c)) {
1902 tmpx += singleWidth(rit_par, c);
1904 tmpx+= fill_separator;
1906 tmpx += singleWidth(rit_par, c);
1911 if ((tmpx + last_tmpx) / 2 > x) {
1916 if (vc > last + 1) // This shouldn't happen.
1920 // This (rtl_support test) is not needed, but gives
1921 // some speedup if rtl_support=false
1922 bool const lastrow = lyxrc.rtl_support &&
1923 (boost::next(rit) == rowlist_.end() ||
1924 boost::next(rit)->par() != rit_par);
1925 // If lastrow is false, we don't need to compute
1926 // the value of rtl.
1927 bool const rtl = (lastrow)
1928 ? rit_par->isRightToLeftPar(bv()->buffer()->params)
1931 ((rtl && left_side && vc == rit->pos() && x < tmpx - 5) ||
1932 (!rtl && !left_side && vc == last + 1 && x > tmpx + 5)))
1934 else if (vc == rit->pos()) {
1936 if (bidi_level(c) % 2 == 1)
1939 c = vis2log(vc - 1);
1940 bool const rtl = (bidi_level(c) % 2 == 1);
1941 if (left_side == rtl) {
1943 boundary = isBoundary(bv()->buffer(), *rit_par, c);
1947 if (rit->pos() <= last && c > last
1948 && rit_par->isNewline(last)) {
1949 if (bidi_level(last) % 2 == 0)
1950 tmpx -= singleWidth(rit_par, last);
1952 tmpx += singleWidth(rit_par, last);
1962 void LyXText::setCursorFromCoordinates(int x, int y)
1964 LyXCursor old_cursor = cursor;
1966 setCursorFromCoordinates(cursor, x, y);
1968 deleteEmptyParagraphMechanism(old_cursor);
1975 * return true if the cursor given is at the end of a row,
1976 * and the next row is filled by an inset that spans an entire
1979 bool beforeFullRowInset(LyXText & lt, RowList::iterator row,
1981 if (boost::next(row) == lt.rows().end())
1983 Row const & next = *boost::next(row);
1985 if (next.pos() != cur.pos() || next.par() != cur.par())
1987 if (!cur.par()->isInset(cur.pos()))
1989 Inset const * inset = cur.par()->getInset(cur.pos());
1990 if (inset->needFullRow() || inset->display())
1997 void LyXText::setCursorFromCoordinates(LyXCursor & cur, int x, int y)
1999 // Get the row first.
2001 RowList::iterator row = getRowNearY(y);
2003 pos_type const column = getColumnNearX(row, x, bound);
2004 cur.par(row->par());
2005 cur.pos(row->pos() + column);
2007 cur.y(y + row->baseline());
2010 if (beforeFullRowInset(*this, row, cur)) {
2011 pos_type last = lastPrintablePos(*this, row);
2012 float x = getCursorX(boost::next(row), cur.pos(), last, bound);
2014 cur.iy(y + row->height() + boost::next(row)->baseline());
2015 cur.irow(boost::next(row));
2021 cur.boundary(bound);
2025 void LyXText::cursorLeft(bool internal)
2027 if (cursor.pos() > 0) {
2028 bool boundary = cursor.boundary();
2029 setCursor(cursor.par(), cursor.pos() - 1, true, false);
2030 if (!internal && !boundary &&
2031 isBoundary(bv()->buffer(), *cursor.par(), cursor.pos() + 1))
2032 setCursor(cursor.par(), cursor.pos() + 1, true, true);
2033 } else if (cursor.par() != ownerParagraphs().begin()) { // steps into the above paragraph.
2034 ParagraphList::iterator pit = boost::prior(cursor.par());
2035 setCursor(pit, pit->size());
2040 void LyXText::cursorRight(bool internal)
2042 bool const at_end = (cursor.pos() == cursor.par()->size());
2043 bool const at_newline = !at_end &&
2044 cursor.par()->isNewline(cursor.pos());
2046 if (!internal && cursor.boundary() && !at_newline)
2047 setCursor(cursor.par(), cursor.pos(), true, false);
2049 setCursor(cursor.par(), cursor.pos() + 1, true, false);
2051 isBoundary(bv()->buffer(), *cursor.par(), cursor.pos()))
2052 setCursor(cursor.par(), cursor.pos(), true, true);
2053 } else if (boost::next(cursor.par()) != ownerParagraphs().end())
2054 setCursor(boost::next(cursor.par()), 0);
2058 void LyXText::cursorUp(bool selecting)
2061 int x = cursor.x_fix();
2062 int y = cursor.y() - cursor.row()->baseline() - 1;
2063 setCursorFromCoordinates(x, y);
2066 int y1 = cursor.iy() - topy;
2069 Inset * inset_hit = checkInsetHit(x, y1);
2070 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2071 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2075 setCursorFromCoordinates(bv(), cursor.x_fix(),
2076 cursor.y() - cursor.row()->baseline() - 1);
2081 void LyXText::cursorDown(bool selecting)
2084 int x = cursor.x_fix();
2085 int y = cursor.y() - cursor.row()->baseline() +
2086 cursor.row()->height() + 1;
2087 setCursorFromCoordinates(x, y);
2088 if (!selecting && cursor.row() == cursor.irow()) {
2090 int y1 = cursor.iy() - topy;
2093 Inset * inset_hit = checkInsetHit(x, y1);
2094 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2095 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2099 setCursorFromCoordinates(bv(), cursor.x_fix(),
2100 cursor.y() - cursor.row()->baseline()
2101 + cursor.row()->height() + 1);
2106 void LyXText::cursorUpParagraph()
2108 if (cursor.pos() > 0) {
2109 setCursor(cursor.par(), 0);
2111 else if (cursor.par() != ownerParagraphs().begin()) {
2112 setCursor(boost::prior(cursor.par()), 0);
2117 void LyXText::cursorDownParagraph()
2119 if (boost::next(cursor.par()) != ownerParagraphs().end()) {
2120 setCursor(boost::next(cursor.par()), 0);
2122 setCursor(cursor.par(), cursor.par()->size());
2126 // fix the cursor `cur' after a characters has been deleted at `where'
2127 // position. Called by deleteEmptyParagraphMechanism
2128 void LyXText::fixCursorAfterDelete(LyXCursor & cur,
2129 LyXCursor const & where)
2131 // if cursor is not in the paragraph where the delete occured,
2133 if (cur.par() != where.par())
2136 // if cursor position is after the place where the delete occured,
2138 if (cur.pos() > where.pos())
2139 cur.pos(cur.pos()-1);
2141 // check also if we don't want to set the cursor on a spot behind the
2142 // pagragraph because we erased the last character.
2143 if (cur.pos() > cur.par()->size())
2144 cur.pos(cur.par()->size());
2146 // recompute row et al. for this cursor
2147 setCursor(cur, cur.par(), cur.pos(), cur.boundary());
2151 bool LyXText::deleteEmptyParagraphMechanism(LyXCursor const & old_cursor)
2153 // Would be wrong to delete anything if we have a selection.
2154 if (selection.set())
2157 // We allow all kinds of "mumbo-jumbo" when freespacing.
2158 if (old_cursor.par()->layout()->free_spacing
2159 || old_cursor.par()->isFreeSpacing()) {
2163 /* Ok I'll put some comments here about what is missing.
2164 I have fixed BackSpace (and thus Delete) to not delete
2165 double-spaces automagically. I have also changed Cut,
2166 Copy and Paste to hopefully do some sensible things.
2167 There are still some small problems that can lead to
2168 double spaces stored in the document file or space at
2169 the beginning of paragraphs. This happens if you have
2170 the cursor betwenn to spaces and then save. Or if you
2171 cut and paste and the selection have a space at the
2172 beginning and then save right after the paste. I am
2173 sure none of these are very hard to fix, but I will
2174 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
2175 that I can get some feedback. (Lgb)
2178 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
2179 // delete the LineSeparator.
2182 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
2183 // delete the LineSeparator.
2186 // If the pos around the old_cursor were spaces, delete one of them.
2187 if (old_cursor.par() != cursor.par()
2188 || old_cursor.pos() != cursor.pos()) {
2189 // Only if the cursor has really moved
2191 if (old_cursor.pos() > 0
2192 && old_cursor.pos() < old_cursor.par()->size()
2193 && old_cursor.par()->isLineSeparator(old_cursor.pos())
2194 && old_cursor.par()->isLineSeparator(old_cursor.pos() - 1)) {
2195 old_cursor.par()->erase(old_cursor.pos() - 1);
2196 redoParagraphs(old_cursor, boost::next(old_cursor.par()));
2198 #ifdef WITH_WARNINGS
2199 #warning This will not work anymore when we have multiple views of the same buffer
2200 // In this case, we will have to correct also the cursors held by
2201 // other bufferviews. It will probably be easier to do that in a more
2202 // automated way in LyXCursor code. (JMarc 26/09/2001)
2204 // correct all cursors held by the LyXText
2205 fixCursorAfterDelete(cursor, old_cursor);
2206 fixCursorAfterDelete(selection.cursor,
2208 fixCursorAfterDelete(selection.start,
2210 fixCursorAfterDelete(selection.end, old_cursor);
2211 fixCursorAfterDelete(last_sel_cursor,
2213 fixCursorAfterDelete(toggle_cursor, old_cursor);
2214 fixCursorAfterDelete(toggle_end_cursor,
2220 // don't delete anything if this is the ONLY paragraph!
2221 if (ownerParagraphs().size() == 1)
2224 // Do not delete empty paragraphs with keepempty set.
2225 if (old_cursor.par()->layout()->keepempty)
2228 // only do our magic if we changed paragraph
2229 if (old_cursor.par() == cursor.par())
2232 // record if we have deleted a paragraph
2233 // we can't possibly have deleted a paragraph before this point
2234 bool deleted = false;
2236 if (old_cursor.par()->empty() ||
2237 (old_cursor.par()->size() == 1 &&
2238 old_cursor.par()->isLineSeparator(0))) {
2239 // ok, we will delete anything
2240 LyXCursor tmpcursor;
2244 if (old_cursor.row() != rows().begin()) {
2246 prevrow = boost::prior(old_cursor.row());
2247 const_cast<LyXText *>(this)->postPaint(old_cursor.y() - old_cursor.row()->baseline() - prevrow->height());
2249 cursor = old_cursor; // that undo can restore the right cursor position
2250 #warning FIXME. --end() iterator is usable here
2251 ParagraphList::iterator endpit = boost::next(old_cursor.par());
2252 while (endpit != ownerParagraphs().end() &&
2253 endpit->getDepth()) {
2257 setUndo(bv(), Undo::DELETE, &*old_cursor.par(), &*endpit);
2261 removeRow(old_cursor.row());
2262 if (ownerParagraphs().begin() == old_cursor.par()) {
2263 ownerParagraph(&*boost::next(ownerParagraphs().begin()));
2265 #warning FIXME Do the proper ParagraphList operation here (Lgb)
2267 delete &*old_cursor.par();
2269 /* Breakagain the next par. Needed because of
2270 * the parindent that can occur or dissappear.
2271 * The next row can change its height, if
2272 * there is another layout before */
2273 if (boost::next(prevrow) != rows().end()) {
2274 breakAgain(boost::next(prevrow));
2277 setHeightOfRow(prevrow);
2279 RowList::iterator nextrow = boost::next(old_cursor.row());
2280 const_cast<LyXText *>(this)->postPaint(
2281 old_cursor.y() - old_cursor.row()->baseline());
2284 cursor = old_cursor; // that undo can restore the right cursor position
2285 #warning FIXME. --end() iterator is usable here
2286 ParagraphList::iterator endpit = boost::next(old_cursor.par());
2287 while (endpit != ownerParagraphs().end() &&
2288 endpit->getDepth()) {
2292 setUndo(bv(), Undo::DELETE, &*old_cursor.par(), &*endpit);
2296 removeRow(old_cursor.row());
2298 if (ownerParagraphs().begin() == old_cursor.par()) {
2299 ownerParagraph(&*boost::next(ownerParagraphs().begin()));
2301 #warning FIXME Do the proper ParagraphList operations here. (Lgb)
2302 delete &*old_cursor.par();
2304 /* Breakagain the next par. Needed because of
2305 the parindent that can occur or dissappear.
2306 The next row can change its height, if
2307 there is another layout before */
2308 if (nextrow != rows().end()) {
2309 breakAgain(nextrow);
2315 setCursorIntern(cursor.par(), cursor.pos());
2317 if (selection.cursor.par() == old_cursor.par()
2318 && selection.cursor.pos() == old_cursor.pos()) {
2319 // correct selection
2320 selection.cursor = cursor;
2324 if (old_cursor.par()->stripLeadingSpaces()) {
2325 redoParagraphs(old_cursor, boost::next(old_cursor.par()));
2327 setCursorIntern(cursor.par(), cursor.pos());
2328 selection.cursor = cursor;
2335 ParagraphList & LyXText::ownerParagraphs() const
2338 return inset_owner->paragraphs;
2340 return bv_owner->buffer()->paragraphs;
2344 void LyXText::ownerParagraph(Paragraph * p) const
2347 inset_owner->paragraph(p);
2349 bv_owner->buffer()->paragraphs.set(p);
2354 void LyXText::ownerParagraph(int id, Paragraph * p) const
2356 Paragraph * op = bv_owner->buffer()->getParFromID(id);
2357 if (op && op->inInset()) {
2358 static_cast<InsetText *>(op->inInset())->paragraph(p);
2365 LyXText::refresh_status LyXText::refreshStatus() const
2367 return refresh_status_;
2371 void LyXText::clearPaint()
2373 refresh_status_ = REFRESH_NONE;
2374 refresh_row = rows().end();
2379 void LyXText::postPaint(int start_y)
2381 refresh_status old = refresh_status_;
2383 refresh_status_ = REFRESH_AREA;
2384 refresh_row = rows().end();
2386 if (old != REFRESH_NONE && refresh_y < start_y)
2389 refresh_y = start_y;
2394 // We are an inset's lyxtext. Tell the top-level lyxtext
2395 // it needs to update the row we're in.
2396 LyXText * t = bv()->text;
2397 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2401 // FIXME: we should probably remove this y parameter,
2402 // make refresh_y be 0, and use row->y etc.
2403 void LyXText::postRowPaint(RowList::iterator rit, int start_y)
2405 if (refresh_status_ != REFRESH_NONE && refresh_y < start_y) {
2406 refresh_status_ = REFRESH_AREA;
2409 refresh_y = start_y;
2412 if (refresh_status_ == REFRESH_AREA)
2415 refresh_status_ = REFRESH_ROW;
2421 // We are an inset's lyxtext. Tell the top-level lyxtext
2422 // it needs to update the row we're in.
2423 LyXText * t = bv()->text;
2424 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2428 bool LyXText::isInInset() const
2430 // Sub-level has non-null bv owner and
2431 // non-null inset owner.
2432 return inset_owner != 0 && bv_owner != 0;
2436 int defaultRowHeight()
2438 LyXFont const font(LyXFont::ALL_SANE);
2439 return int(font_metrics::maxAscent(font)
2440 + font_metrics::maxDescent(font) * 1.5);