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();
71 LyXText::LyXText(BufferView * bv, InsetText * inset)
72 : height(0), width(0), anchor_row_offset_(0),
73 inset_owner(inset), the_locking_inset(0), bv_owner(bv)
75 anchor_row_ = rows().end();
79 void LyXText::init(BufferView * bview)
86 anchor_row_ = rows().end();
87 anchor_row_offset_ = 0;
89 current_font = getFont(ownerParagraphs().begin(), 0);
91 redoParagraphs(ownerParagraphs().begin(), ownerParagraphs().end());
93 setCursorIntern(rowlist_.begin()->par(), 0);
94 selection.cursor = cursor;
100 // Gets the fully instantiated font at a given position in a paragraph
101 // Basically the same routine as Paragraph::getFont() in paragraph.C.
102 // The difference is that this one is used for displaying, and thus we
103 // are allowed to make cosmetic improvements. For instance make footnotes
105 // If position is -1, we get the layout font of the paragraph.
106 // If position is -2, we get the font of the manual label of the paragraph.
107 LyXFont LyXText::getFont(ParagraphList::iterator pit, pos_type pos) const
111 LyXLayout_ptr const & layout = pit->layout();
113 BufferParams const & params = bv()->buffer()->params;
115 // We specialize the 95% common case:
116 if (!pit->getDepth()) {
117 if (layout->labeltype == LABEL_MANUAL
118 && pos < pit->beginningOfBody()) {
120 LyXFont f = pit->getFontSettings(params, pos);
122 pit->inInset()->getDrawFont(f);
123 return f.realize(layout->reslabelfont);
125 LyXFont f = pit->getFontSettings(params, pos);
127 pit->inInset()->getDrawFont(f);
128 return f.realize(layout->resfont);
132 // The uncommon case need not be optimized as much
136 if (pos < pit->beginningOfBody()) {
138 layoutfont = layout->labelfont;
141 layoutfont = layout->font;
144 LyXFont tmpfont = pit->getFontSettings(params, pos);
145 tmpfont.realize(layoutfont);
148 pit->inInset()->getDrawFont(tmpfont);
150 // Realize with the fonts of lesser depth.
151 tmpfont.realize(outerFont(pit, ownerParagraphs()));
152 tmpfont.realize(defaultfont_);
158 LyXFont LyXText::getLayoutFont(ParagraphList::iterator pit) const
160 LyXLayout_ptr const & layout = pit->layout();
162 if (!pit->getDepth())
163 return layout->resfont;
165 LyXFont font = layout->font;
166 // Realize with the fonts of lesser depth.
167 font.realize(outerFont(pit, ownerParagraphs()));
168 font.realize(defaultfont_);
174 LyXFont LyXText::getLabelFont(ParagraphList::iterator pit) const
176 LyXLayout_ptr const & layout = pit->layout();
178 if (!pit->getDepth())
179 return layout->reslabelfont;
181 LyXFont font = layout->labelfont;
182 // Realize with the fonts of lesser depth.
183 font.realize(outerFont(pit, ownerParagraphs()));
184 font.realize(defaultfont_);
190 void LyXText::setCharFont(ParagraphList::iterator pit,
191 pos_type pos, LyXFont const & fnt,
194 BufferParams const & params = bv()->buffer()->params;
195 LyXFont font = getFont(pit, pos);
196 font.update(fnt, params.language, toggleall);
197 // Let the insets convert their font
198 if (pit->isInset(pos)) {
199 InsetOld * inset = pit->getInset(pos);
200 if (isEditableInset(inset)) {
201 static_cast<UpdatableInset *>(inset)
202 ->setFont(bv(), fnt, toggleall, true);
206 // Plug through to version below:
207 setCharFont(pit, pos, font);
211 void LyXText::setCharFont(
212 ParagraphList::iterator pit, pos_type pos, LyXFont const & fnt)
215 LyXLayout_ptr const & layout = pit->layout();
217 // Get concrete layout font to reduce against
220 if (pos < pit->beginningOfBody())
221 layoutfont = layout->labelfont;
223 layoutfont = layout->font;
225 // Realize against environment font information
226 if (pit->getDepth()) {
227 ParagraphList::iterator tp = pit;
228 while (!layoutfont.resolved() &&
229 tp != ownerParagraphs().end() &&
231 tp = outerHook(tp, ownerParagraphs());
232 if (tp != ownerParagraphs().end())
233 layoutfont.realize(tp->layout()->font);
237 layoutfont.realize(defaultfont_);
239 // Now, reduce font against full layout font
240 font.reduce(layoutfont);
242 pit->setFont(pos, font);
246 // removes the row and reset the touched counters
247 void LyXText::removeRow(RowList::iterator rit)
249 if (anchor_row_ == rit) {
250 if (rit != rows().begin()) {
251 anchor_row_ = boost::prior(rit);
252 anchor_row_offset_ += anchor_row_->height();
254 anchor_row_ = boost::next(rit);
255 anchor_row_offset_ -= rit->height();
259 // the text becomes smaller
260 height -= rit->height();
266 // remove all following rows of the paragraph of the specified row.
267 void LyXText::removeParagraph(RowList::iterator rit)
269 ParagraphList::iterator tmppit = rit->par();
272 while (rit != rows().end() && rit->par() == tmppit) {
273 RowList::iterator tmprit = boost::next(rit);
280 void LyXText::insertParagraph(ParagraphList::iterator pit,
281 RowList::iterator rit)
283 // insert a new row, starting at position 0
285 rit = rowlist_.insert(rit, newrow);
287 // and now append the whole paragraph before the new row
288 appendParagraph(rit);
292 InsetOld * LyXText::getInset() const
294 ParagraphList::iterator pit = cursor.par();
295 pos_type const pos = cursor.pos();
297 if (pos < pit->size() && pit->isInset(pos)) {
298 return pit->getInset(pos);
304 void LyXText::toggleInset()
306 InsetOld * inset = getInset();
307 // is there an editable inset at cursor position?
308 if (!isEditableInset(inset)) {
309 // No, try to see if we are inside a collapsable inset
310 if (inset_owner && inset_owner->owner()
311 && inset_owner->owner()->isOpen()) {
312 bv()->unlockInset(inset_owner->owner());
313 inset_owner->owner()->close(bv());
314 bv()->getLyXText()->cursorRight(bv());
318 //bv()->owner()->message(inset->editMessage());
320 // do we want to keep this?? (JMarc)
321 if (!isHighlyEditableInset(inset))
322 recordUndo(bv(), Undo::ATOMIC);
324 if (inset->isOpen()) {
330 bv()->updateInset(inset);
334 /* used in setlayout */
335 // Asger is not sure we want to do this...
336 void LyXText::makeFontEntriesLayoutSpecific(BufferParams const & params,
339 LyXLayout_ptr const & layout = par.layout();
340 pos_type const psize = par.size();
343 for (pos_type pos = 0; pos < psize; ++pos) {
344 if (pos < par.beginningOfBody())
345 layoutfont = layout->labelfont;
347 layoutfont = layout->font;
349 LyXFont tmpfont = par.getFontSettings(params, pos);
350 tmpfont.reduce(layoutfont);
351 par.setFont(pos, tmpfont);
356 ParagraphList::iterator
357 LyXText::setLayout(LyXCursor & cur, LyXCursor & sstart_cur,
358 LyXCursor & send_cur,
359 string const & layout)
361 ParagraphList::iterator endpit = boost::next(send_cur.par());
362 ParagraphList::iterator undoendpit = endpit;
363 ParagraphList::iterator pars_end = ownerParagraphs().end();
365 if (endpit != pars_end && endpit->getDepth()) {
366 while (endpit != pars_end && endpit->getDepth()) {
370 } else if (endpit != pars_end) {
371 // because of parindents etc.
375 recordUndo(bv(), Undo::ATOMIC, sstart_cur.par(), boost::prior(undoendpit));
377 // ok we have a selection. This is always between sstart_cur
378 // and sel_end cursor
380 ParagraphList::iterator pit = sstart_cur.par();
381 ParagraphList::iterator epit = boost::next(send_cur.par());
383 LyXLayout_ptr const & lyxlayout =
384 bv()->buffer()->params.getLyXTextClass()[layout];
387 pit->applyLayout(lyxlayout);
388 makeFontEntriesLayoutSpecific(bv()->buffer()->params, *pit);
389 ParagraphList::iterator fppit = pit;
390 fppit->params().spaceTop(lyxlayout->fill_top ?
391 VSpace(VSpace::VFILL)
392 : VSpace(VSpace::NONE));
393 fppit->params().spaceBottom(lyxlayout->fill_bottom ?
394 VSpace(VSpace::VFILL)
395 : VSpace(VSpace::NONE));
396 if (lyxlayout->margintype == MARGIN_MANUAL)
397 pit->setLabelWidthString(lyxlayout->labelstring());
400 } while (pit != epit);
406 // set layout over selection and make a total rebreak of those paragraphs
407 void LyXText::setLayout(string const & layout)
409 LyXCursor tmpcursor = cursor; // store the current cursor
411 // if there is no selection just set the layout
412 // of the current paragraph
413 if (!selection.set()) {
414 selection.start = cursor; // dummy selection
415 selection.end = cursor;
418 // special handling of new environment insets
419 BufferParams const & params = bv()->buffer()->params;
420 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
421 if (lyxlayout->is_environment) {
422 // move everything in a new environment inset
423 lyxerr << "setting layout " << layout << endl;
424 bv()->owner()->dispatch(FuncRequest(LFUN_HOME));
425 bv()->owner()->dispatch(FuncRequest(LFUN_ENDSEL));
426 bv()->owner()->dispatch(FuncRequest(LFUN_CUT));
427 InsetOld * inset = new InsetEnvironment(params, layout);
428 if (bv()->insertInset(inset)) {
430 //bv()->owner()->dispatch(FuncRequest(LFUN_PASTE));
437 ParagraphList::iterator endpit = setLayout(cursor, selection.start,
438 selection.end, layout);
439 redoParagraphs(selection.start.par(), endpit);
441 // we have to reset the selection, because the
442 // geometry could have changed
443 setCursor(selection.start.par(), selection.start.pos(), false);
444 selection.cursor = cursor;
445 setCursor(selection.end.par(), selection.end.pos(), false);
449 setCursor(tmpcursor.par(), tmpcursor.pos(), true);
453 bool LyXText::changeDepth(bv_funcs::DEPTH_CHANGE type, bool test_only)
455 ParagraphList::iterator pit(cursor.par());
456 ParagraphList::iterator end(cursor.par());
457 ParagraphList::iterator start = pit;
459 if (selection.set()) {
460 pit = selection.start.par();
461 end = selection.end.par();
465 ParagraphList::iterator pastend = boost::next(end);
468 recordUndo(bv(), Undo::ATOMIC, start, end);
470 bool changed = false;
472 int prev_after_depth = 0;
473 #warning parlist ... could be nicer ?
474 if (start != ownerParagraphs().begin()) {
475 prev_after_depth = boost::prior(start)->getMaxDepthAfter();
479 int const depth = pit->params().depth();
480 if (type == bv_funcs::INC_DEPTH) {
481 if (depth < prev_after_depth
482 && pit->layout()->labeltype != LABEL_BIBLIO) {
485 pit->params().depth(depth + 1);
492 pit->params().depth(depth - 1);
495 prev_after_depth = pit->getMaxDepthAfter();
508 redoParagraphs(start, pastend);
510 // We need to actually move the text->cursor. I don't
511 // understand why ...
512 LyXCursor tmpcursor = cursor;
514 // we have to reset the visual selection because the
515 // geometry could have changed
516 if (selection.set()) {
517 setCursor(selection.start.par(), selection.start.pos());
518 selection.cursor = cursor;
519 setCursor(selection.end.par(), selection.end.pos());
522 // this handles the counter labels, and also fixes up
523 // depth values for follow-on (child) paragraphs
527 setCursor(tmpcursor.par(), tmpcursor.pos());
533 // set font over selection and make a total rebreak of those paragraphs
534 void LyXText::setFont(LyXFont const & font, bool toggleall)
536 // if there is no selection just set the current_font
537 if (!selection.set()) {
538 // Determine basis font
540 if (cursor.pos() < cursor.par()->beginningOfBody()) {
541 layoutfont = getLabelFont(cursor.par());
543 layoutfont = getLayoutFont(cursor.par());
545 // Update current font
546 real_current_font.update(font,
547 bv()->buffer()->params.language,
550 // Reduce to implicit settings
551 current_font = real_current_font;
552 current_font.reduce(layoutfont);
553 // And resolve it completely
554 real_current_font.realize(layoutfont);
559 LyXCursor tmpcursor = cursor; // store the current cursor
561 // ok we have a selection. This is always between sel_start_cursor
562 // and sel_end cursor
564 recordUndo(bv(), Undo::ATOMIC, selection.start.par(), selection.end.par());
566 cursor = selection.start;
567 while (cursor.par() != selection.end.par() ||
568 cursor.pos() < selection.end.pos())
570 if (cursor.pos() < cursor.par()->size()) {
571 // an open footnote should behave like a closed one
572 setCharFont(cursor.par(), cursor.pos(),
574 cursor.pos(cursor.pos() + 1);
577 cursor.par(boost::next(cursor.par()));
582 redoParagraph(selection.start.par());
584 // we have to reset the selection, because the
585 // geometry could have changed, but we keep
586 // it for user convenience
587 setCursor(selection.start.par(), selection.start.pos());
588 selection.cursor = cursor;
589 setCursor(selection.end.par(), selection.end.pos());
591 setCursor(tmpcursor.par(), tmpcursor.pos(), true,
592 tmpcursor.boundary());
596 void LyXText::redoHeightOfParagraph()
598 RowList::iterator tmprow = cursorRow();
600 setHeightOfRow(tmprow);
602 while (tmprow != rows().begin()
603 && boost::prior(tmprow)->par() == tmprow->par()) {
605 setHeightOfRow(tmprow);
608 setCursor(cursor.par(), cursor.pos(), false, cursor.boundary());
612 RowList::iterator LyXText::firstRow(ParagraphList::iterator pit)
614 RowList::iterator rit;
615 for (rit = rows().begin(); rit != rows().end(); ++rit)
616 if (rit->par() == pit)
622 // rebreaks all paragraphs between the specified pars
623 // This function is needed after SetLayout and SetFont etc.
624 void LyXText::redoParagraphs(ParagraphList::iterator start,
625 ParagraphList::iterator end)
627 for ( ; start != end; ++start)
628 redoParagraph(start);
632 void LyXText::redoParagraph(ParagraphList::iterator pit)
634 RowList::iterator rit = firstRow(pit);
636 // remove paragraph from rowlist
637 while (rit != rows().end() && rit->par() == pit) {
638 RowList::iterator rit2 = rit++;
642 // reinsert the paragraph
643 // insert a new row, starting at position 0
645 rit = rowlist_.insert(rit, newrow);
646 //newrow.dump("newrow: ");
648 // and now append the whole paragraph before the new row
649 // was: appendParagraph(rit);
651 pos_type const last = rit->par()->size();
655 pos_type z = rowBreakPoint(*rit);
657 RowList::iterator tmprow = rit;
658 //tmprow->dump("tmprow: ");
662 Row newrow(rit->par(), z);
663 //newrow.dump("newrow2: ");
664 rit = rowlist_.insert(boost::next(rit), newrow);
669 // Set the dimensions of the row
670 // fixed fill setting now by calling inset->update() in
671 // SingleWidth when needed!
672 //tmprow->dump("tmprow 1: ");
673 tmprow->fill(fill(tmprow, workWidth()));
674 //tmprow->dump("tmprow 2: ");
675 setHeightOfRow(tmprow);
676 //tmprow->dump("tmprow 3: ");
677 height += rit->height();
681 setHeightOfRow(rows().begin());
685 void LyXText::fullRebreak()
687 lyxerr << "fullRebreak" << endl;
688 redoParagraphs(ownerParagraphs().begin(), ownerParagraphs().end());
689 setCursorIntern(cursor.par(), cursor.pos());
690 selection.cursor = cursor;
694 void LyXText::metrics(MetricsInfo & mi, Dimension & dim)
696 //lyxerr << "LyXText::metrics: width: " << mi.base.textwidth << endl;
697 //Assert(mi.base.textwidth);
703 anchor_row_ = rows().end();
704 anchor_row_offset_ = 0;
706 ParagraphList::iterator pit = ownerParagraphs().begin();
707 ParagraphList::iterator end = ownerParagraphs().end();
709 for (; pit != end; ++pit) {
710 InsetList::iterator ii = pit->insetlist.begin();
711 InsetList::iterator iend = pit->insetlist.end();
712 for (; ii != iend; ++ii) {
715 #warning FIXME: pos != 0
716 m.base.font = getFont(pit, 0);
717 ii->inset->metrics(m, dim);
720 // insert a new row, starting at position 0
722 RowList::iterator rit = rowlist_.insert(rowlist_.end(), newrow);
724 // and now append the whole paragraph before the new row
725 appendParagraph(rit);
729 //lyxerr << "height 0: " << height << endl;
730 //for (RowList::iterator rit = rows().begin(); rit != rows().end(); ++rit) {
731 // height += rit->height();
733 //lyxerr << "height 1: " << height << endl;
736 dim.asc = rows().begin()->ascent_of_text();
737 dim.des = height - dim.asc;
738 dim.wid = std::max(mi.base.textwidth, int(width));
742 void LyXText::partialRebreak()
744 if (rows().empty()) {
748 breakAgain(rows().begin());
752 // important for the screen
755 // the cursor set functions have a special mechanism. When they
756 // realize, that you left an empty paragraph, they will delete it.
757 // They also delete the corresponding row
759 // need the selection cursor:
760 void LyXText::setSelection()
762 bool const lsel = TextCursor::setSelection();
764 if (inset_owner && (selection.set() || lsel))
765 inset_owner->setUpdateStatus(InsetText::SELECTION);
770 void LyXText::clearSelection()
772 TextCursor::clearSelection();
774 // reset this in the bv_owner!
775 if (bv_owner && bv_owner->text)
776 bv_owner->text->xsel_cache.set(false);
780 void LyXText::cursorHome()
782 setCursor(cursor.par(), cursorRow()->pos());
786 void LyXText::cursorEnd()
788 if (cursor.par()->empty())
791 RowList::iterator rit = cursorRow();
792 RowList::iterator next_rit = boost::next(rit);
793 ParagraphList::iterator pit = rit->par();
794 pos_type last_pos = lastPos(*this, rit);
796 if (next_rit == rows().end() || next_rit->par() != pit) {
800 (pit->getChar(last_pos) != ' ' && !pit->isNewline(last_pos))) {
805 setCursor(pit, last_pos);
809 void LyXText::cursorTop()
811 setCursor(ownerParagraphs().begin(), 0);
815 void LyXText::cursorBottom()
817 ParagraphList::iterator lastpit =
818 boost::prior(ownerParagraphs().end());
819 setCursor(lastpit, lastpit->size());
823 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
825 // If the mask is completely neutral, tell user
826 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
827 // Could only happen with user style
828 bv()->owner()->message(_("No font change defined. Use Character under the Layout menu to define font change."));
832 // Try implicit word selection
833 // If there is a change in the language the implicit word selection
835 LyXCursor resetCursor = cursor;
836 bool implicitSelection = (font.language() == ignore_language
837 && font.number() == LyXFont::IGNORE)
838 ? selectWordWhenUnderCursor(lyx::WHOLE_WORD_STRICT) : false;
841 setFont(font, toggleall);
843 // Implicit selections are cleared afterwards
844 //and cursor is set to the original position.
845 if (implicitSelection) {
847 cursor = resetCursor;
848 setCursor(cursor.par(), cursor.pos());
849 selection.cursor = cursor;
852 inset_owner->setUpdateStatus(InsetText::CURSOR_PAR);
856 string LyXText::getStringToIndex()
858 // Try implicit word selection
859 // If there is a change in the language the implicit word selection
861 LyXCursor const reset_cursor = cursor;
862 bool const implicitSelection =
863 selectWordWhenUnderCursor(lyx::PREVIOUS_WORD);
866 if (!selection.set())
867 bv()->owner()->message(_("Nothing to index!"));
868 else if (selection.start.par() != selection.end.par())
869 bv()->owner()->message(_("Cannot index more than one paragraph!"));
871 idxstring = selectionAsString(bv()->buffer(), false);
873 // Reset cursors to their original position.
874 cursor = reset_cursor;
875 setCursor(cursor.par(), cursor.pos());
876 selection.cursor = cursor;
878 // Clear the implicit selection.
879 if (implicitSelection)
886 // the DTP switches for paragraphs. LyX will store them in the first
887 // physicla paragraph. When a paragraph is broken, the top settings rest,
888 // the bottom settings are given to the new one. So I can make shure,
889 // they do not duplicate themself and you cannnot make dirty things with
892 void LyXText::setParagraph(bool line_top, bool line_bottom,
893 bool pagebreak_top, bool pagebreak_bottom,
894 VSpace const & space_top,
895 VSpace const & space_bottom,
896 Spacing const & spacing,
898 string const & labelwidthstring,
901 LyXCursor tmpcursor = cursor;
902 if (!selection.set()) {
903 selection.start = cursor;
904 selection.end = cursor;
907 // make sure that the depth behind the selection are restored, too
908 ParagraphList::iterator endpit = boost::next(selection.end.par());
909 ParagraphList::iterator undoendpit = endpit;
910 ParagraphList::iterator pars_end = ownerParagraphs().end();
912 if (endpit != pars_end && endpit->getDepth()) {
913 while (endpit != pars_end && endpit->getDepth()) {
917 } else if (endpit != pars_end) {
918 // because of parindents etc.
922 recordUndo(bv(), Undo::ATOMIC, selection.start.par(),
923 boost::prior(undoendpit));
926 ParagraphList::iterator tmppit = selection.end.par();
928 while (tmppit != boost::prior(selection.start.par())) {
929 setCursor(tmppit, 0);
931 ParagraphList::iterator pit = cursor.par();
932 ParagraphParameters & params = pit->params();
934 params.lineTop(line_top);
935 params.lineBottom(line_bottom);
936 params.pagebreakTop(pagebreak_top);
937 params.pagebreakBottom(pagebreak_bottom);
938 params.spaceTop(space_top);
939 params.spaceBottom(space_bottom);
940 params.spacing(spacing);
941 // does the layout allow the new alignment?
942 LyXLayout_ptr const & layout = pit->layout();
944 if (align == LYX_ALIGN_LAYOUT)
945 align = layout->align;
946 if (align & layout->alignpossible) {
947 if (align == layout->align)
948 params.align(LYX_ALIGN_LAYOUT);
952 pit->setLabelWidthString(labelwidthstring);
953 params.noindent(noindent);
954 tmppit = boost::prior(pit);
957 redoParagraphs(selection.start.par(), endpit);
960 setCursor(selection.start.par(), selection.start.pos());
961 selection.cursor = cursor;
962 setCursor(selection.end.par(), selection.end.pos());
964 setCursor(tmpcursor.par(), tmpcursor.pos());
966 bv()->updateInset(inset_owner);
970 // set the counter of a paragraph. This includes the labels
971 void LyXText::setCounter(Buffer const * buf, ParagraphList::iterator pit)
973 LyXTextClass const & textclass = buf->params.getLyXTextClass();
974 LyXLayout_ptr const & layout = pit->layout();
976 if (pit != ownerParagraphs().begin()) {
978 pit->params().appendix(boost::prior(pit)->params().appendix());
979 if (!pit->params().appendix() &&
980 pit->params().startOfAppendix()) {
981 pit->params().appendix(true);
982 textclass.counters().reset();
984 pit->enumdepth = boost::prior(pit)->enumdepth;
985 pit->itemdepth = boost::prior(pit)->itemdepth;
987 pit->params().appendix(pit->params().startOfAppendix());
992 /* Maybe we have to increment the enumeration depth.
993 * BUT, enumeration in a footnote is considered in isolation from its
994 * surrounding paragraph so don't increment if this is the
995 * first line of the footnote
996 * AND, bibliographies can't have their depth changed ie. they
997 * are always of depth 0
999 if (pit != ownerParagraphs().begin()
1000 && boost::prior(pit)->getDepth() < pit->getDepth()
1001 && boost::prior(pit)->layout()->labeltype == LABEL_COUNTER_ENUMI
1002 && pit->enumdepth < 3
1003 && layout->labeltype != LABEL_BIBLIO) {
1007 // Maybe we have to decrement the enumeration depth, see note above
1008 if (pit != ownerParagraphs().begin()
1009 && boost::prior(pit)->getDepth() > pit->getDepth()
1010 && layout->labeltype != LABEL_BIBLIO) {
1011 pit->enumdepth = depthHook(pit, ownerParagraphs(),
1012 pit->getDepth())->enumdepth;
1015 if (!pit->params().labelString().empty()) {
1016 pit->params().labelString(string());
1019 if (layout->margintype == MARGIN_MANUAL) {
1020 if (pit->params().labelWidthString().empty()) {
1021 pit->setLabelWidthString(layout->labelstring());
1024 pit->setLabelWidthString(string());
1027 // is it a layout that has an automatic label?
1028 if (layout->labeltype >= LABEL_COUNTER_CHAPTER) {
1029 int const i = layout->labeltype - LABEL_COUNTER_CHAPTER;
1033 if (i >= 0 && i <= buf->params.secnumdepth) {
1037 textclass.counters().step(layout->latexname());
1039 // Is there a label? Useful for Chapter layout
1040 if (!pit->params().appendix()) {
1041 s << buf->B_(layout->labelstring());
1043 s << buf->B_(layout->labelstring_appendix());
1046 // Use of an integer is here less than elegant. For now.
1047 int head = textclass.maxcounter() - LABEL_COUNTER_CHAPTER;
1048 if (!pit->params().appendix()) {
1049 numbertype = "sectioning";
1051 numbertype = "appendix";
1052 if (pit->isRightToLeftPar(buf->params))
1053 langtype = "hebrew";
1059 << textclass.counters()
1060 .numberLabel(layout->latexname(),
1061 numbertype, langtype, head);
1063 pit->params().labelString(STRCONV(s.str()));
1065 // reset enum counters
1066 textclass.counters().reset("enum");
1067 } else if (layout->labeltype < LABEL_COUNTER_ENUMI) {
1068 textclass.counters().reset("enum");
1069 } else if (layout->labeltype == LABEL_COUNTER_ENUMI) {
1071 // Yes I know this is a really, really! bad solution
1073 string enumcounter("enum");
1075 switch (pit->enumdepth) {
1084 enumcounter += "iv";
1087 // not a valid enumdepth...
1091 textclass.counters().step(enumcounter);
1093 s << textclass.counters()
1094 .numberLabel(enumcounter, "enumeration");
1095 pit->params().labelString(STRCONV(s.str()));
1097 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
1098 textclass.counters().step("bibitem");
1099 int number = textclass.counters().value("bibitem");
1100 if (pit->bibitem()) {
1101 pit->bibitem()->setCounter(number);
1102 pit->params().labelString(layout->labelstring());
1104 // In biblio should't be following counters but...
1106 string s = buf->B_(layout->labelstring());
1108 // the caption hack:
1109 if (layout->labeltype == LABEL_SENSITIVE) {
1110 ParagraphList::iterator end = ownerParagraphs().end();
1111 ParagraphList::iterator tmppit = pit;
1114 while (tmppit != end && tmppit->inInset()
1115 // the single '=' is intended below
1116 && (in = tmppit->inInset()->owner()))
1118 if (in->lyxCode() == InsetOld::FLOAT_CODE ||
1119 in->lyxCode() == InsetOld::WRAP_CODE) {
1123 tmppit = ownerParagraphs().begin();
1124 for ( ; tmppit != end; ++tmppit)
1125 if (&*tmppit == in->parOwner())
1133 if (in->lyxCode() == InsetOld::FLOAT_CODE)
1134 type = static_cast<InsetFloat*>(in)->params().type;
1135 else if (in->lyxCode() == InsetOld::WRAP_CODE)
1136 type = static_cast<InsetWrap*>(in)->params().type;
1140 Floating const & fl = textclass.floats().getType(type);
1142 textclass.counters().step(fl.type());
1144 // Doesn't work... yet.
1145 s = bformat(_("%1$s #:"), buf->B_(fl.name()));
1147 // par->SetLayout(0);
1148 // s = layout->labelstring;
1149 s = _("Senseless: ");
1152 pit->params().labelString(s);
1154 // reset the enumeration counter. They are always reset
1155 // when there is any other layout between
1156 // Just fall-through between the cases so that all
1157 // enum counters deeper than enumdepth is also reset.
1158 switch (pit->enumdepth) {
1160 textclass.counters().reset("enumi");
1162 textclass.counters().reset("enumii");
1164 textclass.counters().reset("enumiii");
1166 textclass.counters().reset("enumiv");
1172 // Updates all counters. Paragraphs with changed label string will be rebroken
1173 void LyXText::updateCounters()
1175 RowList::iterator rowit = rows().begin();
1176 ParagraphList::iterator pit = rowit->par();
1178 // CHECK if this is really needed. (Lgb)
1179 bv()->buffer()->params.getLyXTextClass().counters().reset();
1181 ParagraphList::iterator beg = ownerParagraphs().begin();
1182 ParagraphList::iterator end = ownerParagraphs().end();
1183 for (; pit != end; ++pit) {
1184 while (rowit->par() != pit)
1187 string const oldLabel = pit->params().labelString();
1189 size_t maxdepth = 0;
1191 maxdepth = boost::prior(pit)->getMaxDepthAfter();
1193 if (pit->params().depth() > maxdepth)
1194 pit->params().depth(maxdepth);
1196 // setCounter can potentially change the labelString.
1197 setCounter(bv()->buffer(), pit);
1199 string const & newLabel = pit->params().labelString();
1201 if (oldLabel.empty() && !newLabel.empty()) {
1202 removeParagraph(rowit);
1203 appendParagraph(rowit);
1209 void LyXText::insertInset(InsetOld * inset)
1211 if (!cursor.par()->insetAllowed(inset->lyxCode()))
1213 recordUndo(bv(), Undo::ATOMIC, cursor.par());
1215 cursor.par()->insertInset(cursor.pos(), inset);
1216 // Just to rebreak and refresh correctly.
1217 // The character will not be inserted a second time
1218 insertChar(Paragraph::META_INSET);
1219 // If we enter a highly editable inset the cursor should be to before
1220 // the inset. This couldn't happen before as Undo was not handled inside
1221 // inset now after the Undo LyX tries to call inset->Edit(...) again
1222 // and cannot do this as the cursor is behind the inset and GetInset
1223 // does not return the inset!
1224 if (isHighlyEditableInset(inset)) {
1231 void LyXText::cutSelection(bool doclear, bool realcut)
1233 // Stuff what we got on the clipboard. Even if there is no selection.
1235 // There is a problem with having the stuffing here in that the
1236 // larger the selection the slower LyX will get. This can be
1237 // solved by running the line below only when the selection has
1238 // finished. The solution used currently just works, to make it
1239 // faster we need to be more clever and probably also have more
1240 // calls to stuffClipboard. (Lgb)
1241 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1243 // This doesn't make sense, if there is no selection
1244 if (!selection.set())
1247 // OK, we have a selection. This is always between selection.start
1248 // and selection.end
1250 // make sure that the depth behind the selection are restored, too
1251 ParagraphList::iterator endpit = boost::next(selection.end.par());
1252 ParagraphList::iterator undoendpit = endpit;
1253 ParagraphList::iterator pars_end = ownerParagraphs().end();
1255 if (endpit != pars_end && endpit->getDepth()) {
1256 while (endpit != pars_end && endpit->getDepth()) {
1258 undoendpit = endpit;
1260 } else if (endpit != pars_end) {
1261 // because of parindents etc.
1265 recordUndo(bv(), Undo::DELETE, selection.start.par(),
1266 boost::prior(undoendpit));
1269 endpit = selection.end.par();
1270 int endpos = selection.end.pos();
1272 boost::tie(endpit, endpos) = realcut ?
1273 CutAndPaste::cutSelection(bv()->buffer()->params,
1275 selection.start.par(), endpit,
1276 selection.start.pos(), endpos,
1277 bv()->buffer()->params.textclass,
1279 : CutAndPaste::eraseSelection(bv()->buffer()->params,
1281 selection.start.par(), endpit,
1282 selection.start.pos(), endpos,
1284 // sometimes necessary
1286 selection.start.par()->stripLeadingSpaces();
1288 redoParagraphs(selection.start.par(), boost::next(endpit));
1289 #warning FIXME latent bug
1290 // endpit will be invalidated on redoParagraphs once ParagraphList
1291 // becomes a std::list? There are maybe other places on which this
1292 // can happend? (Ab)
1293 // cutSelection can invalidate the cursor so we need to set
1295 // we prefer the end for when tracking changes
1299 // need a valid cursor. (Lgb)
1302 setCursor(cursor.par(), cursor.pos());
1303 selection.cursor = cursor;
1308 void LyXText::copySelection()
1310 // stuff the selection onto the X clipboard, from an explicit copy request
1311 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1313 // this doesnt make sense, if there is no selection
1314 if (!selection.set())
1317 // ok we have a selection. This is always between selection.start
1318 // and sel_end cursor
1320 // copy behind a space if there is one
1321 while (selection.start.par()->size() > selection.start.pos()
1322 && selection.start.par()->isLineSeparator(selection.start.pos())
1323 && (selection.start.par() != selection.end.par()
1324 || selection.start.pos() < selection.end.pos()))
1325 selection.start.pos(selection.start.pos() + 1);
1327 CutAndPaste::copySelection(selection.start.par(),
1328 selection.end.par(),
1329 selection.start.pos(), selection.end.pos(),
1330 bv()->buffer()->params.textclass);
1334 void LyXText::pasteSelection(size_t sel_index)
1336 // this does not make sense, if there is nothing to paste
1337 if (!CutAndPaste::checkPastePossible())
1340 recordUndo(bv(), Undo::INSERT, cursor.par());
1342 ParagraphList::iterator endpit;
1347 boost::tie(ppp, endpit) =
1348 CutAndPaste::pasteSelection(*bv()->buffer(),
1350 cursor.par(), cursor.pos(),
1351 bv()->buffer()->params.textclass,
1353 bufferErrors(*bv()->buffer(), el);
1354 bv()->showErrorList(_("Paste"));
1356 redoParagraphs(cursor.par(), endpit);
1358 setCursor(cursor.par(), cursor.pos());
1361 selection.cursor = cursor;
1362 setCursor(ppp.first, ppp.second);
1368 void LyXText::setSelectionRange(lyx::pos_type length)
1373 selection.cursor = cursor;
1380 // simple replacing. The font of the first selected character is used
1381 void LyXText::replaceSelectionWithString(string const & str)
1383 recordUndo(bv(), Undo::ATOMIC);
1386 if (!selection.set()) { // create a dummy selection
1387 selection.end = cursor;
1388 selection.start = cursor;
1391 // Get font setting before we cut
1392 pos_type pos = selection.end.pos();
1393 LyXFont const font = selection.start.par()
1394 ->getFontSettings(bv()->buffer()->params,
1395 selection.start.pos());
1397 // Insert the new string
1398 string::const_iterator cit = str.begin();
1399 string::const_iterator end = str.end();
1400 for (; cit != end; ++cit) {
1401 selection.end.par()->insertChar(pos, (*cit), font);
1405 // Cut the selection
1406 cutSelection(true, false);
1412 // needed to insert the selection
1413 void LyXText::insertStringAsLines(string const & str)
1415 ParagraphList::iterator pit = cursor.par();
1416 pos_type pos = cursor.pos();
1417 ParagraphList::iterator endpit = boost::next(cursor.par());
1419 recordUndo(bv(), Undo::ATOMIC);
1421 // only to be sure, should not be neccessary
1424 bv()->buffer()->insertStringAsLines(pit, pos, current_font, str);
1426 redoParagraphs(cursor.par(), endpit);
1427 setCursor(cursor.par(), cursor.pos());
1428 selection.cursor = cursor;
1429 setCursor(pit, pos);
1434 // turns double-CR to single CR, others where converted into one
1435 // blank. Then InsertStringAsLines is called
1436 void LyXText::insertStringAsParagraphs(string const & str)
1438 string linestr(str);
1439 bool newline_inserted = false;
1440 string::size_type const siz = linestr.length();
1442 for (string::size_type i = 0; i < siz; ++i) {
1443 if (linestr[i] == '\n') {
1444 if (newline_inserted) {
1445 // we know that \r will be ignored by
1446 // InsertStringA. Of course, it is a dirty
1447 // trick, but it works...
1448 linestr[i - 1] = '\r';
1452 newline_inserted = true;
1454 } else if (IsPrintable(linestr[i])) {
1455 newline_inserted = false;
1458 insertStringAsLines(linestr);
1462 void LyXText::checkParagraph(ParagraphList::iterator pit, pos_type pos)
1464 breakAgain(getRow(pit, pos));
1465 setCursorIntern(cursor.par(), cursor.pos(), false, cursor.boundary());
1469 // returns false if inset wasn't found
1470 bool LyXText::updateInset(InsetOld * inset)
1472 // first check the current paragraph
1473 int pos = cursor.par()->getPositionOfInset(inset);
1475 checkParagraph(cursor.par(), pos);
1479 // check every paragraph
1480 ParagraphList::iterator par = ownerParagraphs().begin();
1481 ParagraphList::iterator end = ownerParagraphs().end();
1482 for (; par != end; ++par) {
1483 pos = par->getPositionOfInset(inset);
1485 checkParagraph(par, pos);
1494 bool LyXText::setCursor(ParagraphList::iterator pit,
1496 bool setfont, bool boundary)
1498 LyXCursor old_cursor = cursor;
1499 setCursorIntern(pit, pos, setfont, boundary);
1500 return deleteEmptyParagraphMechanism(old_cursor);
1504 void LyXText::setCursor(LyXCursor & cur, ParagraphList::iterator pit,
1505 pos_type pos, bool boundary)
1507 Assert(pit != ownerParagraphs().end());
1511 cur.boundary(boundary);
1515 // get the cursor y position in text
1517 RowList::iterator row = getRow(pit, pos, y);
1518 RowList::iterator beg = rows().begin();
1520 RowList::iterator old_row = row;
1521 // if we are before the first char of this row and are still in the
1522 // same paragraph and there is a previous row then put the cursor on
1523 // the end of the previous row
1524 cur.iy(y + row->baseline());
1527 boost::prior(row)->par() == row->par() &&
1528 pos < pit->size() &&
1529 pit->getChar(pos) == Paragraph::META_INSET) {
1530 InsetOld * ins = pit->getInset(pos);
1531 if (ins && (ins->needFullRow() || ins->display())) {
1537 // y is now the beginning of the cursor row
1538 y += row->baseline();
1539 // y is now the cursor baseline
1542 pos_type last = lastPrintablePos(*this, old_row);
1544 // None of these should happen, but we're scaredy-cats
1545 if (pos > pit->size()) {
1546 lyxerr << "dont like 1 please report" << endl;
1549 } else if (pos > last + 1) {
1550 lyxerr << "dont like 2 please report" << endl;
1551 // This shouldn't happen.
1554 } else if (pos < row->pos()) {
1555 lyxerr << "dont like 3 please report" << endl;
1560 // now get the cursors x position
1561 float x = getCursorX(row, pos, last, boundary);
1564 if (old_row != row) {
1565 x = getCursorX(old_row, pos, last, boundary);
1569 /* We take out this for the time being because 1) the redraw code is not
1570 prepared to this yet and 2) because some good policy has yet to be decided
1571 while editting: for instance how to act on rows being created/deleted
1575 //if the cursor is in a visible row, anchor to it
1577 if (topy < y && y < topy + bv()->workHeight())
1583 float LyXText::getCursorX(RowList::iterator rit,
1584 pos_type pos, pos_type last, bool boundary) const
1586 pos_type cursor_vpos = 0;
1588 double fill_separator;
1590 double fill_label_hfill;
1591 // This call HAS to be here because of the BidiTables!!!
1592 prepareToPrint(rit, x, fill_separator, fill_hfill,
1595 ParagraphList::iterator rit_par = rit->par();
1596 pos_type const rit_pos = rit->pos();
1599 cursor_vpos = rit_pos;
1600 else if (pos > last && !boundary)
1601 cursor_vpos = (rit_par->isRightToLeftPar(bv()->buffer()->params))
1602 ? rit_pos : last + 1;
1603 else if (pos > rit_pos && (pos > last || boundary))
1604 /// Place cursor after char at (logical) position pos - 1
1605 cursor_vpos = (bidi_level(pos - 1) % 2 == 0)
1606 ? log2vis(pos - 1) + 1 : log2vis(pos - 1);
1608 /// Place cursor before char at (logical) position pos
1609 cursor_vpos = (bidi_level(pos) % 2 == 0)
1610 ? log2vis(pos) : log2vis(pos) + 1;
1612 pos_type body_pos = rit_par->beginningOfBody();
1614 (body_pos - 1 > last || !rit_par->isLineSeparator(body_pos - 1)))
1617 for (pos_type vpos = rit_pos; vpos < cursor_vpos; ++vpos) {
1618 pos_type pos = vis2log(vpos);
1619 if (body_pos > 0 && pos == body_pos - 1) {
1620 x += fill_label_hfill +
1621 font_metrics::width(
1622 rit_par->layout()->labelsep, getLabelFont(rit_par));
1623 if (rit_par->isLineSeparator(body_pos - 1))
1624 x -= singleWidth(rit_par, body_pos - 1);
1627 if (hfillExpansion(*this, rit, pos)) {
1628 x += singleWidth(rit_par, pos);
1629 if (pos >= body_pos)
1632 x += fill_label_hfill;
1633 } else if (rit_par->isSeparator(pos)) {
1634 x += singleWidth(rit_par, pos);
1635 if (pos >= body_pos)
1636 x += fill_separator;
1638 x += singleWidth(rit_par, pos);
1644 void LyXText::setCursorIntern(ParagraphList::iterator pit,
1645 pos_type pos, bool setfont, bool boundary)
1647 UpdatableInset * it = pit->inInset();
1649 if (it != inset_owner) {
1650 lyxerr[Debug::INSETS] << "InsetText is " << it
1652 << "inset_owner is "
1653 << inset_owner << endl;
1654 #ifdef WITH_WARNINGS
1655 #warning I believe this code is wrong. (Lgb)
1656 #warning Jürgen, have a look at this. (Lgb)
1657 #warning Hmmm, I guess you are right but we
1658 #warning should verify when this is needed
1660 // Jürgen, would you like to have a look?
1661 // I guess we need to move the outer cursor
1662 // and open and lock the inset (bla bla bla)
1663 // stuff I don't know... so can you have a look?
1665 // I moved the lyxerr stuff in here so we can see if
1666 // this is actually really needed and where!
1668 // it->getLyXText(bv())->setCursorIntern(bv(), par, pos, setfont, boundary);
1673 setCursor(cursor, pit, pos, boundary);
1679 void LyXText::setCurrentFont()
1681 pos_type pos = cursor.pos();
1682 ParagraphList::iterator pit = cursor.par();
1684 if (cursor.boundary() && pos > 0)
1688 if (pos == pit->size())
1690 else // potentional bug... BUG (Lgb)
1691 if (pit->isSeparator(pos)) {
1692 if (pos > cursorRow()->pos() &&
1693 bidi_level(pos) % 2 ==
1694 bidi_level(pos - 1) % 2)
1696 else if (pos + 1 < pit->size())
1701 current_font = pit->getFontSettings(bv()->buffer()->params, pos);
1702 real_current_font = getFont(pit, pos);
1704 if (cursor.pos() == pit->size() &&
1705 isBoundary(bv()->buffer(), *pit, cursor.pos()) &&
1706 !cursor.boundary()) {
1707 Language const * lang =
1708 pit->getParLanguage(bv()->buffer()->params);
1709 current_font.setLanguage(lang);
1710 current_font.setNumber(LyXFont::OFF);
1711 real_current_font.setLanguage(lang);
1712 real_current_font.setNumber(LyXFont::OFF);
1717 // returns the column near the specified x-coordinate of the row
1718 // x is set to the real beginning of this column
1720 LyXText::getColumnNearX(RowList::iterator rit, int & x, bool & boundary) const
1723 double fill_separator;
1725 double fill_label_hfill;
1727 prepareToPrint(rit, tmpx, fill_separator, fill_hfill, fill_label_hfill);
1729 pos_type vc = rit->pos();
1730 pos_type last = lastPrintablePos(*this, rit);
1733 ParagraphList::iterator rit_par = rit->par();
1734 LyXLayout_ptr const & layout = rit->par()->layout();
1736 bool left_side = false;
1738 pos_type body_pos = rit_par->beginningOfBody();
1739 double last_tmpx = tmpx;
1742 (body_pos - 1 > last ||
1743 !rit_par->isLineSeparator(body_pos - 1)))
1746 // check for empty row
1747 if (!rit_par->size()) {
1752 while (vc <= last && tmpx <= x) {
1755 if (body_pos > 0 && c == body_pos - 1) {
1756 tmpx += fill_label_hfill +
1757 font_metrics::width(layout->labelsep, getLabelFont(rit_par));
1758 if (rit_par->isLineSeparator(body_pos - 1))
1759 tmpx -= singleWidth(rit_par, body_pos - 1);
1762 if (hfillExpansion(*this, rit, c)) {
1763 tmpx += singleWidth(rit_par, c);
1767 tmpx += fill_label_hfill;
1768 } else if (rit_par->isSeparator(c)) {
1769 tmpx += singleWidth(rit_par, c);
1771 tmpx += fill_separator;
1773 tmpx += singleWidth(rit_par, c);
1778 if ((tmpx + last_tmpx) / 2 > x) {
1783 if (vc > last + 1) // This shouldn't happen.
1787 // This (rtl_support test) is not needed, but gives
1788 // some speedup if rtl_support=false
1789 RowList::iterator next_rit = boost::next(rit);
1791 bool const lastrow = lyxrc.rtl_support &&
1792 (next_rit == rowlist_.end() ||
1793 next_rit->par() != rit_par);
1795 // If lastrow is false, we don't need to compute
1796 // the value of rtl.
1797 bool const rtl = (lastrow)
1798 ? rit_par->isRightToLeftPar(bv()->buffer()->params)
1801 ((rtl && left_side && vc == rit->pos() && x < tmpx - 5) ||
1802 (!rtl && !left_side && vc == last + 1 && x > tmpx + 5)))
1804 else if (vc == rit->pos()) {
1806 if (bidi_level(c) % 2 == 1)
1809 c = vis2log(vc - 1);
1810 bool const rtl = (bidi_level(c) % 2 == 1);
1811 if (left_side == rtl) {
1813 boundary = isBoundary(bv()->buffer(), *rit_par, c);
1817 if (rit->pos() <= last && c > last
1818 && rit_par->isNewline(last)) {
1819 if (bidi_level(last) % 2 == 0)
1820 tmpx -= singleWidth(rit_par, last);
1822 tmpx += singleWidth(rit_par, last);
1832 void LyXText::setCursorFromCoordinates(int x, int y)
1834 //LyXCursor old_cursor = cursor;
1835 setCursorFromCoordinates(cursor, x, y);
1837 #warning DEPM disabled, otherwise crash when entering new table
1838 //deleteEmptyParagraphMechanism(old_cursor);
1845 * return true if the cursor given is at the end of a row,
1846 * and the next row is filled by an inset that spans an entire
1849 bool beforeFullRowInset(LyXText & lt, LyXCursor const & cur)
1851 RowList::iterator row = lt.getRow(cur);
1852 if (boost::next(row) == lt.rows().end())
1855 Row const & next = *boost::next(row);
1857 if (next.pos() != cur.pos() || next.par() != cur.par())
1860 if (cur.pos() == cur.par()->size()
1861 || !cur.par()->isInset(cur.pos()))
1864 InsetOld const * inset = cur.par()->getInset(cur.pos());
1865 if (inset->needFullRow() || inset->display())
1873 void LyXText::setCursorFromCoordinates(LyXCursor & cur, int x, int y)
1875 // Get the row first.
1877 RowList::iterator row = getRowNearY(y);
1879 pos_type const column = getColumnNearX(row, x, bound);
1880 cur.par(row->par());
1881 cur.pos(row->pos() + column);
1883 cur.y(y + row->baseline());
1885 if (beforeFullRowInset(*this, cur)) {
1886 pos_type const last = lastPrintablePos(*this, row);
1887 RowList::iterator next_row = boost::next(row);
1889 float x = getCursorX(next_row, cur.pos(), last, bound);
1891 cur.iy(y + row->height() + next_row->baseline());
1896 cur.boundary(bound);
1900 void LyXText::cursorLeft(bool internal)
1902 if (cursor.pos() > 0) {
1903 bool boundary = cursor.boundary();
1904 setCursor(cursor.par(), cursor.pos() - 1, true, false);
1905 if (!internal && !boundary &&
1906 isBoundary(bv()->buffer(), *cursor.par(), cursor.pos() + 1))
1907 setCursor(cursor.par(), cursor.pos() + 1, true, true);
1908 } else if (cursor.par() != ownerParagraphs().begin()) {
1909 // steps into the paragraph above
1910 ParagraphList::iterator pit = boost::prior(cursor.par());
1911 setCursor(pit, pit->size());
1916 void LyXText::cursorRight(bool internal)
1918 bool const at_end = (cursor.pos() == cursor.par()->size());
1919 bool const at_newline = !at_end &&
1920 cursor.par()->isNewline(cursor.pos());
1922 if (!internal && cursor.boundary() && !at_newline)
1923 setCursor(cursor.par(), cursor.pos(), true, false);
1925 setCursor(cursor.par(), cursor.pos() + 1, true, false);
1927 isBoundary(bv()->buffer(), *cursor.par(), cursor.pos()))
1928 setCursor(cursor.par(), cursor.pos(), true, true);
1929 } else if (boost::next(cursor.par()) != ownerParagraphs().end())
1930 setCursor(boost::next(cursor.par()), 0);
1934 void LyXText::cursorUp(bool selecting)
1937 int x = cursor.x_fix();
1938 int y = cursor.y() - cursorRow()->baseline() - 1;
1939 setCursorFromCoordinates(x, y);
1942 int y1 = cursor.iy() - topy;
1945 InsetOld * inset_hit = checkInsetHit(x, y1);
1946 if (inset_hit && isHighlyEditableInset(inset_hit)) {
1947 inset_hit->localDispatch(
1948 FuncRequest(bv(), LFUN_INSET_EDIT, x, y - (y2 - y1), mouse_button::none));
1952 setCursorFromCoordinates(bv(), cursor.x_fix(),
1953 cursor.y() - cursorRow()->baseline() - 1);
1958 void LyXText::cursorDown(bool selecting)
1961 int x = cursor.x_fix();
1962 int y = cursor.y() - cursorRow()->baseline() + cursorRow()->height() + 1;
1963 setCursorFromCoordinates(x, y);
1964 if (!selecting && cursorRow() == cursorIRow()) {
1966 int y1 = cursor.iy() - topy;
1969 InsetOld * inset_hit = checkInsetHit(x, y1);
1970 if (inset_hit && isHighlyEditableInset(inset_hit)) {
1971 FuncRequest cmd(bv(), LFUN_INSET_EDIT, x, y - (y2 - y1), mouse_button::none);
1972 inset_hit->localDispatch(cmd);
1976 setCursorFromCoordinates(bv(), cursor.x_fix(),
1977 cursor.y() - cursorRow()->baseline()
1978 + cursorRow()->height() + 1);
1983 void LyXText::cursorUpParagraph()
1985 if (cursor.pos() > 0)
1986 setCursor(cursor.par(), 0);
1987 else if (cursor.par() != ownerParagraphs().begin())
1988 setCursor(boost::prior(cursor.par()), 0);
1992 void LyXText::cursorDownParagraph()
1994 ParagraphList::iterator par = cursor.par();
1995 ParagraphList::iterator next_par = boost::next(par);
1997 if (next_par != ownerParagraphs().end())
1998 setCursor(next_par, 0);
2000 setCursor(par, par->size());
2004 // fix the cursor `cur' after a characters has been deleted at `where'
2005 // position. Called by deleteEmptyParagraphMechanism
2006 void LyXText::fixCursorAfterDelete(LyXCursor & cur,
2007 LyXCursor const & where)
2009 // if cursor is not in the paragraph where the delete occured,
2011 if (cur.par() != where.par())
2014 // if cursor position is after the place where the delete occured,
2016 if (cur.pos() > where.pos())
2017 cur.pos(cur.pos()-1);
2019 // check also if we don't want to set the cursor on a spot behind the
2020 // pagragraph because we erased the last character.
2021 if (cur.pos() > cur.par()->size())
2022 cur.pos(cur.par()->size());
2024 // recompute row et al. for this cursor
2025 setCursor(cur, cur.par(), cur.pos(), cur.boundary());
2029 bool LyXText::deleteEmptyParagraphMechanism(LyXCursor const & old_cursor)
2031 // Would be wrong to delete anything if we have a selection.
2032 if (selection.set())
2035 // We allow all kinds of "mumbo-jumbo" when freespacing.
2036 if (old_cursor.par()->layout()->free_spacing
2037 || old_cursor.par()->isFreeSpacing()) {
2041 /* Ok I'll put some comments here about what is missing.
2042 I have fixed BackSpace (and thus Delete) to not delete
2043 double-spaces automagically. I have also changed Cut,
2044 Copy and Paste to hopefully do some sensible things.
2045 There are still some small problems that can lead to
2046 double spaces stored in the document file or space at
2047 the beginning of paragraphs. This happens if you have
2048 the cursor betwenn to spaces and then save. Or if you
2049 cut and paste and the selection have a space at the
2050 beginning and then save right after the paste. I am
2051 sure none of these are very hard to fix, but I will
2052 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
2053 that I can get some feedback. (Lgb)
2056 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
2057 // delete the LineSeparator.
2060 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
2061 // delete the LineSeparator.
2064 // If the pos around the old_cursor were spaces, delete one of them.
2065 if (old_cursor.par() != cursor.par()
2066 || old_cursor.pos() != cursor.pos()) {
2067 // Only if the cursor has really moved
2069 if (old_cursor.pos() > 0
2070 && old_cursor.pos() < old_cursor.par()->size()
2071 && old_cursor.par()->isLineSeparator(old_cursor.pos())
2072 && old_cursor.par()->isLineSeparator(old_cursor.pos() - 1)) {
2073 old_cursor.par()->erase(old_cursor.pos() - 1);
2074 redoParagraph(old_cursor.par());
2076 #ifdef WITH_WARNINGS
2077 #warning This will not work anymore when we have multiple views of the same buffer
2078 // In this case, we will have to correct also the cursors held by
2079 // other bufferviews. It will probably be easier to do that in a more
2080 // automated way in LyXCursor code. (JMarc 26/09/2001)
2082 // correct all cursors held by the LyXText
2083 fixCursorAfterDelete(cursor, old_cursor);
2084 fixCursorAfterDelete(selection.cursor, old_cursor);
2085 fixCursorAfterDelete(selection.start, old_cursor);
2086 fixCursorAfterDelete(selection.end, old_cursor);
2087 fixCursorAfterDelete(last_sel_cursor, old_cursor);
2092 // don't delete anything if this is the ONLY paragraph!
2093 if (ownerParagraphs().size() == 1)
2096 // Do not delete empty paragraphs with keepempty set.
2097 if (old_cursor.par()->allowEmpty())
2100 // only do our magic if we changed paragraph
2101 if (old_cursor.par() == cursor.par())
2104 // record if we have deleted a paragraph
2105 // we can't possibly have deleted a paragraph before this point
2106 bool deleted = false;
2108 if (old_cursor.par()->empty() ||
2109 (old_cursor.par()->size() == 1 &&
2110 old_cursor.par()->isLineSeparator(0))) {
2111 // ok, we will delete anything
2112 LyXCursor tmpcursor;
2116 bool selection_position_was_oldcursor_position = (
2117 selection.cursor.par() == old_cursor.par()
2118 && selection.cursor.pos() == old_cursor.pos());
2120 if (getRow(old_cursor) != rows().begin()) {
2121 RowList::iterator prevrow = boost::prior(getRow(old_cursor));
2123 cursor = old_cursor; // that undo can restore the right cursor position
2124 #warning FIXME. --end() iterator is usable here
2125 ParagraphList::iterator endpit = boost::next(old_cursor.par());
2126 while (endpit != ownerParagraphs().end() &&
2127 endpit->getDepth()) {
2131 recordUndo(bv(), Undo::DELETE, old_cursor.par(),
2132 boost::prior(endpit));
2136 removeRow(getRow(old_cursor));
2138 ownerParagraphs().erase(old_cursor.par());
2140 /* Breakagain the next par. Needed because of
2141 * the parindent that can occur or dissappear.
2142 * The next row can change its height, if
2143 * there is another layout before */
2144 RowList::iterator tmprit = boost::next(prevrow);
2145 if (tmprit != rows().end()) {
2149 setHeightOfRow(prevrow);
2151 RowList::iterator nextrow = boost::next(getRow(old_cursor));
2154 cursor = old_cursor; // that undo can restore the right cursor position
2155 #warning FIXME. --end() iterator is usable here
2156 ParagraphList::iterator endpit = boost::next(old_cursor.par());
2157 while (endpit != ownerParagraphs().end() &&
2158 endpit->getDepth()) {
2162 recordUndo(bv(), Undo::DELETE, old_cursor.par(), boost::prior(endpit));
2166 removeRow(getRow(old_cursor));
2168 ownerParagraphs().erase(old_cursor.par());
2170 /* Breakagain the next par. Needed because of
2171 the parindent that can occur or dissappear.
2172 The next row can change its height, if
2173 there is another layout before */
2174 if (nextrow != rows().end()) {
2175 breakAgain(nextrow);
2181 setCursorIntern(cursor.par(), cursor.pos());
2183 if (selection_position_was_oldcursor_position) {
2184 // correct selection
2185 selection.cursor = cursor;
2189 if (old_cursor.par()->stripLeadingSpaces()) {
2190 redoParagraph(old_cursor.par());
2192 setCursorIntern(cursor.par(), cursor.pos());
2193 selection.cursor = cursor;
2200 ParagraphList & LyXText::ownerParagraphs() const
2203 return inset_owner->paragraphs;
2205 return bv_owner->buffer()->paragraphs;
2209 bool LyXText::isInInset() const
2211 // Sub-level has non-null bv owner and
2212 // non-null inset owner.
2213 return inset_owner != 0 && bv_owner != 0;
2217 int defaultRowHeight()
2219 LyXFont const font(LyXFont::ALL_SANE);
2220 return int(font_metrics::maxAscent(font)
2221 + font_metrics::maxDescent(font) * 1.5);