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 * ====================================================== */
16 #include "paragraph.h"
17 #include "funcrequest.h"
18 #include "frontends/LyXView.h"
19 #include "undo_funcs.h"
21 #include "buffer_funcs.h"
22 #include "bufferparams.h"
23 #include "errorlist.h"
25 #include "BufferView.h"
26 #include "CutAndPaste.h"
27 #include "frontends/Painter.h"
28 #include "frontends/font_metrics.h"
32 #include "FloatList.h"
34 #include "ParagraphParameters.h"
36 #include "lyxrow_funcs.h"
37 #include "metricsinfo.h"
38 #include "paragraph_funcs.h"
40 #include "insets/insetbibitem.h"
41 #include "insets/insetenv.h"
42 #include "insets/insetfloat.h"
43 #include "insets/insetwrap.h"
45 #include "support/LAssert.h"
46 #include "support/textutils.h"
47 #include "support/lstrings.h"
49 #include <boost/tuple/tuple.hpp>
53 using namespace lyx::support;
63 LyXText::LyXText(BufferView * bv)
64 : height(0), width(0), anchor_row_offset_(0),
65 inset_owner(0), the_locking_inset(0), bv_owner(bv)
67 anchor_row_ = rows().end();
72 LyXText::LyXText(BufferView * bv, InsetText * inset)
73 : height(0), width(0), anchor_row_offset_(0),
74 inset_owner(inset), the_locking_inset(0), bv_owner(bv)
76 anchor_row_ = rows().end();
81 void LyXText::init(BufferView * bview)
89 anchor_row_ = rows().end();
90 anchor_row_offset_ = 0;
92 ParagraphList::iterator pit = ownerParagraphs().begin();
93 ParagraphList::iterator end = ownerParagraphs().end();
95 current_font = getFont(pit, 0);
97 for (; pit != end; ++pit)
98 insertParagraph(pit, rowlist_.end());
100 setCursorIntern(rowlist_.begin()->par(), 0);
101 selection.cursor = cursor;
107 // Gets the fully instantiated font at a given position in a paragraph
108 // Basically the same routine as Paragraph::getFont() in paragraph.C.
109 // The difference is that this one is used for displaying, and thus we
110 // are allowed to make cosmetic improvements. For instance make footnotes
112 // If position is -1, we get the layout font of the paragraph.
113 // If position is -2, we get the font of the manual label of the paragraph.
114 LyXFont LyXText::getFont(ParagraphList::iterator pit, pos_type pos) const
118 LyXLayout_ptr const & layout = pit->layout();
120 BufferParams const & params = bv()->buffer()->params;
122 // We specialize the 95% common case:
123 if (!pit->getDepth()) {
124 if (layout->labeltype == LABEL_MANUAL
125 && pos < pit->beginningOfBody()) {
127 LyXFont f = pit->getFontSettings(params, pos);
129 pit->inInset()->getDrawFont(f);
130 return f.realize(layout->reslabelfont);
132 LyXFont f = pit->getFontSettings(params, pos);
134 pit->inInset()->getDrawFont(f);
135 return f.realize(layout->resfont);
139 // The uncommon case need not be optimized as much
143 if (pos < pit->beginningOfBody()) {
145 layoutfont = layout->labelfont;
148 layoutfont = layout->font;
151 LyXFont tmpfont = pit->getFontSettings(params, pos);
152 tmpfont.realize(layoutfont);
155 pit->inInset()->getDrawFont(tmpfont);
157 // Realize with the fonts of lesser depth.
158 tmpfont.realize(outerFont(pit, ownerParagraphs()));
159 tmpfont.realize(defaultfont_);
165 LyXFont LyXText::getLayoutFont(ParagraphList::iterator pit) const
167 LyXLayout_ptr const & layout = pit->layout();
169 if (!pit->getDepth()) {
170 return layout->resfont;
173 LyXFont font = layout->font;
174 // Realize with the fonts of lesser depth.
175 font.realize(outerFont(pit, ownerParagraphs()));
176 font.realize(defaultfont_);
182 LyXFont LyXText::getLabelFont(ParagraphList::iterator pit) const
184 LyXLayout_ptr const & layout = pit->layout();
186 if (!pit->getDepth()) {
187 return layout->reslabelfont;
190 LyXFont font = layout->labelfont;
191 // Realize with the fonts of lesser depth.
192 font.realize(outerFont(pit, ownerParagraphs()));
193 font.realize(defaultfont_);
199 void LyXText::setCharFont(ParagraphList::iterator pit,
200 pos_type pos, LyXFont const & fnt,
203 BufferParams const & params = bv()->buffer()->params;
204 LyXFont font = getFont(pit, pos);
205 font.update(fnt, params.language, toggleall);
206 // Let the insets convert their font
207 if (pit->isInset(pos)) {
208 InsetOld * inset = pit->getInset(pos);
209 if (isEditableInset(inset)) {
210 static_cast<UpdatableInset *>(inset)
211 ->setFont(bv(), fnt, toggleall, true);
215 // Plug through to version below:
216 setCharFont(pit, pos, font);
220 void LyXText::setCharFont(
221 ParagraphList::iterator pit, pos_type pos, LyXFont const & fnt)
224 LyXLayout_ptr const & layout = pit->layout();
226 // Get concrete layout font to reduce against
229 if (pos < pit->beginningOfBody())
230 layoutfont = layout->labelfont;
232 layoutfont = layout->font;
234 // Realize against environment font information
235 if (pit->getDepth()) {
236 ParagraphList::iterator tp = pit;
237 while (!layoutfont.resolved() &&
238 tp != ownerParagraphs().end() &&
240 tp = outerHook(tp, ownerParagraphs());
241 if (tp != ownerParagraphs().end())
242 layoutfont.realize(tp->layout()->font);
246 layoutfont.realize(defaultfont_);
248 // Now, reduce font against full layout font
249 font.reduce(layoutfont);
251 pit->setFont(pos, font);
255 // removes the row and reset the touched counters
256 void LyXText::removeRow(RowList::iterator rit)
258 if (anchor_row_ == rit) {
259 if (rit != rows().begin()) {
260 anchor_row_ = boost::prior(rit);
261 anchor_row_offset_ += anchor_row_->height();
263 anchor_row_ = boost::next(rit);
264 anchor_row_offset_ -= rit->height();
268 // the text becomes smaller
269 height -= rit->height();
275 // remove all following rows of the paragraph of the specified row.
276 void LyXText::removeParagraph(RowList::iterator rit)
278 ParagraphList::iterator tmppit = rit->par();
281 while (rit != rows().end() && rit->par() == tmppit) {
282 RowList::iterator tmprit = boost::next(rit);
289 void LyXText::insertParagraph(ParagraphList::iterator pit,
290 RowList::iterator rowit)
292 // insert a new row, starting at position 0
294 RowList::iterator rit = rowlist_.insert(rowit, newrow);
296 // and now append the whole paragraph before the new row
297 appendParagraph(rit);
301 InsetOld * LyXText::getInset() const
303 ParagraphList::iterator pit = cursor.par();
304 pos_type const pos = cursor.pos();
306 if (pos < pit->size() && pit->isInset(pos)) {
307 return pit->getInset(pos);
313 void LyXText::toggleInset()
315 InsetOld * inset = getInset();
316 // is there an editable inset at cursor position?
317 if (!isEditableInset(inset)) {
318 // No, try to see if we are inside a collapsable inset
319 if (inset_owner && inset_owner->owner()
320 && inset_owner->owner()->isOpen()) {
321 bv()->unlockInset(inset_owner->owner());
322 inset_owner->owner()->close(bv());
323 bv()->getLyXText()->cursorRight(bv());
327 //bv()->owner()->message(inset->editMessage());
329 // do we want to keep this?? (JMarc)
330 if (!isHighlyEditableInset(inset))
331 recordUndo(bv(), Undo::ATOMIC);
333 if (inset->isOpen()) {
339 bv()->updateInset(inset);
343 /* used in setlayout */
344 // Asger is not sure we want to do this...
345 void LyXText::makeFontEntriesLayoutSpecific(BufferParams const & params,
348 LyXLayout_ptr const & layout = par.layout();
349 pos_type const psize = par.size();
352 for (pos_type pos = 0; pos < psize; ++pos) {
353 if (pos < par.beginningOfBody())
354 layoutfont = layout->labelfont;
356 layoutfont = layout->font;
358 LyXFont tmpfont = par.getFontSettings(params, pos);
359 tmpfont.reduce(layoutfont);
360 par.setFont(pos, tmpfont);
365 ParagraphList::iterator
366 LyXText::setLayout(LyXCursor & cur, LyXCursor & sstart_cur,
367 LyXCursor & send_cur,
368 string const & layout)
370 ParagraphList::iterator endpit = boost::next(send_cur.par());
371 ParagraphList::iterator undoendpit = endpit;
372 ParagraphList::iterator pars_end = ownerParagraphs().end();
374 if (endpit != pars_end && endpit->getDepth()) {
375 while (endpit != pars_end && endpit->getDepth()) {
379 } else if (endpit != pars_end) {
380 // because of parindents etc.
384 recordUndo(bv(), Undo::ATOMIC, sstart_cur.par(), boost::prior(undoendpit));
386 // ok we have a selection. This is always between sstart_cur
387 // and sel_end cursor
389 ParagraphList::iterator pit = sstart_cur.par();
390 ParagraphList::iterator epit = boost::next(send_cur.par());
392 LyXLayout_ptr const & lyxlayout =
393 bv()->buffer()->params.getLyXTextClass()[layout];
396 pit->applyLayout(lyxlayout);
397 makeFontEntriesLayoutSpecific(bv()->buffer()->params, *pit);
398 ParagraphList::iterator fppit = pit;
399 fppit->params().spaceTop(lyxlayout->fill_top ?
400 VSpace(VSpace::VFILL)
401 : VSpace(VSpace::NONE));
402 fppit->params().spaceBottom(lyxlayout->fill_bottom ?
403 VSpace(VSpace::VFILL)
404 : VSpace(VSpace::NONE));
405 if (lyxlayout->margintype == MARGIN_MANUAL)
406 pit->setLabelWidthString(lyxlayout->labelstring());
409 } while (pit != epit);
415 // set layout over selection and make a total rebreak of those paragraphs
416 void LyXText::setLayout(string const & layout)
418 LyXCursor tmpcursor = cursor; // store the current cursor
420 // if there is no selection just set the layout
421 // of the current paragraph
422 if (!selection.set()) {
423 selection.start = cursor; // dummy selection
424 selection.end = cursor;
427 // special handling of new environment insets
428 BufferParams const & params = bv()->buffer()->params;
429 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
430 if (lyxlayout->is_environment) {
431 // move everything in a new environment inset
432 lyxerr << "setting layout " << layout << endl;
433 bv()->owner()->dispatch(FuncRequest(LFUN_HOME));
434 bv()->owner()->dispatch(FuncRequest(LFUN_ENDSEL));
435 bv()->owner()->dispatch(FuncRequest(LFUN_CUT));
436 InsetOld * inset = new InsetEnvironment(params, layout);
437 if (bv()->insertInset(inset)) {
439 //bv()->owner()->dispatch(FuncRequest(LFUN_PASTE));
446 ParagraphList::iterator endpit = setLayout(cursor, selection.start,
447 selection.end, layout);
448 redoParagraphs(selection.start, endpit);
450 // we have to reset the selection, because the
451 // geometry could have changed
452 setCursor(selection.start.par(), selection.start.pos(), false);
453 selection.cursor = cursor;
454 setCursor(selection.end.par(), selection.end.pos(), false);
458 setCursor(tmpcursor.par(), tmpcursor.pos(), true);
462 bool LyXText::changeDepth(bv_funcs::DEPTH_CHANGE type, bool test_only)
464 ParagraphList::iterator pit(cursor.par());
465 ParagraphList::iterator end(cursor.par());
466 ParagraphList::iterator start = pit;
468 if (selection.set()) {
469 pit = selection.start.par();
470 end = selection.end.par();
474 ParagraphList::iterator pastend = boost::next(end);
477 recordUndo(bv(), Undo::ATOMIC, start, end);
479 bool changed = false;
481 int prev_after_depth = 0;
482 #warning parlist ... could be nicer ?
483 if (start != ownerParagraphs().begin()) {
484 prev_after_depth = boost::prior(start)->getMaxDepthAfter();
488 int const depth = pit->params().depth();
489 if (type == bv_funcs::INC_DEPTH) {
490 if (depth < prev_after_depth
491 && pit->layout()->labeltype != LABEL_BIBLIO) {
494 pit->params().depth(depth + 1);
501 pit->params().depth(depth - 1);
504 prev_after_depth = pit->getMaxDepthAfter();
516 // Wow, redoParagraphs is stupid.
518 setCursor(tmpcursor, start, 0);
520 redoParagraphs(tmpcursor, pastend);
522 // We need to actually move the text->cursor. I don't
523 // understand why ...
526 // we have to reset the visual selection because the
527 // geometry could have changed
528 if (selection.set()) {
529 setCursor(selection.start.par(), selection.start.pos());
530 selection.cursor = cursor;
531 setCursor(selection.end.par(), selection.end.pos());
534 // this handles the counter labels, and also fixes up
535 // depth values for follow-on (child) paragraphs
539 setCursor(tmpcursor.par(), tmpcursor.pos());
545 // set font over selection and make a total rebreak of those paragraphs
546 void LyXText::setFont(LyXFont const & font, bool toggleall)
548 // if there is no selection just set the current_font
549 if (!selection.set()) {
550 // Determine basis font
552 if (cursor.pos() < cursor.par()->beginningOfBody()) {
553 layoutfont = getLabelFont(cursor.par());
555 layoutfont = getLayoutFont(cursor.par());
557 // Update current font
558 real_current_font.update(font,
559 bv()->buffer()->params.language,
562 // Reduce to implicit settings
563 current_font = real_current_font;
564 current_font.reduce(layoutfont);
565 // And resolve it completely
566 real_current_font.realize(layoutfont);
571 LyXCursor tmpcursor = cursor; // store the current cursor
573 // ok we have a selection. This is always between sel_start_cursor
574 // and sel_end cursor
576 recordUndo(bv(), Undo::ATOMIC, selection.start.par(), selection.end.par());
578 cursor = selection.start;
579 while (cursor.par() != selection.end.par() ||
580 cursor.pos() < selection.end.pos())
582 if (cursor.pos() < cursor.par()->size()) {
583 // an open footnote should behave like a closed one
584 setCharFont(cursor.par(), cursor.pos(),
586 cursor.pos(cursor.pos() + 1);
589 cursor.par(boost::next(cursor.par()));
594 redoParagraphs(selection.start, boost::next(selection.end.par()));
596 // we have to reset the selection, because the
597 // geometry could have changed, but we keep
598 // it for user convenience
599 setCursor(selection.start.par(), selection.start.pos());
600 selection.cursor = cursor;
601 setCursor(selection.end.par(), selection.end.pos());
603 setCursor(tmpcursor.par(), tmpcursor.pos(), true,
604 tmpcursor.boundary());
608 void LyXText::redoHeightOfParagraph()
610 RowList::iterator tmprow = cursorRow();
612 setHeightOfRow(tmprow);
614 while (tmprow != rows().begin()
615 && boost::prior(tmprow)->par() == tmprow->par()) {
617 setHeightOfRow(tmprow);
622 setCursor(cursor.par(), cursor.pos(), false, cursor.boundary());
626 // deletes and inserts again all paragraphs between the cursor
627 // and the specified par
628 // This function is needed after SetLayout and SetFont etc.
629 void LyXText::redoParagraphs(LyXCursor const & cur,
630 ParagraphList::iterator endpit)
632 RowList::iterator tmprit = getRow(cur);
634 ParagraphList::iterator first_phys_pit;
635 RowList::iterator prevrit;
636 if (tmprit == rows().begin()) {
637 // A trick/hack for UNDO.
638 // This is needed because in an UNDO/REDO we could have
639 // changed the ownerParagraph() so the paragraph inside
640 // the row is NOT my really first par anymore.
641 // Got it Lars ;) (Jug 20011206)
642 first_phys_pit = ownerParagraphs().begin();
643 prevrit = rows().end();
645 first_phys_pit = tmprit->par();
646 while (tmprit != rows().begin()
647 && boost::prior(tmprit)->par() == first_phys_pit)
651 prevrit = boost::prior(tmprit);
655 while (tmprit != rows().end() && tmprit->par() != endpit) {
656 RowList::iterator tmprit2 = tmprit++;
660 // Reinsert the paragraphs.
661 ParagraphList::iterator tmppit = first_phys_pit;
663 while (tmppit != ownerParagraphs().end()) {
664 insertParagraph(tmppit, tmprit);
665 while (tmprit != rows().end()
666 && tmprit->par() == tmppit) {
670 if (tmppit == endpit)
673 if (prevrit != rows().end())
674 setHeightOfRow(prevrit);
676 setHeightOfRow(rows().begin());
678 if (tmprit != rows().end())
679 setHeightOfRow(tmprit);
685 void LyXText::fullRebreak()
688 setCursorIntern(cursor.par(), cursor.pos());
692 void LyXText::metrics(MetricsInfo & mi, Dimension & dim)
694 //lyxerr << "LyXText::metrics: width: " << mi.base.textwidth << "\n";
700 anchor_row_ = rows().end();
701 anchor_row_offset_ = 0;
703 ParagraphList::iterator pit = ownerParagraphs().begin();
704 ParagraphList::iterator end = ownerParagraphs().end();
706 for (; pit != end; ++pit) {
707 InsetList::iterator ii = pit->insetlist.begin();
708 InsetList::iterator iend = pit->insetlist.end();
709 for (; ii != iend; ++ii) {
712 ii->inset->metrics(m, dim);
715 // insert a new row, starting at position 0
717 RowList::iterator rit = rowlist_.insert(rowlist_.end(), newrow);
719 // and now append the whole paragraph before the new row
720 appendParagraph(rit);
724 //lyxerr << "height 0: " << height << "\n";
725 //for (RowList::iterator rit = rows().begin(); rit != rows().end(); ++rit) {
726 // height += rit->height();
728 //lyxerr << "height 1: " << height << "\n";
731 dim.asc = rows().begin()->ascent_of_text();
732 dim.des = height - dim.asc;
733 dim.wid = std::max(mi.base.textwidth, int(width));
737 void LyXText::partialRebreak()
739 if (rows().empty()) {
743 breakAgain(rows().begin());
747 // important for the screen
750 // the cursor set functions have a special mechanism. When they
751 // realize, that you left an empty paragraph, they will delete it.
752 // They also delete the corresponding row
754 // need the selection cursor:
755 void LyXText::setSelection()
757 bool const lsel = TextCursor::setSelection();
759 if (inset_owner && (selection.set() || lsel))
760 inset_owner->setUpdateStatus(InsetText::SELECTION);
765 void LyXText::clearSelection()
767 TextCursor::clearSelection();
769 // reset this in the bv_owner!
770 if (bv_owner && bv_owner->text)
771 bv_owner->text->xsel_cache.set(false);
775 void LyXText::cursorHome()
777 setCursor(cursor.par(), cursorRow()->pos());
781 void LyXText::cursorEnd()
783 if (cursor.par()->empty())
786 RowList::iterator rit = cursorRow();
787 RowList::iterator next_rit = boost::next(rit);
788 ParagraphList::iterator pit = rit->par();
789 pos_type last_pos = lastPos(*this, rit);
791 if (next_rit == rows().end() || next_rit->par() != pit) {
795 (pit->getChar(last_pos) != ' ' && !pit->isNewline(last_pos))) {
800 setCursor(pit, last_pos);
804 void LyXText::cursorTop()
806 setCursor(ownerParagraphs().begin(), 0);
810 void LyXText::cursorBottom()
812 ParagraphList::iterator lastpit =
813 boost::prior(ownerParagraphs().end());
814 setCursor(lastpit, lastpit->size());
818 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
820 // If the mask is completely neutral, tell user
821 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
822 // Could only happen with user style
823 bv()->owner()->message(_("No font change defined. Use Character under the Layout menu to define font change."));
827 // Try implicit word selection
828 // If there is a change in the language the implicit word selection
830 LyXCursor resetCursor = cursor;
831 bool implicitSelection = (font.language() == ignore_language
832 && font.number() == LyXFont::IGNORE)
833 ? selectWordWhenUnderCursor(lyx::WHOLE_WORD_STRICT) : false;
836 setFont(font, toggleall);
838 // Implicit selections are cleared afterwards
839 //and cursor is set to the original position.
840 if (implicitSelection) {
842 cursor = resetCursor;
843 setCursor(cursor.par(), cursor.pos());
844 selection.cursor = cursor;
847 inset_owner->setUpdateStatus(InsetText::CURSOR_PAR);
851 string LyXText::getStringToIndex()
853 // Try implicit word selection
854 // If there is a change in the language the implicit word selection
856 LyXCursor const reset_cursor = cursor;
857 bool const implicitSelection =
858 selectWordWhenUnderCursor(lyx::PREVIOUS_WORD);
861 if (!selection.set())
862 bv()->owner()->message(_("Nothing to index!"));
863 else if (selection.start.par() != selection.end.par())
864 bv()->owner()->message(_("Cannot index more than one paragraph!"));
866 idxstring = selectionAsString(bv()->buffer(), false);
868 // Reset cursors to their original position.
869 cursor = reset_cursor;
870 setCursor(cursor.par(), cursor.pos());
871 selection.cursor = cursor;
873 // Clear the implicit selection.
874 if (implicitSelection)
881 // the DTP switches for paragraphs. LyX will store them in the first
882 // physicla paragraph. When a paragraph is broken, the top settings rest,
883 // the bottom settings are given to the new one. So I can make shure,
884 // they do not duplicate themself and you cannnot make dirty things with
887 void LyXText::setParagraph(bool line_top, bool line_bottom,
888 bool pagebreak_top, bool pagebreak_bottom,
889 VSpace const & space_top,
890 VSpace const & space_bottom,
891 Spacing const & spacing,
893 string const & labelwidthstring,
896 LyXCursor tmpcursor = cursor;
897 if (!selection.set()) {
898 selection.start = cursor;
899 selection.end = cursor;
902 // make sure that the depth behind the selection are restored, too
903 ParagraphList::iterator endpit = boost::next(selection.end.par());
904 ParagraphList::iterator undoendpit = endpit;
905 ParagraphList::iterator pars_end = ownerParagraphs().end();
907 if (endpit != pars_end && endpit->getDepth()) {
908 while (endpit != pars_end && endpit->getDepth()) {
912 } else if (endpit != pars_end) {
913 // because of parindents etc.
917 recordUndo(bv(), Undo::ATOMIC, selection.start.par(),
918 boost::prior(undoendpit));
921 ParagraphList::iterator tmppit = selection.end.par();
923 while (tmppit != boost::prior(selection.start.par())) {
924 setCursor(tmppit, 0);
926 ParagraphList::iterator pit = cursor.par();
927 ParagraphParameters & params = pit->params();
929 params.lineTop(line_top);
930 params.lineBottom(line_bottom);
931 params.pagebreakTop(pagebreak_top);
932 params.pagebreakBottom(pagebreak_bottom);
933 params.spaceTop(space_top);
934 params.spaceBottom(space_bottom);
935 params.spacing(spacing);
936 // does the layout allow the new alignment?
937 LyXLayout_ptr const & layout = pit->layout();
939 if (align == LYX_ALIGN_LAYOUT)
940 align = layout->align;
941 if (align & layout->alignpossible) {
942 if (align == layout->align)
943 params.align(LYX_ALIGN_LAYOUT);
947 pit->setLabelWidthString(labelwidthstring);
948 params.noindent(noindent);
949 tmppit = boost::prior(pit);
953 redoParagraphs(selection.start, endpit);
956 setCursor(selection.start.par(), selection.start.pos());
957 selection.cursor = cursor;
958 setCursor(selection.end.par(), selection.end.pos());
960 setCursor(tmpcursor.par(), tmpcursor.pos());
962 bv()->updateInset(inset_owner);
966 // set the counter of a paragraph. This includes the labels
967 void LyXText::setCounter(Buffer const * buf, ParagraphList::iterator pit)
969 LyXTextClass const & textclass = buf->params.getLyXTextClass();
970 LyXLayout_ptr const & layout = pit->layout();
972 if (pit != ownerParagraphs().begin()) {
974 pit->params().appendix(boost::prior(pit)->params().appendix());
975 if (!pit->params().appendix() &&
976 pit->params().startOfAppendix()) {
977 pit->params().appendix(true);
978 textclass.counters().reset();
980 pit->enumdepth = boost::prior(pit)->enumdepth;
981 pit->itemdepth = boost::prior(pit)->itemdepth;
983 pit->params().appendix(pit->params().startOfAppendix());
988 /* Maybe we have to increment the enumeration depth.
989 * BUT, enumeration in a footnote is considered in isolation from its
990 * surrounding paragraph so don't increment if this is the
991 * first line of the footnote
992 * AND, bibliographies can't have their depth changed ie. they
993 * are always of depth 0
995 if (pit != ownerParagraphs().begin()
996 && boost::prior(pit)->getDepth() < pit->getDepth()
997 && boost::prior(pit)->layout()->labeltype == LABEL_COUNTER_ENUMI
998 && pit->enumdepth < 3
999 && layout->labeltype != LABEL_BIBLIO) {
1003 // Maybe we have to decrement the enumeration depth, see note above
1004 if (pit != ownerParagraphs().begin()
1005 && boost::prior(pit)->getDepth() > pit->getDepth()
1006 && layout->labeltype != LABEL_BIBLIO) {
1007 pit->enumdepth = depthHook(pit, ownerParagraphs(),
1008 pit->getDepth())->enumdepth;
1011 if (!pit->params().labelString().empty()) {
1012 pit->params().labelString(string());
1015 if (layout->margintype == MARGIN_MANUAL) {
1016 if (pit->params().labelWidthString().empty()) {
1017 pit->setLabelWidthString(layout->labelstring());
1020 pit->setLabelWidthString(string());
1023 // is it a layout that has an automatic label?
1024 if (layout->labeltype >= LABEL_COUNTER_CHAPTER) {
1025 int const i = layout->labeltype - LABEL_COUNTER_CHAPTER;
1029 if (i >= 0 && i <= buf->params.secnumdepth) {
1033 textclass.counters().step(layout->latexname());
1035 // Is there a label? Useful for Chapter layout
1036 if (!pit->params().appendix()) {
1037 s << buf->B_(layout->labelstring());
1039 s << buf->B_(layout->labelstring_appendix());
1042 // Use of an integer is here less than elegant. For now.
1043 int head = textclass.maxcounter() - LABEL_COUNTER_CHAPTER;
1044 if (!pit->params().appendix()) {
1045 numbertype = "sectioning";
1047 numbertype = "appendix";
1048 if (pit->isRightToLeftPar(buf->params))
1049 langtype = "hebrew";
1055 << textclass.counters()
1056 .numberLabel(layout->latexname(),
1057 numbertype, langtype, head);
1059 pit->params().labelString(STRCONV(s.str()));
1061 // reset enum counters
1062 textclass.counters().reset("enum");
1063 } else if (layout->labeltype < LABEL_COUNTER_ENUMI) {
1064 textclass.counters().reset("enum");
1065 } else if (layout->labeltype == LABEL_COUNTER_ENUMI) {
1067 // Yes I know this is a really, really! bad solution
1069 string enumcounter("enum");
1071 switch (pit->enumdepth) {
1080 enumcounter += "iv";
1083 // not a valid enumdepth...
1087 textclass.counters().step(enumcounter);
1089 s << textclass.counters()
1090 .numberLabel(enumcounter, "enumeration");
1091 pit->params().labelString(STRCONV(s.str()));
1093 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
1094 textclass.counters().step("bibitem");
1095 int number = textclass.counters().value("bibitem");
1096 if (pit->bibitem()) {
1097 pit->bibitem()->setCounter(number);
1098 pit->params().labelString(layout->labelstring());
1100 // In biblio should't be following counters but...
1102 string s = buf->B_(layout->labelstring());
1104 // the caption hack:
1105 if (layout->labeltype == LABEL_SENSITIVE) {
1106 ParagraphList::iterator tmppit = pit;
1109 while (tmppit != ownerParagraphs().end() &&
1111 // the single '=' is intended below
1112 && (in = tmppit->inInset()->owner())) {
1113 if (in->lyxCode() == InsetOld::FLOAT_CODE ||
1114 in->lyxCode() == InsetOld::WRAP_CODE) {
1118 tmppit = std::find(ownerParagraphs().begin(), ownerParagraphs().end(), *in->parOwner());
1125 if (in->lyxCode() == InsetOld::FLOAT_CODE)
1126 type = static_cast<InsetFloat*>(in)->params().type;
1127 else if (in->lyxCode() == InsetOld::WRAP_CODE)
1128 type = static_cast<InsetWrap*>(in)->params().type;
1132 Floating const & fl = textclass.floats().getType(type);
1134 textclass.counters().step(fl.type());
1136 // Doesn't work... yet.
1137 s = bformat(_("%1$s #:"), buf->B_(fl.name()));
1139 // par->SetLayout(0);
1140 // s = layout->labelstring;
1141 s = _("Senseless: ");
1144 pit->params().labelString(s);
1146 // reset the enumeration counter. They are always reset
1147 // when there is any other layout between
1148 // Just fall-through between the cases so that all
1149 // enum counters deeper than enumdepth is also reset.
1150 switch (pit->enumdepth) {
1152 textclass.counters().reset("enumi");
1154 textclass.counters().reset("enumii");
1156 textclass.counters().reset("enumiii");
1158 textclass.counters().reset("enumiv");
1164 // Updates all counters. Paragraphs with changed label string will be rebroken
1165 void LyXText::updateCounters()
1167 RowList::iterator rowit = rows().begin();
1168 ParagraphList::iterator pit = rowit->par();
1170 // CHECK if this is really needed. (Lgb)
1171 bv()->buffer()->params.getLyXTextClass().counters().reset();
1173 ParagraphList::iterator beg = ownerParagraphs().begin();
1174 ParagraphList::iterator end = ownerParagraphs().end();
1175 for (; pit != end; ++pit) {
1176 while (rowit->par() != pit)
1179 string const oldLabel = pit->params().labelString();
1181 size_t maxdepth = 0;
1183 maxdepth = boost::prior(pit)->getMaxDepthAfter();
1185 if (pit->params().depth() > maxdepth)
1186 pit->params().depth(maxdepth);
1188 // setCounter can potentially change the labelString.
1189 setCounter(bv()->buffer(), pit);
1191 string const & newLabel = pit->params().labelString();
1193 if (oldLabel.empty() && !newLabel.empty()) {
1194 removeParagraph(rowit);
1195 appendParagraph(rowit);
1201 void LyXText::insertInset(InsetOld * inset)
1203 if (!cursor.par()->insetAllowed(inset->lyxCode()))
1205 recordUndo(bv(), Undo::ATOMIC, cursor.par());
1207 cursor.par()->insertInset(cursor.pos(), inset);
1208 // Just to rebreak and refresh correctly.
1209 // The character will not be inserted a second time
1210 insertChar(Paragraph::META_INSET);
1211 // If we enter a highly editable inset the cursor should be to before
1212 // the inset. This couldn't happen before as Undo was not handled inside
1213 // inset now after the Undo LyX tries to call inset->Edit(...) again
1214 // and cannot do this as the cursor is behind the inset and GetInset
1215 // does not return the inset!
1216 if (isHighlyEditableInset(inset)) {
1223 void LyXText::cutSelection(bool doclear, bool realcut)
1225 // Stuff what we got on the clipboard. Even if there is no selection.
1227 // There is a problem with having the stuffing here in that the
1228 // larger the selection the slower LyX will get. This can be
1229 // solved by running the line below only when the selection has
1230 // finished. The solution used currently just works, to make it
1231 // faster we need to be more clever and probably also have more
1232 // calls to stuffClipboard. (Lgb)
1233 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1235 // This doesn't make sense, if there is no selection
1236 if (!selection.set())
1239 // OK, we have a selection. This is always between selection.start
1240 // and selection.end
1242 // make sure that the depth behind the selection are restored, too
1243 ParagraphList::iterator endpit = boost::next(selection.end.par());
1244 ParagraphList::iterator undoendpit = endpit;
1245 ParagraphList::iterator pars_end = ownerParagraphs().end();
1247 if (endpit != pars_end && endpit->getDepth()) {
1248 while (endpit != pars_end && endpit->getDepth()) {
1250 undoendpit = endpit;
1252 } else if (endpit != pars_end) {
1253 // because of parindents etc.
1257 recordUndo(bv(), Undo::DELETE, selection.start.par(),
1258 boost::prior(undoendpit));
1261 endpit = selection.end.par();
1262 int endpos = selection.end.pos();
1264 boost::tie(endpit, endpos) = realcut ?
1265 CutAndPaste::cutSelection(bv()->buffer()->params,
1267 selection.start.par(), endpit,
1268 selection.start.pos(), endpos,
1269 bv()->buffer()->params.textclass,
1271 : CutAndPaste::eraseSelection(bv()->buffer()->params,
1273 selection.start.par(), endpit,
1274 selection.start.pos(), endpos,
1276 // sometimes necessary
1278 selection.start.par()->stripLeadingSpaces();
1280 redoParagraphs(selection.start, boost::next(endpit));
1281 #warning FIXME latent bug
1282 // endpit will be invalidated on redoParagraphs once ParagraphList
1283 // becomes a std::list? There are maybe other places on which this
1284 // can happend? (Ab)
1285 // cutSelection can invalidate the cursor so we need to set
1287 // we prefer the end for when tracking changes
1291 // need a valid cursor. (Lgb)
1294 setCursor(cursor.par(), cursor.pos());
1295 selection.cursor = cursor;
1300 void LyXText::copySelection()
1302 // stuff the selection onto the X clipboard, from an explicit copy request
1303 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1305 // this doesnt make sense, if there is no selection
1306 if (!selection.set())
1309 // ok we have a selection. This is always between selection.start
1310 // and sel_end cursor
1312 // copy behind a space if there is one
1313 while (selection.start.par()->size() > selection.start.pos()
1314 && selection.start.par()->isLineSeparator(selection.start.pos())
1315 && (selection.start.par() != selection.end.par()
1316 || selection.start.pos() < selection.end.pos()))
1317 selection.start.pos(selection.start.pos() + 1);
1319 CutAndPaste::copySelection(selection.start.par(),
1320 selection.end.par(),
1321 selection.start.pos(), selection.end.pos(),
1322 bv()->buffer()->params.textclass);
1326 void LyXText::pasteSelection(size_t sel_index)
1328 // this does not make sense, if there is nothing to paste
1329 if (!CutAndPaste::checkPastePossible())
1332 recordUndo(bv(), Undo::INSERT, cursor.par());
1334 ParagraphList::iterator endpit;
1339 boost::tie(ppp, endpit) =
1340 CutAndPaste::pasteSelection(*bv()->buffer(),
1342 cursor.par(), cursor.pos(),
1343 bv()->buffer()->params.textclass,
1345 bufferErrors(*bv()->buffer(), el);
1346 bv()->showErrorList(_("Paste"));
1348 redoParagraphs(cursor, endpit);
1350 setCursor(cursor.par(), cursor.pos());
1353 selection.cursor = cursor;
1354 setCursor(ppp.first, ppp.second);
1360 void LyXText::setSelectionRange(lyx::pos_type length)
1365 selection.cursor = cursor;
1372 // simple replacing. The font of the first selected character is used
1373 void LyXText::replaceSelectionWithString(string const & str)
1375 recordUndo(bv(), Undo::ATOMIC);
1378 if (!selection.set()) { // create a dummy selection
1379 selection.end = cursor;
1380 selection.start = cursor;
1383 // Get font setting before we cut
1384 pos_type pos = selection.end.pos();
1385 LyXFont const font = selection.start.par()
1386 ->getFontSettings(bv()->buffer()->params,
1387 selection.start.pos());
1389 // Insert the new string
1390 string::const_iterator cit = str.begin();
1391 string::const_iterator end = str.end();
1392 for (; cit != end; ++cit) {
1393 selection.end.par()->insertChar(pos, (*cit), font);
1397 // Cut the selection
1398 cutSelection(true, false);
1404 // needed to insert the selection
1405 void LyXText::insertStringAsLines(string const & str)
1407 ParagraphList::iterator pit = cursor.par();
1408 pos_type pos = cursor.pos();
1409 ParagraphList::iterator endpit = boost::next(cursor.par());
1411 recordUndo(bv(), Undo::ATOMIC);
1413 // only to be sure, should not be neccessary
1416 bv()->buffer()->insertStringAsLines(pit, pos, current_font, str);
1418 redoParagraphs(cursor, endpit);
1419 setCursor(cursor.par(), cursor.pos());
1420 selection.cursor = cursor;
1421 setCursor(pit, pos);
1426 // turns double-CR to single CR, others where converted into one
1427 // blank. Then InsertStringAsLines is called
1428 void LyXText::insertStringAsParagraphs(string const & str)
1430 string linestr(str);
1431 bool newline_inserted = false;
1432 string::size_type const siz = linestr.length();
1434 for (string::size_type i = 0; i < siz; ++i) {
1435 if (linestr[i] == '\n') {
1436 if (newline_inserted) {
1437 // we know that \r will be ignored by
1438 // InsertStringA. Of course, it is a dirty
1439 // trick, but it works...
1440 linestr[i - 1] = '\r';
1444 newline_inserted = true;
1446 } else if (IsPrintable(linestr[i])) {
1447 newline_inserted = false;
1450 insertStringAsLines(linestr);
1454 void LyXText::checkParagraph(ParagraphList::iterator pit, pos_type pos)
1456 breakAgain(getRow(pit, pos));
1458 setCursorIntern(cursor.par(), cursor.pos(), false, cursor.boundary());
1462 // returns false if inset wasn't found
1463 bool LyXText::updateInset(InsetOld * inset)
1465 // first check the current paragraph
1466 int pos = cursor.par()->getPositionOfInset(inset);
1468 checkParagraph(cursor.par(), pos);
1472 // check every paragraph
1473 ParagraphList::iterator par = ownerParagraphs().begin();
1474 ParagraphList::iterator end = ownerParagraphs().end();
1475 for (; par != end; ++par) {
1476 pos = par->getPositionOfInset(inset);
1478 checkParagraph(par, pos);
1487 bool LyXText::setCursor(ParagraphList::iterator pit,
1489 bool setfont, bool boundary)
1491 LyXCursor old_cursor = cursor;
1492 setCursorIntern(pit, pos, setfont, boundary);
1493 return deleteEmptyParagraphMechanism(old_cursor);
1497 void LyXText::setCursor(LyXCursor & cur, ParagraphList::iterator pit,
1498 pos_type pos, bool boundary)
1500 Assert(pit != ownerParagraphs().end());
1504 cur.boundary(boundary);
1508 // get the cursor y position in text
1510 RowList::iterator row = getRow(pit, pos, y);
1511 RowList::iterator beg = rows().begin();
1513 RowList::iterator old_row = row;
1514 // if we are before the first char of this row and are still in the
1515 // same paragraph and there is a previous row then put the cursor on
1516 // the end of the previous row
1517 cur.iy(y + row->baseline());
1520 boost::prior(row)->par() == row->par() &&
1521 pos < pit->size() &&
1522 pit->getChar(pos) == Paragraph::META_INSET) {
1523 InsetOld * ins = pit->getInset(pos);
1524 if (ins && (ins->needFullRow() || ins->display())) {
1530 // y is now the beginning of the cursor row
1531 y += row->baseline();
1532 // y is now the cursor baseline
1535 pos_type last = lastPrintablePos(*this, old_row);
1537 // None of these should happen, but we're scaredy-cats
1538 if (pos > pit->size()) {
1539 lyxerr << "dont like 1 please report" << endl;
1542 } else if (pos > last + 1) {
1543 lyxerr << "dont like 2 please report" << endl;
1544 // This shouldn't happen.
1547 } else if (pos < row->pos()) {
1548 lyxerr << "dont like 3 please report" << endl;
1553 // now get the cursors x position
1554 float x = getCursorX(row, pos, last, boundary);
1557 if (old_row != row) {
1558 x = getCursorX(old_row, pos, last, boundary);
1562 /* We take out this for the time being because 1) the redraw code is not
1563 prepared to this yet and 2) because some good policy has yet to be decided
1564 while editting: for instance how to act on rows being created/deleted
1568 //if the cursor is in a visible row, anchor to it
1570 if (topy < y && y < topy + bv()->workHeight())
1576 float LyXText::getCursorX(RowList::iterator rit,
1577 pos_type pos, pos_type last, bool boundary) const
1579 pos_type cursor_vpos = 0;
1581 double fill_separator;
1583 double fill_label_hfill;
1584 // This call HAS to be here because of the BidiTables!!!
1585 prepareToPrint(rit, x, fill_separator, fill_hfill,
1588 ParagraphList::iterator rit_par = rit->par();
1589 pos_type const rit_pos = rit->pos();
1592 cursor_vpos = rit_pos;
1593 else if (pos > last && !boundary)
1594 cursor_vpos = (rit_par->isRightToLeftPar(bv()->buffer()->params))
1595 ? rit_pos : last + 1;
1596 else if (pos > rit_pos && (pos > last || boundary))
1597 /// Place cursor after char at (logical) position pos - 1
1598 cursor_vpos = (bidi_level(pos - 1) % 2 == 0)
1599 ? log2vis(pos - 1) + 1 : log2vis(pos - 1);
1601 /// Place cursor before char at (logical) position pos
1602 cursor_vpos = (bidi_level(pos) % 2 == 0)
1603 ? log2vis(pos) : log2vis(pos) + 1;
1605 pos_type body_pos = rit_par->beginningOfBody();
1607 (body_pos - 1 > last || !rit_par->isLineSeparator(body_pos - 1)))
1610 for (pos_type vpos = rit_pos; vpos < cursor_vpos; ++vpos) {
1611 pos_type pos = vis2log(vpos);
1612 if (body_pos > 0 && pos == body_pos - 1) {
1613 x += fill_label_hfill +
1614 font_metrics::width(
1615 rit_par->layout()->labelsep, getLabelFont(rit_par));
1616 if (rit_par->isLineSeparator(body_pos - 1))
1617 x -= singleWidth(rit_par, body_pos - 1);
1620 if (hfillExpansion(*this, rit, pos)) {
1621 x += singleWidth(rit_par, pos);
1622 if (pos >= body_pos)
1625 x += fill_label_hfill;
1626 } else if (rit_par->isSeparator(pos)) {
1627 x += singleWidth(rit_par, pos);
1628 if (pos >= body_pos)
1629 x += fill_separator;
1631 x += singleWidth(rit_par, pos);
1637 void LyXText::setCursorIntern(ParagraphList::iterator pit,
1638 pos_type pos, bool setfont, bool boundary)
1640 UpdatableInset * it = pit->inInset();
1642 if (it != inset_owner) {
1643 lyxerr[Debug::INSETS] << "InsetText is " << it
1645 << "inset_owner is "
1646 << inset_owner << endl;
1647 #ifdef WITH_WARNINGS
1648 #warning I believe this code is wrong. (Lgb)
1649 #warning Jürgen, have a look at this. (Lgb)
1650 #warning Hmmm, I guess you are right but we
1651 #warning should verify when this is needed
1653 // Jürgen, would you like to have a look?
1654 // I guess we need to move the outer cursor
1655 // and open and lock the inset (bla bla bla)
1656 // stuff I don't know... so can you have a look?
1658 // I moved the lyxerr stuff in here so we can see if
1659 // this is actually really needed and where!
1661 // it->getLyXText(bv())->setCursorIntern(bv(), par, pos, setfont, boundary);
1666 setCursor(cursor, pit, pos, boundary);
1672 void LyXText::setCurrentFont()
1674 pos_type pos = cursor.pos();
1675 ParagraphList::iterator pit = cursor.par();
1677 if (cursor.boundary() && pos > 0)
1681 if (pos == pit->size())
1683 else // potentional bug... BUG (Lgb)
1684 if (pit->isSeparator(pos)) {
1685 if (pos > cursorRow()->pos() &&
1686 bidi_level(pos) % 2 ==
1687 bidi_level(pos - 1) % 2)
1689 else if (pos + 1 < pit->size())
1694 current_font = pit->getFontSettings(bv()->buffer()->params, pos);
1695 real_current_font = getFont(pit, pos);
1697 if (cursor.pos() == pit->size() &&
1698 isBoundary(bv()->buffer(), *pit, cursor.pos()) &&
1699 !cursor.boundary()) {
1700 Language const * lang =
1701 pit->getParLanguage(bv()->buffer()->params);
1702 current_font.setLanguage(lang);
1703 current_font.setNumber(LyXFont::OFF);
1704 real_current_font.setLanguage(lang);
1705 real_current_font.setNumber(LyXFont::OFF);
1710 // returns the column near the specified x-coordinate of the row
1711 // x is set to the real beginning of this column
1713 LyXText::getColumnNearX(RowList::iterator rit, int & x, bool & boundary) const
1716 double fill_separator;
1718 double fill_label_hfill;
1720 prepareToPrint(rit, tmpx, fill_separator, fill_hfill, fill_label_hfill);
1722 pos_type vc = rit->pos();
1723 pos_type last = lastPrintablePos(*this, rit);
1726 ParagraphList::iterator rit_par = rit->par();
1727 LyXLayout_ptr const & layout = rit->par()->layout();
1729 bool left_side = false;
1731 pos_type body_pos = rit_par->beginningOfBody();
1732 double last_tmpx = tmpx;
1735 (body_pos - 1 > last ||
1736 !rit_par->isLineSeparator(body_pos - 1)))
1739 // check for empty row
1740 if (!rit_par->size()) {
1745 while (vc <= last && tmpx <= x) {
1748 if (body_pos > 0 && c == body_pos - 1) {
1749 tmpx += fill_label_hfill +
1750 font_metrics::width(layout->labelsep, getLabelFont(rit_par));
1751 if (rit_par->isLineSeparator(body_pos - 1))
1752 tmpx -= singleWidth(rit_par, body_pos - 1);
1755 if (hfillExpansion(*this, rit, c)) {
1756 tmpx += singleWidth(rit_par, c);
1760 tmpx += fill_label_hfill;
1761 } else if (rit_par->isSeparator(c)) {
1762 tmpx += singleWidth(rit_par, c);
1764 tmpx += fill_separator;
1766 tmpx += singleWidth(rit_par, c);
1771 if ((tmpx + last_tmpx) / 2 > x) {
1776 if (vc > last + 1) // This shouldn't happen.
1780 // This (rtl_support test) is not needed, but gives
1781 // some speedup if rtl_support=false
1782 RowList::iterator next_rit = boost::next(rit);
1784 bool const lastrow = lyxrc.rtl_support &&
1785 (next_rit == rowlist_.end() ||
1786 next_rit->par() != rit_par);
1788 // If lastrow is false, we don't need to compute
1789 // the value of rtl.
1790 bool const rtl = (lastrow)
1791 ? rit_par->isRightToLeftPar(bv()->buffer()->params)
1794 ((rtl && left_side && vc == rit->pos() && x < tmpx - 5) ||
1795 (!rtl && !left_side && vc == last + 1 && x > tmpx + 5)))
1797 else if (vc == rit->pos()) {
1799 if (bidi_level(c) % 2 == 1)
1802 c = vis2log(vc - 1);
1803 bool const rtl = (bidi_level(c) % 2 == 1);
1804 if (left_side == rtl) {
1806 boundary = isBoundary(bv()->buffer(), *rit_par, c);
1810 if (rit->pos() <= last && c > last
1811 && rit_par->isNewline(last)) {
1812 if (bidi_level(last) % 2 == 0)
1813 tmpx -= singleWidth(rit_par, last);
1815 tmpx += singleWidth(rit_par, last);
1825 void LyXText::setCursorFromCoordinates(int x, int y)
1827 //LyXCursor old_cursor = cursor;
1828 setCursorFromCoordinates(cursor, x, y);
1830 #warning DEPM disabled, otherwise crash when entering new table
1831 //deleteEmptyParagraphMechanism(old_cursor);
1838 * return true if the cursor given is at the end of a row,
1839 * and the next row is filled by an inset that spans an entire
1842 bool beforeFullRowInset(LyXText & lt, LyXCursor const & cur)
1844 RowList::iterator row = lt.getRow(cur);
1845 if (boost::next(row) == lt.rows().end())
1848 Row const & next = *boost::next(row);
1850 if (next.pos() != cur.pos() || next.par() != cur.par())
1853 if (cur.pos() == cur.par()->size()
1854 || !cur.par()->isInset(cur.pos()))
1857 InsetOld const * inset = cur.par()->getInset(cur.pos());
1858 if (inset->needFullRow() || inset->display())
1866 void LyXText::setCursorFromCoordinates(LyXCursor & cur, int x, int y)
1868 // Get the row first.
1870 RowList::iterator row = getRowNearY(y);
1872 pos_type const column = getColumnNearX(row, x, bound);
1873 cur.par(row->par());
1874 cur.pos(row->pos() + column);
1876 cur.y(y + row->baseline());
1878 if (beforeFullRowInset(*this, cur)) {
1879 pos_type const last = lastPrintablePos(*this, row);
1880 RowList::iterator next_row = boost::next(row);
1882 float x = getCursorX(next_row, cur.pos(), last, bound);
1884 cur.iy(y + row->height() + next_row->baseline());
1889 cur.boundary(bound);
1893 void LyXText::cursorLeft(bool internal)
1895 if (cursor.pos() > 0) {
1896 bool boundary = cursor.boundary();
1897 setCursor(cursor.par(), cursor.pos() - 1, true, false);
1898 if (!internal && !boundary &&
1899 isBoundary(bv()->buffer(), *cursor.par(), cursor.pos() + 1))
1900 setCursor(cursor.par(), cursor.pos() + 1, true, true);
1901 } else if (cursor.par() != ownerParagraphs().begin()) { // steps into the above paragraph.
1902 ParagraphList::iterator pit = boost::prior(cursor.par());
1903 setCursor(pit, pit->size());
1908 void LyXText::cursorRight(bool internal)
1910 bool const at_end = (cursor.pos() == cursor.par()->size());
1911 bool const at_newline = !at_end &&
1912 cursor.par()->isNewline(cursor.pos());
1914 if (!internal && cursor.boundary() && !at_newline)
1915 setCursor(cursor.par(), cursor.pos(), true, false);
1917 setCursor(cursor.par(), cursor.pos() + 1, true, false);
1919 isBoundary(bv()->buffer(), *cursor.par(), cursor.pos()))
1920 setCursor(cursor.par(), cursor.pos(), true, true);
1921 } else if (boost::next(cursor.par()) != ownerParagraphs().end())
1922 setCursor(boost::next(cursor.par()), 0);
1926 void LyXText::cursorUp(bool selecting)
1929 int x = cursor.x_fix();
1930 int y = cursor.y() - cursorRow()->baseline() - 1;
1931 setCursorFromCoordinates(x, y);
1934 int y1 = cursor.iy() - topy;
1937 InsetOld * inset_hit = checkInsetHit(x, y1);
1938 if (inset_hit && isHighlyEditableInset(inset_hit)) {
1939 inset_hit->localDispatch(
1940 FuncRequest(bv(), LFUN_INSET_EDIT, x, y - (y2 - y1), mouse_button::none));
1944 setCursorFromCoordinates(bv(), cursor.x_fix(),
1945 cursor.y() - cursorRow()->baseline() - 1);
1950 void LyXText::cursorDown(bool selecting)
1953 int x = cursor.x_fix();
1954 int y = cursor.y() - cursorRow()->baseline() + cursorRow()->height() + 1;
1955 setCursorFromCoordinates(x, y);
1956 if (!selecting && cursorRow() == cursorIRow()) {
1958 int y1 = cursor.iy() - topy;
1961 InsetOld * inset_hit = checkInsetHit(x, y1);
1962 if (inset_hit && isHighlyEditableInset(inset_hit)) {
1963 FuncRequest cmd(bv(), LFUN_INSET_EDIT, x, y - (y2 - y1), mouse_button::none);
1964 inset_hit->localDispatch(cmd);
1968 setCursorFromCoordinates(bv(), cursor.x_fix(),
1969 cursor.y() - cursorRow()->baseline()
1970 + cursorRow()->height() + 1);
1975 void LyXText::cursorUpParagraph()
1977 if (cursor.pos() > 0) {
1978 setCursor(cursor.par(), 0);
1980 else if (cursor.par() != ownerParagraphs().begin()) {
1981 setCursor(boost::prior(cursor.par()), 0);
1986 void LyXText::cursorDownParagraph()
1988 ParagraphList::iterator par = cursor.par();
1989 ParagraphList::iterator next_par = boost::next(par);
1991 if (next_par != ownerParagraphs().end()) {
1992 setCursor(next_par, 0);
1994 setCursor(par, par->size());
1998 // fix the cursor `cur' after a characters has been deleted at `where'
1999 // position. Called by deleteEmptyParagraphMechanism
2000 void LyXText::fixCursorAfterDelete(LyXCursor & cur,
2001 LyXCursor const & where)
2003 // if cursor is not in the paragraph where the delete occured,
2005 if (cur.par() != where.par())
2008 // if cursor position is after the place where the delete occured,
2010 if (cur.pos() > where.pos())
2011 cur.pos(cur.pos()-1);
2013 // check also if we don't want to set the cursor on a spot behind the
2014 // pagragraph because we erased the last character.
2015 if (cur.pos() > cur.par()->size())
2016 cur.pos(cur.par()->size());
2018 // recompute row et al. for this cursor
2019 setCursor(cur, cur.par(), cur.pos(), cur.boundary());
2023 bool LyXText::deleteEmptyParagraphMechanism(LyXCursor const & old_cursor)
2025 // Would be wrong to delete anything if we have a selection.
2026 if (selection.set())
2029 // We allow all kinds of "mumbo-jumbo" when freespacing.
2030 if (old_cursor.par()->layout()->free_spacing
2031 || old_cursor.par()->isFreeSpacing()) {
2035 /* Ok I'll put some comments here about what is missing.
2036 I have fixed BackSpace (and thus Delete) to not delete
2037 double-spaces automagically. I have also changed Cut,
2038 Copy and Paste to hopefully do some sensible things.
2039 There are still some small problems that can lead to
2040 double spaces stored in the document file or space at
2041 the beginning of paragraphs. This happens if you have
2042 the cursor betwenn to spaces and then save. Or if you
2043 cut and paste and the selection have a space at the
2044 beginning and then save right after the paste. I am
2045 sure none of these are very hard to fix, but I will
2046 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
2047 that I can get some feedback. (Lgb)
2050 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
2051 // delete the LineSeparator.
2054 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
2055 // delete the LineSeparator.
2058 // If the pos around the old_cursor were spaces, delete one of them.
2059 if (old_cursor.par() != cursor.par()
2060 || old_cursor.pos() != cursor.pos()) {
2061 // Only if the cursor has really moved
2063 if (old_cursor.pos() > 0
2064 && old_cursor.pos() < old_cursor.par()->size()
2065 && old_cursor.par()->isLineSeparator(old_cursor.pos())
2066 && old_cursor.par()->isLineSeparator(old_cursor.pos() - 1)) {
2067 old_cursor.par()->erase(old_cursor.pos() - 1);
2068 redoParagraphs(old_cursor, boost::next(old_cursor.par()));
2070 #ifdef WITH_WARNINGS
2071 #warning This will not work anymore when we have multiple views of the same buffer
2072 // In this case, we will have to correct also the cursors held by
2073 // other bufferviews. It will probably be easier to do that in a more
2074 // automated way in LyXCursor code. (JMarc 26/09/2001)
2076 // correct all cursors held by the LyXText
2077 fixCursorAfterDelete(cursor, old_cursor);
2078 fixCursorAfterDelete(selection.cursor, old_cursor);
2079 fixCursorAfterDelete(selection.start, old_cursor);
2080 fixCursorAfterDelete(selection.end, old_cursor);
2081 fixCursorAfterDelete(last_sel_cursor, old_cursor);
2086 // don't delete anything if this is the ONLY paragraph!
2087 if (ownerParagraphs().size() == 1)
2090 // Do not delete empty paragraphs with keepempty set.
2091 if (old_cursor.par()->allowEmpty())
2094 // only do our magic if we changed paragraph
2095 if (old_cursor.par() == cursor.par())
2098 // record if we have deleted a paragraph
2099 // we can't possibly have deleted a paragraph before this point
2100 bool deleted = false;
2102 if (old_cursor.par()->empty() ||
2103 (old_cursor.par()->size() == 1 &&
2104 old_cursor.par()->isLineSeparator(0))) {
2105 // ok, we will delete anything
2106 LyXCursor tmpcursor;
2110 bool selection_position_was_oldcursor_position = (
2111 selection.cursor.par() == old_cursor.par()
2112 && selection.cursor.pos() == old_cursor.pos());
2114 if (getRow(old_cursor) != rows().begin()) {
2115 RowList::iterator prevrow = boost::prior(getRow(old_cursor));
2118 cursor = old_cursor; // that undo can restore the right cursor position
2119 #warning FIXME. --end() iterator is usable here
2120 ParagraphList::iterator endpit = boost::next(old_cursor.par());
2121 while (endpit != ownerParagraphs().end() &&
2122 endpit->getDepth()) {
2126 recordUndo(bv(), Undo::DELETE, old_cursor.par(),
2127 boost::prior(endpit));
2131 removeRow(getRow(old_cursor));
2133 ownerParagraphs().erase(old_cursor.par());
2135 /* Breakagain the next par. Needed because of
2136 * the parindent that can occur or dissappear.
2137 * The next row can change its height, if
2138 * there is another layout before */
2139 RowList::iterator tmprit = boost::next(prevrow);
2140 if (tmprit != rows().end()) {
2144 setHeightOfRow(prevrow);
2146 RowList::iterator nextrow = boost::next(getRow(old_cursor));
2150 cursor = old_cursor; // that undo can restore the right cursor position
2151 #warning FIXME. --end() iterator is usable here
2152 ParagraphList::iterator endpit = boost::next(old_cursor.par());
2153 while (endpit != ownerParagraphs().end() &&
2154 endpit->getDepth()) {
2158 recordUndo(bv(), Undo::DELETE, old_cursor.par(), boost::prior(endpit));
2162 removeRow(getRow(old_cursor));
2164 ownerParagraphs().erase(old_cursor.par());
2166 /* Breakagain the next par. Needed because of
2167 the parindent that can occur or dissappear.
2168 The next row can change its height, if
2169 there is another layout before */
2170 if (nextrow != rows().end()) {
2171 breakAgain(nextrow);
2177 setCursorIntern(cursor.par(), cursor.pos());
2179 if (selection_position_was_oldcursor_position) {
2180 // correct selection
2181 selection.cursor = cursor;
2185 if (old_cursor.par()->stripLeadingSpaces()) {
2186 redoParagraphs(old_cursor, boost::next(old_cursor.par()));
2188 setCursorIntern(cursor.par(), cursor.pos());
2189 selection.cursor = cursor;
2196 ParagraphList & LyXText::ownerParagraphs() const
2199 return inset_owner->paragraphs;
2201 return bv_owner->buffer()->paragraphs;
2205 bool LyXText::needRefresh() const
2207 return need_refresh_;
2211 void LyXText::clearPaint()
2213 need_refresh_ = false;
2217 void LyXText::postPaint()
2219 need_refresh_ = true;
2221 // We are an inset's lyxtext. Tell the top-level lyxtext
2222 // it needs to update the row we're in.
2224 bv()->text->postPaint();
2228 bool LyXText::isInInset() const
2230 // Sub-level has non-null bv owner and
2231 // non-null inset owner.
2232 return inset_owner != 0 && bv_owner != 0;
2236 int defaultRowHeight()
2238 LyXFont const font(LyXFont::ALL_SANE);
2239 return int(font_metrics::maxAscent(font)
2240 + font_metrics::maxDescent(font) * 1.5);