1 /* This file is part of
2 * ======================================================
4 * LyX, The Document Processor
6 * Copyright 1995 Matthias Ettrich
7 * Copyright 1995-2001 The LyX Team.
9 * ====================================================== */
15 #include "paragraph.h"
16 #include "frontends/LyXView.h"
17 #include "undo_funcs.h"
19 #include "bufferparams.h"
21 #include "BufferView.h"
22 #include "CutAndPaste.h"
23 #include "frontends/Painter.h"
24 #include "frontends/font_metrics.h"
28 #include "FloatList.h"
30 #include "ParagraphParameters.h"
32 #include "lyxrow_funcs.h"
34 #include "insets/insetbibitem.h"
35 #include "insets/insetfloat.h"
37 #include "support/LAssert.h"
38 #include "support/textutils.h"
39 #include "support/lstrings.h"
41 #include "support/BoostFormat.h"
51 LyXText::LyXText(BufferView * bv)
52 : height(0), width(0), anchor_row_offset_(0),
53 inset_owner(0), the_locking_inset(0), bv_owner(bv)
55 anchor_row_ = rows().end();
56 need_break_row = rows().end();
57 refresh_row = rows().end();
63 LyXText::LyXText(BufferView * bv, InsetText * inset)
64 : height(0), width(0), anchor_row_offset_(0),
65 inset_owner(inset), the_locking_inset(0), bv_owner(bv)
67 anchor_row_ = rows().end();
68 need_break_row = rows().end();
69 refresh_row = rows().end();
75 void LyXText::init(BufferView * bview, bool reinit)
79 need_break_row = rows().end();
81 copylayouttype.erase();
84 } else if (!rowlist_.empty())
87 ParagraphList::iterator pit = ownerParagraphs().begin();
88 ParagraphList::iterator end = ownerParagraphs().end();
90 current_font = getFont(bview->buffer(), pit, 0);
92 for (; pit != end; ++pit) {
93 insertParagraph(pit, rowlist_.end());
95 setCursorIntern(rowlist_.begin()->par(), 0);
96 selection.cursor = cursor;
104 LyXFont const realizeFont(LyXFont const & font,
106 ParagraphList & /*plist*/,
107 ParagraphList::iterator pit)
109 LyXTextClass const & tclass = buf->params.getLyXTextClass();
110 LyXFont tmpfont(font);
111 Paragraph::depth_type par_depth = pit->getDepth();
113 Paragraph * par = &*pit;
115 // Resolve against environment font information
116 while (par && par_depth && !tmpfont.resolved()) {
117 par = par->outerHook();
119 tmpfont.realize(par->layout()->font);
120 par_depth = par->getDepth();
124 tmpfont.realize(tclass.defaultfont());
132 // Gets the fully instantiated font at a given position in a paragraph
133 // Basically the same routine as Paragraph::getFont() in paragraph.C.
134 // The difference is that this one is used for displaying, and thus we
135 // are allowed to make cosmetic improvements. For instance make footnotes
137 // If position is -1, we get the layout font of the paragraph.
138 // If position is -2, we get the font of the manual label of the paragraph.
139 LyXFont const LyXText::getFont(Buffer const * buf, ParagraphList::iterator pit,
142 lyx::Assert(pos >= 0);
144 LyXLayout_ptr const & layout = pit->layout();
146 // We specialize the 95% common case:
147 if (!pit->getDepth()) {
148 if (layout->labeltype == LABEL_MANUAL
149 && pos < pit->beginningOfBody()) {
151 LyXFont f = pit->getFontSettings(buf->params, pos);
153 pit->inInset()->getDrawFont(f);
154 return f.realize(layout->reslabelfont);
156 LyXFont f = pit->getFontSettings(buf->params, pos);
158 pit->inInset()->getDrawFont(f);
159 return f.realize(layout->resfont);
163 // The uncommon case need not be optimized as much
167 if (pos < pit->beginningOfBody()) {
169 layoutfont = layout->labelfont;
172 layoutfont = layout->font;
175 LyXFont tmpfont = pit->getFontSettings(buf->params, pos);
176 tmpfont.realize(layoutfont);
179 pit->inInset()->getDrawFont(tmpfont);
181 return realizeFont(tmpfont, buf, ownerParagraphs(), pit);
185 LyXFont const LyXText::getLayoutFont(Buffer const * buf,
186 ParagraphList::iterator pit) const
188 LyXLayout_ptr const & layout = pit->layout();
190 if (!pit->getDepth()) {
191 return layout->resfont;
194 return realizeFont(layout->font, buf, ownerParagraphs(), pit);
198 LyXFont const LyXText::getLabelFont(Buffer const * buf,
199 ParagraphList::iterator pit) const
201 LyXLayout_ptr const & layout = pit->layout();
203 if (!pit->getDepth()) {
204 return layout->reslabelfont;
207 return realizeFont(layout->labelfont, buf, ownerParagraphs(), pit);
211 void LyXText::setCharFont(ParagraphList::iterator pit,
212 pos_type pos, LyXFont const & fnt,
215 Buffer const * buf = bv()->buffer();
216 LyXFont font = getFont(buf, pit, pos);
217 font.update(fnt, buf->params.language, toggleall);
218 // Let the insets convert their font
219 if (pit->isInset(pos)) {
220 Inset * inset = pit->getInset(pos);
221 if (isEditableInset(inset)) {
222 UpdatableInset * uinset =
223 static_cast<UpdatableInset *>(inset);
224 uinset->setFont(bv(), fnt, toggleall, true);
228 // Plug thru to version below:
229 setCharFont(buf, pit, pos, font);
233 void LyXText::setCharFont(Buffer const * buf, ParagraphList::iterator pit,
234 pos_type pos, LyXFont const & fnt)
238 LyXTextClass const & tclass = buf->params.getLyXTextClass();
239 LyXLayout_ptr const & layout = pit->layout();
241 // Get concrete layout font to reduce against
244 if (pos < pit->beginningOfBody())
245 layoutfont = layout->labelfont;
247 layoutfont = layout->font;
249 // Realize against environment font information
250 if (pit->getDepth()) {
251 #warning FIXME I think I hate this outerHood stuff.
252 Paragraph * tp = &*pit;
253 while (!layoutfont.resolved() && tp && tp->getDepth()) {
254 tp = tp->outerHook();
256 layoutfont.realize(tp->layout()->font);
260 layoutfont.realize(tclass.defaultfont());
262 // Now, reduce font against full layout font
263 font.reduce(layoutfont);
265 pit->setFont(pos, font);
269 // removes the row and reset the touched counters
270 void LyXText::removeRow(RowList::iterator rit)
272 /* FIXME: when we cache the bview, this should just
273 * become a postPaint(), I think */
274 if (refresh_row == rit) {
275 if (rit == rows().begin())
276 refresh_row = boost::next(rit);
278 refresh_row = boost::prior(rit);
280 // what about refresh_y
283 if (anchor_row_ == rit) {
284 if (rit != rows().begin()) {
285 anchor_row_ = boost::prior(rit);
286 anchor_row_offset_ += anchor_row_->height();
288 anchor_row_ = boost::next(rit);
289 anchor_row_offset_ -= rit->height();
293 // the text becomes smaller
294 height -= rit->height();
300 // remove all following rows of the paragraph of the specified row.
301 void LyXText::removeParagraph(RowList::iterator rit)
303 ParagraphList::iterator tmppit = rit->par();
306 while (rit != rows().end() && rit->par() == tmppit) {
307 RowList::iterator tmprit = boost::next(rit);
314 void LyXText::insertParagraph(ParagraphList::iterator pit,
315 RowList::iterator rowit)
317 // insert a new row, starting at position 0
319 RowList::iterator rit = rowlist_.insert(rowit, newrow);
321 // and now append the whole paragraph before the new row
322 appendParagraph(rit);
326 Inset * LyXText::getInset() const
328 if (cursor.pos() < cursor.par()->size()
329 && cursor.par()->isInset(cursor.pos())) {
330 return cursor.par()->getInset(cursor.pos());
336 void LyXText::toggleInset()
338 Inset * inset = getInset();
339 // is there an editable inset at cursor position?
340 if (!isEditableInset(inset)) {
341 // No, try to see if we are inside a collapsable inset
342 if (inset_owner && inset_owner->owner()
343 && inset_owner->owner()->isOpen()) {
344 bv()->unlockInset(static_cast<UpdatableInset *>(inset_owner->owner()));
345 inset_owner->owner()->close(bv());
346 bv()->getLyXText()->cursorRight(bv());
350 //bv()->owner()->message(inset->editMessage());
352 // do we want to keep this?? (JMarc)
353 if (!isHighlyEditableInset(inset))
354 setCursorParUndo(bv());
356 if (inset->isOpen()) {
362 bv()->updateInset(inset);
366 /* used in setlayout */
367 // Asger is not sure we want to do this...
368 void LyXText::makeFontEntriesLayoutSpecific(Buffer const & buf,
371 LyXLayout_ptr const & layout = par.layout();
374 for (pos_type pos = 0; pos < par.size(); ++pos) {
375 if (pos < par.beginningOfBody())
376 layoutfont = layout->labelfont;
378 layoutfont = layout->font;
380 LyXFont tmpfont = par.getFontSettings(buf.params, pos);
381 tmpfont.reduce(layoutfont);
382 par.setFont(pos, tmpfont);
387 ParagraphList::iterator
388 LyXText::setLayout(LyXCursor & cur, LyXCursor & sstart_cur,
389 LyXCursor & send_cur,
390 string const & layout)
392 ParagraphList::iterator endpit = boost::next(send_cur.par());
393 ParagraphList::iterator undoendpit = endpit;
395 if (endpit != ownerParagraphs().end() &&
396 endpit->getDepth()) {
397 while (endpit != ownerParagraphs().end() &&
398 endpit->getDepth()) {
402 } else if (endpit != ownerParagraphs().end()) {
403 // because of parindents etc.
407 setUndo(bv(), Undo::EDIT, &*sstart_cur.par(), &*undoendpit);
409 // ok we have a selection. This is always between sstart_cur
410 // and sel_end cursor
412 ParagraphList::iterator pit = sstart_cur.par();
413 ParagraphList::iterator epit = boost::next(send_cur.par());
415 LyXLayout_ptr const & lyxlayout =
416 bv()->buffer()->params.getLyXTextClass()[layout];
419 pit->applyLayout(lyxlayout);
420 makeFontEntriesLayoutSpecific(*bv()->buffer(), *pit);
421 ParagraphList::iterator fppit = pit;
422 fppit->params().spaceTop(lyxlayout->fill_top ?
423 VSpace(VSpace::VFILL)
424 : VSpace(VSpace::NONE));
425 fppit->params().spaceBottom(lyxlayout->fill_bottom ?
426 VSpace(VSpace::VFILL)
427 : VSpace(VSpace::NONE));
428 if (lyxlayout->margintype == MARGIN_MANUAL)
429 pit->setLabelWidthString(lyxlayout->labelstring());
432 } while (pit != epit);
438 // set layout over selection and make a total rebreak of those paragraphs
439 void LyXText::setLayout(string const & layout)
441 LyXCursor tmpcursor = cursor; /* store the current cursor */
443 // if there is no selection just set the layout
444 // of the current paragraph */
445 if (!selection.set()) {
446 selection.start = cursor; // dummy selection
447 selection.end = cursor;
449 ParagraphList::iterator endpit = setLayout(cursor, selection.start,
450 selection.end, layout);
451 redoParagraphs(selection.start, endpit);
453 // we have to reset the selection, because the
454 // geometry could have changed
455 setCursor(selection.start.par(),
456 selection.start.pos(), false);
457 selection.cursor = cursor;
458 setCursor(selection.end.par(), selection.end.pos(), false);
462 setCursor(tmpcursor.par(), tmpcursor.pos(), true);
466 bool LyXText::changeDepth(bv_funcs::DEPTH_CHANGE type, bool test_only)
468 ParagraphList::iterator pit(cursor.par());
469 ParagraphList::iterator end(cursor.par());
470 ParagraphList::iterator start = pit;
472 if (selection.set()) {
473 pit = selection.start.par();
474 end = selection.end.par();
478 ParagraphList::iterator pastend = boost::next(end);
481 setUndo(bv(), Undo::EDIT, &(*start), &(*pastend));
483 bool changed = false;
485 int prev_after_depth = 0;
486 #warning parlist ... could be nicer ?
487 if (start != ownerParagraphs().begin()) {
488 prev_after_depth = boost::prior(start)->getMaxDepthAfter();
492 int const depth = pit->params().depth();
493 if (type == bv_funcs::INC_DEPTH) {
494 if (depth < prev_after_depth
495 && pit->layout()->labeltype != LABEL_BIBLIO) {
498 pit->params().depth(depth + 1);
505 pit->params().depth(depth - 1);
508 prev_after_depth = pit->getMaxDepthAfter();
520 // Wow, redoParagraphs is stupid.
522 setCursor(tmpcursor, &(*start), 0);
524 //redoParagraphs(tmpcursor, &(*pastend));
525 redoParagraphs(tmpcursor, &(*pastend));
527 // We need to actually move the text->cursor. I don't
528 // understand why ...
531 // we have to reset the visual selection because the
532 // geometry could have changed
533 if (selection.set()) {
534 setCursor(selection.start.par(), selection.start.pos());
535 selection.cursor = cursor;
536 setCursor(selection.end.par(), selection.end.pos());
539 // this handles the counter labels, and also fixes up
540 // depth values for follow-on (child) paragraphs
544 setCursor(tmpcursor.par(), tmpcursor.pos());
550 // set font over selection and make a total rebreak of those paragraphs
551 void LyXText::setFont(LyXFont const & font, bool toggleall)
553 // if there is no selection just set the current_font
554 if (!selection.set()) {
555 // Determine basis font
557 if (cursor.pos() < cursor.par()->beginningOfBody()) {
558 layoutfont = getLabelFont(bv()->buffer(),
561 layoutfont = getLayoutFont(bv()->buffer(),
564 // Update current font
565 real_current_font.update(font,
566 bv()->buffer()->params.language,
569 // Reduce to implicit settings
570 current_font = real_current_font;
571 current_font.reduce(layoutfont);
572 // And resolve it completely
573 real_current_font.realize(layoutfont);
578 LyXCursor tmpcursor = cursor; // store the current cursor
580 // ok we have a selection. This is always between sel_start_cursor
581 // and sel_end cursor
583 setUndo(bv(), Undo::EDIT,
584 &*selection.start.par(), &*boost::next(selection.end.par()));
586 cursor = selection.start;
587 while (cursor.par() != selection.end.par() ||
588 cursor.pos() < selection.end.pos())
590 if (cursor.pos() < cursor.par()->size()) {
591 // an open footnote should behave like a closed one
592 setCharFont(cursor.par(), cursor.pos(),
594 cursor.pos(cursor.pos() + 1);
597 cursor.par(boost::next(cursor.par()));
602 redoParagraphs(selection.start, boost::next(selection.end.par()));
604 // we have to reset the selection, because the
605 // geometry could have changed, but we keep
606 // it for user convenience
607 setCursor(selection.start.par(), selection.start.pos());
608 selection.cursor = cursor;
609 setCursor(selection.end.par(), selection.end.pos());
611 setCursor(tmpcursor.par(), tmpcursor.pos(), true,
612 tmpcursor.boundary());
616 void LyXText::redoHeightOfParagraph()
618 RowList::iterator tmprow = cursor.row();
619 int y = cursor.y() - tmprow->baseline();
621 setHeightOfRow(tmprow);
623 while (tmprow != rows().begin()
624 && boost::prior(tmprow)->par() == tmprow->par()) {
626 y -= tmprow->height();
627 setHeightOfRow(tmprow);
632 setCursor(cursor.par(), cursor.pos(), false, cursor.boundary());
636 void LyXText::redoDrawingOfParagraph(LyXCursor const & cur)
638 RowList::iterator tmprow = cur.row();
640 int y = cur.y() - tmprow->baseline();
641 setHeightOfRow(tmprow);
643 while (tmprow != rows().begin()
644 && boost::prior(tmprow)->par() == tmprow->par()) {
646 y -= tmprow->height();
650 setCursor(cur.par(), cur.pos());
654 // deletes and inserts again all paragaphs between the cursor
655 // and the specified par
656 // This function is needed after SetLayout and SetFont etc.
657 void LyXText::redoParagraphs(LyXCursor const & cur,
658 ParagraphList::iterator endpit)
660 RowList::iterator tmprit = cur.row();
661 int y = cur.y() - tmprit->baseline();
663 ParagraphList::iterator first_phys_pit;
664 if (tmprit == rows().begin()) {
665 // A trick/hack for UNDO.
666 // This is needed because in an UNDO/REDO we could have
667 // changed the ownerParagrah() so the paragraph inside
668 // the row is NOT my really first par anymore.
669 // Got it Lars ;) (Jug 20011206)
670 first_phys_pit = ownerParagraphs().begin();
672 // In here prevrit could be set to rows().end(). (Lgb)
674 first_phys_pit = tmprit->par();
675 while (tmprit != rows().begin()
676 && boost::prior(tmprit)->par() == first_phys_pit)
679 y -= tmprit->height();
682 // Is it possible to put the prevrit setting in here? (Lgb)
685 RowList::iterator prevrit;
686 bool good_prevrit = false;
688 // It seems to mee that good_prevrit is not needed if we let
689 // a bad prevrit have the value rows().end() (Lgb)
690 if (tmprit != rows().begin()) {
691 prevrit = boost::prior(tmprit);
696 while (tmprit != rows().end() && tmprit->par() != endpit) {
697 RowList::iterator tmprit2 = tmprit++;
701 // Reinsert the paragraphs.
702 ParagraphList::iterator tmppit = first_phys_pit;
704 // See if this loop can be rewritten as a while loop instead.
705 // That should also make the code a bit easier to read. (Lgb)
707 if (tmppit != ownerParagraphs().end()) {
708 insertParagraph(tmppit, tmprit);
709 while (tmprit != rows().end()
710 && tmprit->par() == tmppit) {
715 } while (tmppit != ownerParagraphs().end() && tmppit != endpit);
718 // If the above changes are done, then we can compare prevrit
719 // with rows().end() here. (Lgb)
721 setHeightOfRow(prevrit);
722 const_cast<LyXText *>(this)->postPaint(y - prevrit->height());
724 setHeightOfRow(rows().begin());
725 const_cast<LyXText *>(this)->postPaint(0);
727 if (tmprit != rows().end())
728 setHeightOfRow(tmprit);
734 void LyXText::fullRebreak()
736 if (rows().empty()) {
740 if (need_break_row != rows().end()) {
741 breakAgain(need_break_row);
742 need_break_row = rows().end();
748 // important for the screen
751 // the cursor set functions have a special mechanism. When they
752 // realize, that you left an empty paragraph, they will delete it.
753 // They also delete the corresponding row
755 // need the selection cursor:
756 void LyXText::setSelection()
758 bool const lsel = selection.set();
760 if (!selection.set()) {
761 last_sel_cursor = selection.cursor;
762 selection.start = selection.cursor;
763 selection.end = selection.cursor;
768 // first the toggling area
769 if (cursor.y() < last_sel_cursor.y()
770 || (cursor.y() == last_sel_cursor.y()
771 && cursor.x() < last_sel_cursor.x())) {
772 toggle_end_cursor = last_sel_cursor;
773 toggle_cursor = cursor;
775 toggle_end_cursor = cursor;
776 toggle_cursor = last_sel_cursor;
779 last_sel_cursor = cursor;
781 // and now the whole selection
783 if (selection.cursor.par() == cursor.par())
784 if (selection.cursor.pos() < cursor.pos()) {
785 selection.end = cursor;
786 selection.start = selection.cursor;
788 selection.end = selection.cursor;
789 selection.start = cursor;
791 else if (selection.cursor.y() < cursor.y() ||
792 (selection.cursor.y() == cursor.y()
793 && selection.cursor.x() < cursor.x())) {
794 selection.end = cursor;
795 selection.start = selection.cursor;
798 selection.end = selection.cursor;
799 selection.start = cursor;
802 // a selection with no contents is not a selection
803 if (selection.start.par() == selection.end.par() &&
804 selection.start.pos() == selection.end.pos())
805 selection.set(false);
807 if (inset_owner && (selection.set() || lsel))
808 inset_owner->setUpdateStatus(bv(), InsetText::SELECTION);
812 string const LyXText::selectionAsString(Buffer const * buffer,
815 if (!selection.set()) return string();
817 // should be const ...
818 ParagraphList::iterator startpit = selection.start.par();
819 ParagraphList::iterator endpit = selection.end.par();
820 pos_type const startpos(selection.start.pos());
821 pos_type const endpos(selection.end.pos());
823 if (startpit == endpit) {
824 return startpit->asString(buffer, startpos, endpos, label);
829 // First paragraph in selection
830 result += startpit->asString(buffer, startpos, startpit->size(), label) + "\n\n";
832 // The paragraphs in between (if any)
833 #warning FIXME Why isnt ParagraphList::iterator used here?
835 LyXCursor tmpcur(selection.start);
836 tmpcur.par(boost::next(tmpcur.par()));
837 while (tmpcur.par() != endpit) {
838 result += tmpcur.par()->asString(buffer, 0,
839 tmpcur.par()->size(),
841 tmpcur.par(boost::next(tmpcur.par()));
844 // Last paragraph in selection
845 result += endpit->asString(buffer, 0, endpos, label);
851 void LyXText::clearSelection()
853 selection.set(false);
854 selection.mark(false);
855 last_sel_cursor = selection.end = selection.start = selection.cursor = cursor;
856 // reset this in the bv_owner!
857 if (bv_owner && bv_owner->text)
858 bv_owner->text->xsel_cache.set(false);
862 void LyXText::cursorHome()
864 setCursor(cursor.par(), cursor.row()->pos());
868 void LyXText::cursorEnd()
870 if (cursor.par()->empty())
873 // There is a lot of unneeded recalculation going on here:
874 // - boost::next(curosr.row())
875 // - lastPost(*this, cursor.row())
877 if (boost::next(cursor.row()) == rows().end()
878 || boost::next(cursor.row())->par() != cursor.row()->par()) {
879 setCursor(cursor.par(), lastPos(*this, cursor.row()) + 1);
881 if (!cursor.par()->empty() &&
882 (cursor.par()->getChar(lastPos(*this, cursor.row())) == ' '
883 || cursor.par()->isNewline(lastPos(*this, cursor.row())))) {
884 setCursor(cursor.par(), lastPos(*this, cursor.row()));
886 setCursor(cursor.par(),
887 lastPos(*this, cursor.row()) + 1);
893 void LyXText::cursorTop()
895 setCursor(ownerParagraphs().begin(), 0);
899 void LyXText::cursorBottom()
902 // This is how it should be:
903 // ParagraphList::iterator lastpit = boost::prior(ownerParagraphs().end());
904 ParagraphList::iterator lastpit = &ownerParagraphs().back();
905 int pos = lastpit->size();
906 setCursor(lastpit, pos);
910 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
912 // If the mask is completely neutral, tell user
913 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
914 // Could only happen with user style
915 bv()->owner()->message(_("No font change defined. Use Character under the Layout menu to define font change."));
919 // Try implicit word selection
920 // If there is a change in the language the implicit word selection
922 LyXCursor resetCursor = cursor;
923 bool implicitSelection = (font.language() == ignore_language
924 && font.number() == LyXFont::IGNORE)
925 ? selectWordWhenUnderCursor(WHOLE_WORD_STRICT) : false;
928 setFont(font, toggleall);
930 // Implicit selections are cleared afterwards
931 //and cursor is set to the original position.
932 if (implicitSelection) {
934 cursor = resetCursor;
935 setCursor(cursor.par(), cursor.pos());
936 selection.cursor = cursor;
939 inset_owner->setUpdateStatus(bv(), InsetText::CURSOR_PAR);
943 string LyXText::getStringToIndex()
945 // Try implicit word selection
946 // If there is a change in the language the implicit word selection
948 LyXCursor const reset_cursor = cursor;
949 bool const implicitSelection = selectWordWhenUnderCursor(PREVIOUS_WORD);
952 if (!selection.set())
953 bv()->owner()->message(_("Nothing to index!"));
954 else if (selection.start.par() != selection.end.par())
955 bv()->owner()->message(_("Cannot index more than one paragraph!"));
957 idxstring = selectionAsString(bv()->buffer(), false);
959 // Reset cursors to their original position.
960 cursor = reset_cursor;
961 setCursor(cursor.par(), cursor.pos());
962 selection.cursor = cursor;
964 // Clear the implicit selection.
965 if (implicitSelection)
972 // the DTP switches for paragraphs. LyX will store them in the first
973 // physicla paragraph. When a paragraph is broken, the top settings rest,
974 // the bottom settings are given to the new one. So I can make shure,
975 // they do not duplicate themself and you cannnot make dirty things with
978 void LyXText::setParagraph(bool line_top, bool line_bottom,
979 bool pagebreak_top, bool pagebreak_bottom,
980 VSpace const & space_top,
981 VSpace const & space_bottom,
982 Spacing const & spacing,
984 string const & labelwidthstring,
987 LyXCursor tmpcursor = cursor;
988 if (!selection.set()) {
989 selection.start = cursor;
990 selection.end = cursor;
993 // make sure that the depth behind the selection are restored, too
994 ParagraphList::iterator endpit = boost::next(selection.end.par());
995 ParagraphList::iterator undoendpit = endpit;
997 if (endpit != ownerParagraphs().end() && endpit->getDepth()) {
998 while (endpit != ownerParagraphs().end() &&
999 endpit->getDepth()) {
1001 undoendpit = endpit;
1004 else if (endpit != ownerParagraphs().end()) {
1005 // because of parindents etc.
1009 setUndo(bv(), Undo::EDIT, &*selection.start.par(), &*undoendpit);
1012 ParagraphList::iterator tmppit = selection.end.par();
1014 while (tmppit != boost::prior(selection.start.par())) {
1015 setCursor(tmppit, 0);
1016 postPaint(cursor.y() - cursor.row()->baseline());
1017 cursor.par()->params().lineTop(line_top);
1018 cursor.par()->params().lineBottom(line_bottom);
1019 cursor.par()->params().pagebreakTop(pagebreak_top);
1020 cursor.par()->params().pagebreakBottom(pagebreak_bottom);
1021 cursor.par()->params().spaceTop(space_top);
1022 cursor.par()->params().spaceBottom(space_bottom);
1023 cursor.par()->params().spacing(spacing);
1024 // does the layout allow the new alignment?
1025 LyXLayout_ptr const & layout = cursor.par()->layout();
1027 if (align == LYX_ALIGN_LAYOUT)
1028 align = layout->align;
1029 if (align & layout->alignpossible) {
1030 if (align == layout->align)
1031 cursor.par()->params().align(LYX_ALIGN_LAYOUT);
1033 cursor.par()->params().align(align);
1035 cursor.par()->setLabelWidthString(labelwidthstring);
1036 cursor.par()->params().noindent(noindent);
1037 tmppit = boost::prior(cursor.par());
1040 redoParagraphs(selection.start, endpit);
1043 setCursor(selection.start.par(), selection.start.pos());
1044 selection.cursor = cursor;
1045 setCursor(selection.end.par(), selection.end.pos());
1047 setCursor(tmpcursor.par(), tmpcursor.pos());
1049 bv()->updateInset(inset_owner);
1053 // set the counter of a paragraph. This includes the labels
1054 void LyXText::setCounter(Buffer const * buf, ParagraphList::iterator pit)
1056 LyXTextClass const & textclass = buf->params.getLyXTextClass();
1057 LyXLayout_ptr const & layout = pit->layout();
1059 if (pit != ownerParagraphs().begin()) {
1061 pit->params().appendix(boost::prior(pit)->params().appendix());
1062 if (!pit->params().appendix() &&
1063 pit->params().startOfAppendix()) {
1064 pit->params().appendix(true);
1065 textclass.counters().reset();
1067 pit->enumdepth = boost::prior(pit)->enumdepth;
1068 pit->itemdepth = boost::prior(pit)->itemdepth;
1070 pit->params().appendix(pit->params().startOfAppendix());
1075 /* Maybe we have to increment the enumeration depth.
1076 * BUT, enumeration in a footnote is considered in isolation from its
1077 * surrounding paragraph so don't increment if this is the
1078 * first line of the footnote
1079 * AND, bibliographies can't have their depth changed ie. they
1080 * are always of depth 0
1082 if (pit != ownerParagraphs().begin()
1083 && boost::prior(pit)->getDepth() < pit->getDepth()
1084 && boost::prior(pit)->layout()->labeltype == LABEL_COUNTER_ENUMI
1085 && pit->enumdepth < 3
1086 && layout->labeltype != LABEL_BIBLIO) {
1090 // Maybe we have to decrement the enumeration depth, see note above
1091 if (pit != ownerParagraphs().begin()
1092 && boost::prior(pit)->getDepth() > pit->getDepth()
1093 && layout->labeltype != LABEL_BIBLIO) {
1094 pit->enumdepth = pit->depthHook(pit->getDepth())->enumdepth;
1097 if (!pit->params().labelString().empty()) {
1098 pit->params().labelString(string());
1101 if (layout->margintype == MARGIN_MANUAL) {
1102 if (pit->params().labelWidthString().empty()) {
1103 pit->setLabelWidthString(layout->labelstring());
1106 pit->setLabelWidthString(string());
1109 // is it a layout that has an automatic label?
1110 if (layout->labeltype >= LABEL_COUNTER_CHAPTER) {
1111 int const i = layout->labeltype - LABEL_COUNTER_CHAPTER;
1115 if (i >= 0 && i <= buf->params.secnumdepth) {
1119 textclass.counters().step(layout->latexname());
1121 // Is there a label? Useful for Chapter layout
1122 if (!pit->params().appendix()) {
1123 s << layout->labelstring();
1125 s << layout->labelstring_appendix();
1128 // Use of an integer is here less than elegant. For now.
1129 int head = textclass.maxcounter() - LABEL_COUNTER_CHAPTER;
1130 if (!pit->params().appendix()) {
1131 numbertype = "sectioning";
1133 numbertype = "appendix";
1134 if (pit->isRightToLeftPar(buf->params))
1135 langtype = "hebrew";
1140 s << textclass.counters()
1141 .numberLabel(layout->latexname(),
1142 numbertype, langtype, head);
1144 pit->params().labelString(STRCONV(s.str()));
1146 // reset enum counters
1147 textclass.counters().reset("enum");
1148 } else if (layout->labeltype < LABEL_COUNTER_ENUMI) {
1149 textclass.counters().reset("enum");
1150 } else if (layout->labeltype == LABEL_COUNTER_ENUMI) {
1152 // Yes I know this is a really, really! bad solution
1154 string enumcounter("enum");
1156 switch (pit->enumdepth) {
1165 enumcounter += "iv";
1168 // not a valid enumdepth...
1172 textclass.counters().step(enumcounter);
1174 s << textclass.counters()
1175 .numberLabel(enumcounter, "enumeration");
1176 pit->params().labelString(STRCONV(s.str()));
1178 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
1179 textclass.counters().step("bibitem");
1180 int number = textclass.counters().value("bibitem");
1181 if (pit->bibitem()) {
1182 pit->bibitem()->setCounter(number);
1183 pit->params().labelString(layout->labelstring());
1185 // In biblio should't be following counters but...
1187 string s = layout->labelstring();
1189 // the caption hack:
1190 if (layout->labeltype == LABEL_SENSITIVE) {
1191 ParagraphList::iterator tmppit = pit;
1194 while (tmppit != ownerParagraphs().end() &&
1196 // the single '=' is intended below
1197 && (in = tmppit->inInset()->owner())) {
1198 if (in->lyxCode() == Inset::FLOAT_CODE ||
1199 in->lyxCode() == Inset::WRAP_CODE) {
1203 tmppit = in->parOwner();
1209 = textclass.floats().getType(static_cast<InsetFloat*>(in)->type());
1211 textclass.counters().step(fl.type());
1213 // Doesn't work... yet.
1214 #if USE_BOOST_FORMAT
1215 s = boost::io::str(boost::format(_("%1$s #:")) % fl.name());
1216 // s << boost::format(_("%1$s %1$d:")
1218 // % buf->counters().value(fl.name());
1221 //o << fl.name() << ' ' << buf->counters().value(fl.name()) << ":";
1222 o << fl.name() << " #:";
1223 s = STRCONV(o.str());
1226 // par->SetLayout(0);
1227 // s = layout->labelstring;
1228 s = _("Senseless: ");
1231 pit->params().labelString(s);
1233 // reset the enumeration counter. They are always reset
1234 // when there is any other layout between
1235 // Just fall-through between the cases so that all
1236 // enum counters deeper than enumdepth is also reset.
1237 switch (pit->enumdepth) {
1239 textclass.counters().reset("enumi");
1241 textclass.counters().reset("enumii");
1243 textclass.counters().reset("enumiii");
1245 textclass.counters().reset("enumiv");
1251 // Updates all counters. Paragraphs with changed label string will be rebroken
1252 void LyXText::updateCounters()
1254 RowList::iterator rowit = rows().begin();
1255 ParagraphList::iterator pit = rowit->par();
1257 // CHECK if this is really needed. (Lgb)
1258 bv()->buffer()->params.getLyXTextClass().counters().reset();
1260 while (pit != ownerParagraphs().end()) {
1261 while (rowit->par() != pit)
1264 string const oldLabel = pit->params().labelString();
1267 if (pit != ownerParagraphs().begin())
1268 maxdepth = boost::prior(pit)->getMaxDepthAfter();
1270 if (pit->params().depth() > maxdepth)
1271 pit->params().depth(maxdepth);
1273 // setCounter can potentially change the labelString.
1274 setCounter(bv()->buffer(), pit);
1276 string const & newLabel = pit->params().labelString();
1278 if (oldLabel.empty() && !newLabel.empty()) {
1279 removeParagraph(rowit);
1280 appendParagraph(rowit);
1288 void LyXText::insertInset(Inset * inset)
1290 if (!cursor.par()->insetAllowed(inset->lyxCode()))
1292 setUndo(bv(), Undo::FINISH, &*cursor.par(),
1293 &*boost::next(cursor.par()));
1295 cursor.par()->insertInset(cursor.pos(), inset);
1296 // Just to rebreak and refresh correctly.
1297 // The character will not be inserted a second time
1298 insertChar(Paragraph::META_INSET);
1299 // If we enter a highly editable inset the cursor should be to before
1300 // the inset. This couldn't happen before as Undo was not handled inside
1301 // inset now after the Undo LyX tries to call inset->Edit(...) again
1302 // and cannot do this as the cursor is behind the inset and GetInset
1303 // does not return the inset!
1304 if (isHighlyEditableInset(inset)) {
1311 void LyXText::copyEnvironmentType()
1313 copylayouttype = cursor.par()->layout()->name();
1317 void LyXText::pasteEnvironmentType()
1319 // do nothing if there has been no previous copyEnvironmentType()
1320 if (!copylayouttype.empty())
1321 setLayout(copylayouttype);
1325 void LyXText::cutSelection(bool doclear, bool realcut)
1327 // Stuff what we got on the clipboard. Even if there is no selection.
1329 // There is a problem with having the stuffing here in that the
1330 // larger the selection the slower LyX will get. This can be
1331 // solved by running the line below only when the selection has
1332 // finished. The solution used currently just works, to make it
1333 // faster we need to be more clever and probably also have more
1334 // calls to stuffClipboard. (Lgb)
1335 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1337 // This doesn't make sense, if there is no selection
1338 if (!selection.set())
1341 // OK, we have a selection. This is always between selection.start
1342 // and selection.end
1344 // make sure that the depth behind the selection are restored, too
1345 ParagraphList::iterator endpit = boost::next(selection.end.par());
1346 ParagraphList::iterator undoendpit = endpit;
1348 if (endpit != ownerParagraphs().end() &&
1349 endpit->getDepth()) {
1350 while (endpit != ownerParagraphs().end() &&
1351 endpit->getDepth()) {
1353 undoendpit = endpit;
1355 } else if (endpit != ownerParagraphs().end()) {
1356 // because of parindents etc.
1360 setUndo(bv(), Undo::DELETE, &*selection.start.par(), &*undoendpit);
1362 // there are two cases: cut only within one paragraph or
1363 // more than one paragraph
1364 if (selection.start.par() == selection.end.par()) {
1365 // only within one paragraph
1366 endpit = selection.end.par();
1367 int pos = selection.end.pos();
1368 CutAndPaste::cutSelection(&*selection.start.par(), &*endpit,
1369 selection.start.pos(), pos,
1370 bv()->buffer()->params.textclass,
1372 selection.end.pos(pos);
1374 endpit = selection.end.par();
1375 int pos = selection.end.pos();
1376 CutAndPaste::cutSelection(&*selection.start.par(), &*endpit,
1377 selection.start.pos(), pos,
1378 bv()->buffer()->params.textclass,
1381 selection.end.par(endpit);
1382 selection.end.pos(pos);
1383 cursor.pos(selection.end.pos());
1387 // sometimes necessary
1389 selection.start.par()->stripLeadingSpaces();
1391 redoParagraphs(selection.start, endpit);
1393 // cutSelection can invalidate the cursor so we need to set
1395 // we prefer the end for when tracking changes
1396 cursor = selection.end;
1398 // need a valid cursor. (Lgb)
1401 setCursor(cursor.par(), cursor.pos());
1402 selection.cursor = cursor;
1407 void LyXText::copySelection()
1409 // stuff the selection onto the X clipboard, from an explicit copy request
1410 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1412 // this doesnt make sense, if there is no selection
1413 if (!selection.set())
1416 // ok we have a selection. This is always between selection.start
1417 // and sel_end cursor
1419 // copy behind a space if there is one
1420 while (selection.start.par()->size() > selection.start.pos()
1421 && selection.start.par()->isLineSeparator(selection.start.pos())
1422 && (selection.start.par() != selection.end.par()
1423 || selection.start.pos() < selection.end.pos()))
1424 selection.start.pos(selection.start.pos() + 1);
1426 CutAndPaste::copySelection(&*selection.start.par(),
1427 &*selection.end.par(),
1428 selection.start.pos(), selection.end.pos(),
1429 bv()->buffer()->params.textclass);
1433 void LyXText::pasteSelection()
1435 // this does not make sense, if there is nothing to paste
1436 if (!CutAndPaste::checkPastePossible())
1439 setUndo(bv(), Undo::INSERT,
1440 &*cursor.par(), &*boost::next(cursor.par()));
1443 ParagraphList::iterator actpit = cursor.par();
1444 int pos = cursor.pos();
1446 Paragraph * actpar = &*actpit;
1447 CutAndPaste::pasteSelection(&actpar, &endpar, pos,
1448 bv()->buffer()->params.textclass);
1450 redoParagraphs(cursor, endpar);
1452 setCursor(cursor.par(), cursor.pos());
1455 selection.cursor = cursor;
1456 setCursor(actpit, pos);
1462 void LyXText::setSelectionRange(lyx::pos_type length)
1467 selection.cursor = cursor;
1474 // simple replacing. The font of the first selected character is used
1475 void LyXText::replaceSelectionWithString(string const & str)
1477 setCursorParUndo(bv());
1480 if (!selection.set()) { // create a dummy selection
1481 selection.end = cursor;
1482 selection.start = cursor;
1485 // Get font setting before we cut
1486 pos_type pos = selection.end.pos();
1487 LyXFont const font = selection.start.par()
1488 ->getFontSettings(bv()->buffer()->params,
1489 selection.start.pos());
1491 // Insert the new string
1492 for (string::const_iterator cit = str.begin(); cit != str.end(); ++cit) {
1493 selection.end.par()->insertChar(pos, (*cit), font);
1497 // Cut the selection
1498 cutSelection(true, false);
1504 // needed to insert the selection
1505 void LyXText::insertStringAsLines(string const & str)
1507 ParagraphList::iterator pit = cursor.par();
1508 pos_type pos = cursor.pos();
1509 ParagraphList::iterator endpit = boost::next(cursor.par());
1511 setCursorParUndo(bv());
1513 // only to be sure, should not be neccessary
1516 Paragraph * par = &*pit;
1517 bv()->buffer()->insertStringAsLines(par, pos, current_font, str);
1519 redoParagraphs(cursor, endpit);
1520 setCursor(cursor.par(), cursor.pos());
1521 selection.cursor = cursor;
1522 setCursor(pit, pos);
1527 // turns double-CR to single CR, others where converted into one
1528 // blank. Then InsertStringAsLines is called
1529 void LyXText::insertStringAsParagraphs(string const & str)
1531 string linestr(str);
1532 bool newline_inserted = false;
1533 for (string::size_type i = 0; i < linestr.length(); ++i) {
1534 if (linestr[i] == '\n') {
1535 if (newline_inserted) {
1536 // we know that \r will be ignored by
1537 // InsertStringA. Of course, it is a dirty
1538 // trick, but it works...
1539 linestr[i - 1] = '\r';
1543 newline_inserted = true;
1545 } else if (IsPrintable(linestr[i])) {
1546 newline_inserted = false;
1549 insertStringAsLines(linestr);
1553 void LyXText::checkParagraph(ParagraphList::iterator pit, pos_type pos)
1555 LyXCursor tmpcursor;
1559 RowList::iterator row = getRow(pit, pos, y);
1560 RowList::iterator beg = rows().begin();
1562 // is there a break one row above
1564 && boost::prior(row)->par() == row->par()) {
1565 z = rowBreakPoint(*boost::prior(row));
1566 if (z >= row->pos()) {
1567 // set the dimensions of the row above
1568 y -= boost::prior(row)->height();
1571 breakAgain(boost::prior(row));
1573 // set the cursor again. Otherwise
1574 // dangling pointers are possible
1575 setCursor(cursor.par(), cursor.pos(),
1576 false, cursor.boundary());
1577 selection.cursor = cursor;
1582 int const tmpheight = row->height();
1583 pos_type const tmplast = lastPos(*this, row);
1586 if (row->height() == tmpheight && lastPos(*this, row) == tmplast) {
1587 postRowPaint(row, y);
1592 // check the special right address boxes
1593 if (pit->layout()->margintype == MARGIN_RIGHT_ADDRESS_BOX) {
1600 redoDrawingOfParagraph(tmpcursor);
1603 // set the cursor again. Otherwise dangling pointers are possible
1604 // also set the selection
1606 if (selection.set()) {
1608 setCursorIntern(selection.cursor.par(), selection.cursor.pos(),
1609 false, selection.cursor.boundary());
1610 selection.cursor = cursor;
1611 setCursorIntern(selection.start.par(),
1612 selection.start.pos(),
1613 false, selection.start.boundary());
1614 selection.start = cursor;
1615 setCursorIntern(selection.end.par(),
1616 selection.end.pos(),
1617 false, selection.end.boundary());
1618 selection.end = cursor;
1619 setCursorIntern(last_sel_cursor.par(),
1620 last_sel_cursor.pos(),
1621 false, last_sel_cursor.boundary());
1622 last_sel_cursor = cursor;
1625 setCursorIntern(cursor.par(), cursor.pos(),
1626 false, cursor.boundary());
1630 // returns false if inset wasn't found
1631 bool LyXText::updateInset(Inset * inset)
1633 // first check the current paragraph
1634 int pos = cursor.par()->getPositionOfInset(inset);
1636 checkParagraph(cursor.par(), pos);
1640 // check every paragraph
1642 ParagraphList::iterator par = ownerParagraphs().begin();
1643 ParagraphList::iterator end = ownerParagraphs().end();
1646 pos = par->getPositionOfInset(inset);
1648 checkParagraph(par, pos);
1652 } while (par != end);
1658 bool LyXText::setCursor(ParagraphList::iterator pit,
1660 bool setfont, bool boundary)
1662 LyXCursor old_cursor = cursor;
1663 setCursorIntern(pit, pos, setfont, boundary);
1664 return deleteEmptyParagraphMechanism(old_cursor);
1668 void LyXText::setCursor(LyXCursor & cur, ParagraphList::iterator pit,
1669 pos_type pos, bool boundary)
1671 lyx::Assert(pit != ownerParagraphs().end());
1675 cur.boundary(boundary);
1677 // get the cursor y position in text
1679 RowList::iterator row = getRow(pit, pos, y);
1680 RowList::iterator beg = rows().begin();
1682 RowList::iterator old_row = row;
1684 // if we are before the first char of this row and are still in the
1685 // same paragraph and there is a previous row then put the cursor on
1686 // the end of the previous row
1687 cur.iy(y + row->baseline());
1689 if (row != beg && pos &&
1690 boost::prior(row)->par() == row->par() &&
1691 pos < pit->size() &&
1692 pit->getChar(pos) == Paragraph::META_INSET &&
1693 (ins = pit->getInset(pos)) && (ins->needFullRow() || ins->display()))
1700 // y is now the beginning of the cursor row
1701 y += row->baseline();
1702 // y is now the cursor baseline
1705 pos_type last = lastPrintablePos(*this, old_row);
1707 // None of these should happen, but we're scaredy-cats
1708 if (pos > pit->size()) {
1709 lyxerr << "dont like 1 please report" << endl;
1712 } else if (pos > last + 1) {
1713 lyxerr << "dont like 2 please report" << endl;
1714 // This shouldn't happen.
1717 } else if (pos < row->pos()) {
1718 lyxerr << "dont like 3 please report" << endl;
1723 // now get the cursors x position
1724 float x = getCursorX(row, pos, last, boundary);
1727 if (old_row != row) {
1728 x = getCursorX(old_row, pos, last, boundary);
1732 /* We take out this for the time being because 1) the redraw code is not
1733 prepared to this yet and 2) because some good policy has yet to be decided
1734 while editting: for instance how to act on rows being created/deleted
1738 //if the cursor is in a visible row, anchor to it
1740 if (topy < y && y < topy + bv()->workHeight())
1746 float LyXText::getCursorX(RowList::iterator rit,
1747 pos_type pos, pos_type last, bool boundary) const
1749 pos_type cursor_vpos = 0;
1751 float fill_separator;
1753 float fill_label_hfill;
1754 // This call HAS to be here because of the BidiTables!!!
1755 prepareToPrint(rit, x, fill_separator, fill_hfill,
1758 if (last < rit->pos())
1759 cursor_vpos = rit->pos();
1760 else if (pos > last && !boundary)
1761 cursor_vpos = (rit->par()->isRightToLeftPar(bv()->buffer()->params))
1762 ? rit->pos() : last + 1;
1763 else if (pos > rit->pos() &&
1764 (pos > last || boundary))
1765 /// Place cursor after char at (logical) position pos - 1
1766 cursor_vpos = (bidi_level(pos - 1) % 2 == 0)
1767 ? log2vis(pos - 1) + 1 : log2vis(pos - 1);
1769 /// Place cursor before char at (logical) position pos
1770 cursor_vpos = (bidi_level(pos) % 2 == 0)
1771 ? log2vis(pos) : log2vis(pos) + 1;
1773 pos_type body_pos = rit->par()->beginningOfBody();
1774 if ((body_pos > 0) &&
1775 ((body_pos - 1 > last) ||
1776 !rit->par()->isLineSeparator(body_pos - 1)))
1779 for (pos_type vpos = rit->pos(); vpos < cursor_vpos; ++vpos) {
1780 pos_type pos = vis2log(vpos);
1781 if (body_pos > 0 && pos == body_pos - 1) {
1782 x += fill_label_hfill +
1783 font_metrics::width(
1784 rit->par()->layout()->labelsep,
1785 getLabelFont(bv()->buffer(),
1787 if (rit->par()->isLineSeparator(body_pos - 1))
1788 x -= singleWidth(rit->par(), body_pos - 1);
1791 if (hfillExpansion(*this, rit, pos)) {
1792 x += singleWidth(rit->par(), pos);
1793 if (pos >= body_pos)
1796 x += fill_label_hfill;
1797 } else if (rit->par()->isSeparator(pos)) {
1798 x += singleWidth(rit->par(), pos);
1799 if (pos >= body_pos)
1800 x += fill_separator;
1802 x += singleWidth(rit->par(), pos);
1808 void LyXText::setCursorIntern(ParagraphList::iterator pit,
1809 pos_type pos, bool setfont, bool boundary)
1811 InsetText * it = static_cast<InsetText *>(pit->inInset());
1813 if (it != inset_owner) {
1814 lyxerr[Debug::INSETS] << "InsetText is " << it
1816 << "inset_owner is "
1817 << inset_owner << endl;
1818 #ifdef WITH_WARNINGS
1819 #warning I believe this code is wrong. (Lgb)
1820 #warning Jürgen, have a look at this. (Lgb)
1821 #warning Hmmm, I guess you are right but we
1822 #warning should verify when this is needed
1824 // Jürgen, would you like to have a look?
1825 // I guess we need to move the outer cursor
1826 // and open and lock the inset (bla bla bla)
1827 // stuff I don't know... so can you have a look?
1829 // I moved the lyxerr stuff in here so we can see if
1830 // this is actually really needed and where!
1832 // it->getLyXText(bv())->setCursorIntern(bv(), par, pos, setfont, boundary);
1837 setCursor(cursor, pit, pos, boundary);
1843 void LyXText::setCurrentFont()
1845 pos_type pos = cursor.pos();
1846 if (cursor.boundary() && pos > 0)
1850 if (pos == cursor.par()->size())
1852 else // potentional bug... BUG (Lgb)
1853 if (cursor.par()->isSeparator(pos)) {
1854 if (pos > cursor.row()->pos() &&
1855 bidi_level(pos) % 2 ==
1856 bidi_level(pos - 1) % 2)
1858 else if (pos + 1 < cursor.par()->size())
1864 cursor.par()->getFontSettings(bv()->buffer()->params, pos);
1865 real_current_font = getFont(bv()->buffer(), cursor.par(), pos);
1867 if (cursor.pos() == cursor.par()->size() &&
1868 isBoundary(bv()->buffer(), *cursor.par(), cursor.pos()) &&
1869 !cursor.boundary()) {
1870 Language const * lang =
1871 cursor.par()->getParLanguage(bv()->buffer()->params);
1872 current_font.setLanguage(lang);
1873 current_font.setNumber(LyXFont::OFF);
1874 real_current_font.setLanguage(lang);
1875 real_current_font.setNumber(LyXFont::OFF);
1880 // returns the column near the specified x-coordinate of the row
1881 // x is set to the real beginning of this column
1883 LyXText::getColumnNearX(RowList::iterator rit, int & x, bool & boundary) const
1886 float fill_separator;
1888 float fill_label_hfill;
1890 prepareToPrint(rit, tmpx, fill_separator,
1891 fill_hfill, fill_label_hfill);
1893 pos_type vc = rit->pos();
1894 pos_type last = lastPrintablePos(*this, rit);
1897 LyXLayout_ptr const & layout = rit->par()->layout();
1899 bool left_side = false;
1901 pos_type body_pos = rit->par()->beginningOfBody();
1902 float last_tmpx = tmpx;
1905 (body_pos - 1 > last ||
1906 !rit->par()->isLineSeparator(body_pos - 1)))
1909 // check for empty row
1910 if (!rit->par()->size()) {
1915 while (vc <= last && tmpx <= x) {
1918 if (body_pos > 0 && c == body_pos - 1) {
1919 tmpx += fill_label_hfill +
1920 font_metrics::width(layout->labelsep,
1921 getLabelFont(bv()->buffer(), &*rit->par()));
1922 if (rit->par()->isLineSeparator(body_pos - 1))
1923 tmpx -= singleWidth(rit->par(), body_pos - 1);
1926 if (hfillExpansion(*this, rit, c)) {
1927 tmpx += singleWidth(rit->par(), c);
1931 tmpx += fill_label_hfill;
1932 } else if (rit->par()->isSeparator(c)) {
1933 tmpx += singleWidth(rit->par(), c);
1935 tmpx+= fill_separator;
1937 tmpx += singleWidth(rit->par(), c);
1942 if ((tmpx + last_tmpx) / 2 > x) {
1947 if (vc > last + 1) // This shouldn't happen.
1951 // This (rtl_support test) is not needed, but gives
1952 // some speedup if rtl_support=false
1953 bool const lastrow = lyxrc.rtl_support &&
1954 (boost::next(rit) == rowlist_.end() ||
1955 boost::next(rit)->par() != rit->par());
1956 // If lastrow is false, we don't need to compute
1957 // the value of rtl.
1958 bool const rtl = (lastrow)
1959 ? rit->par()->isRightToLeftPar(bv()->buffer()->params)
1962 ((rtl && left_side && vc == rit->pos() && x < tmpx - 5) ||
1963 (!rtl && !left_side && vc == last + 1 && x > tmpx + 5)))
1965 else if (vc == rit->pos()) {
1967 if (bidi_level(c) % 2 == 1)
1970 c = vis2log(vc - 1);
1971 bool const rtl = (bidi_level(c) % 2 == 1);
1972 if (left_side == rtl) {
1974 boundary = isBoundary(bv()->buffer(), *rit->par(), c);
1978 if (rit->pos() <= last && c > last
1979 && rit->par()->isNewline(last)) {
1980 if (bidi_level(last) % 2 == 0)
1981 tmpx -= singleWidth(rit->par(), last);
1983 tmpx += singleWidth(rit->par(), last);
1993 void LyXText::setCursorFromCoordinates(int x, int y)
1995 LyXCursor old_cursor = cursor;
1997 setCursorFromCoordinates(cursor, x, y);
1999 deleteEmptyParagraphMechanism(old_cursor);
2006 * return true if the cursor given is at the end of a row,
2007 * and the next row is filled by an inset that spans an entire
2010 bool beforeFullRowInset(LyXText & lt, RowList::iterator row,
2012 if (boost::next(row) == lt.rows().end())
2014 Row const & next = *boost::next(row);
2016 if (next.pos() != cur.pos() || next.par() != cur.par())
2018 if (!cur.par()->isInset(cur.pos()))
2020 Inset const * inset = cur.par()->getInset(cur.pos());
2021 if (inset->needFullRow() || inset->display())
2028 void LyXText::setCursorFromCoordinates(LyXCursor & cur, int x, int y)
2030 // Get the row first.
2032 RowList::iterator row = getRowNearY(y);
2034 pos_type const column = getColumnNearX(row, x, bound);
2035 cur.par(row->par());
2036 cur.pos(row->pos() + column);
2038 cur.y(y + row->baseline());
2041 if (beforeFullRowInset(*this, row, cur)) {
2042 pos_type last = lastPrintablePos(*this, row);
2043 float x = getCursorX(boost::next(row), cur.pos(), last, bound);
2045 cur.iy(y + row->height() + boost::next(row)->baseline());
2046 cur.irow(boost::next(row));
2052 cur.boundary(bound);
2056 void LyXText::cursorLeft(bool internal)
2058 if (cursor.pos() > 0) {
2059 bool boundary = cursor.boundary();
2060 setCursor(cursor.par(), cursor.pos() - 1, true, false);
2061 if (!internal && !boundary &&
2062 isBoundary(bv()->buffer(), *cursor.par(), cursor.pos() + 1))
2063 setCursor(cursor.par(), cursor.pos() + 1, true, true);
2064 } else if (cursor.par() != ownerParagraphs().begin()) { // steps into the above paragraph.
2065 ParagraphList::iterator pit = boost::prior(cursor.par());
2066 setCursor(pit, pit->size());
2071 void LyXText::cursorRight(bool internal)
2073 bool const at_end = (cursor.pos() == cursor.par()->size());
2074 bool const at_newline = !at_end &&
2075 cursor.par()->isNewline(cursor.pos());
2077 if (!internal && cursor.boundary() && !at_newline)
2078 setCursor(cursor.par(), cursor.pos(), true, false);
2080 setCursor(cursor.par(), cursor.pos() + 1, true, false);
2082 isBoundary(bv()->buffer(), *cursor.par(), cursor.pos()))
2083 setCursor(cursor.par(), cursor.pos(), true, true);
2084 } else if (boost::next(cursor.par()) != ownerParagraphs().end())
2085 setCursor(boost::next(cursor.par()), 0);
2089 void LyXText::cursorUp(bool selecting)
2092 int x = cursor.x_fix();
2093 int y = cursor.y() - cursor.row()->baseline() - 1;
2094 setCursorFromCoordinates(x, y);
2097 int y1 = cursor.iy() - topy;
2100 Inset * inset_hit = checkInsetHit(x, y1);
2101 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2102 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2106 setCursorFromCoordinates(bv(), cursor.x_fix(),
2107 cursor.y() - cursor.row()->baseline() - 1);
2112 void LyXText::cursorDown(bool selecting)
2115 int x = cursor.x_fix();
2116 int y = cursor.y() - cursor.row()->baseline() +
2117 cursor.row()->height() + 1;
2118 setCursorFromCoordinates(x, y);
2119 if (!selecting && cursor.row() == cursor.irow()) {
2121 int y1 = cursor.iy() - topy;
2124 Inset * inset_hit = checkInsetHit(x, y1);
2125 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2126 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2130 setCursorFromCoordinates(bv(), cursor.x_fix(),
2131 cursor.y() - cursor.row()->baseline()
2132 + cursor.row()->height() + 1);
2137 void LyXText::cursorUpParagraph()
2139 if (cursor.pos() > 0) {
2140 setCursor(cursor.par(), 0);
2142 else if (cursor.par() != ownerParagraphs().begin()) {
2143 setCursor(boost::prior(cursor.par()), 0);
2148 void LyXText::cursorDownParagraph()
2150 if (boost::next(cursor.par()) != ownerParagraphs().end()) {
2151 setCursor(boost::next(cursor.par()), 0);
2153 setCursor(cursor.par(), cursor.par()->size());
2157 // fix the cursor `cur' after a characters has been deleted at `where'
2158 // position. Called by deleteEmptyParagraphMechanism
2159 void LyXText::fixCursorAfterDelete(LyXCursor & cur,
2160 LyXCursor const & where)
2162 // if cursor is not in the paragraph where the delete occured,
2164 if (cur.par() != where.par())
2167 // if cursor position is after the place where the delete occured,
2169 if (cur.pos() > where.pos())
2170 cur.pos(cur.pos()-1);
2172 // check also if we don't want to set the cursor on a spot behind the
2173 // pagragraph because we erased the last character.
2174 if (cur.pos() > cur.par()->size())
2175 cur.pos(cur.par()->size());
2177 // recompute row et al. for this cursor
2178 setCursor(cur, cur.par(), cur.pos(), cur.boundary());
2182 bool LyXText::deleteEmptyParagraphMechanism(LyXCursor const & old_cursor)
2184 // Would be wrong to delete anything if we have a selection.
2185 if (selection.set())
2188 // We allow all kinds of "mumbo-jumbo" when freespacing.
2189 if (old_cursor.par()->layout()->free_spacing
2190 || old_cursor.par()->isFreeSpacing()) {
2194 /* Ok I'll put some comments here about what is missing.
2195 I have fixed BackSpace (and thus Delete) to not delete
2196 double-spaces automagically. I have also changed Cut,
2197 Copy and Paste to hopefully do some sensible things.
2198 There are still some small problems that can lead to
2199 double spaces stored in the document file or space at
2200 the beginning of paragraphs. This happens if you have
2201 the cursor betwenn to spaces and then save. Or if you
2202 cut and paste and the selection have a space at the
2203 beginning and then save right after the paste. I am
2204 sure none of these are very hard to fix, but I will
2205 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
2206 that I can get some feedback. (Lgb)
2209 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
2210 // delete the LineSeparator.
2213 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
2214 // delete the LineSeparator.
2217 // If the pos around the old_cursor were spaces, delete one of them.
2218 if (old_cursor.par() != cursor.par()
2219 || old_cursor.pos() != cursor.pos()) {
2220 // Only if the cursor has really moved
2222 if (old_cursor.pos() > 0
2223 && old_cursor.pos() < old_cursor.par()->size()
2224 && old_cursor.par()->isLineSeparator(old_cursor.pos())
2225 && old_cursor.par()->isLineSeparator(old_cursor.pos() - 1)) {
2226 old_cursor.par()->erase(old_cursor.pos() - 1);
2227 redoParagraphs(old_cursor, boost::next(old_cursor.par()));
2229 #ifdef WITH_WARNINGS
2230 #warning This will not work anymore when we have multiple views of the same buffer
2231 // In this case, we will have to correct also the cursors held by
2232 // other bufferviews. It will probably be easier to do that in a more
2233 // automated way in LyXCursor code. (JMarc 26/09/2001)
2235 // correct all cursors held by the LyXText
2236 fixCursorAfterDelete(cursor, old_cursor);
2237 fixCursorAfterDelete(selection.cursor,
2239 fixCursorAfterDelete(selection.start,
2241 fixCursorAfterDelete(selection.end, old_cursor);
2242 fixCursorAfterDelete(last_sel_cursor,
2244 fixCursorAfterDelete(toggle_cursor, old_cursor);
2245 fixCursorAfterDelete(toggle_end_cursor,
2251 // don't delete anything if this is the ONLY paragraph!
2252 if (ownerParagraphs().size() == 1)
2255 // Do not delete empty paragraphs with keepempty set.
2256 if (old_cursor.par()->layout()->keepempty)
2259 // only do our magic if we changed paragraph
2260 if (old_cursor.par() == cursor.par())
2263 // record if we have deleted a paragraph
2264 // we can't possibly have deleted a paragraph before this point
2265 bool deleted = false;
2267 if (old_cursor.par()->empty() ||
2268 (old_cursor.par()->size() == 1 &&
2269 old_cursor.par()->isLineSeparator(0))) {
2270 // ok, we will delete anything
2271 LyXCursor tmpcursor;
2275 if (old_cursor.row() != rows().begin()) {
2277 prevrow = boost::prior(old_cursor.row());
2278 const_cast<LyXText *>(this)->postPaint(old_cursor.y() - old_cursor.row()->baseline() - prevrow->height());
2280 cursor = old_cursor; // that undo can restore the right cursor position
2281 #warning FIXME. --end() iterator is usable here
2282 ParagraphList::iterator endpit = boost::next(old_cursor.par());
2283 while (endpit != ownerParagraphs().end() &&
2284 endpit->getDepth()) {
2288 setUndo(bv(), Undo::DELETE, &*old_cursor.par(), &*endpit);
2292 removeRow(old_cursor.row());
2293 if (ownerParagraphs().begin() == old_cursor.par()) {
2294 ownerParagraph(&*boost::next(ownerParagraphs().begin()));
2296 #warning FIXME Do the proper ParagraphList operation here (Lgb)
2298 delete &*old_cursor.par();
2300 /* Breakagain the next par. Needed because of
2301 * the parindent that can occur or dissappear.
2302 * The next row can change its height, if
2303 * there is another layout before */
2304 if (boost::next(prevrow) != rows().end()) {
2305 breakAgain(boost::next(prevrow));
2308 setHeightOfRow(prevrow);
2310 RowList::iterator nextrow = boost::next(old_cursor.row());
2311 const_cast<LyXText *>(this)->postPaint(
2312 old_cursor.y() - old_cursor.row()->baseline());
2315 cursor = old_cursor; // that undo can restore the right cursor position
2316 #warning FIXME. --end() iterator is usable here
2317 ParagraphList::iterator endpit = boost::next(old_cursor.par());
2318 while (endpit != ownerParagraphs().end() &&
2319 endpit->getDepth()) {
2323 setUndo(bv(), Undo::DELETE, &*old_cursor.par(), &*endpit);
2327 removeRow(old_cursor.row());
2329 if (ownerParagraphs().begin() == old_cursor.par()) {
2330 ownerParagraph(&*boost::next(ownerParagraphs().begin()));
2332 #warning FIXME Do the proper ParagraphList operations here. (Lgb)
2333 delete &*old_cursor.par();
2335 /* Breakagain the next par. Needed because of
2336 the parindent that can occur or dissappear.
2337 The next row can change its height, if
2338 there is another layout before */
2339 if (nextrow != rows().end()) {
2340 breakAgain(nextrow);
2346 setCursorIntern(cursor.par(), cursor.pos());
2348 if (selection.cursor.par() == old_cursor.par()
2349 && selection.cursor.pos() == old_cursor.pos()) {
2350 // correct selection
2351 selection.cursor = cursor;
2355 if (old_cursor.par()->stripLeadingSpaces()) {
2356 redoParagraphs(old_cursor, boost::next(old_cursor.par()));
2358 setCursorIntern(cursor.par(), cursor.pos());
2359 selection.cursor = cursor;
2366 ParagraphList & LyXText::ownerParagraphs() const
2369 return inset_owner->paragraphs;
2371 return bv_owner->buffer()->paragraphs;
2375 void LyXText::ownerParagraph(Paragraph * p) const
2378 inset_owner->paragraph(p);
2380 bv_owner->buffer()->paragraphs.set(p);
2385 void LyXText::ownerParagraph(int id, Paragraph * p) const
2387 Paragraph * op = bv_owner->buffer()->getParFromID(id);
2388 if (op && op->inInset()) {
2389 static_cast<InsetText *>(op->inInset())->paragraph(p);
2396 LyXText::refresh_status LyXText::refreshStatus() const
2398 return refresh_status_;
2402 void LyXText::clearPaint()
2404 refresh_status_ = REFRESH_NONE;
2405 refresh_row = rows().end();
2410 void LyXText::postPaint(int start_y)
2412 refresh_status old = refresh_status_;
2414 refresh_status_ = REFRESH_AREA;
2415 refresh_row = rows().end();
2417 if (old != REFRESH_NONE && refresh_y < start_y)
2420 refresh_y = start_y;
2425 // We are an inset's lyxtext. Tell the top-level lyxtext
2426 // it needs to update the row we're in.
2427 LyXText * t = bv()->text;
2428 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2432 // FIXME: we should probably remove this y parameter,
2433 // make refresh_y be 0, and use row->y etc.
2434 void LyXText::postRowPaint(RowList::iterator rit, int start_y)
2436 if (refresh_status_ != REFRESH_NONE && refresh_y < start_y) {
2437 refresh_status_ = REFRESH_AREA;
2440 refresh_y = start_y;
2443 if (refresh_status_ == REFRESH_AREA)
2446 refresh_status_ = REFRESH_ROW;
2452 // We are an inset's lyxtext. Tell the top-level lyxtext
2453 // it needs to update the row we're in.
2454 LyXText * t = bv()->text;
2455 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2459 bool LyXText::isInInset() const
2461 // Sub-level has non-null bv owner and
2462 // non-null inset owner.
2463 return inset_owner != 0 && bv_owner != 0;
2467 int defaultRowHeight()
2469 LyXFont const font(LyXFont::ALL_SANE);
2470 return int(font_metrics::maxAscent(font)
2471 + font_metrics::maxDescent(font) * 1.5);