1 /* This file is part of
2 * ======================================================
4 * LyX, The Document Processor
6 * Copyright 1995 Matthias Ettrich
7 * Copyright 1995-2001 The LyX Team.
9 * ====================================================== */
15 #include "paragraph.h"
16 #include "frontends/LyXView.h"
17 #include "undo_funcs.h"
19 #include "bufferparams.h"
21 #include "BufferView.h"
22 #include "CutAndPaste.h"
23 #include "frontends/Painter.h"
24 #include "frontends/font_metrics.h"
28 #include "FloatList.h"
30 #include "ParagraphParameters.h"
32 #include "lyxrow_funcs.h"
34 #include "insets/insetbibitem.h"
35 #include "insets/insetfloat.h"
37 #include "support/LAssert.h"
38 #include "support/textutils.h"
39 #include "support/lstrings.h"
41 #include "support/BoostFormat.h"
51 LyXText::LyXText(BufferView * bv)
52 : height(0), width(0), anchor_row_offset_(0),
53 inset_owner(0), the_locking_inset(0), bv_owner(bv)
55 anchor_row_ = rows().end();
56 need_break_row = rows().end();
57 refresh_row = rows().end();
63 LyXText::LyXText(BufferView * bv, InsetText * inset)
64 : height(0), width(0), anchor_row_offset_(0),
65 inset_owner(inset), the_locking_inset(0), bv_owner(bv)
67 anchor_row_ = rows().end();
68 need_break_row = rows().end();
69 refresh_row = rows().end();
75 void LyXText::init(BufferView * bview, bool reinit)
79 need_break_row = rows().end();
81 copylayouttype.erase();
84 } else if (!rowlist_.empty())
87 ParagraphList::iterator par = ownerParagraphs().begin();
88 ParagraphList::iterator end = ownerParagraphs().end();
90 current_font = getFont(bview->buffer(), &*par, 0);
92 for (; par != end; ++par) {
93 insertParagraph(&*par, rowlist_.end());
95 setCursorIntern(&*rowlist_.begin()->par(), 0);
96 selection.cursor = cursor;
104 LyXFont const realizeFont(LyXFont const & font,
108 LyXTextClass const & tclass = buf->params.getLyXTextClass();
109 LyXFont tmpfont(font);
110 Paragraph::depth_type par_depth = par->getDepth();
112 // Resolve against environment font information
113 while (par && par_depth && !tmpfont.resolved()) {
114 par = par->outerHook();
116 tmpfont.realize(par->layout()->font);
117 par_depth = par->getDepth();
121 tmpfont.realize(tclass.defaultfont());
129 // Gets the fully instantiated font at a given position in a paragraph
130 // Basically the same routine as Paragraph::getFont() in paragraph.C.
131 // The difference is that this one is used for displaying, and thus we
132 // are allowed to make cosmetic improvements. For instance make footnotes
134 // If position is -1, we get the layout font of the paragraph.
135 // If position is -2, we get the font of the manual label of the paragraph.
136 LyXFont const LyXText::getFont(Buffer const * buf, Paragraph * par,
139 lyx::Assert(pos >= 0);
141 LyXLayout_ptr const & layout = par->layout();
143 // We specialize the 95% common case:
144 if (!par->getDepth()) {
145 if (layout->labeltype == LABEL_MANUAL
146 && pos < par->beginningOfBody()) {
148 LyXFont f = par->getFontSettings(buf->params, pos);
150 par->inInset()->getDrawFont(f);
151 return f.realize(layout->reslabelfont);
153 LyXFont f = par->getFontSettings(buf->params, pos);
155 par->inInset()->getDrawFont(f);
156 return f.realize(layout->resfont);
160 // The uncommon case need not be optimized as much
164 if (pos < par->beginningOfBody()) {
166 layoutfont = layout->labelfont;
169 layoutfont = layout->font;
172 LyXFont tmpfont = par->getFontSettings(buf->params, pos);
173 tmpfont.realize(layoutfont);
176 par->inInset()->getDrawFont(tmpfont);
178 return realizeFont(tmpfont, buf, par);
182 LyXFont const LyXText::getLayoutFont(Buffer const * buf, Paragraph * par) const
184 LyXLayout_ptr const & layout = par->layout();
186 if (!par->getDepth()) {
187 return layout->resfont;
190 return realizeFont(layout->font, buf, par);
194 LyXFont const LyXText::getLabelFont(Buffer const * buf, Paragraph * par) const
196 LyXLayout_ptr const & layout = par->layout();
198 if (!par->getDepth()) {
199 return layout->reslabelfont;
202 return realizeFont(layout->labelfont, buf, par);
206 void LyXText::setCharFont(Paragraph * par,
207 pos_type pos, LyXFont const & fnt,
210 Buffer const * buf = bv()->buffer();
211 LyXFont font = getFont(buf, par, pos);
212 font.update(fnt, buf->params.language, toggleall);
213 // Let the insets convert their font
214 if (par->isInset(pos)) {
215 Inset * inset = par->getInset(pos);
216 if (isEditableInset(inset)) {
217 UpdatableInset * uinset =
218 static_cast<UpdatableInset *>(inset);
219 uinset->setFont(bv(), fnt, toggleall, true);
223 // Plug thru to version below:
224 setCharFont(buf, par, pos, font);
228 void LyXText::setCharFont(Buffer const * buf, Paragraph * par,
229 pos_type pos, LyXFont const & fnt)
233 LyXTextClass const & tclass = buf->params.getLyXTextClass();
234 LyXLayout_ptr const & layout = par->layout();
236 // Get concrete layout font to reduce against
239 if (pos < par->beginningOfBody())
240 layoutfont = layout->labelfont;
242 layoutfont = layout->font;
244 // Realize against environment font information
245 if (par->getDepth()) {
246 Paragraph * tp = par;
247 while (!layoutfont.resolved() && tp && tp->getDepth()) {
248 tp = tp->outerHook();
250 layoutfont.realize(tp->layout()->font);
254 layoutfont.realize(tclass.defaultfont());
256 // Now, reduce font against full layout font
257 font.reduce(layoutfont);
259 par->setFont(pos, font);
263 // removes the row and reset the touched counters
264 void LyXText::removeRow(RowList::iterator rit)
266 /* FIXME: when we cache the bview, this should just
267 * become a postPaint(), I think */
268 if (refresh_row == rit) {
269 if (rit == rows().begin())
270 refresh_row = boost::next(rit);
272 refresh_row = boost::prior(rit);
274 // what about refresh_y
277 if (anchor_row_ == rit) {
278 if (rit != rows().begin()) {
279 anchor_row_ = boost::prior(rit);
280 anchor_row_offset_ += boost::prior(rit)->height();
282 anchor_row_ = boost::next(rit);
283 anchor_row_offset_ -= rit->height();
287 // the text becomes smaller
288 height -= rit->height();
294 // remove all following rows of the paragraph of the specified row.
295 void LyXText::removeParagraph(RowList::iterator rit)
297 ParagraphList::iterator tmppit = rit->par();
300 while (rit != rows().end() && rit->par() == tmppit) {
301 RowList::iterator tmprit = boost::next(rit);
308 #warning FIXME Convert this to ParagraphList::iterator
309 void LyXText::insertParagraph(Paragraph * par, RowList::iterator rowit)
311 // insert a new row, starting at position 0
313 RowList::iterator rit = rowlist_.insert(rowit, newrow);
315 // and now append the whole paragraph before the new row
316 appendParagraph(rit);
320 Inset * LyXText::getInset() const
322 if (cursor.pos() < cursor.par()->size()
323 && cursor.par()->isInset(cursor.pos())) {
324 return cursor.par()->getInset(cursor.pos());
330 void LyXText::toggleInset()
332 Inset * inset = getInset();
333 // is there an editable inset at cursor position?
334 if (!isEditableInset(inset)) {
335 // No, try to see if we are inside a collapsable inset
336 if (inset_owner && inset_owner->owner()
337 && inset_owner->owner()->isOpen()) {
338 bv()->unlockInset(static_cast<UpdatableInset *>(inset_owner->owner()));
339 inset_owner->owner()->close(bv());
340 bv()->getLyXText()->cursorRight(bv());
344 //bv()->owner()->message(inset->editMessage());
346 // do we want to keep this?? (JMarc)
347 if (!isHighlyEditableInset(inset))
348 setCursorParUndo(bv());
350 if (inset->isOpen()) {
356 bv()->updateInset(inset);
360 /* used in setlayout */
361 // Asger is not sure we want to do this...
362 void LyXText::makeFontEntriesLayoutSpecific(Buffer const & buf,
365 LyXLayout_ptr const & layout = par.layout();
368 for (pos_type pos = 0; pos < par.size(); ++pos) {
369 if (pos < par.beginningOfBody())
370 layoutfont = layout->labelfont;
372 layoutfont = layout->font;
374 LyXFont tmpfont = par.getFontSettings(buf.params, pos);
375 tmpfont.reduce(layoutfont);
376 par.setFont(pos, tmpfont);
381 Paragraph * LyXText::setLayout(LyXCursor & cur, LyXCursor & sstart_cur,
382 LyXCursor & send_cur,
383 string const & layout)
385 Paragraph * endpar = send_cur.par()->next();
386 Paragraph * undoendpar = endpar;
388 if (endpar && endpar->getDepth()) {
389 while (endpar && endpar->getDepth()) {
390 endpar = endpar->next();
394 endpar = endpar->next(); // because of parindents etc.
397 setUndo(bv(), Undo::EDIT, &*sstart_cur.par(), undoendpar);
399 // ok we have a selection. This is always between sstart_cur
400 // and sel_end cursor
402 ParagraphList::iterator pit = sstart_cur.par();
403 ParagraphList::iterator epit = boost::next(send_cur.par());
405 LyXLayout_ptr const & lyxlayout =
406 bv()->buffer()->params.getLyXTextClass()[layout];
409 pit->applyLayout(lyxlayout);
410 makeFontEntriesLayoutSpecific(*bv()->buffer(), *pit);
411 ParagraphList::iterator fppit = pit;
412 fppit->params().spaceTop(lyxlayout->fill_top ?
413 VSpace(VSpace::VFILL)
414 : VSpace(VSpace::NONE));
415 fppit->params().spaceBottom(lyxlayout->fill_bottom ?
416 VSpace(VSpace::VFILL)
417 : VSpace(VSpace::NONE));
418 if (lyxlayout->margintype == MARGIN_MANUAL)
419 pit->setLabelWidthString(lyxlayout->labelstring());
422 } while (pit != epit);
428 // set layout over selection and make a total rebreak of those paragraphs
429 void LyXText::setLayout(string const & layout)
431 LyXCursor tmpcursor = cursor; /* store the current cursor */
433 // if there is no selection just set the layout
434 // of the current paragraph */
435 if (!selection.set()) {
436 selection.start = cursor; // dummy selection
437 selection.end = cursor;
439 Paragraph * endpar = setLayout(cursor, selection.start,
440 selection.end, layout);
441 redoParagraphs(selection.start, endpar);
443 // we have to reset the selection, because the
444 // geometry could have changed
445 setCursor(selection.start.par(),
446 selection.start.pos(), false);
447 selection.cursor = cursor;
448 setCursor(selection.end.par(), selection.end.pos(), false);
452 setCursor(tmpcursor.par(), tmpcursor.pos(), true);
456 bool LyXText::changeDepth(bv_funcs::DEPTH_CHANGE type, bool test_only)
458 ParagraphList::iterator pit(cursor.par());
459 ParagraphList::iterator end(cursor.par());
460 ParagraphList::iterator start = pit;
462 if (selection.set()) {
463 pit = selection.start.par();
464 end = selection.end.par();
468 ParagraphList::iterator pastend = boost::next(end);
471 setUndo(bv(), Undo::EDIT, &(*start), &(*pastend));
473 bool changed = false;
475 int prev_after_depth = 0;
476 #warning parlist ... could be nicer ?
477 if (start != ownerParagraphs().begin()) {
478 prev_after_depth = boost::prior(start)->getMaxDepthAfter();
482 int const depth = pit->params().depth();
483 if (type == bv_funcs::INC_DEPTH) {
484 if (depth < prev_after_depth
485 && pit->layout()->labeltype != LABEL_BIBLIO) {
488 pit->params().depth(depth + 1);
495 pit->params().depth(depth - 1);
498 prev_after_depth = pit->getMaxDepthAfter();
510 // Wow, redoParagraphs is stupid.
512 setCursor(tmpcursor, &(*start), 0);
514 //redoParagraphs(tmpcursor, &(*pastend));
515 redoParagraphs(tmpcursor, &(*pastend));
517 // We need to actually move the text->cursor. I don't
518 // understand why ...
521 // we have to reset the visual selection because the
522 // geometry could have changed
523 if (selection.set()) {
524 setCursor(selection.start.par(), selection.start.pos());
525 selection.cursor = cursor;
526 setCursor(selection.end.par(), selection.end.pos());
529 // this handles the counter labels, and also fixes up
530 // depth values for follow-on (child) paragraphs
534 setCursor(tmpcursor.par(), tmpcursor.pos());
540 // set font over selection and make a total rebreak of those paragraphs
541 void LyXText::setFont(LyXFont const & font, bool toggleall)
543 // if there is no selection just set the current_font
544 if (!selection.set()) {
545 // Determine basis font
547 if (cursor.pos() < cursor.par()->beginningOfBody()) {
548 layoutfont = getLabelFont(bv()->buffer(),
551 layoutfont = getLayoutFont(bv()->buffer(),
554 // Update current font
555 real_current_font.update(font,
556 bv()->buffer()->params.language,
559 // Reduce to implicit settings
560 current_font = real_current_font;
561 current_font.reduce(layoutfont);
562 // And resolve it completely
563 real_current_font.realize(layoutfont);
568 LyXCursor tmpcursor = cursor; // store the current cursor
570 // ok we have a selection. This is always between sel_start_cursor
571 // and sel_end cursor
573 setUndo(bv(), Undo::EDIT,
574 &*selection.start.par(), &*boost::next(selection.end.par()));
576 cursor = selection.start;
577 while (cursor.par() != selection.end.par() ||
578 cursor.pos() < selection.end.pos())
580 if (cursor.pos() < cursor.par()->size()) {
581 // an open footnote should behave like a closed one
582 setCharFont(&*cursor.par(), cursor.pos(),
584 cursor.pos(cursor.pos() + 1);
587 cursor.par(cursor.par()->next());
592 redoParagraphs(selection.start, selection.end.par()->next());
594 // we have to reset the selection, because the
595 // geometry could have changed, but we keep
596 // it for user convenience
597 setCursor(selection.start.par(), selection.start.pos());
598 selection.cursor = cursor;
599 setCursor(selection.end.par(), selection.end.pos());
601 setCursor(tmpcursor.par(), tmpcursor.pos(), true,
602 tmpcursor.boundary());
606 void LyXText::redoHeightOfParagraph()
608 RowList::iterator tmprow = cursor.row();
609 int y = cursor.y() - tmprow->baseline();
611 setHeightOfRow(tmprow);
613 while (tmprow != rows().begin()
614 && boost::prior(tmprow)->par() == tmprow->par()) {
616 y -= tmprow->height();
617 setHeightOfRow(tmprow);
622 setCursor(cursor.par(), cursor.pos(), false, cursor.boundary());
626 void LyXText::redoDrawingOfParagraph(LyXCursor const & cur)
628 RowList::iterator tmprow = cur.row();
630 int y = cur.y() - tmprow->baseline();
631 setHeightOfRow(tmprow);
633 while (tmprow != rows().begin()
634 && boost::prior(tmprow)->par() == tmprow->par()) {
636 y -= tmprow->height();
640 setCursor(cur.par(), cur.pos());
644 // deletes and inserts again all paragaphs between the cursor
645 // and the specified par
646 // This function is needed after SetLayout and SetFont etc.
647 void LyXText::redoParagraphs(LyXCursor const & cur,
648 Paragraph const * ep)
650 RowList::iterator tmprit = cur.row();
651 ParagraphList::iterator begpit = cur.row()->par();
652 ParagraphList::iterator endpit(const_cast<Paragraph*>(ep));
653 int y = cur.y() - tmprit->baseline();
655 ParagraphList::iterator first_phys_pit;
656 if (tmprit == rows().begin()) {
657 // A trick/hack for UNDO.
658 // This is needed because in an UNDO/REDO we could have
659 // changed the ownerParagrah() so the paragraph inside
660 // the row is NOT my really first par anymore.
661 // Got it Lars ;) (Jug 20011206)
662 first_phys_pit = ownerParagraphs().begin();
664 // In here prevrit could be set to rows().end(). (Lgb)
666 first_phys_pit = tmprit->par();
667 while (tmprit != rows().begin()
668 && boost::prior(tmprit)->par() == first_phys_pit)
671 y -= tmprit->height();
674 // Is it possible to put the prevrit setting in here? (Lgb)
677 RowList::iterator prevrit;
678 bool good_prevrit = false;
680 // It seems to mee that good_prevrit is not needed if we let
681 // a bad prevrit have the value rows().end() (Lgb)
682 if (tmprit != rows().begin()) {
683 prevrit = boost::prior(tmprit);
688 while (tmprit != rows().end() && tmprit->par() != endpit) {
689 RowList::iterator tmprit2 = tmprit++;
693 // Reinsert the paragraphs.
694 ParagraphList::iterator tmppit = first_phys_pit;
696 // See if this loop can be rewritten as a while loop instead.
697 // That should also make the code a bit easier to read. (Lgb)
699 if (tmppit != ownerParagraphs().end()) {
700 insertParagraph(&*tmppit, tmprit);
701 while (tmprit != rows().end()
702 && tmprit->par() == tmppit) {
707 } while (tmppit != ownerParagraphs().end() && tmppit != endpit);
710 // If the above changes are done, then we can compare prevrit
711 // with rows().end() here. (Lgb)
713 setHeightOfRow(prevrit);
714 const_cast<LyXText *>(this)->postPaint(y - prevrit->height());
716 setHeightOfRow(rows().begin());
717 const_cast<LyXText *>(this)->postPaint(0);
719 if (tmprit != rows().end())
720 setHeightOfRow(tmprit);
726 void LyXText::fullRebreak()
728 if (rows().empty()) {
732 if (need_break_row != rows().end()) {
733 breakAgain(need_break_row);
734 need_break_row = rows().end();
740 // important for the screen
743 // the cursor set functions have a special mechanism. When they
744 // realize, that you left an empty paragraph, they will delete it.
745 // They also delete the corresponding row
747 // need the selection cursor:
748 void LyXText::setSelection()
750 bool const lsel = selection.set();
752 if (!selection.set()) {
753 last_sel_cursor = selection.cursor;
754 selection.start = selection.cursor;
755 selection.end = selection.cursor;
760 // first the toggling area
761 if (cursor.y() < last_sel_cursor.y()
762 || (cursor.y() == last_sel_cursor.y()
763 && cursor.x() < last_sel_cursor.x())) {
764 toggle_end_cursor = last_sel_cursor;
765 toggle_cursor = cursor;
767 toggle_end_cursor = cursor;
768 toggle_cursor = last_sel_cursor;
771 last_sel_cursor = cursor;
773 // and now the whole selection
775 if (selection.cursor.par() == cursor.par())
776 if (selection.cursor.pos() < cursor.pos()) {
777 selection.end = cursor;
778 selection.start = selection.cursor;
780 selection.end = selection.cursor;
781 selection.start = cursor;
783 else if (selection.cursor.y() < cursor.y() ||
784 (selection.cursor.y() == cursor.y()
785 && selection.cursor.x() < cursor.x())) {
786 selection.end = cursor;
787 selection.start = selection.cursor;
790 selection.end = selection.cursor;
791 selection.start = cursor;
794 // a selection with no contents is not a selection
795 if (selection.start.par() == selection.end.par() &&
796 selection.start.pos() == selection.end.pos())
797 selection.set(false);
799 if (inset_owner && (selection.set() || lsel))
800 inset_owner->setUpdateStatus(bv(), InsetText::SELECTION);
804 string const LyXText::selectionAsString(Buffer const * buffer,
807 if (!selection.set()) return string();
809 // should be const ...
810 ParagraphList::iterator startpit = selection.start.par();
811 ParagraphList::iterator endpit = selection.end.par();
812 pos_type const startpos(selection.start.pos());
813 pos_type const endpos(selection.end.pos());
815 if (startpit == endpit) {
816 return startpit->asString(buffer, startpos, endpos, label);
821 // First paragraph in selection
822 result += startpit->asString(buffer, startpos, startpit->size(), label) + "\n\n";
824 // The paragraphs in between (if any)
825 #warning FIXME Why isnt ParagraphList::iterator used here?
827 LyXCursor tmpcur(selection.start);
828 tmpcur.par(tmpcur.par()->next());
829 while (tmpcur.par() != endpit) {
830 result += tmpcur.par()->asString(buffer, 0,
831 tmpcur.par()->size(),
833 tmpcur.par(boost::next(tmpcur.par()));
836 // Last paragraph in selection
837 result += endpit->asString(buffer, 0, endpos, label);
843 void LyXText::clearSelection()
845 selection.set(false);
846 selection.mark(false);
847 last_sel_cursor = selection.end = selection.start = selection.cursor = cursor;
848 // reset this in the bv_owner!
849 if (bv_owner && bv_owner->text)
850 bv_owner->text->xsel_cache.set(false);
854 void LyXText::cursorHome()
856 setCursor(cursor.par(), cursor.row()->pos());
860 void LyXText::cursorEnd()
862 if (cursor.par()->empty())
865 // There is a lot of unneeded recalculation going on here:
866 // - boost::next(curosr.row())
867 // - lastPost(*this, cursor.row())
869 if (boost::next(cursor.row()) == rows().end()
870 || boost::next(cursor.row())->par() != cursor.row()->par()) {
871 setCursor(cursor.par(), lastPos(*this, cursor.row()) + 1);
873 if (!cursor.par()->empty() &&
874 (cursor.par()->getChar(lastPos(*this, cursor.row())) == ' '
875 || cursor.par()->isNewline(lastPos(*this, cursor.row())))) {
876 setCursor(cursor.par(), lastPos(*this, cursor.row()));
878 setCursor(cursor.par(),
879 lastPos(*this, cursor.row()) + 1);
885 void LyXText::cursorTop()
887 while (cursor.par()->previous())
888 cursor.par(cursor.par()->previous());
889 setCursor(cursor.par(), 0);
893 void LyXText::cursorBottom()
895 while (cursor.par()->next())
896 cursor.par(cursor.par()->next());
897 setCursor(cursor.par(), cursor.par()->size());
901 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
903 // If the mask is completely neutral, tell user
904 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
905 // Could only happen with user style
906 bv()->owner()->message(_("No font change defined. Use Character under the Layout menu to define font change."));
910 // Try implicit word selection
911 // If there is a change in the language the implicit word selection
913 LyXCursor resetCursor = cursor;
914 bool implicitSelection = (font.language() == ignore_language
915 && font.number() == LyXFont::IGNORE)
916 ? selectWordWhenUnderCursor(WHOLE_WORD_STRICT) : false;
919 setFont(font, toggleall);
921 // Implicit selections are cleared afterwards
922 //and cursor is set to the original position.
923 if (implicitSelection) {
925 cursor = resetCursor;
926 setCursor(cursor.par(), cursor.pos());
927 selection.cursor = cursor;
930 inset_owner->setUpdateStatus(bv(), InsetText::CURSOR_PAR);
934 string LyXText::getStringToIndex()
936 // Try implicit word selection
937 // If there is a change in the language the implicit word selection
939 LyXCursor const reset_cursor = cursor;
940 bool const implicitSelection = selectWordWhenUnderCursor(PREVIOUS_WORD);
943 if (!selection.set())
944 bv()->owner()->message(_("Nothing to index!"));
945 else if (selection.start.par() != selection.end.par())
946 bv()->owner()->message(_("Cannot index more than one paragraph!"));
948 idxstring = selectionAsString(bv()->buffer(), false);
950 // Reset cursors to their original position.
951 cursor = reset_cursor;
952 setCursor(cursor.par(), cursor.pos());
953 selection.cursor = cursor;
955 // Clear the implicit selection.
956 if (implicitSelection)
963 // the DTP switches for paragraphs. LyX will store them in the first
964 // physicla paragraph. When a paragraph is broken, the top settings rest,
965 // the bottom settings are given to the new one. So I can make shure,
966 // they do not duplicate themself and you cannnot make dirty things with
969 void LyXText::setParagraph(bool line_top, bool line_bottom,
970 bool pagebreak_top, bool pagebreak_bottom,
971 VSpace const & space_top,
972 VSpace const & space_bottom,
973 Spacing const & spacing,
975 string const & labelwidthstring,
978 LyXCursor tmpcursor = cursor;
979 if (!selection.set()) {
980 selection.start = cursor;
981 selection.end = cursor;
984 // make sure that the depth behind the selection are restored, too
985 Paragraph * endpar = selection.end.par()->next();
986 Paragraph * undoendpar = endpar;
988 if (endpar && endpar->getDepth()) {
989 while (endpar && endpar->getDepth()) {
990 endpar = endpar->next();
995 // because of parindents etc.
996 endpar = endpar->next();
999 setUndo(bv(), Undo::EDIT, &*selection.start.par(), undoendpar);
1002 ParagraphList::iterator tmppit = selection.end.par();
1004 while (tmppit != boost::prior(selection.start.par())) {
1005 setCursor(tmppit, 0);
1006 postPaint(cursor.y() - cursor.row()->baseline());
1007 cursor.par()->params().lineTop(line_top);
1008 cursor.par()->params().lineBottom(line_bottom);
1009 cursor.par()->params().pagebreakTop(pagebreak_top);
1010 cursor.par()->params().pagebreakBottom(pagebreak_bottom);
1011 cursor.par()->params().spaceTop(space_top);
1012 cursor.par()->params().spaceBottom(space_bottom);
1013 cursor.par()->params().spacing(spacing);
1014 // does the layout allow the new alignment?
1015 LyXLayout_ptr const & layout = cursor.par()->layout();
1017 if (align == LYX_ALIGN_LAYOUT)
1018 align = layout->align;
1019 if (align & layout->alignpossible) {
1020 if (align == layout->align)
1021 cursor.par()->params().align(LYX_ALIGN_LAYOUT);
1023 cursor.par()->params().align(align);
1025 cursor.par()->setLabelWidthString(labelwidthstring);
1026 cursor.par()->params().noindent(noindent);
1027 tmppit = boost::prior(cursor.par());
1030 redoParagraphs(selection.start, endpar);
1033 setCursor(selection.start.par(), selection.start.pos());
1034 selection.cursor = cursor;
1035 setCursor(selection.end.par(), selection.end.pos());
1037 setCursor(tmpcursor.par(), tmpcursor.pos());
1039 bv()->updateInset(inset_owner);
1043 // set the counter of a paragraph. This includes the labels
1044 void LyXText::setCounter(Buffer const * buf, Paragraph * par)
1046 LyXTextClass const & textclass = buf->params.getLyXTextClass();
1047 LyXLayout_ptr const & layout = par->layout();
1049 if (par->previous()) {
1051 par->params().appendix(par->previous()->params().appendix());
1052 if (!par->params().appendix() && par->params().startOfAppendix()) {
1053 par->params().appendix(true);
1054 textclass.counters().reset();
1056 par->enumdepth = par->previous()->enumdepth;
1057 par->itemdepth = par->previous()->itemdepth;
1059 par->params().appendix(par->params().startOfAppendix());
1064 /* Maybe we have to increment the enumeration depth.
1065 * BUT, enumeration in a footnote is considered in isolation from its
1066 * surrounding paragraph so don't increment if this is the
1067 * first line of the footnote
1068 * AND, bibliographies can't have their depth changed ie. they
1069 * are always of depth 0
1072 && par->previous()->getDepth() < par->getDepth()
1073 && par->previous()->layout()->labeltype == LABEL_COUNTER_ENUMI
1074 && par->enumdepth < 3
1075 && layout->labeltype != LABEL_BIBLIO) {
1079 // Maybe we have to decrement the enumeration depth, see note above
1081 && par->previous()->getDepth() > par->getDepth()
1082 && layout->labeltype != LABEL_BIBLIO) {
1083 par->enumdepth = par->depthHook(par->getDepth())->enumdepth;
1086 if (!par->params().labelString().empty()) {
1087 par->params().labelString(string());
1090 if (layout->margintype == MARGIN_MANUAL) {
1091 if (par->params().labelWidthString().empty()) {
1092 par->setLabelWidthString(layout->labelstring());
1095 par->setLabelWidthString(string());
1098 // is it a layout that has an automatic label?
1099 if (layout->labeltype >= LABEL_COUNTER_CHAPTER) {
1100 int const i = layout->labeltype - LABEL_COUNTER_CHAPTER;
1104 if (i >= 0 && i <= buf->params.secnumdepth) {
1108 textclass.counters().step(layout->latexname());
1110 // Is there a label? Useful for Chapter layout
1111 if (!par->params().appendix()) {
1112 s << layout->labelstring();
1114 s << layout->labelstring_appendix();
1117 // Use of an integer is here less than elegant. For now.
1118 int head = textclass.maxcounter() - LABEL_COUNTER_CHAPTER;
1119 if (!par->params().appendix()) {
1120 numbertype = "sectioning";
1122 numbertype = "appendix";
1123 if (par->isRightToLeftPar(buf->params))
1124 langtype = "hebrew";
1129 s << textclass.counters()
1130 .numberLabel(layout->latexname(),
1131 numbertype, langtype, head);
1133 par->params().labelString(STRCONV(s.str()));
1135 // reset enum counters
1136 textclass.counters().reset("enum");
1137 } else if (layout->labeltype < LABEL_COUNTER_ENUMI) {
1138 textclass.counters().reset("enum");
1139 } else if (layout->labeltype == LABEL_COUNTER_ENUMI) {
1141 // Yes I know this is a really, really! bad solution
1143 string enumcounter("enum");
1145 switch (par->enumdepth) {
1154 enumcounter += "iv";
1157 // not a valid enumdepth...
1161 textclass.counters().step(enumcounter);
1163 s << textclass.counters()
1164 .numberLabel(enumcounter, "enumeration");
1165 par->params().labelString(STRCONV(s.str()));
1167 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
1168 textclass.counters().step("bibitem");
1169 int number = textclass.counters().value("bibitem");
1170 if (par->bibitem()) {
1171 par->bibitem()->setCounter(number);
1172 par->params().labelString(layout->labelstring());
1174 // In biblio should't be following counters but...
1176 string s = layout->labelstring();
1178 // the caption hack:
1179 if (layout->labeltype == LABEL_SENSITIVE) {
1180 Paragraph * tmppar = par;
1183 while (tmppar && tmppar->inInset()
1184 // the single '=' is intended below
1185 && (in = tmppar->inInset()->owner())) {
1186 if (in->lyxCode() == Inset::FLOAT_CODE ||
1187 in->lyxCode() == Inset::WRAP_CODE) {
1191 tmppar = in->parOwner();
1197 = textclass.floats().getType(static_cast<InsetFloat*>(in)->type());
1199 textclass.counters().step(fl.type());
1201 // Doesn't work... yet.
1202 #if USE_BOOST_FORMAT
1203 s = boost::io::str(boost::format(_("%1$s #:")) % fl.name());
1204 // s << boost::format(_("%1$s %1$d:")
1206 // % buf->counters().value(fl.name());
1209 //o << fl.name() << ' ' << buf->counters().value(fl.name()) << ":";
1210 o << fl.name() << " #:";
1211 s = STRCONV(o.str());
1214 // par->SetLayout(0);
1215 // s = layout->labelstring;
1216 s = _("Senseless: ");
1219 par->params().labelString(s);
1221 // reset the enumeration counter. They are always reset
1222 // when there is any other layout between
1223 // Just fall-through between the cases so that all
1224 // enum counters deeper than enumdepth is also reset.
1225 switch (par->enumdepth) {
1227 textclass.counters().reset("enumi");
1229 textclass.counters().reset("enumii");
1231 textclass.counters().reset("enumiii");
1233 textclass.counters().reset("enumiv");
1239 // Updates all counters. Paragraphs with changed label string will be rebroken
1240 void LyXText::updateCounters()
1242 RowList::iterator rowit = rows().begin();
1243 ParagraphList::iterator pit = rowit->par();
1245 // CHECK if this is really needed. (Lgb)
1246 bv()->buffer()->params.getLyXTextClass().counters().reset();
1248 while (pit != ownerParagraphs().end()) {
1249 while (rowit->par() != pit)
1252 string const oldLabel = pit->params().labelString();
1255 if (pit != ownerParagraphs().begin())
1256 maxdepth = boost::prior(pit)->getMaxDepthAfter();
1258 if (pit->params().depth() > maxdepth)
1259 pit->params().depth(maxdepth);
1261 // setCounter can potentially change the labelString.
1262 setCounter(bv()->buffer(), &*pit);
1264 string const & newLabel = pit->params().labelString();
1266 if (oldLabel.empty() && !newLabel.empty()) {
1267 removeParagraph(rowit);
1268 appendParagraph(rowit);
1276 void LyXText::insertInset(Inset * inset)
1278 if (!cursor.par()->insetAllowed(inset->lyxCode()))
1280 setUndo(bv(), Undo::FINISH, &*cursor.par(),
1281 &*boost::next(cursor.par()));
1283 cursor.par()->insertInset(cursor.pos(), inset);
1284 // Just to rebreak and refresh correctly.
1285 // The character will not be inserted a second time
1286 insertChar(Paragraph::META_INSET);
1287 // If we enter a highly editable inset the cursor should be to before
1288 // the inset. This couldn't happen before as Undo was not handled inside
1289 // inset now after the Undo LyX tries to call inset->Edit(...) again
1290 // and cannot do this as the cursor is behind the inset and GetInset
1291 // does not return the inset!
1292 if (isHighlyEditableInset(inset)) {
1299 void LyXText::copyEnvironmentType()
1301 copylayouttype = cursor.par()->layout()->name();
1305 void LyXText::pasteEnvironmentType()
1307 // do nothing if there has been no previous copyEnvironmentType()
1308 if (!copylayouttype.empty())
1309 setLayout(copylayouttype);
1313 void LyXText::cutSelection(bool doclear, bool realcut)
1315 // Stuff what we got on the clipboard. Even if there is no selection.
1317 // There is a problem with having the stuffing here in that the
1318 // larger the selection the slower LyX will get. This can be
1319 // solved by running the line below only when the selection has
1320 // finished. The solution used currently just works, to make it
1321 // faster we need to be more clever and probably also have more
1322 // calls to stuffClipboard. (Lgb)
1323 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1325 // This doesn't make sense, if there is no selection
1326 if (!selection.set())
1329 // OK, we have a selection. This is always between selection.start
1330 // and selection.end
1332 // make sure that the depth behind the selection are restored, too
1333 Paragraph * endpar = selection.end.par()->next();
1334 Paragraph * undoendpar = endpar;
1336 if (endpar && endpar->getDepth()) {
1337 while (endpar && endpar->getDepth()) {
1338 endpar = endpar->next();
1339 undoendpar = endpar;
1341 } else if (endpar) {
1342 endpar = endpar->next(); // because of parindents etc.
1345 setUndo(bv(), Undo::DELETE,
1346 &*selection.start.par(), undoendpar);
1348 // there are two cases: cut only within one paragraph or
1349 // more than one paragraph
1350 if (selection.start.par() == selection.end.par()) {
1351 // only within one paragraph
1352 endpar = &*selection.end.par();
1353 int pos = selection.end.pos();
1354 CutAndPaste::cutSelection(&*selection.start.par(), &endpar,
1355 selection.start.pos(), pos,
1356 bv()->buffer()->params.textclass,
1358 selection.end.pos(pos);
1360 endpar = &*selection.end.par();
1361 int pos = selection.end.pos();
1362 CutAndPaste::cutSelection(&*selection.start.par(), &endpar,
1363 selection.start.pos(), pos,
1364 bv()->buffer()->params.textclass,
1367 selection.end.par(endpar);
1368 selection.end.pos(pos);
1369 cursor.pos(selection.end.pos());
1371 endpar = endpar->next();
1373 // sometimes necessary
1375 selection.start.par()->stripLeadingSpaces();
1377 redoParagraphs(selection.start, endpar);
1379 // cutSelection can invalidate the cursor so we need to set
1381 // we prefer the end for when tracking changes
1382 cursor = selection.end;
1384 // need a valid cursor. (Lgb)
1387 setCursor(cursor.par(), cursor.pos());
1388 selection.cursor = cursor;
1393 void LyXText::copySelection()
1395 // stuff the selection onto the X clipboard, from an explicit copy request
1396 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1398 // this doesnt make sense, if there is no selection
1399 if (!selection.set())
1402 // ok we have a selection. This is always between selection.start
1403 // and sel_end cursor
1405 // copy behind a space if there is one
1406 while (selection.start.par()->size() > selection.start.pos()
1407 && selection.start.par()->isLineSeparator(selection.start.pos())
1408 && (selection.start.par() != selection.end.par()
1409 || selection.start.pos() < selection.end.pos()))
1410 selection.start.pos(selection.start.pos() + 1);
1412 CutAndPaste::copySelection(&*selection.start.par(),
1413 &*selection.end.par(),
1414 selection.start.pos(), selection.end.pos(),
1415 bv()->buffer()->params.textclass);
1419 void LyXText::pasteSelection()
1421 // this does not make sense, if there is nothing to paste
1422 if (!CutAndPaste::checkPastePossible())
1425 setUndo(bv(), Undo::INSERT,
1426 &*cursor.par(), &*boost::next(cursor.par()));
1429 ParagraphList::iterator actpit = cursor.par();
1430 int pos = cursor.pos();
1432 Paragraph * actpar = &*actpit;
1433 CutAndPaste::pasteSelection(&actpar, &endpar, pos,
1434 bv()->buffer()->params.textclass);
1436 redoParagraphs(cursor, endpar);
1438 setCursor(cursor.par(), cursor.pos());
1441 selection.cursor = cursor;
1442 setCursor(actpit, pos);
1448 void LyXText::setSelectionRange(lyx::pos_type length)
1453 selection.cursor = cursor;
1460 // simple replacing. The font of the first selected character is used
1461 void LyXText::replaceSelectionWithString(string const & str)
1463 setCursorParUndo(bv());
1466 if (!selection.set()) { // create a dummy selection
1467 selection.end = cursor;
1468 selection.start = cursor;
1471 // Get font setting before we cut
1472 pos_type pos = selection.end.pos();
1473 LyXFont const font = selection.start.par()
1474 ->getFontSettings(bv()->buffer()->params,
1475 selection.start.pos());
1477 // Insert the new string
1478 for (string::const_iterator cit = str.begin(); cit != str.end(); ++cit) {
1479 selection.end.par()->insertChar(pos, (*cit), font);
1483 // Cut the selection
1484 cutSelection(true, false);
1490 // needed to insert the selection
1491 void LyXText::insertStringAsLines(string const & str)
1493 ParagraphList::iterator pit = cursor.par();
1494 pos_type pos = cursor.pos();
1495 ParagraphList::iterator endpit = boost::next(cursor.par());
1497 setCursorParUndo(bv());
1499 // only to be sure, should not be neccessary
1502 Paragraph * par = &*pit;
1503 bv()->buffer()->insertStringAsLines(par, pos, current_font, str);
1505 redoParagraphs(cursor, &*endpit);
1506 setCursor(cursor.par(), cursor.pos());
1507 selection.cursor = cursor;
1508 setCursor(pit, pos);
1513 // turns double-CR to single CR, others where converted into one
1514 // blank. Then InsertStringAsLines is called
1515 void LyXText::insertStringAsParagraphs(string const & str)
1517 string linestr(str);
1518 bool newline_inserted = false;
1519 for (string::size_type i = 0; i < linestr.length(); ++i) {
1520 if (linestr[i] == '\n') {
1521 if (newline_inserted) {
1522 // we know that \r will be ignored by
1523 // InsertStringA. Of course, it is a dirty
1524 // trick, but it works...
1525 linestr[i - 1] = '\r';
1529 newline_inserted = true;
1531 } else if (IsPrintable(linestr[i])) {
1532 newline_inserted = false;
1535 insertStringAsLines(linestr);
1539 void LyXText::checkParagraph(Paragraph * par, pos_type pos)
1541 LyXCursor tmpcursor;
1545 RowList::iterator row = getRow(par, pos, y);
1546 RowList::iterator beg = rows().begin();
1548 // is there a break one row above
1550 && boost::prior(row)->par() == row->par()) {
1551 z = rowBreakPoint(*boost::prior(row));
1552 if (z >= row->pos()) {
1553 // set the dimensions of the row above
1554 y -= boost::prior(row)->height();
1557 breakAgain(boost::prior(row));
1559 // set the cursor again. Otherwise
1560 // dangling pointers are possible
1561 setCursor(cursor.par(), cursor.pos(),
1562 false, cursor.boundary());
1563 selection.cursor = cursor;
1568 int const tmpheight = row->height();
1569 pos_type const tmplast = lastPos(*this, row);
1572 if (row->height() == tmpheight && lastPos(*this, row) == tmplast) {
1573 postRowPaint(row, y);
1578 // check the special right address boxes
1579 if (par->layout()->margintype == MARGIN_RIGHT_ADDRESS_BOX) {
1586 redoDrawingOfParagraph(tmpcursor);
1589 // set the cursor again. Otherwise dangling pointers are possible
1590 // also set the selection
1592 if (selection.set()) {
1594 setCursorIntern(selection.cursor.par(), selection.cursor.pos(),
1595 false, selection.cursor.boundary());
1596 selection.cursor = cursor;
1597 setCursorIntern(selection.start.par(),
1598 selection.start.pos(),
1599 false, selection.start.boundary());
1600 selection.start = cursor;
1601 setCursorIntern(selection.end.par(),
1602 selection.end.pos(),
1603 false, selection.end.boundary());
1604 selection.end = cursor;
1605 setCursorIntern(last_sel_cursor.par(),
1606 last_sel_cursor.pos(),
1607 false, last_sel_cursor.boundary());
1608 last_sel_cursor = cursor;
1611 setCursorIntern(cursor.par(), cursor.pos(),
1612 false, cursor.boundary());
1616 // returns false if inset wasn't found
1617 bool LyXText::updateInset(Inset * inset)
1619 // first check the current paragraph
1620 int pos = cursor.par()->getPositionOfInset(inset);
1622 checkParagraph(&*cursor.par(), pos);
1626 // check every paragraph
1628 ParagraphList::iterator par = ownerParagraphs().begin();
1629 ParagraphList::iterator end = ownerParagraphs().end();
1632 pos = par->getPositionOfInset(inset);
1634 checkParagraph(&*par, pos);
1638 } while (par != end);
1644 bool LyXText::setCursor(ParagraphList::iterator pit,
1646 bool setfont, bool boundary)
1648 LyXCursor old_cursor = cursor;
1649 setCursorIntern(pit, pos, setfont, boundary);
1650 return deleteEmptyParagraphMechanism(old_cursor);
1654 void LyXText::setCursor(LyXCursor & cur, ParagraphList::iterator pit,
1655 pos_type pos, bool boundary)
1657 lyx::Assert(pit != ownerParagraphs().end());
1661 cur.boundary(boundary);
1663 // get the cursor y position in text
1665 RowList::iterator row = getRow(&*pit, pos, y);
1666 RowList::iterator beg = rows().begin();
1668 RowList::iterator old_row = row;
1670 // if we are before the first char of this row and are still in the
1671 // same paragraph and there is a previous row then put the cursor on
1672 // the end of the previous row
1673 cur.iy(y + row->baseline());
1675 if (row != beg && pos &&
1676 boost::prior(row)->par() == row->par() &&
1677 pos < pit->size() &&
1678 pit->getChar(pos) == Paragraph::META_INSET &&
1679 (ins = pit->getInset(pos)) && (ins->needFullRow() || ins->display()))
1686 // y is now the beginning of the cursor row
1687 y += row->baseline();
1688 // y is now the cursor baseline
1691 pos_type last = lastPrintablePos(*this, old_row);
1693 // None of these should happen, but we're scaredy-cats
1694 if (pos > pit->size()) {
1695 lyxerr << "dont like 1 please report" << endl;
1698 } else if (pos > last + 1) {
1699 lyxerr << "dont like 2 please report" << endl;
1700 // This shouldn't happen.
1703 } else if (pos < row->pos()) {
1704 lyxerr << "dont like 3 please report" << endl;
1709 // now get the cursors x position
1710 float x = getCursorX(row, pos, last, boundary);
1713 if (old_row != row) {
1714 x = getCursorX(old_row, pos, last, boundary);
1718 /* We take out this for the time being because 1) the redraw code is not
1719 prepared to this yet and 2) because some good policy has yet to be decided
1720 while editting: for instance how to act on rows being created/deleted
1724 //if the cursor is in a visible row, anchor to it
1726 if (topy < y && y < topy + bv()->workHeight())
1732 float LyXText::getCursorX(RowList::iterator rit,
1733 pos_type pos, pos_type last, bool boundary) const
1735 pos_type cursor_vpos = 0;
1737 float fill_separator;
1739 float fill_label_hfill;
1740 // This call HAS to be here because of the BidiTables!!!
1741 prepareToPrint(rit, x, fill_separator, fill_hfill,
1744 if (last < rit->pos())
1745 cursor_vpos = rit->pos();
1746 else if (pos > last && !boundary)
1747 cursor_vpos = (rit->par()->isRightToLeftPar(bv()->buffer()->params))
1748 ? rit->pos() : last + 1;
1749 else if (pos > rit->pos() &&
1750 (pos > last || boundary))
1751 /// Place cursor after char at (logical) position pos - 1
1752 cursor_vpos = (bidi_level(pos - 1) % 2 == 0)
1753 ? log2vis(pos - 1) + 1 : log2vis(pos - 1);
1755 /// Place cursor before char at (logical) position pos
1756 cursor_vpos = (bidi_level(pos) % 2 == 0)
1757 ? log2vis(pos) : log2vis(pos) + 1;
1759 pos_type body_pos = rit->par()->beginningOfBody();
1760 if ((body_pos > 0) &&
1761 ((body_pos - 1 > last) ||
1762 !rit->par()->isLineSeparator(body_pos - 1)))
1765 for (pos_type vpos = rit->pos(); vpos < cursor_vpos; ++vpos) {
1766 pos_type pos = vis2log(vpos);
1767 if (body_pos > 0 && pos == body_pos - 1) {
1768 x += fill_label_hfill +
1769 font_metrics::width(
1770 rit->par()->layout()->labelsep,
1771 getLabelFont(bv()->buffer(),
1773 if (rit->par()->isLineSeparator(body_pos - 1))
1774 x -= singleWidth(&*rit->par(), body_pos - 1);
1777 if (hfillExpansion(*this, rit, pos)) {
1778 x += singleWidth(&*rit->par(), pos);
1779 if (pos >= body_pos)
1782 x += fill_label_hfill;
1783 } else if (rit->par()->isSeparator(pos)) {
1784 x += singleWidth(&*rit->par(), pos);
1785 if (pos >= body_pos)
1786 x += fill_separator;
1788 x += singleWidth(&*rit->par(), pos);
1794 void LyXText::setCursorIntern(ParagraphList::iterator pit,
1795 pos_type pos, bool setfont, bool boundary)
1797 InsetText * it = static_cast<InsetText *>(pit->inInset());
1799 if (it != inset_owner) {
1800 lyxerr[Debug::INSETS] << "InsetText is " << it
1802 << "inset_owner is "
1803 << inset_owner << endl;
1804 #ifdef WITH_WARNINGS
1805 #warning I believe this code is wrong. (Lgb)
1806 #warning Jürgen, have a look at this. (Lgb)
1807 #warning Hmmm, I guess you are right but we
1808 #warning should verify when this is needed
1810 // Jürgen, would you like to have a look?
1811 // I guess we need to move the outer cursor
1812 // and open and lock the inset (bla bla bla)
1813 // stuff I don't know... so can you have a look?
1815 // I moved the lyxerr stuff in here so we can see if
1816 // this is actually really needed and where!
1818 // it->getLyXText(bv())->setCursorIntern(bv(), par, pos, setfont, boundary);
1823 setCursor(cursor, pit, pos, boundary);
1829 void LyXText::setCurrentFont()
1831 pos_type pos = cursor.pos();
1832 if (cursor.boundary() && pos > 0)
1836 if (pos == cursor.par()->size())
1838 else // potentional bug... BUG (Lgb)
1839 if (cursor.par()->isSeparator(pos)) {
1840 if (pos > cursor.row()->pos() &&
1841 bidi_level(pos) % 2 ==
1842 bidi_level(pos - 1) % 2)
1844 else if (pos + 1 < cursor.par()->size())
1850 cursor.par()->getFontSettings(bv()->buffer()->params, pos);
1851 real_current_font = getFont(bv()->buffer(), &*cursor.par(), pos);
1853 if (cursor.pos() == cursor.par()->size() &&
1854 isBoundary(bv()->buffer(), &*cursor.par(), cursor.pos()) &&
1855 !cursor.boundary()) {
1856 Language const * lang =
1857 cursor.par()->getParLanguage(bv()->buffer()->params);
1858 current_font.setLanguage(lang);
1859 current_font.setNumber(LyXFont::OFF);
1860 real_current_font.setLanguage(lang);
1861 real_current_font.setNumber(LyXFont::OFF);
1866 // returns the column near the specified x-coordinate of the row
1867 // x is set to the real beginning of this column
1869 LyXText::getColumnNearX(RowList::iterator rit, int & x, bool & boundary) const
1872 float fill_separator;
1874 float fill_label_hfill;
1876 prepareToPrint(rit, tmpx, fill_separator,
1877 fill_hfill, fill_label_hfill);
1879 pos_type vc = rit->pos();
1880 pos_type last = lastPrintablePos(*this, rit);
1883 LyXLayout_ptr const & layout = rit->par()->layout();
1885 bool left_side = false;
1887 pos_type body_pos = rit->par()->beginningOfBody();
1888 float last_tmpx = tmpx;
1891 (body_pos - 1 > last ||
1892 !rit->par()->isLineSeparator(body_pos - 1)))
1895 // check for empty row
1896 if (!rit->par()->size()) {
1901 while (vc <= last && tmpx <= x) {
1904 if (body_pos > 0 && c == body_pos - 1) {
1905 tmpx += fill_label_hfill +
1906 font_metrics::width(layout->labelsep,
1907 getLabelFont(bv()->buffer(), &*rit->par()));
1908 if (rit->par()->isLineSeparator(body_pos - 1))
1909 tmpx -= singleWidth(&*rit->par(), body_pos - 1);
1912 if (hfillExpansion(*this, rit, c)) {
1913 tmpx += singleWidth(&*rit->par(), c);
1917 tmpx += fill_label_hfill;
1918 } else if (rit->par()->isSeparator(c)) {
1919 tmpx += singleWidth(&*rit->par(), c);
1921 tmpx+= fill_separator;
1923 tmpx += singleWidth(&*rit->par(), c);
1928 if ((tmpx + last_tmpx) / 2 > x) {
1933 if (vc > last + 1) // This shouldn't happen.
1937 // This (rtl_support test) is not needed, but gives
1938 // some speedup if rtl_support=false
1939 bool const lastrow = lyxrc.rtl_support &&
1940 (boost::next(rit) == rowlist_.end() ||
1941 boost::next(rit)->par() != rit->par());
1942 // If lastrow is false, we don't need to compute
1943 // the value of rtl.
1944 bool const rtl = (lastrow)
1945 ? rit->par()->isRightToLeftPar(bv()->buffer()->params)
1948 ((rtl && left_side && vc == rit->pos() && x < tmpx - 5) ||
1949 (!rtl && !left_side && vc == last + 1 && x > tmpx + 5)))
1951 else if (vc == rit->pos()) {
1953 if (bidi_level(c) % 2 == 1)
1956 c = vis2log(vc - 1);
1957 bool const rtl = (bidi_level(c) % 2 == 1);
1958 if (left_side == rtl) {
1960 boundary = isBoundary(bv()->buffer(), &*rit->par(), c);
1964 if (rit->pos() <= last && c > last
1965 && rit->par()->isNewline(last)) {
1966 if (bidi_level(last) % 2 == 0)
1967 tmpx -= singleWidth(&*rit->par(), last);
1969 tmpx += singleWidth(&*rit->par(), last);
1979 void LyXText::setCursorFromCoordinates(int x, int y)
1981 LyXCursor old_cursor = cursor;
1983 setCursorFromCoordinates(cursor, x, y);
1985 deleteEmptyParagraphMechanism(old_cursor);
1992 * return true if the cursor given is at the end of a row,
1993 * and the next row is filled by an inset that spans an entire
1996 bool beforeFullRowInset(LyXText & lt, RowList::iterator row,
1998 if (boost::next(row) == lt.rows().end())
2000 Row const & next = *boost::next(row);
2002 if (next.pos() != cur.pos() || next.par() != cur.par())
2004 if (!cur.par()->isInset(cur.pos()))
2006 Inset const * inset = cur.par()->getInset(cur.pos());
2007 if (inset->needFullRow() || inset->display())
2014 void LyXText::setCursorFromCoordinates(LyXCursor & cur, int x, int y)
2016 // Get the row first.
2018 RowList::iterator row = getRowNearY(y);
2020 pos_type const column = getColumnNearX(row, x, bound);
2021 cur.par(&*row->par());
2022 cur.pos(row->pos() + column);
2024 cur.y(y + row->baseline());
2027 if (beforeFullRowInset(*this, row, cur)) {
2028 pos_type last = lastPrintablePos(*this, row);
2029 float x = getCursorX(boost::next(row), cur.pos(), last, bound);
2031 cur.iy(y + row->height() + boost::next(row)->baseline());
2032 cur.irow(boost::next(row));
2038 cur.boundary(bound);
2042 void LyXText::cursorLeft(bool internal)
2044 if (cursor.pos() > 0) {
2045 bool boundary = cursor.boundary();
2046 setCursor(cursor.par(), cursor.pos() - 1, true, false);
2047 if (!internal && !boundary &&
2048 isBoundary(bv()->buffer(), &*cursor.par(), cursor.pos() + 1))
2049 setCursor(cursor.par(), cursor.pos() + 1, true, true);
2050 } else if (cursor.par()->previous()) { // steps into the above paragraph.
2051 Paragraph * par = cursor.par()->previous();
2052 setCursor(par, par->size());
2057 void LyXText::cursorRight(bool internal)
2059 if (!internal && cursor.boundary() &&
2060 !cursor.par()->isNewline(cursor.pos()))
2061 setCursor(cursor.par(), cursor.pos(), true, false);
2062 else if (cursor.pos() < cursor.par()->size()) {
2063 setCursor(cursor.par(), cursor.pos() + 1, true, false);
2065 isBoundary(bv()->buffer(), &*cursor.par(), cursor.pos()))
2066 setCursor(cursor.par(), cursor.pos(), true, true);
2067 } else if (cursor.par()->next())
2068 setCursor(cursor.par()->next(), 0);
2072 void LyXText::cursorUp(bool selecting)
2075 int x = cursor.x_fix();
2076 int y = cursor.y() - cursor.row()->baseline() - 1;
2077 setCursorFromCoordinates(x, y);
2080 int y1 = cursor.iy() - topy;
2083 Inset * inset_hit = checkInsetHit(x, y1);
2084 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2085 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2089 setCursorFromCoordinates(bv(), cursor.x_fix(),
2090 cursor.y() - cursor.row()->baseline() - 1);
2095 void LyXText::cursorDown(bool selecting)
2098 int x = cursor.x_fix();
2099 int y = cursor.y() - cursor.row()->baseline() +
2100 cursor.row()->height() + 1;
2101 setCursorFromCoordinates(x, y);
2102 if (!selecting && cursor.row() == cursor.irow()) {
2104 int y1 = cursor.iy() - topy;
2107 Inset * inset_hit = checkInsetHit(x, y1);
2108 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2109 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2113 setCursorFromCoordinates(bv(), cursor.x_fix(),
2114 cursor.y() - cursor.row()->baseline()
2115 + cursor.row()->height() + 1);
2120 void LyXText::cursorUpParagraph()
2122 if (cursor.pos() > 0) {
2123 setCursor(cursor.par(), 0);
2125 else if (cursor.par()->previous()) {
2126 setCursor(cursor.par()->previous(), 0);
2131 void LyXText::cursorDownParagraph()
2133 if (cursor.par()->next()) {
2134 setCursor(cursor.par()->next(), 0);
2136 setCursor(cursor.par(), cursor.par()->size());
2140 // fix the cursor `cur' after a characters has been deleted at `where'
2141 // position. Called by deleteEmptyParagraphMechanism
2142 void LyXText::fixCursorAfterDelete(LyXCursor & cur,
2143 LyXCursor const & where)
2145 // if cursor is not in the paragraph where the delete occured,
2147 if (cur.par() != where.par())
2150 // if cursor position is after the place where the delete occured,
2152 if (cur.pos() > where.pos())
2153 cur.pos(cur.pos()-1);
2155 // check also if we don't want to set the cursor on a spot behind the
2156 // pagragraph because we erased the last character.
2157 if (cur.pos() > cur.par()->size())
2158 cur.pos(cur.par()->size());
2160 // recompute row et al. for this cursor
2161 setCursor(cur, cur.par(), cur.pos(), cur.boundary());
2165 bool LyXText::deleteEmptyParagraphMechanism(LyXCursor const & old_cursor)
2167 // Would be wrong to delete anything if we have a selection.
2168 if (selection.set())
2171 // We allow all kinds of "mumbo-jumbo" when freespacing.
2172 if (old_cursor.par()->layout()->free_spacing
2173 || old_cursor.par()->isFreeSpacing()) {
2177 /* Ok I'll put some comments here about what is missing.
2178 I have fixed BackSpace (and thus Delete) to not delete
2179 double-spaces automagically. I have also changed Cut,
2180 Copy and Paste to hopefully do some sensible things.
2181 There are still some small problems that can lead to
2182 double spaces stored in the document file or space at
2183 the beginning of paragraphs. This happens if you have
2184 the cursor betwenn to spaces and then save. Or if you
2185 cut and paste and the selection have a space at the
2186 beginning and then save right after the paste. I am
2187 sure none of these are very hard to fix, but I will
2188 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
2189 that I can get some feedback. (Lgb)
2192 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
2193 // delete the LineSeparator.
2196 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
2197 // delete the LineSeparator.
2200 // If the pos around the old_cursor were spaces, delete one of them.
2201 if (old_cursor.par() != cursor.par()
2202 || old_cursor.pos() != cursor.pos()) {
2203 // Only if the cursor has really moved
2205 if (old_cursor.pos() > 0
2206 && old_cursor.pos() < old_cursor.par()->size()
2207 && old_cursor.par()->isLineSeparator(old_cursor.pos())
2208 && old_cursor.par()->isLineSeparator(old_cursor.pos() - 1)) {
2209 old_cursor.par()->erase(old_cursor.pos() - 1);
2210 redoParagraphs(old_cursor, old_cursor.par()->next());
2212 #ifdef WITH_WARNINGS
2213 #warning This will not work anymore when we have multiple views of the same buffer
2214 // In this case, we will have to correct also the cursors held by
2215 // other bufferviews. It will probably be easier to do that in a more
2216 // automated way in LyXCursor code. (JMarc 26/09/2001)
2218 // correct all cursors held by the LyXText
2219 fixCursorAfterDelete(cursor, old_cursor);
2220 fixCursorAfterDelete(selection.cursor,
2222 fixCursorAfterDelete(selection.start,
2224 fixCursorAfterDelete(selection.end, old_cursor);
2225 fixCursorAfterDelete(last_sel_cursor,
2227 fixCursorAfterDelete(toggle_cursor, old_cursor);
2228 fixCursorAfterDelete(toggle_end_cursor,
2234 // don't delete anything if this is the ONLY paragraph!
2235 if (!old_cursor.par()->next() && !old_cursor.par()->previous())
2238 // Do not delete empty paragraphs with keepempty set.
2239 if (old_cursor.par()->layout()->keepempty)
2242 // only do our magic if we changed paragraph
2243 if (old_cursor.par() == cursor.par())
2246 // record if we have deleted a paragraph
2247 // we can't possibly have deleted a paragraph before this point
2248 bool deleted = false;
2250 if ((old_cursor.par()->empty()
2251 || (old_cursor.par()->size() == 1
2252 && old_cursor.par()->isLineSeparator(0)))) {
2253 // ok, we will delete anything
2254 LyXCursor tmpcursor;
2258 if (old_cursor.row() != rows().begin()) {
2260 prevrow = boost::prior(old_cursor.row());
2261 const_cast<LyXText *>(this)->postPaint(old_cursor.y() - old_cursor.row()->baseline() - prevrow->height());
2263 cursor = old_cursor; // that undo can restore the right cursor position
2264 Paragraph * endpar = old_cursor.par()->next();
2265 if (endpar && endpar->getDepth()) {
2266 while (endpar && endpar->getDepth()) {
2267 endpar = endpar->next();
2270 setUndo(bv(), Undo::DELETE, &*old_cursor.par(), endpar);
2274 removeRow(old_cursor.row());
2275 if (ownerParagraphs().begin() == old_cursor.par()) {
2276 ownerParagraph(&*boost::next(ownerParagraphs().begin()));
2278 #warning FIXME Do the proper ParagraphList operation here (Lgb)
2280 delete &*old_cursor.par();
2282 /* Breakagain the next par. Needed because of
2283 * the parindent that can occur or dissappear.
2284 * The next row can change its height, if
2285 * there is another layout before */
2286 if (boost::next(prevrow) != rows().end()) {
2287 breakAgain(boost::next(prevrow));
2290 setHeightOfRow(prevrow);
2292 RowList::iterator nextrow = boost::next(old_cursor.row());
2293 const_cast<LyXText *>(this)->postPaint(
2294 old_cursor.y() - old_cursor.row()->baseline());
2297 cursor = old_cursor; // that undo can restore the right cursor position
2298 Paragraph * endpar = old_cursor.par()->next();
2299 if (endpar && endpar->getDepth()) {
2300 while (endpar && endpar->getDepth()) {
2301 endpar = endpar->next();
2304 setUndo(bv(), Undo::DELETE, &*old_cursor.par(), endpar);
2308 removeRow(old_cursor.row());
2310 if (ownerParagraphs().begin() == old_cursor.par()) {
2311 ownerParagraph(&*boost::next(ownerParagraphs().begin()));
2313 #warning FIXME Do the proper ParagraphList operations here. (Lgb)
2314 delete &*old_cursor.par();
2316 /* Breakagain the next par. Needed because of
2317 the parindent that can occur or dissappear.
2318 The next row can change its height, if
2319 there is another layout before */
2320 if (nextrow != rows().end()) {
2321 breakAgain(nextrow);
2327 setCursorIntern(cursor.par(), cursor.pos());
2329 if (selection.cursor.par() == old_cursor.par()
2330 && selection.cursor.pos() == old_cursor.pos()) {
2331 // correct selection
2332 selection.cursor = cursor;
2336 if (old_cursor.par()->stripLeadingSpaces()) {
2337 redoParagraphs(old_cursor,
2338 old_cursor.par()->next());
2340 setCursorIntern(cursor.par(), cursor.pos());
2341 selection.cursor = cursor;
2348 ParagraphList & LyXText::ownerParagraphs() const
2351 return inset_owner->paragraphs;
2353 return bv_owner->buffer()->paragraphs;
2357 void LyXText::ownerParagraph(Paragraph * p) const
2360 inset_owner->paragraph(p);
2362 bv_owner->buffer()->paragraphs.set(p);
2367 void LyXText::ownerParagraph(int id, Paragraph * p) const
2369 Paragraph * op = bv_owner->buffer()->getParFromID(id);
2370 if (op && op->inInset()) {
2371 static_cast<InsetText *>(op->inInset())->paragraph(p);
2378 LyXText::refresh_status LyXText::refreshStatus() const
2380 return refresh_status_;
2384 void LyXText::clearPaint()
2386 refresh_status_ = REFRESH_NONE;
2387 refresh_row = rows().end();
2392 void LyXText::postPaint(int start_y)
2394 refresh_status old = refresh_status_;
2396 refresh_status_ = REFRESH_AREA;
2397 refresh_row = rows().end();
2399 if (old != REFRESH_NONE && refresh_y < start_y)
2402 refresh_y = start_y;
2407 // We are an inset's lyxtext. Tell the top-level lyxtext
2408 // it needs to update the row we're in.
2409 LyXText * t = bv()->text;
2410 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2414 // FIXME: we should probably remove this y parameter,
2415 // make refresh_y be 0, and use row->y etc.
2416 void LyXText::postRowPaint(RowList::iterator rit, int start_y)
2418 if (refresh_status_ != REFRESH_NONE && refresh_y < start_y) {
2419 refresh_status_ = REFRESH_AREA;
2422 refresh_y = start_y;
2425 if (refresh_status_ == REFRESH_AREA)
2428 refresh_status_ = REFRESH_ROW;
2434 // We are an inset's lyxtext. Tell the top-level lyxtext
2435 // it needs to update the row we're in.
2436 LyXText * t = bv()->text;
2437 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2441 bool LyXText::isInInset() const
2443 // Sub-level has non-null bv owner and
2444 // non-null inset owner.
2445 return inset_owner != 0 && bv_owner != 0;
2449 int defaultRowHeight()
2451 LyXFont const font(LyXFont::ALL_SANE);
2452 return int(font_metrics::maxAscent(font)
2453 + font_metrics::maxDescent(font) * 1.5);